@kenyaemr/esm-patient-registration-app 4.5.6 → 4.5.8
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/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/218.js +1 -1
- package/dist/319.js +1 -1
- package/dist/330.js +1 -1
- package/dist/348.js +1 -1
- package/dist/460.js +1 -0
- package/dist/520.js +2 -0
- package/dist/520.js.LICENSE.txt +14 -0
- package/dist/520.js.map +1 -0
- package/dist/537.js +1 -1
- package/dist/68.js +1 -1
- package/dist/68.js.map +1 -1
- package/dist/693.js +1 -1
- package/dist/693.js.map +1 -1
- package/dist/735.js +1 -1
- package/dist/742.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +79 -57
- package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/offline.resources.ts +37 -2
- package/src/offline.ts +3 -2
- package/src/patient-registration/field/__mocks__/identifier-types.mock.ts +76 -0
- package/src/patient-registration/field/__mocks__/identifiers.mock.ts +27 -0
- package/src/patient-registration/field/address/address-field.component.tsx +12 -6
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +4 -1
- package/src/patient-registration/field/dob/dob.component.tsx +17 -14
- package/src/patient-registration/field/dob/dob.test.tsx +13 -0
- package/src/patient-registration/field/id/id-field.component.tsx +3 -3
- package/src/patient-registration/field/id/id-field.test.tsx +105 -0
- package/src/patient-registration/field/obs/obs-field.component.tsx +19 -4
- package/src/patient-registration/form-manager.ts +3 -4
- package/src/patient-registration/patient-registration-hooks.ts +6 -4
- package/src/patient-registration/patient-registration.test.tsx +18 -3
- package/src/patient-registration/patient-registration.types.tsx +2 -2
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +13 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +3 -5
- package/src/patient-registration/ui-components/overlay/index.tsx +1 -1
- package/src/root.component.tsx +1 -1
- package/src/routes.json +3 -1
- package/translations/ar.json +84 -0
- package/translations/es.json +80 -80
- package/dist/208.js +0 -2
- package/dist/208.js.LICENSE.txt +0 -27
- package/dist/208.js.map +0 -1
package/src/offline.ts
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
navigate,
|
|
5
5
|
setupDynamicOfflineDataHandler,
|
|
6
6
|
setupOfflineSync,
|
|
7
|
-
subscribePrecacheStaticDependencies,
|
|
8
7
|
SyncProcessOptions,
|
|
9
8
|
} from '@openmrs/esm-framework';
|
|
10
9
|
import { patientRegistration, personRelationshipRepresentation } from './constants';
|
|
11
10
|
import {
|
|
12
11
|
fetchAddressTemplate,
|
|
12
|
+
fetchAllFieldDefinitionTypes,
|
|
13
13
|
fetchAllRelationshipTypes,
|
|
14
14
|
fetchCurrentSession,
|
|
15
15
|
fetchPatientIdentifierTypesWithSources,
|
|
@@ -24,7 +24,7 @@ export function setupOffline() {
|
|
|
24
24
|
},
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
precacheStaticAssets();
|
|
28
28
|
|
|
29
29
|
setupDynamicOfflineDataHandler({
|
|
30
30
|
id: 'esm-patient-registration-app:patient',
|
|
@@ -66,6 +66,7 @@ async function precacheStaticAssets() {
|
|
|
66
66
|
fetchCurrentSession(),
|
|
67
67
|
fetchAddressTemplate(),
|
|
68
68
|
fetchAllRelationshipTypes(),
|
|
69
|
+
fetchAllFieldDefinitionTypes(),
|
|
69
70
|
fetchPatientIdentifierTypesWithSources(),
|
|
70
71
|
]);
|
|
71
72
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const mockedIdentifierTypes = [
|
|
2
|
+
{
|
|
3
|
+
fieldName: 'openMrsId',
|
|
4
|
+
format: null,
|
|
5
|
+
identifierSources: [
|
|
6
|
+
{
|
|
7
|
+
uuid: '8549f706-7e85-4c1d-9424-217d50a2988b',
|
|
8
|
+
name: 'Generator for OpenMRS ID',
|
|
9
|
+
description: 'Generator for OpenMRS ID',
|
|
10
|
+
baseCharacterSet: '0123456789ACDEFGHJKLMNPRTUVWXY',
|
|
11
|
+
prefix: '',
|
|
12
|
+
autoGenerationOption: {
|
|
13
|
+
manualEntryEnabled: false,
|
|
14
|
+
automaticGenerationEnabled: true,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
uuid: '01af8526-cea4-4175-aa90-340acb411771',
|
|
19
|
+
name: 'Generator 2 for OpenMRS ID',
|
|
20
|
+
description: 'Generator 2 for OpenMRS ID',
|
|
21
|
+
baseCharacterSet: '0123456789ACDEFGHJKLMNPRTUVWXY',
|
|
22
|
+
prefix: '',
|
|
23
|
+
autoGenerationOption: {
|
|
24
|
+
manualEntryEnabled: true,
|
|
25
|
+
automaticGenerationEnabled: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
isPrimary: true,
|
|
30
|
+
name: 'OpenMRS ID',
|
|
31
|
+
required: true,
|
|
32
|
+
uniquenessBehavior: 'UNIQUE',
|
|
33
|
+
uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
34
|
+
autoGenerationSource: null,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
fieldName: 'idCard',
|
|
38
|
+
format: null,
|
|
39
|
+
identifierSources: [],
|
|
40
|
+
isPrimary: false,
|
|
41
|
+
name: 'ID Card',
|
|
42
|
+
required: false,
|
|
43
|
+
uniquenessBehavior: 'UNIQUE',
|
|
44
|
+
uuid: 'b4143563-16cd-4439-b288-f83d61670fc8',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
fieldName: 'legacyId',
|
|
48
|
+
format: null,
|
|
49
|
+
identifierSources: [],
|
|
50
|
+
isPrimary: false,
|
|
51
|
+
name: 'Legacy ID',
|
|
52
|
+
required: false,
|
|
53
|
+
uniquenessBehavior: null,
|
|
54
|
+
uuid: '22348099-3873-459e-a32e-d93b17eda533',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
fieldName: 'oldIdentificationNumber',
|
|
58
|
+
format: '',
|
|
59
|
+
identifierSources: [],
|
|
60
|
+
isPrimary: false,
|
|
61
|
+
name: 'Old Identification Number',
|
|
62
|
+
required: false,
|
|
63
|
+
uniquenessBehavior: null,
|
|
64
|
+
uuid: '8d79403a-c2cc-11de-8d13-0010c6dffd0f',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
fieldName: 'openMrsIdentificationNumber',
|
|
68
|
+
format: '',
|
|
69
|
+
identifierSources: [],
|
|
70
|
+
isPrimary: false,
|
|
71
|
+
name: 'OpenMRS Identification Number',
|
|
72
|
+
required: false,
|
|
73
|
+
uniquenessBehavior: null,
|
|
74
|
+
uuid: '8d793bee-c2cc-11de-8d13-0010c6dffd0f',
|
|
75
|
+
},
|
|
76
|
+
];
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export const openmrsID = {
|
|
2
|
+
name: 'OpenMRS ID',
|
|
3
|
+
fieldName: 'openMrsId',
|
|
4
|
+
required: true,
|
|
5
|
+
uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
6
|
+
format: null,
|
|
7
|
+
isPrimary: true,
|
|
8
|
+
identifierSources: [
|
|
9
|
+
{
|
|
10
|
+
uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
|
|
11
|
+
name: 'Generator 1 for OpenMRS ID',
|
|
12
|
+
autoGenerationOption: {
|
|
13
|
+
manualEntryEnabled: false,
|
|
14
|
+
automaticGenerationEnabled: true,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
uuid: '01af8526-cea4-4175-aa90-340acb411771',
|
|
19
|
+
name: 'Generator 2 for OpenMRS ID',
|
|
20
|
+
autoGenerationOption: {
|
|
21
|
+
manualEntryEnabled: true,
|
|
22
|
+
automaticGenerationEnabled: true,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
autoGenerationSource: null,
|
|
27
|
+
};
|
|
@@ -22,14 +22,18 @@ export const AddressComponent: React.FC = () => {
|
|
|
22
22
|
if (!addressTemplate?.lines) {
|
|
23
23
|
return [];
|
|
24
24
|
}
|
|
25
|
+
|
|
25
26
|
const allFields = addressTemplate?.lines?.flat();
|
|
26
27
|
const fields = allFields?.filter(({ isToken }) => isToken === 'IS_ADDR_TOKEN');
|
|
27
|
-
|
|
28
|
-
return fields.map(({ displayText, codeName }) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const allRequiredFields = Object.fromEntries(addressTemplate?.requiredElements?.map((curr) => [curr, curr]) || []);
|
|
29
|
+
return fields.map(({ displayText, codeName }) => {
|
|
30
|
+
return {
|
|
31
|
+
id: codeName,
|
|
32
|
+
name: codeName,
|
|
33
|
+
label: displayText,
|
|
34
|
+
required: Boolean(allRequiredFields[codeName]),
|
|
35
|
+
};
|
|
36
|
+
});
|
|
33
37
|
}, [addressTemplate]);
|
|
34
38
|
|
|
35
39
|
const { t } = useTranslation();
|
|
@@ -83,6 +87,7 @@ export const AddressComponent: React.FC = () => {
|
|
|
83
87
|
labelText={t(attributes.label)}
|
|
84
88
|
id={attributes.name}
|
|
85
89
|
selected={selected}
|
|
90
|
+
required={attributes.required}
|
|
86
91
|
/>
|
|
87
92
|
))}
|
|
88
93
|
</AddressComponentContainer>
|
|
@@ -123,6 +128,7 @@ export const AddressComponent: React.FC = () => {
|
|
|
123
128
|
labelText={t(attributes.label)}
|
|
124
129
|
id={attributes.name}
|
|
125
130
|
selected={selected}
|
|
131
|
+
required={attributes.required}
|
|
126
132
|
/>
|
|
127
133
|
))
|
|
128
134
|
)}
|
|
@@ -28,6 +28,7 @@ interface AddressComboBoxProps {
|
|
|
28
28
|
name: string;
|
|
29
29
|
value: string;
|
|
30
30
|
label: string;
|
|
31
|
+
required?: boolean;
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -36,6 +37,7 @@ const AddressComboBox: React.FC<AddressComboBoxProps> = ({ attribute }) => {
|
|
|
36
37
|
const [field, meta, { setValue }] = useField(`address.${attribute.name}`);
|
|
37
38
|
const { fetchEntriesForField, searchString, updateChildElements } = useAddressEntryFetchConfig(attribute.name);
|
|
38
39
|
const { entries } = useAddressEntries(fetchEntriesForField, searchString);
|
|
40
|
+
const label = t(attribute.label) + (attribute?.required ? '' : ` (${t('optional', 'optional')})`);
|
|
39
41
|
|
|
40
42
|
const handleInputChange = useCallback(
|
|
41
43
|
(newValue) => {
|
|
@@ -62,7 +64,8 @@ const AddressComboBox: React.FC<AddressComboBoxProps> = ({ attribute }) => {
|
|
|
62
64
|
fieldProps={{
|
|
63
65
|
...field,
|
|
64
66
|
id: attribute.name,
|
|
65
|
-
labelText:
|
|
67
|
+
labelText: label,
|
|
68
|
+
required: attribute?.required,
|
|
66
69
|
}}
|
|
67
70
|
handleInputChange={handleInputChange}
|
|
68
71
|
/>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { useContext } from 'react';
|
|
2
|
-
import { ContentSwitcher,
|
|
2
|
+
import { ContentSwitcher, Layer, Switch, TextInput } from '@carbon/react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { useField } from 'formik';
|
|
5
5
|
import { generateFormatting } from '../../date-util';
|
|
6
6
|
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
7
|
-
import { useConfig } from '@openmrs/esm-framework';
|
|
7
|
+
import { OpenmrsDatePicker, useConfig } from '@openmrs/esm-framework';
|
|
8
8
|
import { RegistrationConfig } from '../../../config-schema';
|
|
9
9
|
import styles from '../field.scss';
|
|
10
10
|
|
|
@@ -42,7 +42,7 @@ export const DobField: React.FC = () => {
|
|
|
42
42
|
setFieldValue('monthsEstimated', '');
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
const onDateChange = (
|
|
45
|
+
const onDateChange = (birthdate) => {
|
|
46
46
|
setFieldValue('birthdate', birthdate);
|
|
47
47
|
};
|
|
48
48
|
|
|
@@ -89,17 +89,20 @@ export const DobField: React.FC = () => {
|
|
|
89
89
|
<Layer>
|
|
90
90
|
{!dobUnknown ? (
|
|
91
91
|
<div className={styles.dobField}>
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
<OpenmrsDatePicker
|
|
93
|
+
id="birthdate"
|
|
94
|
+
{...birthdate}
|
|
95
|
+
dateFormat={dateFormat}
|
|
96
|
+
onChange={onDateChange}
|
|
97
|
+
maxDate={format(today)}
|
|
98
|
+
labelText={t('dateOfBirthLabelText', 'Date of Birth')}
|
|
99
|
+
invalid={!!(birthdateMeta.touched && birthdateMeta.error)}
|
|
100
|
+
invalidText={birthdateMeta.error && t(birthdateMeta.error)}
|
|
101
|
+
value={format(birthdate.value)}
|
|
102
|
+
carbonOptions={{
|
|
103
|
+
placeholder: placeHolder,
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
103
106
|
</div>
|
|
104
107
|
) : (
|
|
105
108
|
<div className={styles.grid}>
|
|
@@ -8,6 +8,7 @@ import { DobField } from './dob.component';
|
|
|
8
8
|
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
9
9
|
import { initialFormValues } from '../../patient-registration.component';
|
|
10
10
|
import { FormValues } from '../../patient-registration-types';
|
|
11
|
+
import { OpenmrsDatePicker } from '@openmrs/esm-styleguide/src/public';
|
|
11
12
|
|
|
12
13
|
jest.mock('@openmrs/esm-framework', () => {
|
|
13
14
|
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
@@ -21,6 +22,18 @@ jest.mock('@openmrs/esm-framework', () => {
|
|
|
21
22
|
},
|
|
22
23
|
},
|
|
23
24
|
})),
|
|
25
|
+
getLocale: jest.fn().mockReturnValue('en'),
|
|
26
|
+
OpenmrsDatePicker: (datePickerProps) => (
|
|
27
|
+
<OpenmrsDatePicker
|
|
28
|
+
id={datePickerProps.id}
|
|
29
|
+
dateFormat={datePickerProps.dateFormat}
|
|
30
|
+
onChange={datePickerProps.onChange}
|
|
31
|
+
maxDate={datePickerProps.maxDate}
|
|
32
|
+
labelText={datePickerProps.labelText}
|
|
33
|
+
value={datePickerProps.value}
|
|
34
|
+
carbonOptions={datePickerProps.carbonOptions}
|
|
35
|
+
/>
|
|
36
|
+
),
|
|
24
37
|
};
|
|
25
38
|
});
|
|
26
39
|
|
|
@@ -59,7 +59,7 @@ export function deleteIdentifierType(identifiers: FormValues['identifiers'], ide
|
|
|
59
59
|
export const Identifiers: React.FC = () => {
|
|
60
60
|
const { identifierTypes } = useContext(ResourcesContext);
|
|
61
61
|
const isLoading = !identifierTypes;
|
|
62
|
-
const { values, setFieldValue, initialFormValues } = useContext(PatientRegistrationContext);
|
|
62
|
+
const { values, setFieldValue, initialFormValues, isOffline } = useContext(PatientRegistrationContext);
|
|
63
63
|
const { t } = useTranslation();
|
|
64
64
|
const layout = useLayoutType();
|
|
65
65
|
const [showIdentifierOverlay, setShowIdentifierOverlay] = useState(false);
|
|
@@ -101,9 +101,9 @@ export const Identifiers: React.FC = () => {
|
|
|
101
101
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
102
102
|
}, [identifierTypes, setFieldValue, defaultPatientIdentifierTypes, values.identifiers, initializeIdentifier]);
|
|
103
103
|
|
|
104
|
-
if (isLoading) {
|
|
104
|
+
if (isLoading && !isOffline) {
|
|
105
105
|
return (
|
|
106
|
-
<div className={styles.halfWidthInDesktopView}>
|
|
106
|
+
<div data-testid="loading-skeleton" className={styles.halfWidthInDesktopView}>
|
|
107
107
|
<div className={styles.identifierLabelText}>
|
|
108
108
|
<h4 className={styles.productiveHeading02Light}>{t('idFieldLabelText', 'Identifiers')}</h4>
|
|
109
109
|
</div>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { Identifiers } from './id-field.component';
|
|
4
|
+
import { Resources, ResourcesContext } from '../../../offline.resources';
|
|
5
|
+
import { Form, Formik } from 'formik';
|
|
6
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
7
|
+
import { openmrsID } from '../__mocks__/identifiers.mock';
|
|
8
|
+
import { mockedIdentifierTypes } from '../__mocks__/identifier-types.mock';
|
|
9
|
+
|
|
10
|
+
jest.mock('@openmrs/esm-framework', () => ({
|
|
11
|
+
...jest.requireActual('@openmrs/esm-framework'),
|
|
12
|
+
useConfig: jest.fn().mockImplementation(() => ({
|
|
13
|
+
defaultPatientIdentifierTypes: ['OpenMRS ID'],
|
|
14
|
+
})),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('Identifiers', () => {
|
|
18
|
+
const mockResourcesContextValue = {
|
|
19
|
+
addressTemplate: [],
|
|
20
|
+
currentSession: {
|
|
21
|
+
authenticated: true,
|
|
22
|
+
sessionId: 'JSESSION',
|
|
23
|
+
currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
|
|
24
|
+
},
|
|
25
|
+
relationshipTypes: [],
|
|
26
|
+
identifierTypes: [...mockedIdentifierTypes],
|
|
27
|
+
} as Resources;
|
|
28
|
+
|
|
29
|
+
it('should render loading skeleton when identifier types are loading', () => {
|
|
30
|
+
render(
|
|
31
|
+
<ResourcesContext.Provider value={[]}>
|
|
32
|
+
<Formik initialValues={{}} onSubmit={null}>
|
|
33
|
+
<Form>
|
|
34
|
+
<PatientRegistrationContext.Provider
|
|
35
|
+
value={{
|
|
36
|
+
setFieldValue: jest.fn(),
|
|
37
|
+
initialFormValues: { identifiers: { ...mockedIdentifierTypes[0] } },
|
|
38
|
+
setInitialFormValues: jest.fn(),
|
|
39
|
+
values: {
|
|
40
|
+
identifiers: { openmrsID },
|
|
41
|
+
},
|
|
42
|
+
}}>
|
|
43
|
+
<Identifiers />
|
|
44
|
+
</PatientRegistrationContext.Provider>
|
|
45
|
+
</Form>
|
|
46
|
+
</Formik>
|
|
47
|
+
</ResourcesContext.Provider>,
|
|
48
|
+
);
|
|
49
|
+
expect(screen.getByTestId('loading-skeleton')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should render identifier inputs when identifier types are loaded', () => {
|
|
53
|
+
render(
|
|
54
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
55
|
+
<Formik initialValues={{}} onSubmit={null}>
|
|
56
|
+
<Form>
|
|
57
|
+
<PatientRegistrationContext.Provider
|
|
58
|
+
value={{
|
|
59
|
+
setFieldValue: jest.fn(),
|
|
60
|
+
initialFormValues: { identifiers: { ...mockedIdentifierTypes[0] } },
|
|
61
|
+
setInitialFormValues: jest.fn(),
|
|
62
|
+
values: {
|
|
63
|
+
identifiers: { openmrsID },
|
|
64
|
+
},
|
|
65
|
+
}}>
|
|
66
|
+
<Identifiers />
|
|
67
|
+
</PatientRegistrationContext.Provider>
|
|
68
|
+
</Form>
|
|
69
|
+
</Formik>
|
|
70
|
+
</ResourcesContext.Provider>,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(screen.getByText('Identifiers')).toBeInTheDocument();
|
|
74
|
+
const configureButton = screen.getByRole('button', { name: 'Configure' });
|
|
75
|
+
expect(configureButton).toBeInTheDocument();
|
|
76
|
+
expect(configureButton).toBeEnabled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should open identifier selection overlay when "Configure" button is clicked', () => {
|
|
80
|
+
render(
|
|
81
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
82
|
+
<Formik initialValues={{}} onSubmit={null}>
|
|
83
|
+
<Form>
|
|
84
|
+
<PatientRegistrationContext.Provider
|
|
85
|
+
value={{
|
|
86
|
+
setFieldValue: jest.fn(),
|
|
87
|
+
initialFormValues: { identifiers: { ...mockedIdentifierTypes[0] } },
|
|
88
|
+
setInitialFormValues: jest.fn(),
|
|
89
|
+
values: {
|
|
90
|
+
identifiers: { openmrsID },
|
|
91
|
+
},
|
|
92
|
+
}}>
|
|
93
|
+
<Identifiers />
|
|
94
|
+
</PatientRegistrationContext.Provider>
|
|
95
|
+
</Form>
|
|
96
|
+
</Formik>
|
|
97
|
+
</ResourcesContext.Provider>,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const configureButton = screen.getByRole('button', { name: 'Configure' });
|
|
101
|
+
fireEvent.click(configureButton);
|
|
102
|
+
|
|
103
|
+
expect(screen.getByRole('button', { name: 'Close overlay' })).toBeInTheDocument();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -36,16 +36,24 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) {
|
|
|
36
36
|
concept={concept}
|
|
37
37
|
validationRegex={fieldDefinition.validation.matches}
|
|
38
38
|
label={fieldDefinition.label}
|
|
39
|
+
required={fieldDefinition.validation.required}
|
|
39
40
|
/>
|
|
40
41
|
);
|
|
41
42
|
case 'Numeric':
|
|
42
|
-
return
|
|
43
|
+
return (
|
|
44
|
+
<NumericObsField
|
|
45
|
+
concept={concept}
|
|
46
|
+
label={fieldDefinition.label}
|
|
47
|
+
required={fieldDefinition.validation.required}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
43
50
|
case 'Coded':
|
|
44
51
|
return (
|
|
45
52
|
<CodedObsField
|
|
46
53
|
concept={concept}
|
|
47
54
|
answerConceptSetUuid={fieldDefinition.answerConceptSetUuid}
|
|
48
55
|
label={fieldDefinition.label}
|
|
56
|
+
required={fieldDefinition.validation.required}
|
|
49
57
|
/>
|
|
50
58
|
);
|
|
51
59
|
default:
|
|
@@ -61,9 +69,10 @@ interface TextObsFieldProps {
|
|
|
61
69
|
concept: ConceptResponse;
|
|
62
70
|
validationRegex: string;
|
|
63
71
|
label: string;
|
|
72
|
+
required?: boolean;
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
function TextObsField({ concept, validationRegex, label }: TextObsFieldProps) {
|
|
75
|
+
function TextObsField({ concept, validationRegex, label, required }: TextObsFieldProps) {
|
|
67
76
|
const { t } = useTranslation();
|
|
68
77
|
|
|
69
78
|
const validateInput = (value: string) => {
|
|
@@ -87,6 +96,7 @@ function TextObsField({ concept, validationRegex, label }: TextObsFieldProps) {
|
|
|
87
96
|
<Input
|
|
88
97
|
id={fieldName}
|
|
89
98
|
labelText={label ?? concept.display}
|
|
99
|
+
required={required}
|
|
90
100
|
invalid={errors[fieldName] && touched[fieldName]}
|
|
91
101
|
{...field}
|
|
92
102
|
/>
|
|
@@ -100,9 +110,10 @@ function TextObsField({ concept, validationRegex, label }: TextObsFieldProps) {
|
|
|
100
110
|
interface NumericObsFieldProps {
|
|
101
111
|
concept: ConceptResponse;
|
|
102
112
|
label: string;
|
|
113
|
+
required?: boolean;
|
|
103
114
|
}
|
|
104
115
|
|
|
105
|
-
function NumericObsField({ concept, label }: NumericObsFieldProps) {
|
|
116
|
+
function NumericObsField({ concept, label, required }: NumericObsFieldProps) {
|
|
106
117
|
const { t } = useTranslation();
|
|
107
118
|
|
|
108
119
|
const fieldName = `obs.${concept.uuid}`;
|
|
@@ -115,6 +126,7 @@ function NumericObsField({ concept, label }: NumericObsFieldProps) {
|
|
|
115
126
|
<Input
|
|
116
127
|
id={fieldName}
|
|
117
128
|
labelText={label ?? concept.display}
|
|
129
|
+
required={required}
|
|
118
130
|
invalid={errors[fieldName] && touched[fieldName]}
|
|
119
131
|
type="number"
|
|
120
132
|
{...field}
|
|
@@ -130,9 +142,10 @@ interface CodedObsFieldProps {
|
|
|
130
142
|
concept: ConceptResponse;
|
|
131
143
|
answerConceptSetUuid?: string;
|
|
132
144
|
label?: string;
|
|
145
|
+
required?: boolean;
|
|
133
146
|
}
|
|
134
147
|
|
|
135
|
-
function CodedObsField({ concept, answerConceptSetUuid, label }: CodedObsFieldProps) {
|
|
148
|
+
function CodedObsField({ concept, answerConceptSetUuid, label, required }: CodedObsFieldProps) {
|
|
136
149
|
const config = useConfig() as RegistrationConfig;
|
|
137
150
|
const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(
|
|
138
151
|
answerConceptSetUuid ?? concept.uuid,
|
|
@@ -152,6 +165,7 @@ function CodedObsField({ concept, answerConceptSetUuid, label }: CodedObsFieldPr
|
|
|
152
165
|
<Select
|
|
153
166
|
id={fieldName}
|
|
154
167
|
name={fieldName}
|
|
168
|
+
required={required}
|
|
155
169
|
labelText={label ?? concept?.display}
|
|
156
170
|
invalid={errors[fieldName] && touched[fieldName]}
|
|
157
171
|
{...field}>
|
|
@@ -169,6 +183,7 @@ function CodedObsField({ concept, answerConceptSetUuid, label }: CodedObsFieldPr
|
|
|
169
183
|
id={fieldName}
|
|
170
184
|
name={fieldName}
|
|
171
185
|
labelText={label ?? concept?.display}
|
|
186
|
+
required={required}
|
|
172
187
|
invalid={errors[fieldName] && touched[fieldName]}
|
|
173
188
|
{...field}>
|
|
174
189
|
<SelectItem key={`no-answer-select-item-${fieldName}`} value={''} text="" />
|
|
@@ -192,10 +192,9 @@ export class FormManager {
|
|
|
192
192
|
},
|
|
193
193
|
],
|
|
194
194
|
form: config.registrationObs.registrationFormUuid,
|
|
195
|
-
obs: Object.
|
|
196
|
-
|
|
197
|
-
value:
|
|
198
|
-
})),
|
|
195
|
+
obs: Object.entries(obss)
|
|
196
|
+
.filter(([, value]) => value !== '')
|
|
197
|
+
.map(([conceptUuid, value]) => ({ concept: conceptUuid, value })),
|
|
199
198
|
};
|
|
200
199
|
return saveEncounter(encounterToSave);
|
|
201
200
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FetchResponse, getSynchronizationItems, openmrsFetch, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
1
|
+
import { FetchResponse, OpenmrsResource, getSynchronizationItems, openmrsFetch, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
2
2
|
import last from 'lodash-es/last';
|
|
3
3
|
import camelCase from 'lodash-es/camelCase';
|
|
4
4
|
import { Dispatch, useEffect, useMemo, useState } from 'react';
|
|
@@ -261,14 +261,16 @@ export function useInitialPatientIdentifiers(patientUuid: string): {
|
|
|
261
261
|
function useInitialEncounters(patientUuid: string, patientToEdit: fhir.Patient) {
|
|
262
262
|
const { registrationObs } = useConfig() as RegistrationConfig;
|
|
263
263
|
const { data, error, isLoading } = useSWR<FetchResponse<{ results: Array<Encounter> }>>(
|
|
264
|
-
patientToEdit
|
|
265
|
-
? `/ws/rest/v1/encounter?patient=${patientUuid}&v=
|
|
264
|
+
patientToEdit && registrationObs.encounterTypeUuid
|
|
265
|
+
? `/ws/rest/v1/encounter?patient=${patientUuid}&v=custom:(encounterDatetime,obs:(concept:ref,value:ref))&encounterType=${registrationObs.encounterTypeUuid}`
|
|
266
266
|
: null,
|
|
267
267
|
openmrsFetch,
|
|
268
268
|
);
|
|
269
269
|
const obs = data?.data.results.sort(latestFirstEncounter)?.at(0)?.obs;
|
|
270
270
|
const encounters = obs
|
|
271
|
-
?.map(({ concept, value }) => ({
|
|
271
|
+
?.map(({ concept, value }) => ({
|
|
272
|
+
[(concept as OpenmrsResource).uuid]: typeof value === 'object' ? value?.uuid : value,
|
|
273
|
+
}))
|
|
272
274
|
.reduce((accu, curr) => Object.assign(accu, curr), {});
|
|
273
275
|
|
|
274
276
|
return { data: encounters, isLoading, error };
|
|
@@ -5,12 +5,13 @@ import userEvent from '@testing-library/user-event';
|
|
|
5
5
|
import { showToast, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
6
6
|
import { FormManager } from './form-manager';
|
|
7
7
|
import { saveEncounter, savePatient } from './patient-registration.resource';
|
|
8
|
-
import { Encounter } from './patient-registration
|
|
8
|
+
import type { Encounter } from './patient-registration.types';
|
|
9
9
|
import { Resources, ResourcesContext } from '../offline.resources';
|
|
10
10
|
import { PatientRegistration } from './patient-registration.component';
|
|
11
11
|
import { RegistrationConfig } from '../config-schema';
|
|
12
12
|
import { mockedAddressTemplate } from './field/address/tests/mocks';
|
|
13
13
|
import { mockPatient } from '../../../../tools/test-helpers';
|
|
14
|
+
import { OpenmrsDatePicker } from '@openmrs/esm-styleguide/src/public';
|
|
14
15
|
|
|
15
16
|
const mockedUseConfig = useConfig as jest.Mock;
|
|
16
17
|
const mockedUsePatient = usePatient as jest.Mock;
|
|
@@ -18,6 +19,8 @@ const mockedSaveEncounter = saveEncounter as jest.Mock;
|
|
|
18
19
|
const mockedSavePatient = savePatient as jest.Mock;
|
|
19
20
|
const mockedShowToast = showToast as jest.Mock;
|
|
20
21
|
|
|
22
|
+
jest.setTimeout(10000);
|
|
23
|
+
|
|
21
24
|
jest.mock('@openmrs/esm-framework', () => {
|
|
22
25
|
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
23
26
|
|
|
@@ -55,6 +58,18 @@ jest.mock('@openmrs/esm-framework', () => {
|
|
|
55
58
|
return {
|
|
56
59
|
...originalModule,
|
|
57
60
|
validator: jest.fn(),
|
|
61
|
+
getLocale: jest.fn().mockReturnValue('en'),
|
|
62
|
+
OpenmrsDatePicker: (datePickerProps) => (
|
|
63
|
+
<OpenmrsDatePicker
|
|
64
|
+
id={datePickerProps.id}
|
|
65
|
+
dateFormat={datePickerProps.dateFormat}
|
|
66
|
+
onChange={datePickerProps.onChange}
|
|
67
|
+
maxDate={datePickerProps.maxDate}
|
|
68
|
+
labelText={datePickerProps.labelText}
|
|
69
|
+
value={datePickerProps.value}
|
|
70
|
+
carbonOptions={datePickerProps.carbonOptions}
|
|
71
|
+
/>
|
|
72
|
+
),
|
|
58
73
|
};
|
|
59
74
|
});
|
|
60
75
|
|
|
@@ -345,7 +360,7 @@ describe('patient registration component', () => {
|
|
|
345
360
|
jest.clearAllMocks();
|
|
346
361
|
});
|
|
347
362
|
|
|
348
|
-
|
|
363
|
+
fit('edits patient demographics', async () => {
|
|
349
364
|
const user = userEvent.setup();
|
|
350
365
|
|
|
351
366
|
mockedSavePatient.mockResolvedValue({});
|
|
@@ -379,7 +394,7 @@ describe('patient registration component', () => {
|
|
|
379
394
|
expect(givenNameInput.value).toBe('John');
|
|
380
395
|
expect(familyNameInput.value).toBe('Wilson');
|
|
381
396
|
expect(middleNameInput.value).toBeFalsy();
|
|
382
|
-
expect(dateOfBirthInput.value).toBe('
|
|
397
|
+
expect(dateOfBirthInput.value).toBe('04/04/1972');
|
|
383
398
|
expect(genderInput.value).toBe('Male');
|
|
384
399
|
|
|
385
400
|
// do some edits
|
|
@@ -5,6 +5,7 @@ import { initialFormValues } from '../../patient-registration.component';
|
|
|
5
5
|
import { DemographicsSection } from './demographics-section.component';
|
|
6
6
|
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
7
7
|
import { FormValues } from '../../patient-registration-types';
|
|
8
|
+
import { OpenmrsDatePicker } from '@openmrs/esm-styleguide/src/public';
|
|
8
9
|
|
|
9
10
|
jest.mock('@openmrs/esm-framework', () => {
|
|
10
11
|
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
@@ -15,6 +16,18 @@ jest.mock('@openmrs/esm-framework', () => {
|
|
|
15
16
|
useConfig: jest.fn().mockImplementation(() => ({
|
|
16
17
|
fieldConfigurations: { dateOfBirth: { useEstimatedDateOfBirth: { enabled: true, dayOfMonth: 0, month: 0 } } },
|
|
17
18
|
})),
|
|
19
|
+
getLocale: jest.fn().mockReturnValue('en'),
|
|
20
|
+
OpenmrsDatePicker: (datePickerProps) => (
|
|
21
|
+
<OpenmrsDatePicker
|
|
22
|
+
id={datePickerProps.id}
|
|
23
|
+
dateFormat={datePickerProps.dateFormat}
|
|
24
|
+
onChange={datePickerProps.onChange}
|
|
25
|
+
maxDate={datePickerProps.maxDate}
|
|
26
|
+
labelText={datePickerProps.labelText}
|
|
27
|
+
value={datePickerProps.value}
|
|
28
|
+
carbonOptions={datePickerProps.carbonOptions}
|
|
29
|
+
/>
|
|
30
|
+
),
|
|
18
31
|
};
|
|
19
32
|
});
|
|
20
33
|
|
package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx
CHANGED
|
@@ -83,9 +83,7 @@ const RelationshipView: React.FC<RelationshipViewProps> = ({
|
|
|
83
83
|
<div className={styles.relationship}>
|
|
84
84
|
<div className={styles.searchBox}>
|
|
85
85
|
<div className={styles.relationshipHeader}>
|
|
86
|
-
<h4 className={styles.productiveHeading}>
|
|
87
|
-
{relationship?.relation ?? t('relationshipPlaceholder', 'Relationship')}
|
|
88
|
-
</h4>
|
|
86
|
+
<h4 className={styles.productiveHeading}>{t('relationshipPlaceholder', 'Relationship')}</h4>
|
|
89
87
|
<Button
|
|
90
88
|
kind="ghost"
|
|
91
89
|
iconDescription={t('deleteRelationshipTooltipText', 'Delete')}
|
|
@@ -163,12 +161,12 @@ export const RelationshipsSection = () => {
|
|
|
163
161
|
const tmp: RelationshipType[] = [];
|
|
164
162
|
relationshipTypes.results.forEach((type) => {
|
|
165
163
|
const aIsToB = {
|
|
166
|
-
display: type.aIsToB,
|
|
164
|
+
display: type.displayAIsToB ? type.displayAIsToB : type.aIsToB,
|
|
167
165
|
uuid: type.uuid,
|
|
168
166
|
direction: 'aIsToB',
|
|
169
167
|
};
|
|
170
168
|
const bIsToA = {
|
|
171
|
-
display: type.bIsToA,
|
|
169
|
+
display: type.displayBIsToA ? type.displayBIsToA : type.bIsToA,
|
|
172
170
|
uuid: type.uuid,
|
|
173
171
|
direction: 'bIsToA',
|
|
174
172
|
};
|