@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.
- package/.turbo/turbo-build.log +21 -23
- package/dist/108.js +1 -1
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/173.js +2 -0
- package/dist/{895.js.LICENSE.txt → 173.js.LICENSE.txt} +25 -0
- package/dist/173.js.map +1 -0
- package/dist/236.js +1 -0
- package/dist/240.js +1 -0
- package/dist/261.js +1 -0
- package/dist/271.js +1 -1
- package/dist/272.js +1 -0
- package/dist/319.js +1 -1
- package/dist/336.js +1 -0
- package/dist/371.js +1 -0
- package/dist/371.js.map +1 -0
- package/dist/378.js +1 -0
- package/dist/460.js +1 -1
- package/dist/501.js +1 -1
- package/dist/501.js.map +1 -1
- package/dist/539.js +1 -0
- package/dist/566.js +1 -0
- package/dist/574.js +1 -1
- package/dist/623.js +1 -0
- package/dist/623.js.map +1 -0
- package/dist/644.js +1 -1
- package/dist/652.js +1 -0
- package/dist/657.js +1 -0
- package/dist/657.js.map +1 -0
- package/dist/673.js +1 -0
- package/dist/705.js +1 -0
- package/dist/711.js +1 -0
- package/dist/727.js +1 -0
- package/dist/737.js +1 -0
- package/dist/744.js +1 -0
- package/dist/757.js +1 -1
- package/dist/759.js +1 -0
- package/dist/759.js.map +1 -0
- package/dist/76.js +1 -1
- package/dist/788.js +1 -1
- package/dist/807.js +1 -1
- package/dist/833.js +1 -1
- package/dist/899.js +1 -0
- package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +445 -93
- package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +25 -0
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package-lock.json +6400 -0
- package/package.json +4 -4
- package/src/client-registry/hie-client-registry/dependants/dependants.component.tsx +46 -0
- package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +38 -8
- package/src/client-registry/hie-client-registry/hie-resource.ts +126 -21
- package/src/client-registry/hie-client-registry/hie-types.ts +102 -0
- package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +78 -62
- package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +55 -3
- package/src/client-registry/hie-client-registry/modal/hie-otp-verification-form.component.tsx +88 -0
- package/src/client-registry/hie-client-registry/modal/hie-patient-detail-preview.component.tsx +77 -0
- package/src/client-registry/hie-client-registry/patient-info/patient-info.component.tsx +17 -0
- package/src/config-schema.ts +30 -2
- package/src/patient-registration/field/address/address-search.component.tsx +5 -2
- package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
- package/src/patient-registration/field/dob/dob.component.tsx +1 -1
- package/src/patient-registration/field/field.scss +4 -4
- package/src/patient-registration/field/gender/gender-field.component.tsx +6 -2
- package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +3 -3
- package/src/patient-registration/field/name/name-field.component.tsx +2 -2
- package/src/patient-registration/field/obs/obs-field.component.tsx +9 -5
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +4 -3
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +22 -11
- package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx +76 -27
- package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +1 -1
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +12 -4
- package/src/patient-registration/field/person-attributes/useUpdateIdentifierRequirement.tsx +83 -0
- package/src/patient-registration/form-manager.test.ts +4 -1
- package/src/patient-registration/form-manager.ts +0 -1
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +52 -62
- package/src/patient-registration/mpi/mpi-patient.resource.ts +21 -0
- package/src/patient-registration/patient-registration-hooks.ts +90 -25
- package/src/patient-registration/patient-registration-utils.test.ts +33 -0
- package/src/patient-registration/patient-registration-utils.ts +63 -13
- package/src/patient-registration/patient-registration.component.tsx +17 -2
- package/src/patient-registration/patient-registration.test.tsx +442 -56
- package/src/patient-registration/section/demographics/demographics-section.component.tsx +3 -3
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +1 -1
- package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +28 -28
- package/src/widgets/cancel-patient-edit.modal.tsx +2 -0
- package/src/widgets/cancel-patient-edit.scss +29 -0
- package/src/widgets/delete-identifier-confirmation.modal.tsx +2 -0
- package/src/widgets/delete-identifier-confirmation.scss +29 -0
- package/translations/am.json +1 -0
- package/translations/ar.json +6 -4
- package/translations/de.json +118 -0
- package/translations/en.json +17 -0
- package/translations/es.json +2 -0
- package/translations/fr.json +1 -0
- package/translations/he.json +1 -0
- package/translations/hi.json +118 -0
- package/translations/hi_IN.json +118 -0
- package/translations/id.json +118 -0
- package/translations/it.json +118 -0
- package/translations/km.json +1 -0
- package/translations/ne.json +118 -0
- package/translations/pt.json +118 -0
- package/translations/pt_BR.json +118 -0
- package/translations/qu.json +118 -0
- package/translations/si.json +118 -0
- package/translations/sw.json +118 -0
- package/translations/sw_KE.json +118 -0
- package/translations/tr.json +118 -0
- package/translations/tr_TR.json +118 -0
- package/translations/uk.json +118 -0
- package/translations/vi.json +118 -0
- package/translations/zh.json +3 -1
- package/translations/zh_CN.json +2 -0
- package/dist/10.js +0 -1
- package/dist/10.js.map +0 -1
- package/dist/250.js +0 -1
- package/dist/250.js.map +0 -1
- package/dist/662.js +0 -1
- package/dist/662.js.map +0 -1
- package/dist/753.js +0 -1
- package/dist/753.js.map +0 -1
- package/dist/895.js +0 -2
- package/dist/895.js.map +0 -1
package/src/patient-registration/field/person-attributes/custom-person-attribute-field.component.tsx
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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(/
|
|
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
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
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
|
-
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
153
|
-
<
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
}, [
|
|
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,
|
|
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,
|
|
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,
|
|
417
|
+
}, [data?.data, isLoading]);
|
|
353
418
|
return result;
|
|
354
419
|
}
|
|
355
420
|
|