@ampath/esm-patient-registration-app 6.0.1-pre.98 → 9.2.0-next.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/1119.js +1 -0
  2. package/dist/1197.js +1 -0
  3. package/dist/21.js +1 -0
  4. package/dist/21.js.map +1 -0
  5. package/dist/2146.js +1 -0
  6. package/dist/2372.js +1 -0
  7. package/dist/2372.js.map +1 -0
  8. package/dist/2470.js +1 -0
  9. package/dist/2470.js.map +1 -0
  10. package/dist/2690.js +1 -0
  11. package/dist/2913.js +2 -0
  12. package/dist/{913.js.LICENSE.txt → 2913.js.LICENSE.txt} +3 -23
  13. package/dist/2913.js.map +1 -0
  14. package/dist/3093.js +1 -0
  15. package/dist/3093.js.map +1 -0
  16. package/dist/3099.js +1 -0
  17. package/dist/3144.js +2 -0
  18. package/dist/3144.js.LICENSE.txt +19 -0
  19. package/dist/3144.js.map +1 -0
  20. package/dist/320.js +2 -0
  21. package/dist/{876.js.LICENSE.txt → 320.js.LICENSE.txt} +2 -3
  22. package/dist/320.js.map +1 -0
  23. package/dist/3464.js +1 -0
  24. package/dist/3464.js.map +1 -0
  25. package/dist/3474.js +2 -0
  26. package/dist/3474.js.LICENSE.txt +8 -0
  27. package/dist/3474.js.map +1 -0
  28. package/dist/3584.js +1 -0
  29. package/dist/4041.js +2 -0
  30. package/dist/4041.js.map +1 -0
  31. package/dist/4055.js +1 -0
  32. package/dist/4132.js +1 -0
  33. package/dist/4300.js +1 -0
  34. package/dist/4335.js +1 -0
  35. package/dist/4463.js +1 -0
  36. package/dist/4463.js.map +1 -0
  37. package/dist/4618.js +1 -0
  38. package/dist/4652.js +1 -0
  39. package/dist/4944.js +1 -0
  40. package/dist/5173.js +1 -0
  41. package/dist/5220.js +2 -0
  42. package/dist/5220.js.LICENSE.txt +29 -0
  43. package/dist/5220.js.map +1 -0
  44. package/dist/5241.js +1 -0
  45. package/dist/5442.js +1 -0
  46. package/dist/5661.js +1 -0
  47. package/dist/6022.js +1 -0
  48. package/dist/6078.js +2 -0
  49. package/dist/6078.js.LICENSE.txt +9 -0
  50. package/dist/6078.js.map +1 -0
  51. package/dist/627.js +1 -0
  52. package/dist/627.js.map +1 -0
  53. package/dist/6276.js +1 -0
  54. package/dist/6276.js.map +1 -0
  55. package/dist/6468.js +1 -0
  56. package/dist/6679.js +1 -0
  57. package/dist/6737.js +2 -0
  58. package/dist/6737.js.LICENSE.txt +9 -0
  59. package/dist/6737.js.map +1 -0
  60. package/dist/6840.js +1 -0
  61. package/dist/6859.js +1 -0
  62. package/dist/7092.js +1 -0
  63. package/dist/7092.js.map +1 -0
  64. package/dist/7097.js +1 -0
  65. package/dist/7159.js +1 -0
  66. package/dist/723.js +1 -0
  67. package/dist/7495.js +2 -0
  68. package/dist/7495.js.LICENSE.txt +9 -0
  69. package/dist/7495.js.map +1 -0
  70. package/dist/7617.js +1 -0
  71. package/dist/795.js +1 -0
  72. package/dist/8163.js +1 -0
  73. package/dist/8349.js +1 -0
  74. package/dist/8404.js +2 -0
  75. package/dist/{629.js.LICENSE.txt → 8404.js.LICENSE.txt} +9 -3
  76. package/dist/8404.js.map +1 -0
  77. package/dist/8434.js +1 -0
  78. package/dist/8434.js.map +1 -0
  79. package/dist/8618.js +1 -0
  80. package/dist/89.js +2 -0
  81. package/dist/89.js.LICENSE.txt +9 -0
  82. package/dist/89.js.map +1 -0
  83. package/dist/890.js +1 -0
  84. package/dist/9214.js +1 -0
  85. package/dist/9538.js +1 -0
  86. package/dist/9569.js +1 -0
  87. package/dist/986.js +1 -0
  88. package/dist/9876.js +1 -0
  89. package/dist/9876.js.map +1 -0
  90. package/dist/9879.js +1 -0
  91. package/dist/9895.js +1 -0
  92. package/dist/9900.js +1 -0
  93. package/dist/9913.js +1 -0
  94. package/dist/main.js +1 -1
  95. package/dist/main.js.LICENSE.txt +36 -1
  96. package/dist/main.js.map +1 -1
  97. package/dist/openmrs-esm-patient-registration-app.js +1 -0
  98. package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +1576 -0
  99. package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
  100. package/dist/routes.json +1 -1
  101. package/package.json +16 -15
  102. package/src/{add-patient-link.tsx → add-patient-link.extension.tsx} +4 -2
  103. package/src/add-patient-link.test.tsx +6 -10
  104. package/src/config-schema.ts +109 -55
  105. package/src/constants.ts +1 -1
  106. package/src/declarations.d.ts +5 -4
  107. package/src/index.ts +10 -29
  108. package/src/nav-link.test.tsx +3 -3
  109. package/src/offline.resources.ts +26 -18
  110. package/src/patient-photo.extension.tsx +3 -1
  111. package/src/patient-registration/field/address/address-field.component.tsx +58 -37
  112. package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +16 -18
  113. package/src/patient-registration/field/address/address-hierarchy.resource.tsx +3 -3
  114. package/src/patient-registration/field/address/address-hierarchy.test.tsx +290 -0
  115. package/src/patient-registration/field/address/address-search.component.tsx +7 -5
  116. package/src/patient-registration/field/address/address-search.scss +5 -5
  117. package/src/patient-registration/field/address/address-search.test.tsx +140 -0
  118. package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +98 -0
  119. package/src/patient-registration/field/custom-field.component.tsx +3 -9
  120. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +84 -0
  121. package/src/patient-registration/field/dob/dob.component.tsx +55 -50
  122. package/src/patient-registration/field/dob/dob.test.tsx +90 -0
  123. package/src/patient-registration/field/field.component.tsx +12 -6
  124. package/src/patient-registration/field/field.resource.ts +11 -4
  125. package/src/patient-registration/field/field.scss +69 -25
  126. package/src/patient-registration/field/field.test.tsx +329 -0
  127. package/src/patient-registration/field/gender/gender-field.component.tsx +14 -9
  128. package/src/patient-registration/field/gender/gender-field.test.tsx +73 -33
  129. package/src/patient-registration/field/id/id-field.component.tsx +24 -23
  130. package/src/patient-registration/field/id/id-field.test.tsx +147 -0
  131. package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +12 -10
  132. package/src/patient-registration/field/id/identifier-selection.scss +12 -8
  133. package/src/patient-registration/field/name/name-field.component.tsx +10 -5
  134. package/src/patient-registration/field/obs/obs-field.component.tsx +59 -2
  135. package/src/patient-registration/field/obs/obs-field.test.tsx +133 -39
  136. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +3 -3
  137. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +141 -0
  138. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +105 -0
  139. package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +48 -0
  140. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +19 -22
  141. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +193 -0
  142. package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +90 -0
  143. package/src/patient-registration/form-manager.test.ts +91 -0
  144. package/src/patient-registration/form-manager.ts +49 -23
  145. package/src/patient-registration/input/basic-input/input/input.component.tsx +6 -2
  146. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +49 -0
  147. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.scss +5 -5
  148. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +164 -0
  149. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +73 -36
  150. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +335 -0
  151. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +3 -0
  152. package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +2 -11
  153. package/src/patient-registration/input/input.scss +17 -13
  154. package/src/patient-registration/patient-registration-context.ts +22 -11
  155. package/src/patient-registration/patient-registration-hooks.ts +158 -193
  156. package/src/patient-registration/patient-registration-utils.test.ts +33 -0
  157. package/src/patient-registration/patient-registration-utils.ts +11 -13
  158. package/src/patient-registration/patient-registration.component.tsx +87 -103
  159. package/src/patient-registration/{patient-registration.resource.testt.tsx → patient-registration.resource.test.tsx} +0 -4
  160. package/src/patient-registration/patient-registration.resource.ts +27 -3
  161. package/src/patient-registration/patient-registration.scss +27 -38
  162. package/src/patient-registration/patient-registration.test.tsx +579 -0
  163. package/src/patient-registration/patient-registration.types.ts +23 -25
  164. package/src/patient-registration/section/death-info/death-info-section.component.tsx +22 -17
  165. package/src/patient-registration/section/death-info/death-info-section.test.tsx +47 -0
  166. package/src/patient-registration/section/demographics/demographics-section.component.tsx +5 -5
  167. package/src/patient-registration/section/demographics/demographics-section.test.tsx +98 -0
  168. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +8 -7
  169. package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +113 -0
  170. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +28 -28
  171. package/src/patient-registration/section/patient-relationships/relationships.scss +4 -4
  172. package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
  173. package/src/patient-registration/section/section.component.tsx +1 -1
  174. package/src/patient-registration/section/section.scss +21 -1
  175. package/src/patient-registration/ui-components/overlay/overlay.scss +8 -8
  176. package/src/patient-registration/validation/{patient-registration-validation.test.tsx → patient-registration-validation.test.ts} +71 -23
  177. package/src/patient-registration/validation/patient-registration-validation.ts +123 -0
  178. package/src/resources-context.ts +14 -0
  179. package/src/root.component.tsx +3 -3
  180. package/src/routes.json +10 -24
  181. package/src/widgets/cancel-patient-edit.modal.tsx +33 -0
  182. package/src/widgets/cancel-patient-edit.test.tsx +22 -0
  183. package/src/widgets/delete-identifier-confirmation.modal.tsx +48 -0
  184. package/src/widgets/{delete-identifier-confirmation-modal.testt.tsx → delete-identifier-confirmation.test.tsx} +5 -7
  185. package/src/widgets/edit-patient-details-button.component.tsx +0 -1
  186. package/src/widgets/edit-patient-details-button.test.tsx +35 -0
  187. package/translations/am.json +43 -35
  188. package/translations/ar.json +41 -33
  189. package/translations/ar_SY.json +119 -0
  190. package/translations/bn.json +119 -0
  191. package/translations/de.json +119 -0
  192. package/translations/en.json +44 -42
  193. package/translations/en_US.json +119 -0
  194. package/translations/es.json +69 -57
  195. package/translations/es_MX.json +119 -0
  196. package/translations/fr.json +74 -58
  197. package/translations/he.json +44 -40
  198. package/translations/hi.json +119 -0
  199. package/translations/hi_IN.json +119 -0
  200. package/translations/id.json +119 -0
  201. package/translations/it.json +119 -0
  202. package/translations/ka.json +119 -0
  203. package/translations/km.json +44 -40
  204. package/translations/ku.json +119 -0
  205. package/translations/ky.json +119 -0
  206. package/translations/lg.json +119 -0
  207. package/translations/ne.json +119 -0
  208. package/translations/pl.json +119 -0
  209. package/translations/pt.json +119 -0
  210. package/translations/pt_BR.json +119 -0
  211. package/translations/qu.json +119 -0
  212. package/translations/ro_RO.json +119 -0
  213. package/translations/ru_RU.json +119 -0
  214. package/translations/si.json +119 -0
  215. package/translations/sw.json +119 -0
  216. package/translations/sw_KE.json +119 -0
  217. package/translations/tr.json +119 -0
  218. package/translations/tr_TR.json +119 -0
  219. package/translations/uk.json +119 -0
  220. package/translations/uz.json +119 -0
  221. package/translations/uz@Latn.json +119 -0
  222. package/translations/uz_UZ.json +119 -0
  223. package/translations/vi.json +119 -0
  224. package/translations/zh.json +45 -23
  225. package/translations/zh_CN.json +39 -17
  226. package/.turbo/turbo-build.log +0 -40
  227. package/dist/132.js +0 -1
  228. package/dist/197.js +0 -1
  229. package/dist/236.js +0 -1
  230. package/dist/236.js.map +0 -1
  231. package/dist/300.js +0 -1
  232. package/dist/335.js +0 -1
  233. package/dist/372.js +0 -1
  234. package/dist/372.js.map +0 -1
  235. package/dist/41.js +0 -2
  236. package/dist/41.js.map +0 -1
  237. package/dist/449.js +0 -1
  238. package/dist/449.js.map +0 -1
  239. package/dist/464.js +0 -1
  240. package/dist/464.js.map +0 -1
  241. package/dist/495.js +0 -1
  242. package/dist/495.js.map +0 -1
  243. package/dist/55.js +0 -1
  244. package/dist/56.js +0 -1
  245. package/dist/56.js.map +0 -1
  246. package/dist/621.js +0 -1
  247. package/dist/621.js.map +0 -1
  248. package/dist/629.js +0 -2
  249. package/dist/629.js.map +0 -1
  250. package/dist/652.js +0 -1
  251. package/dist/661.js +0 -1
  252. package/dist/757.js +0 -1
  253. package/dist/757.js.map +0 -1
  254. package/dist/828.js +0 -1
  255. package/dist/828.js.map +0 -1
  256. package/dist/830.js +0 -1
  257. package/dist/830.js.map +0 -1
  258. package/dist/831.js +0 -2
  259. package/dist/831.js.LICENSE.txt +0 -3
  260. package/dist/831.js.map +0 -1
  261. package/dist/876.js +0 -2
  262. package/dist/876.js.map +0 -1
  263. package/dist/879.js +0 -1
  264. package/dist/913.js +0 -2
  265. package/dist/913.js.map +0 -1
  266. package/dist/927.js +0 -1
  267. package/dist/927.js.map +0 -1
  268. package/dist/99.js +0 -1
  269. package/dist/ampath-esm-patient-registration-app.js +0 -1
  270. package/dist/ampath-esm-patient-registration-app.js.buildmanifest.json +0 -694
  271. package/dist/ampath-esm-patient-registration-app.js.map +0 -1
  272. package/src/patient-registration/date-util.ts +0 -52
  273. package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +0 -56
  274. package/src/patient-registration/validation/patient-registration-validation.tsx +0 -60
  275. package/src/patient-verification/assets/counties.json +0 -236
  276. package/src/patient-verification/assets/verification-assets.ts +0 -11
  277. package/src/patient-verification/patient-verification-hook.tsx +0 -176
  278. package/src/patient-verification/patient-verification-utils.ts +0 -179
  279. package/src/patient-verification/patient-verification.component.tsx +0 -124
  280. package/src/patient-verification/patient-verification.scss +0 -25
  281. package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +0 -72
  282. package/src/patient-verification/verification-modal/empty-prompt.component.tsx +0 -35
  283. package/src/patient-verification/verification-types.ts +0 -50
  284. package/src/widgets/cancel-patient-edit.component.tsx +0 -37
  285. package/src/widgets/delete-identifier-confirmation-modal.tsx +0 -41
  286. package/src/widgets/delete-identifier-modal.scss +0 -34
  287. /package/dist/{41.js.LICENSE.txt → 4041.js.LICENSE.txt} +0 -0
  288. /package/src/patient-registration/input/custom-input/identifier/{utils.testt.ts → utils.test.ts} +0 -0
@@ -0,0 +1,140 @@
1
+ import React from 'react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { screen } from '@testing-library/react';
4
+ import { Formik, Form, useFormikContext } from 'formik';
5
+ import { renderWithContext } from 'tools';
6
+ import { type Resources } from '../../../offline.resources';
7
+ import { PatientRegistrationContextProvider } from '../../patient-registration-context';
8
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
9
+ import { useAddressHierarchy, useOrderedAddressHierarchyLevels } from './address-hierarchy.resource';
10
+ import { type RegistrationConfig, esmPatientRegistrationSchema } from '../../../config-schema';
11
+ import { mockedAddressTemplate, mockedAddressOptions, mockedOrderedFields } from '__mocks__';
12
+ import { ResourcesContextProvider } from '../../../resources-context';
13
+ import AddressSearchComponent from './address-search.component';
14
+
15
+ const mockUseConfig = jest.mocked(useConfig<RegistrationConfig>);
16
+ const mockUseAddressHierarchy = jest.mocked(useAddressHierarchy);
17
+ const mockUseOrderedAddressHierarchyLevels = jest.mocked(useOrderedAddressHierarchyLevels);
18
+ const mockUseFormikContext = useFormikContext as jest.Mock;
19
+
20
+ jest.mock('./address-hierarchy.resource', () => ({
21
+ ...(jest.requireActual('./address-hierarchy.resource') as jest.Mock),
22
+ useAddressHierarchy: jest.fn(),
23
+ useOrderedAddressHierarchyLevels: jest.fn(),
24
+ }));
25
+
26
+ jest.mock('../../patient-registration.resource', () => ({
27
+ ...(jest.requireActual('../../../patient-registration.resource') as jest.Mock),
28
+ useAddressHierarchy: jest.fn(),
29
+ }));
30
+
31
+ jest.mock('formik', () => ({
32
+ ...(jest.requireActual('formik') as jest.Mock),
33
+ useFormikContext: jest.fn(() => ({})),
34
+ }));
35
+
36
+ const allFields = mockedAddressTemplate.lines
37
+ .flat()
38
+ .filter((field) => field.isToken === 'IS_ADDR_TOKEN')
39
+ .map(({ codeName, displayText }) => ({
40
+ id: codeName,
41
+ name: codeName,
42
+ label: displayText,
43
+ }));
44
+ const orderMap = Object.fromEntries(mockedOrderedFields.map((field, indx) => [field, indx]));
45
+ allFields.sort((existingField1, existingField2) => orderMap[existingField1.name] - orderMap[existingField2.name]);
46
+
47
+ async function renderAddressHierarchy(addressTemplate = mockedAddressTemplate) {
48
+ const mockResourcesContextValue = { addressTemplate } as unknown as Resources;
49
+
50
+ await renderWithContext(
51
+ <Formik initialValues={{}} onSubmit={null}>
52
+ <Form>
53
+ <PatientRegistrationContextProvider value={{ setFieldValue: jest.fn() } as any}>
54
+ <AddressSearchComponent addressLayout={allFields} />
55
+ </PatientRegistrationContextProvider>
56
+ </Form>
57
+ </Formik>,
58
+ ResourcesContextProvider,
59
+ mockResourcesContextValue,
60
+ );
61
+ }
62
+
63
+ const setFieldValue = jest.fn();
64
+
65
+ describe('Testing address search bar', () => {
66
+ beforeEach(() => {
67
+ mockUseConfig.mockReturnValue({
68
+ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema),
69
+ fieldConfigurations: {
70
+ address: {
71
+ useAddressHierarchy: {
72
+ enabled: true,
73
+ useQuickSearch: true,
74
+ searchAddressByLevel: false,
75
+ },
76
+ },
77
+ } as RegistrationConfig['fieldConfigurations'],
78
+ });
79
+ mockUseOrderedAddressHierarchyLevels.mockReturnValue({
80
+ orderedFields: mockedOrderedFields,
81
+ isLoadingFieldOrder: false,
82
+ errorFetchingFieldOrder: null,
83
+ });
84
+ mockUseFormikContext.mockReturnValue({
85
+ setFieldValue,
86
+ });
87
+ });
88
+
89
+ it('should render the search bar', () => {
90
+ mockUseAddressHierarchy.mockReturnValue({
91
+ addresses: [],
92
+ error: null,
93
+ isLoading: false,
94
+ });
95
+
96
+ renderAddressHierarchy();
97
+
98
+ const searchbox = screen.getByRole('searchbox');
99
+ expect(searchbox).toBeInTheDocument();
100
+
101
+ const ul = screen.queryByRole('list');
102
+ expect(ul).not.toBeInTheDocument();
103
+ });
104
+
105
+ it("should render only the results for the search term matched address' parents", async () => {
106
+ const user = userEvent.setup();
107
+
108
+ mockUseAddressHierarchy.mockReturnValue({
109
+ addresses: mockedAddressOptions,
110
+ error: null,
111
+ isLoading: false,
112
+ });
113
+
114
+ renderAddressHierarchy();
115
+
116
+ const searchString = 'nea';
117
+ const separator = ' > ';
118
+ const options: Set<string> = new Set();
119
+
120
+ mockedAddressOptions.forEach((address) => {
121
+ const values = address.split(separator);
122
+ values.forEach((val, index) => {
123
+ if (val.toLowerCase().includes(searchString.toLowerCase())) {
124
+ options.add(values.slice(0, index + 1).join(separator));
125
+ }
126
+ });
127
+ });
128
+
129
+ const addressOptions = [...options];
130
+ addressOptions.forEach(async (address) => {
131
+ const optionElement = screen.getByText(address);
132
+ expect(optionElement).toBeInTheDocument();
133
+ await user.click(optionElement);
134
+ const values = address.split(separator);
135
+ allFields.map(({ name }, index) => {
136
+ expect(setFieldValue).toHaveBeenCalledWith(`address.${name}`, values?.[index]);
137
+ });
138
+ });
139
+ });
140
+ });
@@ -0,0 +1,98 @@
1
+ import React, { useMemo } from 'react';
2
+ import classNames from 'classnames';
3
+ import { Field, useField } from 'formik';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { InlineNotification, Layer, Select, SelectItem, SelectSkeleton, TextInput } from '@carbon/react';
6
+ import { useConfig } from '@openmrs/esm-framework';
7
+ import { type RegistrationConfig } from '../../../config-schema';
8
+ import { useConceptAnswers } from '../field.resource';
9
+ import styles from '../field.scss';
10
+
11
+ export const CauseOfDeathField: React.FC = () => {
12
+ const { t } = useTranslation();
13
+ const { fieldConfigurations, freeTextFieldConceptUuid } = useConfig<RegistrationConfig>();
14
+ const [deathCause, deathCauseMeta] = useField('deathCause');
15
+
16
+ const conceptUuid = fieldConfigurations?.causeOfDeath?.conceptUuid;
17
+ const required = fieldConfigurations?.causeOfDeath?.required;
18
+
19
+ const {
20
+ data: conceptAnswers,
21
+ isLoading: isLoadingConceptAnswers,
22
+ error: errorLoadingConceptAnswers,
23
+ } = useConceptAnswers(conceptUuid);
24
+
25
+ const answers = useMemo(() => {
26
+ if (!isLoadingConceptAnswers && conceptAnswers) {
27
+ return conceptAnswers.map((answer) => ({ ...answer, label: answer.display }));
28
+ }
29
+ return [];
30
+ }, [conceptAnswers, isLoadingConceptAnswers]);
31
+
32
+ if (isLoadingConceptAnswers) {
33
+ return (
34
+ <div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
35
+ <h4 className={styles.productiveHeading02Light}>{t('causeOfDeathInputLabel', 'Cause of death')}</h4>
36
+ <SelectSkeleton />
37
+ </div>
38
+ );
39
+ }
40
+
41
+ return (
42
+ <div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
43
+ <h4 className={styles.productiveHeading02Light}>{t('causeOfDeathInputLabel', 'Cause of death')}</h4>
44
+ {errorLoadingConceptAnswers || !conceptUuid ? (
45
+ <InlineNotification
46
+ hideCloseButton
47
+ kind="error"
48
+ title={t('errorFetchingCodedCausesOfDeath', 'Error fetching coded causes of death')}
49
+ subtitle={t('refreshOrContactAdmin', 'Try refreshing the page or contact your system administrator')}
50
+ />
51
+ ) : (
52
+ <>
53
+ <Field name="deathCause">
54
+ {({ field, form: { touched, errors }, meta }) => {
55
+ return (
56
+ <Layer>
57
+ <Select
58
+ {...field}
59
+ id="deathCause"
60
+ invalid={errors.deathCause && touched.deathCause}
61
+ invalidText={errors.deathCause?.message}
62
+ labelText={t('causeOfDeathInputLabel', 'Cause of Death')}
63
+ name="deathCause"
64
+ required={required}>
65
+ <SelectItem id="empty-default-option" value={null} text={t('selectAnOption', 'Select an option')} />
66
+ {answers.map((answer) => (
67
+ <SelectItem id={answer.uuid} key={answer.uuid} text={answer.label} value={answer.uuid} />
68
+ ))}
69
+ </Select>
70
+ </Layer>
71
+ );
72
+ }}
73
+ </Field>
74
+ {deathCause.value === freeTextFieldConceptUuid && (
75
+ <div className={styles.nonCodedCauseOfDeath}>
76
+ <Field name="nonCodedCauseOfDeath">
77
+ {({ field, form: { touched, errors }, meta }) => {
78
+ return (
79
+ <Layer>
80
+ <TextInput
81
+ {...field}
82
+ id="nonCodedCauseOfDeath"
83
+ invalid={errors?.nonCodedCauseOfDeath && touched.nonCodedCauseOfDeath}
84
+ invalidText={errors?.nonCodedCauseOfDeath?.message}
85
+ labelText={t('nonCodedCauseOfDeath', 'Non-coded cause of death')}
86
+ placeholder={t('enterNonCodedCauseOfDeath', 'Enter non-coded cause of death')}
87
+ />
88
+ </Layer>
89
+ );
90
+ }}
91
+ </Field>
92
+ </div>
93
+ )}
94
+ </>
95
+ )}
96
+ </div>
97
+ );
98
+ };
@@ -1,24 +1,18 @@
1
- import { useConfig } from '@openmrs/esm-framework';
2
1
  import React from 'react';
3
- import { type RegistrationConfig } from '../../config-schema';
2
+ import { useConfig } from '@openmrs/esm-framework';
4
3
  import { AddressField } from './address/custom-address-field.component';
5
4
  import { ObsField } from './obs/obs-field.component';
6
5
  import { PersonAttributeField } from './person-attributes/person-attribute-field.component';
7
- import { useField } from 'formik';
6
+ import { type RegistrationConfig } from '../../config-schema';
8
7
 
9
8
  export interface CustomFieldProps {
10
9
  name: string;
11
10
  }
12
11
 
13
12
  export function CustomField({ name }: CustomFieldProps) {
14
- const config = useConfig() as RegistrationConfig;
13
+ const config = useConfig<RegistrationConfig>();
15
14
  const fieldDefinition = config.fieldDefinitions.filter((def) => def.id == name)[0];
16
15
 
17
- const [{ value }] = useField(`attributes.${fieldDefinition.showWhenExpression?.field}`);
18
- if (fieldDefinition.showWhenExpression && value !== fieldDefinition.showWhenExpression.value) {
19
- return null;
20
- }
21
-
22
16
  if (fieldDefinition.type === 'person attribute') {
23
17
  return <PersonAttributeField fieldDefinition={fieldDefinition} />;
24
18
  } else if (fieldDefinition.type === 'obs') {
@@ -0,0 +1,84 @@
1
+ import React, { useCallback } from 'react';
2
+ import classNames from 'classnames';
3
+ import dayjs from 'dayjs';
4
+ import { Layer, SelectItem, TimePicker, TimePickerSelect } from '@carbon/react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { useField } from 'formik';
7
+ import { OpenmrsDatePicker } from '@openmrs/esm-framework';
8
+ import { usePatientRegistrationContext } from '../../patient-registration-context';
9
+ import type { FormValues } from '../../patient-registration.types';
10
+ import styles from '../field.scss';
11
+
12
+ export const DateAndTimeOfDeathField: React.FC = () => {
13
+ const { t } = useTranslation();
14
+
15
+ return (
16
+ <div className={classNames(styles.dodField, styles.halfWidthInDesktopView)}>
17
+ <h4 className={styles.productiveHeading02Light}>{t('deathDateInputLabel', 'Date of Death')}</h4>
18
+ <span>
19
+ <DeathDateField />
20
+ <DeathTimeField />
21
+ </span>
22
+ </div>
23
+ );
24
+ };
25
+
26
+ function DeathDateField() {
27
+ const { values, setFieldValue } = usePatientRegistrationContext();
28
+ const [deathDate, deathDateMeta] = useField<keyof FormValues>('deathDate');
29
+ const { t } = useTranslation();
30
+ const today = dayjs().hour(23).minute(59).second(59).toDate();
31
+ const onDateChange = useCallback(
32
+ (selectedDate: Date) => {
33
+ setFieldValue(
34
+ 'deathDate',
35
+ selectedDate ? dayjs(selectedDate).hour(0).minute(0).second(0).millisecond(0).toDate() : undefined,
36
+ );
37
+ },
38
+ [setFieldValue],
39
+ );
40
+
41
+ return (
42
+ <Layer>
43
+ <OpenmrsDatePicker
44
+ {...deathDate}
45
+ id="deathDate"
46
+ invalidText={t(deathDateMeta.error)}
47
+ invalid={!!(deathDateMeta.touched && deathDateMeta.error)}
48
+ isRequired={values.isDead}
49
+ labelText={t('deathDateInputLabel', 'Date of death')}
50
+ maxDate={today}
51
+ onChange={onDateChange}
52
+ />
53
+ </Layer>
54
+ );
55
+ }
56
+
57
+ function DeathTimeField() {
58
+ const { t } = useTranslation();
59
+ const [deathTimeField, deathTimeMeta] = useField<keyof FormValues>('deathTime');
60
+ const [deathTimeFormatField, deathTimeFormatMeta] = useField<keyof FormValues>('deathTimeFormat');
61
+
62
+ return (
63
+ <Layer>
64
+ <TimePicker
65
+ {...deathTimeField}
66
+ id="time-picker"
67
+ labelText={t('timeOfDeathInputLabel', 'Time of death (hh:mm)')}
68
+ className={styles.timeOfDeathField}
69
+ pattern="^(1[0-2]|0?[1-9]):([0-5]?[0-9])$"
70
+ invalid={!!(deathTimeMeta.touched && deathTimeMeta.error)}
71
+ invalidText={t(deathTimeMeta.error)}>
72
+ <TimePickerSelect
73
+ {...deathTimeFormatField}
74
+ id="time-format-picker"
75
+ aria-label={t('timeFormat', 'Time Format')}
76
+ invalid={!!deathTimeFormatMeta.touched && deathTimeFormatMeta.error}
77
+ invalidText={t(deathTimeFormatMeta.error)}>
78
+ <SelectItem value="AM" text="AM" />
79
+ <SelectItem value="PM" text="PM" />
80
+ </TimePickerSelect>
81
+ </TimePicker>
82
+ </Layer>
83
+ );
84
+ }
@@ -1,10 +1,9 @@
1
- import React, { type ChangeEvent, useCallback, useContext } from 'react';
2
- import { ContentSwitcher, DatePicker, DatePickerInput, Layer, Switch, TextInput } from '@carbon/react';
1
+ import React, { type ChangeEvent, useCallback } from 'react';
2
+ import { ContentSwitcher, Layer, Switch, TextInput } from '@carbon/react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { useField } from 'formik';
5
- import { generateFormatting } from '../../date-util';
6
- import { PatientRegistrationContext } from '../../patient-registration-context';
7
5
  import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework';
6
+ import { usePatientRegistrationContext } from '../../patient-registration-context';
8
7
  import { type RegistrationConfig } from '../../../config-schema';
9
8
  import styles from '../field.scss';
10
9
 
@@ -31,8 +30,7 @@ export const DobField: React.FC = () => {
31
30
  const [birthdate, birthdateMeta] = useField('birthdate');
32
31
  const [yearsEstimated, yearsEstimateMeta] = useField('yearsEstimated');
33
32
  const [monthsEstimated, monthsEstimateMeta] = useField('monthsEstimated');
34
- const { setFieldValue } = useContext(PatientRegistrationContext);
35
- const { format, placeHolder, dateFormat } = generateFormatting(['d', 'm', 'Y'], '/');
33
+ const { setFieldValue, setFieldTouched } = usePatientRegistrationContext();
36
34
  const today = new Date();
37
35
 
38
36
  const onToggle = useCallback(
@@ -41,15 +39,17 @@ export const DobField: React.FC = () => {
41
39
  setFieldValue('birthdate', '');
42
40
  setFieldValue('yearsEstimated', 0);
43
41
  setFieldValue('monthsEstimated', '');
42
+ setFieldTouched('birthdateEstimated', true, false);
44
43
  },
45
- [setFieldValue],
44
+ [setFieldTouched, setFieldValue],
46
45
  );
47
46
 
48
47
  const onDateChange = useCallback(
49
48
  (birthdate: Date) => {
50
49
  setFieldValue('birthdate', birthdate);
50
+ setFieldTouched('birthdate', true, false);
51
51
  },
52
- [setFieldValue],
52
+ [setFieldValue, setFieldTouched],
53
53
  );
54
54
 
55
55
  const onEstimatedYearsChange = useCallback(
@@ -82,7 +82,10 @@ export const DobField: React.FC = () => {
82
82
  setFieldValue('yearsEstimated', years);
83
83
  setFieldValue('monthsEstimated', months > 0 ? months : '');
84
84
  setFieldValue('birthdate', calcBirthdate(years, months, dateOfBirth));
85
- }, [setFieldValue, monthsEstimateMeta, yearsEstimateMeta, dateOfBirth]);
85
+ setFieldTouched('yearsEstimated', true, false);
86
+ setFieldTouched('monthsEstimated', true, false);
87
+ setFieldTouched('birthdate', true, false);
88
+ }, [setFieldValue, setFieldTouched, monthsEstimateMeta, yearsEstimateMeta, dateOfBirth]);
86
89
 
87
90
  return (
88
91
  <div className={styles.halfWidthInDesktopView}>
@@ -90,7 +93,7 @@ export const DobField: React.FC = () => {
90
93
  {(allowEstimatedBirthDate || dobUnknown) && (
91
94
  <div className={styles.dobField}>
92
95
  <div className={styles.dobContentSwitcherLabel}>
93
- <span className={styles.label01}>{t('dobToggleLabelText', 'Date of Birth Known?')}</span>
96
+ <span className={styles.label01}>{t('dobToggleLabelText', 'Date of birth known?')}</span>
94
97
  </div>
95
98
  <ContentSwitcher onChange={onToggle} selectedIndex={dobUnknown ? 1 : 0}>
96
99
  <Switch name="known" text={t('yes', 'Yes')} />
@@ -103,56 +106,58 @@ export const DobField: React.FC = () => {
103
106
  <div className={styles.dobField}>
104
107
  <OpenmrsDatePicker
105
108
  id="birthdate"
109
+ data-testid="birthdate"
106
110
  {...birthdate}
107
- dateFormat={dateFormat}
108
111
  onChange={onDateChange}
109
- //maxDate={today}
110
- labelText={t('dateOfBirthLabelText', 'Date of Birth')}
111
- invalid={!!(birthdateMeta.touched && birthdateMeta.error)}
112
- invalidText={birthdateMeta.error && t(birthdateMeta.error)}
112
+ onBlur={() => setFieldTouched('birthdate', true, false)}
113
+ maxDate={today}
114
+ labelText={t('dateOfBirthLabelText', 'Date of birth')}
115
+ isInvalid={!!(birthdateMeta.touched && birthdateMeta.error)}
116
+ invalidText={t(birthdateMeta.error)}
113
117
  value={birthdate.value}
114
- carbonOptions={{
115
- placeholder: placeHolder,
116
- }}
117
118
  />
118
119
  </div>
119
120
  ) : (
120
121
  <div className={styles.grid}>
121
122
  <div className={styles.dobField}>
122
- <Layer>
123
- <TextInput
124
- id="yearsEstimated"
125
- type="number"
126
- name={yearsEstimated.name}
127
- onChange={onEstimatedYearsChange}
128
- labelText={t('estimatedAgeInYearsLabelText', 'Estimated age in years')}
129
- invalid={!!(yearsEstimateMeta.touched && yearsEstimateMeta.error)}
130
- invalidText={yearsEstimateMeta.error && t(yearsEstimateMeta.error)}
131
- value={yearsEstimated.value}
132
- min={0}
133
- required
134
- {...yearsEstimated}
135
- onBlur={updateBirthdate}
136
- />
137
- </Layer>
123
+ <TextInput
124
+ id="yearsEstimated"
125
+ type="number"
126
+ name={yearsEstimated.name}
127
+ onChange={onEstimatedYearsChange}
128
+ labelText={t('estimatedAgeInYearsLabelText', 'Estimated age in years')}
129
+ invalid={!!(yearsEstimateMeta.touched && yearsEstimateMeta.error)}
130
+ invalidText={yearsEstimateMeta.error && t(yearsEstimateMeta.error)}
131
+ value={yearsEstimated.value}
132
+ min={0}
133
+ required
134
+ {...yearsEstimated}
135
+ onBlur={(e) => {
136
+ yearsEstimated.onBlur(e);
137
+ setFieldTouched('yearsEstimated', true, false);
138
+ updateBirthdate();
139
+ }}
140
+ />
138
141
  </div>
139
142
  <div className={styles.dobField}>
140
- <Layer>
141
- <TextInput
142
- id="monthsEstimated"
143
- type="number"
144
- name={monthsEstimated.name}
145
- onChange={onEstimatedMonthsChange}
146
- labelText={t('estimatedAgeInMonthsLabelText', 'Estimated age in months')}
147
- invalid={!!(monthsEstimateMeta.touched && monthsEstimateMeta.error)}
148
- invalidText={monthsEstimateMeta.error && t(monthsEstimateMeta.error)}
149
- value={monthsEstimated.value}
150
- min={0}
151
- {...monthsEstimated}
152
- required={!yearsEstimateMeta.value}
153
- onBlur={updateBirthdate}
154
- />
155
- </Layer>
143
+ <TextInput
144
+ id="monthsEstimated"
145
+ type="number"
146
+ name={monthsEstimated.name}
147
+ onChange={onEstimatedMonthsChange}
148
+ labelText={t('estimatedAgeInMonthsLabelText', 'Estimated age in months')}
149
+ invalid={!!(monthsEstimateMeta.touched && monthsEstimateMeta.error)}
150
+ invalidText={monthsEstimateMeta.error && t(monthsEstimateMeta.error)}
151
+ value={monthsEstimated.value}
152
+ min={0}
153
+ {...monthsEstimated}
154
+ required={!yearsEstimateMeta.value}
155
+ onBlur={(e) => {
156
+ monthsEstimated.onBlur(e);
157
+ setFieldTouched('monthsEstimated', true, false);
158
+ updateBirthdate();
159
+ }}
160
+ />
156
161
  </div>
157
162
  </div>
158
163
  )}
@@ -0,0 +1,90 @@
1
+ import React from 'react';
2
+ import { Formik, Form } from 'formik';
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework';
6
+ import { type RegistrationConfig, esmPatientRegistrationSchema } from '../../../config-schema';
7
+ import { PatientRegistrationContextProvider } from '../../patient-registration-context';
8
+ import { initialFormValues } from '../../patient-registration.component';
9
+ import { DobField } from './dob.component';
10
+
11
+ const mockUseConfig = jest.mocked(useConfig<RegistrationConfig>);
12
+
13
+ describe('Dob', () => {
14
+ beforeEach(() => {
15
+ mockUseConfig.mockReturnValue({
16
+ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema),
17
+ fieldConfigurations: {
18
+ dateOfBirth: {
19
+ allowEstimatedDateOfBirth: true,
20
+ useEstimatedDateOfBirth: { enabled: true, dayOfMonth: 0, month: 0 },
21
+ },
22
+ } as RegistrationConfig['fieldConfigurations'],
23
+ });
24
+ });
25
+
26
+ it('renders the fields in the birth section of the registration form', async () => {
27
+ render(
28
+ <Formik initialValues={{ birthdate: '' }} onSubmit={() => {}}>
29
+ <Form>
30
+ <PatientRegistrationContextProvider
31
+ value={{
32
+ identifierTypes: [],
33
+ values: initialFormValues,
34
+ validationSchema: null,
35
+ inEditMode: false,
36
+ setFieldValue: () => {},
37
+ setCapturePhotoProps: (value) => {},
38
+ setFieldTouched: () => {},
39
+ currentPhoto: '',
40
+ isOffline: false,
41
+ initialFormValues: initialFormValues,
42
+ }}>
43
+ <DobField />
44
+ </PatientRegistrationContextProvider>
45
+ </Form>
46
+ </Formik>,
47
+ );
48
+
49
+ expect(screen.getByRole('heading', { name: /birth/i })).toBeInTheDocument();
50
+ expect(screen.getByText(/date of birth known?/i)).toBeInTheDocument();
51
+ expect(screen.getByRole('tab', { name: /no/i })).toBeInTheDocument();
52
+ expect(screen.getByRole('tab', { name: /yes/i })).toBeInTheDocument();
53
+ expect(screen.getByRole('tab', { name: /yes/i })).toHaveAttribute('aria-selected', 'true');
54
+ expect(screen.getByRole('tab', { name: /no/i })).toHaveAttribute('aria-selected', 'false');
55
+ expect(screen.getByLabelText(/date of birth/i)).toBeInTheDocument();
56
+ });
57
+
58
+ it('typing in the date picker input sets the date of birth', async () => {
59
+ const user = userEvent.setup();
60
+
61
+ render(
62
+ <Formik initialValues={{ birthdate: '' }} onSubmit={() => {}}>
63
+ <Form>
64
+ <PatientRegistrationContextProvider
65
+ value={{
66
+ identifierTypes: [],
67
+ values: initialFormValues,
68
+ validationSchema: null,
69
+ inEditMode: false,
70
+ setFieldValue: () => {},
71
+ setCapturePhotoProps: (value) => {},
72
+ currentPhoto: '',
73
+ isOffline: false,
74
+ initialFormValues: initialFormValues,
75
+ setFieldTouched: () => {},
76
+ }}>
77
+ <DobField />
78
+ </PatientRegistrationContextProvider>
79
+ </Form>
80
+ </Formik>,
81
+ );
82
+
83
+ const dateOfBirthInput = screen.getByLabelText(/date of birth/i);
84
+ expect(dateOfBirthInput).toBeInTheDocument();
85
+ await user.clear(dateOfBirthInput);
86
+ await user.type(dateOfBirthInput, '10/10/2022');
87
+ // FIXME: Make the date input work
88
+ // expect(dateOfBirthInput).toHaveValue('10/10/2022');
89
+ });
90
+ });
@@ -1,12 +1,14 @@
1
1
  import React from 'react';
2
- import { NameField } from './name/name-field.component';
3
- import { GenderField } from './gender/gender-field.component';
4
- import { Identifiers } from './id/id-field.component';
5
- import { DobField } from './dob/dob.component';
6
2
  import { reportError, useConfig } from '@openmrs/esm-framework';
7
3
  import { builtInFields, type RegistrationConfig } from '../../config-schema';
8
- import { CustomField } from './custom-field.component';
9
4
  import { AddressComponent } from './address/address-field.component';
5
+ import { CauseOfDeathField } from './cause-of-death/cause-of-death.component';
6
+ import { CustomField } from './custom-field.component';
7
+ import { DateAndTimeOfDeathField } from './date-and-time-of-death/date-and-time-of-death.component';
8
+ import { DobField } from './dob/dob.component';
9
+ import { GenderField } from './gender/gender-field.component';
10
+ import { Identifiers } from './id/id-field.component';
11
+ import { NameField } from './name/name-field.component';
10
12
  import { PhoneField } from './phone/phone-field.component';
11
13
 
12
14
  export interface FieldProps {
@@ -14,7 +16,7 @@ export interface FieldProps {
14
16
  }
15
17
 
16
18
  export function Field({ name }: FieldProps) {
17
- const config = useConfig() as RegistrationConfig;
19
+ const config = useConfig<RegistrationConfig>();
18
20
  if (
19
21
  !(builtInFields as ReadonlyArray<string>).includes(name) &&
20
22
  !config.fieldDefinitions.some((def) => def.id == name)
@@ -35,6 +37,10 @@ export function Field({ name }: FieldProps) {
35
37
  return <GenderField />;
36
38
  case 'dob':
37
39
  return <DobField />;
40
+ case 'dateAndTimeOfDeath':
41
+ return <DateAndTimeOfDeathField />;
42
+ case 'causeOfDeath':
43
+ return <CauseOfDeathField />;
38
44
  case 'address':
39
45
  return <AddressComponent />;
40
46
  case 'id':