@kenyaemr/esm-patient-registration-app 8.1.1-pre.124 → 8.1.2-pre.152

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 (127) hide show
  1. package/.turbo/turbo-build.log +21 -23
  2. package/dist/108.js +1 -1
  3. package/dist/130.js +1 -1
  4. package/dist/130.js.map +1 -1
  5. package/dist/173.js +2 -0
  6. package/dist/{895.js.LICENSE.txt → 173.js.LICENSE.txt} +25 -0
  7. package/dist/173.js.map +1 -0
  8. package/dist/236.js +1 -0
  9. package/dist/240.js +1 -0
  10. package/dist/261.js +1 -0
  11. package/dist/271.js +1 -1
  12. package/dist/272.js +1 -0
  13. package/dist/319.js +1 -1
  14. package/dist/336.js +1 -0
  15. package/dist/371.js +1 -0
  16. package/dist/371.js.map +1 -0
  17. package/dist/378.js +1 -0
  18. package/dist/460.js +1 -1
  19. package/dist/501.js +1 -1
  20. package/dist/501.js.map +1 -1
  21. package/dist/539.js +1 -0
  22. package/dist/566.js +1 -0
  23. package/dist/574.js +1 -1
  24. package/dist/623.js +1 -0
  25. package/dist/623.js.map +1 -0
  26. package/dist/644.js +1 -1
  27. package/dist/652.js +1 -0
  28. package/dist/657.js +1 -0
  29. package/dist/657.js.map +1 -0
  30. package/dist/673.js +1 -0
  31. package/dist/705.js +1 -0
  32. package/dist/711.js +1 -0
  33. package/dist/727.js +1 -0
  34. package/dist/737.js +1 -0
  35. package/dist/744.js +1 -0
  36. package/dist/757.js +1 -1
  37. package/dist/759.js +1 -0
  38. package/dist/759.js.map +1 -0
  39. package/dist/76.js +1 -1
  40. package/dist/788.js +1 -1
  41. package/dist/807.js +1 -1
  42. package/dist/833.js +1 -1
  43. package/dist/899.js +1 -0
  44. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  45. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +445 -93
  46. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  47. package/dist/main.js +1 -1
  48. package/dist/main.js.LICENSE.txt +25 -0
  49. package/dist/main.js.map +1 -1
  50. package/dist/routes.json +1 -1
  51. package/package-lock.json +6400 -0
  52. package/package.json +4 -4
  53. package/src/client-registry/hie-client-registry/dependants/dependants.component.tsx +46 -0
  54. package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +38 -8
  55. package/src/client-registry/hie-client-registry/hie-resource.ts +126 -21
  56. package/src/client-registry/hie-client-registry/hie-types.ts +102 -0
  57. package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +78 -62
  58. package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +55 -3
  59. package/src/client-registry/hie-client-registry/modal/hie-otp-verification-form.component.tsx +88 -0
  60. package/src/client-registry/hie-client-registry/modal/hie-patient-detail-preview.component.tsx +77 -0
  61. package/src/client-registry/hie-client-registry/patient-info/patient-info.component.tsx +17 -0
  62. package/src/config-schema.ts +30 -2
  63. package/src/patient-registration/field/address/address-search.component.tsx +5 -2
  64. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
  65. package/src/patient-registration/field/dob/dob.component.tsx +1 -1
  66. package/src/patient-registration/field/field.scss +4 -4
  67. package/src/patient-registration/field/gender/gender-field.component.tsx +6 -2
  68. package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +3 -3
  69. package/src/patient-registration/field/name/name-field.component.tsx +2 -2
  70. package/src/patient-registration/field/obs/obs-field.component.tsx +9 -5
  71. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +4 -3
  72. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +22 -11
  73. package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +76 -27
  74. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +1 -1
  75. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +12 -4
  76. package/src/patient-registration/field/person-attributes/useUpdateIdentifierRequirement.tsx +83 -0
  77. package/src/patient-registration/form-manager.test.ts +4 -1
  78. package/src/patient-registration/form-manager.ts +0 -1
  79. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +52 -62
  80. package/src/patient-registration/mpi/mpi-patient.resource.ts +21 -0
  81. package/src/patient-registration/patient-registration-hooks.ts +90 -25
  82. package/src/patient-registration/patient-registration-utils.test.ts +33 -0
  83. package/src/patient-registration/patient-registration-utils.ts +63 -13
  84. package/src/patient-registration/patient-registration.component.tsx +17 -2
  85. package/src/patient-registration/patient-registration.test.tsx +442 -56
  86. package/src/patient-registration/section/demographics/demographics-section.component.tsx +3 -3
  87. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +1 -1
  88. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +28 -28
  89. package/src/widgets/cancel-patient-edit.modal.tsx +2 -0
  90. package/src/widgets/cancel-patient-edit.scss +29 -0
  91. package/src/widgets/delete-identifier-confirmation.modal.tsx +2 -0
  92. package/src/widgets/delete-identifier-confirmation.scss +29 -0
  93. package/translations/am.json +1 -0
  94. package/translations/ar.json +6 -4
  95. package/translations/de.json +118 -0
  96. package/translations/en.json +17 -0
  97. package/translations/es.json +2 -0
  98. package/translations/fr.json +1 -0
  99. package/translations/he.json +1 -0
  100. package/translations/hi.json +118 -0
  101. package/translations/hi_IN.json +118 -0
  102. package/translations/id.json +118 -0
  103. package/translations/it.json +118 -0
  104. package/translations/km.json +1 -0
  105. package/translations/ne.json +118 -0
  106. package/translations/pt.json +118 -0
  107. package/translations/pt_BR.json +118 -0
  108. package/translations/qu.json +118 -0
  109. package/translations/si.json +118 -0
  110. package/translations/sw.json +118 -0
  111. package/translations/sw_KE.json +118 -0
  112. package/translations/tr.json +118 -0
  113. package/translations/tr_TR.json +118 -0
  114. package/translations/uk.json +118 -0
  115. package/translations/vi.json +118 -0
  116. package/translations/zh.json +3 -1
  117. package/translations/zh_CN.json +2 -0
  118. package/dist/10.js +0 -1
  119. package/dist/10.js.map +0 -1
  120. package/dist/250.js +0 -1
  121. package/dist/250.js.map +0 -1
  122. package/dist/662.js +0 -1
  123. package/dist/662.js.map +0 -1
  124. package/dist/753.js +0 -1
  125. package/dist/753.js.map +0 -1
  126. package/dist/895.js +0 -2
  127. package/dist/895.js.map +0 -1
@@ -1,19 +1,43 @@
1
- import React from 'react';
2
- import { Field } from 'formik';
1
+ import React, { useContext, useEffect } from 'react';
2
+ import { Field, type FieldProps } from 'formik';
3
3
  import { Layer, Select, SelectItem } from '@carbon/react';
4
- import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
5
4
  import { useTranslation } from 'react-i18next';
6
- import styles from './../field.scss';
7
5
  import classNames from 'classnames';
6
+ import { PatientRegistrationContext } from '../../patient-registration-context';
7
+ import styles from './../field.scss';
8
+ import { ResourcesContext } from '../../../offline.resources';
9
+ import useUpdateIdentifierRequirement from './useUpdateIdentifierRequirement';
8
10
 
9
- type CustomPersonAttributeFieldProps = {
11
+ interface PersonAttributeTypeResponse {
12
+ uuid: string;
13
+ display?: string;
14
+ }
15
+
16
+ interface ConceptAnswer {
17
+ uuid?: string;
18
+ name?: string;
19
+ label?: string;
20
+ showServiceExpression?: {
21
+ attributeTypeUuid: string;
22
+ value: string;
23
+ };
24
+ }
25
+
26
+ interface CustomPersonAttributeFieldProps {
10
27
  id: string;
11
28
  personAttributeType: PersonAttributeTypeResponse;
12
29
  answerConceptSetUuid: string;
13
30
  label?: string;
14
- customConceptAnswers: Array<{ uuid: string; label?: string }>;
31
+ customConceptAnswers: ConceptAnswer[];
15
32
  required: boolean;
16
- };
33
+ }
34
+
35
+ interface PatientRegistrationContextType {
36
+ setFieldValue: (field: string, value: any) => void;
37
+ values: {
38
+ attributes?: Record<string, string>;
39
+ };
40
+ }
17
41
 
18
42
  const CustomPersonAttributeField: React.FC<CustomPersonAttributeFieldProps> = ({
19
43
  personAttributeType,
@@ -24,30 +48,55 @@ const CustomPersonAttributeField: React.FC<CustomPersonAttributeFieldProps> = ({
24
48
  }) => {
25
49
  const { t } = useTranslation();
26
50
  const fieldName = `attributes.${personAttributeType.uuid}`;
51
+ const { setFieldValue, values } = useContext(PatientRegistrationContext);
52
+ useUpdateIdentifierRequirement(setFieldValue, values);
53
+ // TODO: Improve this logic
54
+ const filteredCustomConceptAnswers = customConceptAnswers.filter((answer) => {
55
+ const showExpression = answer.showServiceExpression;
56
+ if (!showExpression) return true;
57
+
58
+ const attributeValue = values?.attributes?.[showExpression.attributeTypeUuid];
59
+ const answerCadreId = answer.name;
60
+
61
+ if (answerCadreId == null) return true;
62
+
63
+ return showExpression.value.toLowerCase() === attributeValue?.toLowerCase();
64
+ });
65
+
66
+ useEffect(() => {
67
+ return () => {
68
+ setFieldValue(fieldName, '');
69
+ };
70
+ }, [fieldName, setFieldValue]);
71
+
72
+ const renderSelect = ({ field, form: { touched, errors } }: FieldProps) => {
73
+ const hasError = errors[fieldName] && touched[fieldName];
74
+ const displayLabel = label ?? personAttributeType?.display ?? '';
75
+
76
+ return (
77
+ <Select
78
+ id={id}
79
+ name={`person-attribute-${personAttributeType.uuid}`}
80
+ labelText={displayLabel}
81
+ invalid={Boolean(hasError)}
82
+ required={required}
83
+ {...field}>
84
+ <SelectItem value="" text={t('selectAnOption', 'Select an option')} />
85
+ {filteredCustomConceptAnswers.map((answer) => (
86
+ <SelectItem
87
+ key={answer.uuid ?? answer.name}
88
+ value={answer.uuid ?? answer.name ?? ''}
89
+ text={answer.label ?? answer.uuid ?? answer.name ?? ''}
90
+ />
91
+ ))}
92
+ </Select>
93
+ );
94
+ };
27
95
 
28
96
  return (
29
97
  <div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
30
98
  <Layer>
31
- <Field name={fieldName}>
32
- {({ field, form: { touched, errors }, meta }) => {
33
- return (
34
- <>
35
- <Select
36
- id={id}
37
- name={`person-attribute-${personAttributeType.uuid}`}
38
- labelText={label ?? personAttributeType?.display}
39
- invalid={errors[fieldName] && touched[fieldName]}
40
- required={required}
41
- {...field}>
42
- <SelectItem value={''} text={t('selectAnOption', 'Select an option')} />
43
- {customConceptAnswers.map((answer) => (
44
- <SelectItem key={answer.uuid} value={answer.uuid} text={answer.uuid} />
45
- ))}
46
- </Select>
47
- </>
48
- );
49
- }}
50
- </Field>
99
+ <Field name={fieldName}>{renderSelect}</Field>
51
100
  </Layer>
52
101
  </div>
53
102
  );
@@ -25,7 +25,7 @@ export function LocationPersonAttributeField({
25
25
  const { t } = useTranslation();
26
26
  const fieldName = `attributes.${personAttributeType.uuid}`;
27
27
  const [field, meta, { setValue }] = useField(`attributes.${personAttributeType.uuid}`);
28
- const [searchQuery, setSearchQuery] = useState<string>('');
28
+ const [searchQuery, setSearchQuery] = useState('');
29
29
  const { locations, isLoading, loadingNewData } = useLocations(locationTag || null, searchQuery);
30
30
  const prevLocationOptions = useRef([]);
31
31
 
@@ -1,13 +1,20 @@
1
1
  import React from 'react';
2
2
  import { Form, Formik } from 'formik';
3
3
  import { render, screen } from '@testing-library/react';
4
+ import { type FieldDefinition } from '../../../config-schema';
4
5
  import { usePersonAttributeType } from './person-attributes.resource';
5
6
  import { useConceptAnswers } from '../field.resource';
6
- import { type FieldDefinition } from '../../../config-schema';
7
7
  import { PersonAttributeField } from './person-attribute-field.component';
8
8
 
9
- jest.mock('./person-attributes.resource');
10
- jest.mock('../field.resource');
9
+ jest.mock('./person-attributes.resource', () => ({
10
+ ...jest.requireActual('./person-attributes.resource'),
11
+ usePersonAttributeType: jest.fn(),
12
+ }));
13
+
14
+ jest.mock('../field.resource', () => ({
15
+ ...jest.requireActual('../field.resource'),
16
+ useConceptAnswers: jest.fn(),
17
+ }));
11
18
 
12
19
  const mockUsePersonAttributeType = jest.mocked(usePersonAttributeType);
13
20
  const mockUseConceptAnswers = jest.mocked(useConceptAnswers);
@@ -92,6 +99,7 @@ describe('PersonAttributeField', () => {
92
99
  { uuid: '1', display: 'Option 1' },
93
100
  { uuid: '2', display: 'Option 2' },
94
101
  ],
102
+ error: null,
95
103
  isLoading: false,
96
104
  });
97
105
 
@@ -180,6 +188,6 @@ describe('PersonAttributeField', () => {
180
188
  );
181
189
 
182
190
  await screen.findByRole('heading', { name: /attribute/i });
183
- expect(screen.queryByLabelText(/Referred by/i)).not.toBeInTheDocument();
191
+ expect(screen.queryByLabelText(/referred by/i)).not.toBeInTheDocument();
184
192
  });
185
193
  });
@@ -0,0 +1,83 @@
1
+ import { useEffect, useCallback, useRef, useMemo, useContext } from 'react';
2
+ import { deleteIdentifierType, initializeIdentifier } from '../id/id-field.component';
3
+ import { ResourcesContext } from '../../../offline.resources';
4
+
5
+ const useUpdateIdentifierRequirement = (setFieldValue, values) => {
6
+ const { identifierTypes = [] } = useContext(ResourcesContext);
7
+ const previousAttributes = useRef(values.attributes);
8
+ const previousIdentifiers = useRef(values.identifiers);
9
+
10
+ const publicationNumberIdentifier = useMemo(
11
+ () => identifierTypes?.find((identifier) => identifier.name === 'Publication Number'),
12
+ [identifierTypes],
13
+ );
14
+
15
+ // Memoize the civilian check
16
+ const isCivilian = useMemo(() => Object.values(values.attributes ?? {}).includes('Civilian'), [values.attributes]);
17
+
18
+ // Memoize the identifier initialization logic
19
+ const initializePublicationIdentifier = useCallback(
20
+ (currentIdentifiers) => {
21
+ if (!publicationNumberIdentifier) {
22
+ console.warn('Publication Number identifier type not found');
23
+ return null;
24
+ }
25
+
26
+ const initializedIdentifier = initializeIdentifier(
27
+ publicationNumberIdentifier,
28
+ currentIdentifiers[publicationNumberIdentifier.uuid],
29
+ );
30
+
31
+ return initializedIdentifier;
32
+ },
33
+ [publicationNumberIdentifier],
34
+ );
35
+ // Only run the effect if isCivilian is true
36
+ useEffect(() => {
37
+ // Skip if we don't have the required data
38
+ if (!values.attributes || !publicationNumberIdentifier) {
39
+ return;
40
+ }
41
+
42
+ // Check if relevant values have actually changed
43
+ const attributesChanged = previousAttributes.current !== values.attributes;
44
+ const identifiersChanged = previousIdentifiers.current !== values.identifiers;
45
+
46
+ if (!attributesChanged && !identifiersChanged) {
47
+ return;
48
+ }
49
+
50
+ // Update refs
51
+ previousAttributes.current = values.attributes;
52
+ previousIdentifiers.current = values.identifiers;
53
+ const isDependant = Object.values(values.attributes ?? {}).includes('Dependant');
54
+ // Only proceed if the user is a civilian
55
+ if (isCivilian && isDependant) {
56
+ const initializedIdentifier = initializePublicationIdentifier(values.identifiers);
57
+
58
+ // check if values.identifiers already has the publication number identifier
59
+ const hasPublicationNumberIdentifier = values.identifiers[publicationNumberIdentifier.fieldName];
60
+
61
+ if (initializedIdentifier && !hasPublicationNumberIdentifier) {
62
+ setFieldValue('identifiers', {
63
+ ...values.identifiers,
64
+ [publicationNumberIdentifier.fieldName]: { ...initializedIdentifier, required: true },
65
+ });
66
+ }
67
+ } else {
68
+ // Before deleting the publication number identifier, check if it exists
69
+ if (values.identifiers[publicationNumberIdentifier.fieldName]) {
70
+ setFieldValue('identifiers', deleteIdentifierType(values.identifiers, publicationNumberIdentifier.fieldName));
71
+ }
72
+ }
73
+ }, [
74
+ values.attributes,
75
+ values.identifiers,
76
+ isCivilian,
77
+ publicationNumberIdentifier,
78
+ initializePublicationIdentifier,
79
+ setFieldValue,
80
+ ]);
81
+ };
82
+
83
+ export default useUpdateIdentifierRequirement;
@@ -2,7 +2,10 @@ import { FormManager } from './form-manager';
2
2
  import { type FormValues } from './patient-registration.types';
3
3
  import { generateIdentifier } from './patient-registration.resource';
4
4
 
5
- jest.mock('./patient-registration.resource');
5
+ jest.mock('./patient-registration.resource', () => ({
6
+ ...jest.requireActual('./patient-registration.resource'),
7
+ generateIdentifier: jest.fn(),
8
+ }));
6
9
 
7
10
  const mockGenerateIdentifier = generateIdentifier as jest.Mock;
8
11
 
@@ -35,7 +35,6 @@ import {
35
35
  updateRelationship,
36
36
  } from './patient-registration.resource';
37
37
  import { type RegistrationConfig } from '../config-schema';
38
- import dayjs from 'dayjs';
39
38
 
40
39
  export type SavePatientForm = (
41
40
  isNewPatient: boolean,
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
- import { BrowserRouter } from 'react-router-dom';
3
- import { render, screen } from '@testing-library/react';
2
+ import { screen } from '@testing-library/react';
4
3
  import userEvent from '@testing-library/user-event';
4
+ import { renderWithRouter } from 'tools';
5
5
  import { Autosuggest } from './autosuggest.component';
6
6
 
7
7
  const mockPersons = [
@@ -33,18 +33,16 @@ const mockHandleSuggestionSelected = jest.fn((field, value) => [field, value]);
33
33
 
34
34
  describe('Autosuggest', () => {
35
35
  it('renders a search box', () => {
36
- render(
37
- <BrowserRouter>
38
- <Autosuggest
39
- getSearchResults={mockGetSearchResults}
40
- getDisplayValue={(item) => item.display}
41
- getFieldValue={(item) => item.uuid}
42
- id="person"
43
- labelText=""
44
- onSuggestionSelected={mockHandleSuggestionSelected}
45
- placeholder="Find Person"
46
- />
47
- </BrowserRouter>,
36
+ renderWithRouter(
37
+ <Autosuggest
38
+ getSearchResults={mockGetSearchResults}
39
+ getDisplayValue={(item) => item.display}
40
+ getFieldValue={(item) => item.uuid}
41
+ id="person"
42
+ labelText=""
43
+ onSuggestionSelected={mockHandleSuggestionSelected}
44
+ placeholder="Find Person"
45
+ />,
48
46
  );
49
47
 
50
48
  expect(screen.getByRole('searchbox')).toBeInTheDocument();
@@ -54,18 +52,16 @@ describe('Autosuggest', () => {
54
52
  it('renders matching search results in a list when the user types a query', async () => {
55
53
  const user = userEvent.setup();
56
54
 
57
- render(
58
- <BrowserRouter>
59
- <Autosuggest
60
- getSearchResults={mockGetSearchResults}
61
- getDisplayValue={(item) => item.display}
62
- getFieldValue={(item) => item.uuid}
63
- id="person"
64
- labelText=""
65
- onSuggestionSelected={mockHandleSuggestionSelected}
66
- placeholder="Find Person"
67
- />
68
- </BrowserRouter>,
55
+ renderWithRouter(
56
+ <Autosuggest
57
+ getSearchResults={mockGetSearchResults}
58
+ getDisplayValue={(item) => item.display}
59
+ getFieldValue={(item) => item.uuid}
60
+ id="person"
61
+ labelText=""
62
+ onSuggestionSelected={mockHandleSuggestionSelected}
63
+ placeholder="Find Person"
64
+ />,
69
65
  );
70
66
 
71
67
  const searchbox = screen.getByRole('searchbox');
@@ -82,18 +78,16 @@ describe('Autosuggest', () => {
82
78
  it('clears the list of suggestions when a suggestion is selected', async () => {
83
79
  const user = userEvent.setup();
84
80
 
85
- render(
86
- <BrowserRouter>
87
- <Autosuggest
88
- getSearchResults={mockGetSearchResults}
89
- getDisplayValue={(item) => item.display}
90
- getFieldValue={(item) => item.uuid}
91
- id="person"
92
- labelText=""
93
- onSuggestionSelected={mockHandleSuggestionSelected}
94
- placeholder="Find Person"
95
- />
96
- </BrowserRouter>,
81
+ renderWithRouter(
82
+ <Autosuggest
83
+ getSearchResults={mockGetSearchResults}
84
+ getDisplayValue={(item) => item.display}
85
+ getFieldValue={(item) => item.uuid}
86
+ id="person"
87
+ labelText=""
88
+ onSuggestionSelected={mockHandleSuggestionSelected}
89
+ placeholder="Find Person"
90
+ />,
97
91
  );
98
92
 
99
93
  let list = screen.queryByRole('list');
@@ -117,18 +111,16 @@ describe('Autosuggest', () => {
117
111
  it('changes suggestions when a search input is changed', async () => {
118
112
  const user = userEvent.setup();
119
113
 
120
- render(
121
- <BrowserRouter>
122
- <Autosuggest
123
- getSearchResults={mockGetSearchResults}
124
- getDisplayValue={(item) => item.display}
125
- getFieldValue={(item) => item.uuid}
126
- id="person"
127
- labelText=""
128
- onSuggestionSelected={mockHandleSuggestionSelected}
129
- placeholder="Find Person"
130
- />
131
- </BrowserRouter>,
114
+ renderWithRouter(
115
+ <Autosuggest
116
+ getSearchResults={mockGetSearchResults}
117
+ getDisplayValue={(item) => item.display}
118
+ getFieldValue={(item) => item.uuid}
119
+ id="person"
120
+ labelText=""
121
+ onSuggestionSelected={mockHandleSuggestionSelected}
122
+ placeholder="Find Person"
123
+ />,
132
124
  );
133
125
 
134
126
  let list = screen.queryByRole('list');
@@ -149,18 +141,16 @@ describe('Autosuggest', () => {
149
141
  it('hides the list of suggestions when the user clicks outside of the component', async () => {
150
142
  const user = userEvent.setup();
151
143
 
152
- render(
153
- <BrowserRouter>
154
- <Autosuggest
155
- getSearchResults={mockGetSearchResults}
156
- getDisplayValue={(item) => item.display}
157
- getFieldValue={(item) => item.uuid}
158
- id="person"
159
- labelText=""
160
- onSuggestionSelected={mockHandleSuggestionSelected}
161
- placeholder="Find Person"
162
- />
163
- </BrowserRouter>,
144
+ renderWithRouter(
145
+ <Autosuggest
146
+ getSearchResults={mockGetSearchResults}
147
+ getDisplayValue={(item) => item.display}
148
+ getFieldValue={(item) => item.uuid}
149
+ id="person"
150
+ labelText=""
151
+ onSuggestionSelected={mockHandleSuggestionSelected}
152
+ placeholder="Find Person"
153
+ />,
164
154
  );
165
155
 
166
156
  const input = screen.getByRole('searchbox');
@@ -0,0 +1,21 @@
1
+ import useSWR from 'swr';
2
+
3
+ const fetcher = (url: string) => {
4
+ const headers = new Headers();
5
+ headers.append('Content-Type', 'application/json');
6
+ headers.append('Authorization', `Basic ${btoa('kemr:password')}`);
7
+ return fetch(url, { headers }).then((res) => res.json());
8
+ };
9
+
10
+ export function useMpiPatient(patientId: string) {
11
+ const url = `https://hiedhs.intellisoftkenya.com/fhir/Patient?_id=${patientId}`;
12
+
13
+ const { data: patient, error: error, isLoading: isLoading } = useSWR<{ data: fhir.Bundle }, Error>(url, fetcher);
14
+ const patientInfo = patient?.['entry']?.[0]?.resource;
15
+
16
+ return {
17
+ isLoading,
18
+ patient: patientInfo,
19
+ error,
20
+ };
21
+ }
@@ -1,15 +1,16 @@
1
+ import { type Dispatch, useEffect, useMemo, useState } from 'react';
1
2
  import {
2
3
  type FetchResponse,
4
+ type OpenmrsResource,
3
5
  getSynchronizationItems,
4
6
  openmrsFetch,
5
- type OpenmrsResource,
6
7
  restBaseUrl,
7
8
  useConfig,
8
9
  usePatient,
9
10
  } from '@openmrs/esm-framework';
10
11
  import last from 'lodash-es/last';
11
12
  import camelCase from 'lodash-es/camelCase';
12
- import { type Dispatch, useEffect, useMemo, useState } from 'react';
13
+ import dayjs from 'dayjs';
13
14
  import useSWR from 'swr';
14
15
  import { v4 } from 'uuid';
15
16
  import { type RegistrationConfig } from '../config-schema';
@@ -31,15 +32,25 @@ import {
31
32
  import {
32
33
  getAddressFieldValuesFromFhirPatient,
33
34
  getFormValuesFromFhirPatient,
35
+ getIdentifierFieldValuesFromFhirPatient,
34
36
  getPatientUuidMapFromFhirPatient,
35
37
  getPhonePersonAttributeValueFromFhirPatient,
36
38
  latestFirstEncounter,
37
39
  } from './patient-registration-utils';
38
40
  import { useInitialPatientRelationships } from './section/patient-relationships/relationships.resource';
39
- import dayjs from 'dayjs';
41
+ import { useMpiPatient } from './mpi/mpi-patient.resource';
42
+
43
+ interface DeathInfoResults {
44
+ uuid: string;
45
+ display: string;
46
+ causeOfDeath: OpenmrsResource | null;
47
+ dead: boolean;
48
+ deathDate: string;
49
+ causeOfDeathNonCoded: string | null;
50
+ }
40
51
 
41
- export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
42
- const { freeTextFieldConceptUuid } = useConfig<RegistrationConfig>();
52
+ export function useInitialFormValuesLocal(patientUuid: string): [FormValues, Dispatch<FormValues>] {
53
+ const { freeTextFieldConceptUuid, fieldConfigurations } = useConfig<RegistrationConfig>()
43
54
  const { martialStatus, education, occupation, educationLoad } = useConcepts();
44
55
  const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
45
56
  const { data: deathInfo, isLoading: isLoadingDeathInfo } = useInitialPersonDeathInfo(patientUuid);
@@ -89,7 +100,7 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
89
100
  ...initialFormValues,
90
101
  ...getFormValuesFromFhirPatient(patientToEdit),
91
102
  address: getAddressFieldValuesFromFhirPatient(patientToEdit),
92
- ...getPhonePersonAttributeValueFromFhirPatient(patientToEdit),
103
+ ...getPhonePersonAttributeValueFromFhirPatient(patientToEdit, fieldConfigurations.phone.personAttributeUuid),
93
104
  birthdateEstimated: !/^\d{4}-\d{2}-\d{2}$/.test(patientToEdit.birthDate),
94
105
  yearsEstimated,
95
106
  monthsEstimated,
@@ -107,7 +118,13 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
107
118
  setInitialFormValues(registration._patientRegistrationData.formValues);
108
119
  }
109
120
  })();
110
- }, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
121
+ }, [
122
+ initialFormValues,
123
+ isLoadingPatientToEdit,
124
+ patientToEdit,
125
+ patientUuid,
126
+ fieldConfigurations.phone.personAttributeUuid,
127
+ ]);
111
128
 
112
129
  // Set initial patient death info
113
130
  useEffect(() => {
@@ -126,9 +143,9 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
126
143
  nonCodedCauseOfDeath: deathInfo.causeOfDeathNonCoded,
127
144
  }));
128
145
  }
129
- }, [isLoadingDeathInfo, deathInfo, setInitialFormValues]);
130
- // Setting authentication token
146
+ }, [isLoadingDeathInfo, deathInfo, setInitialFormValues, freeTextFieldConceptUuid]);
131
147
 
148
+ // Setting authentication token
132
149
  useEffect(() => {
133
150
  if (!isLoadingToken && token) {
134
151
  setInitialFormValues((initialFormValues) => ({ ...initialFormValues, token: String(token.access_token) }));
@@ -179,7 +196,7 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
179
196
  if (!isLoadingObs) {
180
197
  setInitialFormValues((initialFormValues) => ({ ...initialFormValues, obs: obs, observation: observations }));
181
198
  }
182
- }, [isLoadingObs]);
199
+ }, [isLoadingObs, obs, observations]);
183
200
 
184
201
  // Set Initial encounter
185
202
 
@@ -190,11 +207,68 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
190
207
  concepts: [...occupation, ...martialStatus, ...education],
191
208
  }));
192
209
  }
193
- }, [educationLoad]);
210
+ }, [educationLoad, martialStatus, education, occupation]);
194
211
 
195
212
  return [initialFormValues, setInitialFormValues];
196
213
  }
197
214
 
215
+ export function useMpiInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
216
+ const { fieldConfigurations } = useConfig<RegistrationConfig>();
217
+ const { isLoading: isLoadingMpiPatient, patient: mpiPatient } = useMpiPatient(patientUuid);
218
+
219
+ const [initialMPIFormValues, setInitialMPIFormValues] = useState<FormValues>({
220
+ patientUuid: v4(),
221
+ givenName: '',
222
+ middleName: '',
223
+ familyName: '',
224
+ additionalGivenName: '',
225
+ additionalMiddleName: '',
226
+ additionalFamilyName: '',
227
+ addNameInLocalLanguage: false,
228
+ gender: '',
229
+ birthdate: null,
230
+ yearsEstimated: 0,
231
+ monthsEstimated: 0,
232
+ birthdateEstimated: false,
233
+ telephoneNumber: '',
234
+ isDead: false,
235
+ deathDate: undefined,
236
+ deathTime: undefined,
237
+ deathTimeFormat: 'AM',
238
+ deathCause: '',
239
+ nonCodedCauseOfDeath: '',
240
+ relationships: [],
241
+ identifiers: {},
242
+ address: {},
243
+ });
244
+
245
+ useEffect(() => {
246
+ (async () => {
247
+ if (mpiPatient) {
248
+ // const identifiers = await getIdentifierFieldValuesFromFhirPatient(
249
+ // mpiPatient.data,
250
+ // fieldConfigurations.identifier,
251
+ // );
252
+
253
+ const values = {
254
+ ...initialMPIFormValues,
255
+ ...getFormValuesFromFhirPatient(mpiPatient),
256
+ address: getAddressFieldValuesFromFhirPatient(mpiPatient),
257
+ attributes: getPhonePersonAttributeValueFromFhirPatient(
258
+ mpiPatient,
259
+ fieldConfigurations.phone.personAttributeUuid,
260
+ ),
261
+ };
262
+ setInitialMPIFormValues(values);
263
+ }
264
+ })();
265
+
266
+ // eslint-disable-next-line react-hooks/exhaustive-deps
267
+ }, [mpiPatient, isLoadingMpiPatient]);
268
+
269
+ return [initialMPIFormValues, setInitialMPIFormValues];
270
+ }
271
+
198
272
  export function useInitialAddressFieldValues(patientUuid: string, fallback = {}): [object, Dispatch<object>] {
199
273
  const { isLoading, patient } = usePatient(patientUuid);
200
274
  const [initialAddressFieldValues, setInitialAddressFieldValues] = useState<object>(fallback);
@@ -211,7 +285,7 @@ export function useInitialAddressFieldValues(patientUuid: string, fallback = {})
211
285
  setInitialAddressFieldValues(registration?._patientRegistrationData.initialAddressFieldValues ?? fallback);
212
286
  }
213
287
  })();
214
- }, [isLoading, patient, patientUuid]);
288
+ }, [fallback, initialAddressFieldValues, isLoading, patient, patientUuid]);
215
289
 
216
290
  return [initialAddressFieldValues, setInitialAddressFieldValues];
217
291
  }
@@ -232,7 +306,7 @@ export function usePatientUuidMap(
232
306
  setPatientUuidMap(registration?._patientRegistrationData.initialAddressFieldValues ?? fallback),
233
307
  );
234
308
  }
235
- }, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
309
+ }, [fallback, isLoadingPatientToEdit, patientToEdit, patientUuid, patientUuidMap]);
236
310
 
237
311
  useEffect(() => {
238
312
  if (attributes) {
@@ -287,7 +361,7 @@ export function useInitialPatientIdentifiers(patientUuid: string): {
287
361
  data: identifiers,
288
362
  isLoading,
289
363
  };
290
- }, [data, error]);
364
+ }, [data?.data?.results, isLoading]);
291
365
 
292
366
  return result;
293
367
  }
@@ -323,19 +397,10 @@ function useInitialPersonAttributes(personUuid: string) {
323
397
  data: data?.data?.results,
324
398
  isLoading,
325
399
  };
326
- }, [data, error]);
400
+ }, [data?.data?.results, isLoading]);
327
401
  return result;
328
402
  }
329
403
 
330
- interface DeathInfoResults {
331
- uuid: string;
332
- display: string;
333
- causeOfDeath: OpenmrsResource | null;
334
- dead: boolean;
335
- deathDate: string;
336
- causeOfDeathNonCoded: string | null;
337
- }
338
-
339
404
  function useInitialPersonDeathInfo(personUuid: string) {
340
405
  const { data, error, isLoading } = useSWR<FetchResponse<DeathInfoResults>, Error>(
341
406
  !!personUuid
@@ -349,7 +414,7 @@ function useInitialPersonDeathInfo(personUuid: string) {
349
414
  data: data?.data,
350
415
  isLoading,
351
416
  };
352
- }, [data, error]);
417
+ }, [data?.data, isLoading]);
353
418
  return result;
354
419
  }
355
420