@ampath/esm-patient-registration-app 6.0.1-pre.6
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 +41 -0
- package/README.md +7 -0
- package/dist/130.js +2 -0
- package/dist/130.js.LICENSE.txt +3 -0
- package/dist/130.js.map +1 -0
- package/dist/152.js +1 -0
- package/dist/152.js.map +1 -0
- package/dist/249.js +2 -0
- package/dist/249.js.LICENSE.txt +46 -0
- package/dist/249.js.map +1 -0
- package/dist/255.js +2 -0
- package/dist/255.js.LICENSE.txt +9 -0
- package/dist/255.js.map +1 -0
- package/dist/271.js +1 -0
- package/dist/303.js +1 -0
- package/dist/303.js.map +1 -0
- package/dist/319.js +1 -0
- package/dist/365.js +1 -0
- package/dist/365.js.map +1 -0
- package/dist/460.js +1 -0
- package/dist/525.js +1 -0
- package/dist/525.js.map +1 -0
- package/dist/537.js +1 -0
- package/dist/537.js.map +1 -0
- package/dist/574.js +1 -0
- package/dist/591.js +2 -0
- package/dist/591.js.LICENSE.txt +32 -0
- package/dist/591.js.map +1 -0
- package/dist/621.js +1 -0
- package/dist/621.js.map +1 -0
- package/dist/644.js +1 -0
- package/dist/729.js +1 -0
- package/dist/729.js.map +1 -0
- package/dist/735.js +1 -0
- package/dist/735.js.map +1 -0
- package/dist/757.js +1 -0
- package/dist/784.js +2 -0
- package/dist/784.js.LICENSE.txt +9 -0
- package/dist/784.js.map +1 -0
- package/dist/788.js +1 -0
- package/dist/807.js +1 -0
- package/dist/833.js +1 -0
- package/dist/879.js +1 -0
- package/dist/879.js.map +1 -0
- package/dist/ampath-esm-patient-registration-app.js +1 -0
- package/dist/ampath-esm-patient-registration-app.js.buildmanifest.json +649 -0
- package/dist/ampath-esm-patient-registration-app.js.map +1 -0
- package/dist/main.js +2 -0
- package/dist/main.js.LICENSE.txt +56 -0
- package/dist/main.js.map +1 -0
- package/dist/routes.json +1 -0
- package/docs/images/patient-registration-hierarchy.png +0 -0
- package/jest.config.js +3 -0
- package/package.json +61 -0
- package/src/add-patient-link.scss +3 -0
- package/src/add-patient-link.test.tsx +20 -0
- package/src/add-patient-link.tsx +21 -0
- package/src/config-schema.ts +410 -0
- package/src/constants.ts +14 -0
- package/src/declarations.d.ts +6 -0
- package/src/index.ts +71 -0
- package/src/nav-link.test.tsx +13 -0
- package/src/nav-link.tsx +10 -0
- package/src/offline.resources.ts +155 -0
- package/src/offline.ts +91 -0
- package/src/patient-registration/before-save-prompt.tsx +73 -0
- package/src/patient-registration/date-util.ts +52 -0
- package/src/patient-registration/field/__mocks__/field.resource.ts +60 -0
- package/src/patient-registration/field/address/address-field.component.tsx +153 -0
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +73 -0
- package/src/patient-registration/field/address/address-hierarchy.resource.tsx +157 -0
- package/src/patient-registration/field/address/address-search.component.tsx +85 -0
- package/src/patient-registration/field/address/address-search.scss +53 -0
- package/src/patient-registration/field/address/custom-address-field.component.tsx +31 -0
- package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +214 -0
- package/src/patient-registration/field/address/tests/address-search-component.test.tsx +135 -0
- package/src/patient-registration/field/custom-field.component.tsx +25 -0
- package/src/patient-registration/field/dob/dob.component.tsx +159 -0
- package/src/patient-registration/field/dob/dob.test.tsx +75 -0
- package/src/patient-registration/field/field.component.tsx +47 -0
- package/src/patient-registration/field/field.resource.ts +35 -0
- package/src/patient-registration/field/field.scss +127 -0
- package/src/patient-registration/field/field.test.tsx +294 -0
- package/src/patient-registration/field/gender/gender-field.component.tsx +49 -0
- package/src/patient-registration/field/gender/gender-field.test.tsx +59 -0
- package/src/patient-registration/field/id/id-field.component.tsx +144 -0
- package/src/patient-registration/field/id/id-field.test.tsx +107 -0
- package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +198 -0
- package/src/patient-registration/field/id/identifier-selection.scss +37 -0
- package/src/patient-registration/field/name/name-field.component.tsx +142 -0
- package/src/patient-registration/field/obs/obs-field.component.tsx +204 -0
- package/src/patient-registration/field/obs/obs-field.test.tsx +205 -0
- package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +60 -0
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +116 -0
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +127 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +88 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +187 -0
- package/src/patient-registration/field/person-attributes/person-attributes.resource.ts +20 -0
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +58 -0
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +88 -0
- package/src/patient-registration/field/phone/phone-field.component.tsx +16 -0
- package/src/patient-registration/form-manager.test.ts +67 -0
- package/src/patient-registration/form-manager.ts +414 -0
- package/src/patient-registration/input/basic-input/input/input.component.tsx +179 -0
- package/src/patient-registration/input/basic-input/input/input.test.tsx +72 -0
- package/src/patient-registration/input/basic-input/select/select-input.component.tsx +32 -0
- package/src/patient-registration/input/basic-input/select/select-input.test.tsx +49 -0
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +128 -0
- package/src/patient-registration/input/combo-input/selection-tick.component.tsx +20 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +187 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.scss +62 -0
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +132 -0
- package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +156 -0
- package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +107 -0
- package/src/patient-registration/input/custom-input/identifier/utils.test.ts +81 -0
- package/src/patient-registration/input/custom-input/identifier/utils.ts +19 -0
- package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +53 -0
- package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +43 -0
- package/src/patient-registration/input/input.scss +118 -0
- package/src/patient-registration/patient-registration-context.ts +24 -0
- package/src/patient-registration/patient-registration-hooks.ts +287 -0
- package/src/patient-registration/patient-registration-utils.ts +216 -0
- package/src/patient-registration/patient-registration.component.tsx +240 -0
- package/src/patient-registration/patient-registration.resource.test.tsx +26 -0
- package/src/patient-registration/patient-registration.resource.ts +250 -0
- package/src/patient-registration/patient-registration.scss +122 -0
- package/src/patient-registration/patient-registration.test.tsx +471 -0
- package/src/patient-registration/patient-registration.types.ts +318 -0
- package/src/patient-registration/section/death-info/death-info-section.component.tsx +31 -0
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +64 -0
- package/src/patient-registration/section/demographics/demographics-section.component.tsx +30 -0
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +83 -0
- package/src/patient-registration/section/generic-section.component.tsx +17 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +235 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +100 -0
- package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +78 -0
- package/src/patient-registration/section/patient-relationships/relationships.scss +35 -0
- package/src/patient-registration/section/section-wrapper.component.tsx +40 -0
- package/src/patient-registration/section/section.component.tsx +23 -0
- package/src/patient-registration/section/section.scss +1 -0
- package/src/patient-registration/ui-components/overlay/overlay.component.tsx +51 -0
- package/src/patient-registration/ui-components/overlay/overlay.scss +63 -0
- package/src/patient-registration/validation/patient-registration-validation.test.tsx +157 -0
- package/src/patient-registration/validation/patient-registration-validation.tsx +60 -0
- package/src/patient-verification/client-registry-constants.ts +13 -0
- package/src/patient-verification/client-registry.component.tsx +66 -0
- package/src/patient-verification/client-registry.scss +1 -0
- package/src/patient-verification/utils.tsx +56 -0
- package/src/patient-verification/verification-modal.scss +20 -0
- package/src/patient-verification/verification.component.tsx +48 -0
- package/src/resource.ts +12 -0
- package/src/root.component.tsx +63 -0
- package/src/root.scss +7 -0
- package/src/root.test.tsx +32 -0
- package/src/routes.json +66 -0
- package/src/widgets/cancel-patient-edit.component.tsx +37 -0
- package/src/widgets/cancel-patient-edit.test.tsx +27 -0
- package/src/widgets/delete-identifier-confirmation-modal.test.tsx +34 -0
- package/src/widgets/delete-identifier-confirmation-modal.tsx +41 -0
- package/src/widgets/delete-identifier-modal.scss +34 -0
- package/src/widgets/display-photo.component.tsx +30 -0
- package/src/widgets/display-photo.test.tsx +37 -0
- package/src/widgets/edit-patient-details-button.component.tsx +34 -0
- package/src/widgets/edit-patient-details-button.scss +3 -0
- package/src/widgets/edit-patient-details-button.test.tsx +41 -0
- package/translations/am.json +97 -0
- package/translations/ar.json +97 -0
- package/translations/en.json +103 -0
- package/translations/es.json +97 -0
- package/translations/fr.json +97 -0
- package/translations/he.json +97 -0
- package/translations/km.json +97 -0
- package/translations/zh.json +89 -0
- package/translations/zh_CN.json +89 -0
- package/tsconfig.json +5 -0
- package/webpack.config.js +1 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React, { useState, useCallback, useContext, useMemo } from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import { useField } from 'formik';
|
|
4
|
+
import { Button } from '@carbon/react';
|
|
5
|
+
import { TrashCan, Edit, Reset } from '@carbon/react/icons';
|
|
6
|
+
import { ResourcesContext } from '../../../../offline.resources';
|
|
7
|
+
import { showModal, useConfig, UserHasAccess } from '@openmrs/esm-framework';
|
|
8
|
+
import { shouldBlockPatientIdentifierInOfflineMode } from './utils';
|
|
9
|
+
import { deleteIdentifierType, setIdentifierSource } from '../../../field/id/id-field.component';
|
|
10
|
+
import { type PatientIdentifierValue } from '../../../patient-registration.types';
|
|
11
|
+
import { PatientRegistrationContext } from '../../../patient-registration-context';
|
|
12
|
+
import { Input } from '../../basic-input/input/input.component';
|
|
13
|
+
import styles from '../../input.scss';
|
|
14
|
+
|
|
15
|
+
interface IdentifierInputProps {
|
|
16
|
+
patientIdentifier: PatientIdentifierValue;
|
|
17
|
+
fieldName: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fieldName }) => {
|
|
21
|
+
const { t } = useTranslation();
|
|
22
|
+
const { defaultPatientIdentifierTypes } = useConfig();
|
|
23
|
+
const { identifierTypes } = useContext(ResourcesContext);
|
|
24
|
+
const { isOffline, values, setFieldValue } = useContext(PatientRegistrationContext);
|
|
25
|
+
const identifierType = useMemo(
|
|
26
|
+
() => identifierTypes.find((identifierType) => identifierType.uuid === patientIdentifier.identifierTypeUuid),
|
|
27
|
+
[patientIdentifier, identifierTypes],
|
|
28
|
+
);
|
|
29
|
+
const { autoGeneration, initialValue, identifierValue, identifierName, required } = patientIdentifier;
|
|
30
|
+
const [hideInputField, setHideInputField] = useState(autoGeneration || initialValue === identifierValue);
|
|
31
|
+
const name = `identifiers.${fieldName}.identifierValue`;
|
|
32
|
+
const [identifierField, identifierFieldMeta] = useField(name);
|
|
33
|
+
|
|
34
|
+
const disabled = isOffline && shouldBlockPatientIdentifierInOfflineMode(identifierType);
|
|
35
|
+
|
|
36
|
+
const defaultPatientIdentifierTypesMap = useMemo(() => {
|
|
37
|
+
const map = {};
|
|
38
|
+
defaultPatientIdentifierTypes?.forEach((typeUuid) => {
|
|
39
|
+
map[typeUuid] = true;
|
|
40
|
+
});
|
|
41
|
+
return map;
|
|
42
|
+
}, [defaultPatientIdentifierTypes]);
|
|
43
|
+
|
|
44
|
+
const handleReset = useCallback(() => {
|
|
45
|
+
setHideInputField(true);
|
|
46
|
+
setFieldValue(`identifiers.${fieldName}`, {
|
|
47
|
+
...patientIdentifier,
|
|
48
|
+
identifierValue: initialValue,
|
|
49
|
+
selectedSource: null,
|
|
50
|
+
autoGeneration: false,
|
|
51
|
+
} as PatientIdentifierValue);
|
|
52
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
53
|
+
}, [initialValue, setHideInputField]);
|
|
54
|
+
|
|
55
|
+
const handleEdit = () => {
|
|
56
|
+
setHideInputField(false);
|
|
57
|
+
setFieldValue(`identifiers.${fieldName}`, {
|
|
58
|
+
...patientIdentifier,
|
|
59
|
+
...setIdentifierSource(identifierType?.identifierSources?.[0], initialValue, initialValue),
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleDelete = () => {
|
|
64
|
+
/*
|
|
65
|
+
If there is an initialValue to the identifier, a confirmation modal seeking
|
|
66
|
+
confirmation to delete the identifier should be shown, else in the other case,
|
|
67
|
+
we can directly delete the identifier.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
if (initialValue) {
|
|
71
|
+
const confirmDeleteIdentifierModal = showModal('delete-identifier-confirmation-modal', {
|
|
72
|
+
deleteIdentifier: (deleteIdentifier) => {
|
|
73
|
+
if (deleteIdentifier) {
|
|
74
|
+
setFieldValue('identifiers', deleteIdentifierType(values.identifiers, fieldName));
|
|
75
|
+
}
|
|
76
|
+
confirmDeleteIdentifierModal();
|
|
77
|
+
},
|
|
78
|
+
identifierName,
|
|
79
|
+
initialValue,
|
|
80
|
+
});
|
|
81
|
+
} else {
|
|
82
|
+
setFieldValue('identifiers', deleteIdentifierType(values.identifiers, fieldName));
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className={styles.IDInput}>
|
|
88
|
+
{!autoGeneration && !hideInputField ? (
|
|
89
|
+
<Input
|
|
90
|
+
id={name}
|
|
91
|
+
labelText={identifierName}
|
|
92
|
+
name={name}
|
|
93
|
+
disabled={disabled}
|
|
94
|
+
required={required}
|
|
95
|
+
invalid={!!(identifierFieldMeta.touched && identifierFieldMeta.error)}
|
|
96
|
+
invalidText={identifierFieldMeta.error && t(identifierFieldMeta.error)}
|
|
97
|
+
// t('identifierValueRequired', 'Identifier value is required')
|
|
98
|
+
{...identifierField}
|
|
99
|
+
/>
|
|
100
|
+
) : (
|
|
101
|
+
<div className={styles.textID}>
|
|
102
|
+
<p className={styles.label}>{identifierName}</p>
|
|
103
|
+
<p className={styles.bodyShort02}>
|
|
104
|
+
{autoGeneration ? t('autoGeneratedPlaceholderText', 'Auto-generated') : identifierValue}
|
|
105
|
+
</p>
|
|
106
|
+
<input type="hidden" {...identifierField} disabled />
|
|
107
|
+
{/* This is added for any error descriptions */}
|
|
108
|
+
{!!(identifierFieldMeta.touched && identifierFieldMeta.error) && (
|
|
109
|
+
<span className={styles.dangerLabel01}>{identifierFieldMeta.error && t(identifierFieldMeta.error)}</span>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
<div style={{ marginBottom: '1rem' }}>
|
|
114
|
+
{!patientIdentifier.required && patientIdentifier.initialValue && hideInputField && (
|
|
115
|
+
<UserHasAccess privilege="Edit Patient Identifiers">
|
|
116
|
+
<Button
|
|
117
|
+
size="md"
|
|
118
|
+
kind="ghost"
|
|
119
|
+
onClick={handleEdit}
|
|
120
|
+
iconDescription={t('editIdentifierTooltip', 'Edit')}
|
|
121
|
+
disabled={disabled}
|
|
122
|
+
hasIconOnly>
|
|
123
|
+
<Edit size={16} />
|
|
124
|
+
</Button>
|
|
125
|
+
</UserHasAccess>
|
|
126
|
+
)}
|
|
127
|
+
{initialValue && initialValue !== identifierValue && (
|
|
128
|
+
<UserHasAccess privilege="Edit Patient Identifiers">
|
|
129
|
+
<Button
|
|
130
|
+
size="md"
|
|
131
|
+
kind="ghost"
|
|
132
|
+
onClick={handleReset}
|
|
133
|
+
iconDescription={t('resetIdentifierTooltip', 'Reset')}
|
|
134
|
+
disabled={disabled}
|
|
135
|
+
hasIconOnly>
|
|
136
|
+
<Reset size={16} />
|
|
137
|
+
</Button>
|
|
138
|
+
</UserHasAccess>
|
|
139
|
+
)}
|
|
140
|
+
{!patientIdentifier.required && !defaultPatientIdentifierTypesMap[patientIdentifier.identifierTypeUuid] && (
|
|
141
|
+
<UserHasAccess privilege="Delete Patient Identifiers">
|
|
142
|
+
<Button
|
|
143
|
+
size="md"
|
|
144
|
+
kind="ghost"
|
|
145
|
+
onClick={handleDelete}
|
|
146
|
+
iconDescription={t('deleteIdentifierTooltip', 'Delete')}
|
|
147
|
+
disabled={disabled}
|
|
148
|
+
hasIconOnly>
|
|
149
|
+
<TrashCan size={16} />
|
|
150
|
+
</Button>
|
|
151
|
+
</UserHasAccess>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { Formik, Form } from 'formik';
|
|
4
|
+
import { IdentifierInput } from './identifier-input.component';
|
|
5
|
+
import { initialFormValues } from '../../../patient-registration.component';
|
|
6
|
+
import { type PatientIdentifierType } from '../../../patient-registration-types';
|
|
7
|
+
|
|
8
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
9
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
...originalModule,
|
|
13
|
+
validator: jest.fn(),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe.skip('identifier input', () => {
|
|
18
|
+
const openmrsID = {
|
|
19
|
+
name: 'OpenMRS ID',
|
|
20
|
+
fieldName: 'openMrsId',
|
|
21
|
+
required: true,
|
|
22
|
+
uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
23
|
+
format: null,
|
|
24
|
+
isPrimary: true,
|
|
25
|
+
identifierSources: [
|
|
26
|
+
{
|
|
27
|
+
uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
|
|
28
|
+
name: 'Generator 1 for OpenMRS ID',
|
|
29
|
+
autoGenerationOption: {
|
|
30
|
+
manualEntryEnabled: false,
|
|
31
|
+
automaticGenerationEnabled: true,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
uuid: '01af8526-cea4-4175-aa90-340acb411771',
|
|
36
|
+
name: 'Generator 2 for OpenMRS ID',
|
|
37
|
+
autoGenerationOption: {
|
|
38
|
+
manualEntryEnabled: true,
|
|
39
|
+
automaticGenerationEnabled: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
autoGenerationSource: null,
|
|
44
|
+
};
|
|
45
|
+
const setupIdentifierInput = async (identifierType: PatientIdentifierType) => {
|
|
46
|
+
initialFormValues['source-for-' + identifierType.fieldName] = identifierType.identifierSources[0].name;
|
|
47
|
+
render(
|
|
48
|
+
<Formik initialValues={initialFormValues} onSubmit={null}>
|
|
49
|
+
<Form>
|
|
50
|
+
<IdentifierInput identifierType={identifierType} />
|
|
51
|
+
</Form>
|
|
52
|
+
</Formik>,
|
|
53
|
+
);
|
|
54
|
+
const identifierInput = screen.getByLabelText(identifierType.fieldName) as HTMLInputElement;
|
|
55
|
+
let identifierSourceSelectInput = screen.getByLabelText('source-for-' + identifierType.fieldName);
|
|
56
|
+
return {
|
|
57
|
+
identifierInput,
|
|
58
|
+
identifierSourceSelectInput,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
it('exists', async () => {
|
|
63
|
+
const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
|
|
64
|
+
expect(identifierInput.type).toBe('text');
|
|
65
|
+
expect(identifierSourceSelectInput.type).toBe('select-one');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('has correct props for identifier source select input', async () => {
|
|
69
|
+
const { identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
|
|
70
|
+
expect(identifierSourceSelectInput.childElementCount).toBe(3);
|
|
71
|
+
expect(identifierSourceSelectInput.value).toBe('Generator 1 for OpenMRS ID');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('has correct props for identifier input', async () => {
|
|
75
|
+
const { identifierInput } = await setupIdentifierInput(openmrsID);
|
|
76
|
+
expect(identifierInput.placeholder).toBe('Auto-generated');
|
|
77
|
+
expect(identifierInput.disabled).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('text input should not be disabled if manual entry is enabled', async () => {
|
|
81
|
+
// setup
|
|
82
|
+
openmrsID.identifierSources[0].autoGenerationOption.manualEntryEnabled = true;
|
|
83
|
+
// replay
|
|
84
|
+
const { identifierInput } = await setupIdentifierInput(openmrsID);
|
|
85
|
+
expect(identifierInput.placeholder).toBe('Auto-generated');
|
|
86
|
+
expect(identifierInput.disabled).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should not render select widget if auto-entry is false', async () => {
|
|
90
|
+
// setup
|
|
91
|
+
openmrsID.identifierSources = [
|
|
92
|
+
{
|
|
93
|
+
uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
|
|
94
|
+
name: 'Generator 1 for OpenMRS ID',
|
|
95
|
+
autoGenerationOption: {
|
|
96
|
+
manualEntryEnabled: true,
|
|
97
|
+
automaticGenerationEnabled: false,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
// replay
|
|
102
|
+
const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
|
|
103
|
+
expect(identifierInput.placeholder).toBe('Enter identifier');
|
|
104
|
+
expect(identifierInput.disabled).toBe(false);
|
|
105
|
+
expect(identifierSourceSelectInput).toBe(undefined);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isUniqueIdentifierTypeForOffline, shouldBlockPatientIdentifierInOfflineMode } from './utils';
|
|
2
|
+
|
|
3
|
+
interface IdentifierTypeOptions {
|
|
4
|
+
uniquenessBehavior?: 'UNIQUE' | 'LOCATION' | 'NON_UNIQUE';
|
|
5
|
+
manualEntryEnabled?: boolean;
|
|
6
|
+
automaticGenerationEnabled?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function createIdentifierType(options: IdentifierTypeOptions) {
|
|
10
|
+
return {
|
|
11
|
+
uniquenessBehavior: options.uniquenessBehavior,
|
|
12
|
+
identifierSources: [
|
|
13
|
+
{
|
|
14
|
+
uuid: 'identifier-source-uuid',
|
|
15
|
+
name: 'Identifier Source Name',
|
|
16
|
+
autoGenerationOption: {
|
|
17
|
+
manualEntryEnabled: options.manualEntryEnabled,
|
|
18
|
+
automaticGenerationEnabled: options.automaticGenerationEnabled,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
name: 'Identifier Type Name',
|
|
23
|
+
required: true,
|
|
24
|
+
uuid: 'identifier-type-uuid',
|
|
25
|
+
fieldName: 'identifierFieldName',
|
|
26
|
+
format: 'identifierFormat',
|
|
27
|
+
isPrimary: true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('shouldBlockPatientIdentifierInOfflineMode function', () => {
|
|
32
|
+
it('should return false if identifierType is not unique', () => {
|
|
33
|
+
const identifierType = createIdentifierType({ uniquenessBehavior: null });
|
|
34
|
+
|
|
35
|
+
const result = shouldBlockPatientIdentifierInOfflineMode(identifierType);
|
|
36
|
+
|
|
37
|
+
expect(result).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should return false if identifierType is unique and no manual entry is enabled', () => {
|
|
41
|
+
const identifierType = createIdentifierType({ uniquenessBehavior: null });
|
|
42
|
+
|
|
43
|
+
const result = shouldBlockPatientIdentifierInOfflineMode(identifierType);
|
|
44
|
+
|
|
45
|
+
expect(result).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should return true if identifierType is unique and manual entry is enabled', () => {
|
|
49
|
+
const identifierType = createIdentifierType({ manualEntryEnabled: true, uniquenessBehavior: 'UNIQUE' });
|
|
50
|
+
|
|
51
|
+
const result = shouldBlockPatientIdentifierInOfflineMode(identifierType);
|
|
52
|
+
|
|
53
|
+
expect(result).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('isUniqueIdentifierTypeForOffline function', () => {
|
|
58
|
+
it('should return true if uniquenessBehavior is UNIQUE', () => {
|
|
59
|
+
const identifierType = createIdentifierType({ uniquenessBehavior: 'UNIQUE' });
|
|
60
|
+
|
|
61
|
+
const result = isUniqueIdentifierTypeForOffline(identifierType);
|
|
62
|
+
|
|
63
|
+
expect(result).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should return true if uniquenessBehavior is LOCATION', () => {
|
|
67
|
+
const identifierType = createIdentifierType({ uniquenessBehavior: 'LOCATION' });
|
|
68
|
+
|
|
69
|
+
const result = isUniqueIdentifierTypeForOffline(identifierType);
|
|
70
|
+
|
|
71
|
+
expect(result).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should return false for other uniqueness behaviors', () => {
|
|
75
|
+
const identifierType = createIdentifierType({ uniquenessBehavior: null });
|
|
76
|
+
|
|
77
|
+
const result = isUniqueIdentifierTypeForOffline(identifierType);
|
|
78
|
+
|
|
79
|
+
expect(result).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type FetchedPatientIdentifierType, type PatientIdentifierType } from '../../../patient-registration.types';
|
|
2
|
+
|
|
3
|
+
export function shouldBlockPatientIdentifierInOfflineMode(identifierType: PatientIdentifierType) {
|
|
4
|
+
// Patient Identifiers which are unique and can be manually entered are prohibited while offline because
|
|
5
|
+
// of the chance of generating conflicts when syncing later.
|
|
6
|
+
return (
|
|
7
|
+
isUniqueIdentifierTypeForOffline(identifierType) &&
|
|
8
|
+
!identifierType.identifierSources.some(
|
|
9
|
+
(source) =>
|
|
10
|
+
!source.autoGenerationOption?.manualEntryEnabled && source.autoGenerationOption?.automaticGenerationEnabled,
|
|
11
|
+
)
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isUniqueIdentifierTypeForOffline(identifierType: FetchedPatientIdentifierType) {
|
|
16
|
+
// In offline mode we consider each uniqueness behavior which could cause conflicts during syncing as 'unique'.
|
|
17
|
+
// Syncing conflicts can appear for the following behaviors:
|
|
18
|
+
return identifierType.uniquenessBehavior === 'UNIQUE' || identifierType.uniquenessBehavior === 'LOCATION';
|
|
19
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { v4 } from 'uuid';
|
|
4
|
+
import { type FormValues } from '../../patient-registration.types';
|
|
5
|
+
import styles from './../input.scss';
|
|
6
|
+
|
|
7
|
+
interface DummyDataInputProps {
|
|
8
|
+
setValues(values: FormValues, shouldValidate?: boolean): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const dummyFormValues: FormValues = {
|
|
12
|
+
patientUuid: v4(),
|
|
13
|
+
givenName: 'John',
|
|
14
|
+
middleName: '',
|
|
15
|
+
familyName: 'Smith',
|
|
16
|
+
additionalGivenName: 'Joey',
|
|
17
|
+
additionalMiddleName: '',
|
|
18
|
+
additionalFamilyName: 'Smitty',
|
|
19
|
+
addNameInLocalLanguage: true,
|
|
20
|
+
gender: 'Male',
|
|
21
|
+
birthdate: new Date(2020, 1, 1) as any,
|
|
22
|
+
yearsEstimated: 1,
|
|
23
|
+
monthsEstimated: 2,
|
|
24
|
+
birthdateEstimated: true,
|
|
25
|
+
telephoneNumber: '0800001066',
|
|
26
|
+
isDead: false,
|
|
27
|
+
deathDate: '',
|
|
28
|
+
deathCause: '',
|
|
29
|
+
relationships: [],
|
|
30
|
+
address: {
|
|
31
|
+
address1: 'Bom Jesus Street',
|
|
32
|
+
address2: '',
|
|
33
|
+
cityVillage: 'Recife',
|
|
34
|
+
stateProvince: 'Pernambuco',
|
|
35
|
+
country: 'Brazil',
|
|
36
|
+
postalCode: '50030-310',
|
|
37
|
+
},
|
|
38
|
+
identifiers: {},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const DummyDataInput: React.FC<DummyDataInputProps> = ({ setValues }) => {
|
|
42
|
+
return (
|
|
43
|
+
<main>
|
|
44
|
+
<button
|
|
45
|
+
className={classNames('omrs-btn omrs-filled-neutral', styles.dummyData)}
|
|
46
|
+
onClick={() => setValues(dummyFormValues)}
|
|
47
|
+
type="button"
|
|
48
|
+
aria-label="Dummy Data Input">
|
|
49
|
+
Input Dummy Data
|
|
50
|
+
</button>
|
|
51
|
+
</main>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { DummyDataInput, dummyFormValues } from './dummy-data-input.component';
|
|
5
|
+
import { initialFormValues } from '../../patient-registration.component';
|
|
6
|
+
import { type FormValues } from '../../patient-registration-types';
|
|
7
|
+
|
|
8
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
9
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
...originalModule,
|
|
13
|
+
validator: jest.fn(),
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('dummy data input', () => {
|
|
18
|
+
let formValues: FormValues = initialFormValues;
|
|
19
|
+
|
|
20
|
+
const setupInput = async () => {
|
|
21
|
+
render(
|
|
22
|
+
<DummyDataInput
|
|
23
|
+
setValues={(values) => {
|
|
24
|
+
formValues = values;
|
|
25
|
+
}}
|
|
26
|
+
/>,
|
|
27
|
+
);
|
|
28
|
+
return screen.getByLabelText('Dummy Data Input') as HTMLButtonElement;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
it('exists', async () => {
|
|
32
|
+
const input = await setupInput();
|
|
33
|
+
expect(input.type).toEqual('button');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('can input data on button click', async () => {
|
|
37
|
+
const user = userEvent.setup();
|
|
38
|
+
const input = await setupInput();
|
|
39
|
+
|
|
40
|
+
await user.click(input);
|
|
41
|
+
expect(formValues).toEqual(dummyFormValues);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
@use '@carbon/styles/scss/spacing';
|
|
2
|
+
@use '@carbon/styles/scss/type';
|
|
3
|
+
@import '../../patient-registration/patient-registration.scss';
|
|
4
|
+
|
|
5
|
+
.fieldRow {
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: row;
|
|
8
|
+
justify-content: flex-start;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.subFieldRow {
|
|
13
|
+
margin-bottom: 5px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.label {
|
|
17
|
+
@include type.type-style('label-01');
|
|
18
|
+
color: $text-02;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.textID,
|
|
22
|
+
.comboInput {
|
|
23
|
+
margin-bottom: spacing.$spacing-05;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.requiredField {
|
|
27
|
+
color: $danger;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.input {
|
|
31
|
+
margin-right: 5px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.checkboxField {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: row;
|
|
37
|
+
justify-content: flex-start;
|
|
38
|
+
width: 400px;
|
|
39
|
+
margin-left: 10px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.textInput {
|
|
43
|
+
width: 250px !important;
|
|
44
|
+
height: 40px !important;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.numberInput {
|
|
48
|
+
width: 200px !important;
|
|
49
|
+
height: 40px !important;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.checkboxInput {
|
|
53
|
+
height: 1.5rem !important;
|
|
54
|
+
width: 1.5rem !important;
|
|
55
|
+
margin-top: 8px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.selectInput {
|
|
59
|
+
width: 150px !important;
|
|
60
|
+
height: 40px !important;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.dateInput {
|
|
64
|
+
width: 200px !important;
|
|
65
|
+
height: 40px !important;
|
|
66
|
+
font-size: large !important;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.telInput {
|
|
70
|
+
width: 250px !important;
|
|
71
|
+
height: 40px !important;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.errorInput {
|
|
75
|
+
border: 2px solid $danger !important;
|
|
76
|
+
padding-left: 0.875rem !important;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.errorMessage {
|
|
80
|
+
color: $danger;
|
|
81
|
+
align-self: center;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.dummyData {
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
margin-left: 5px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.IDInput {
|
|
90
|
+
display: grid;
|
|
91
|
+
grid-template-columns: 1fr auto;
|
|
92
|
+
align-items: end;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.trashCan {
|
|
96
|
+
color: $danger;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
:global(.omrs-breakpoint-lt-desktop) {
|
|
100
|
+
.IDInput {
|
|
101
|
+
max-width: unset;
|
|
102
|
+
width: 100%;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.dangerLabel01 {
|
|
107
|
+
@include type.type-style('label-01');
|
|
108
|
+
color: $danger;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.comboInputEntries {
|
|
112
|
+
position: relative;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.comboInputItemOption {
|
|
116
|
+
margin: 0;
|
|
117
|
+
padding-left: 1rem;
|
|
118
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
2
|
+
import { createContext, type SetStateAction } from 'react';
|
|
3
|
+
import { type RegistrationConfig } from '../config-schema';
|
|
4
|
+
import { type FormValues, type CapturePhotoProps } from './patient-registration.types';
|
|
5
|
+
|
|
6
|
+
export interface PatientRegistrationContextProps {
|
|
7
|
+
identifierTypes: Array<any>;
|
|
8
|
+
values: FormValues;
|
|
9
|
+
validationSchema: any;
|
|
10
|
+
inEditMode: boolean;
|
|
11
|
+
setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
|
|
12
|
+
setCapturePhotoProps(value: SetStateAction<CapturePhotoProps>): void;
|
|
13
|
+
currentPhoto: string;
|
|
14
|
+
isOffline: boolean;
|
|
15
|
+
initialFormValues: FormValues;
|
|
16
|
+
setInitialFormValues?: React.Dispatch<SetStateAction<FormValues>>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const PatientRegistrationContext = createContext<PatientRegistrationContextProps | undefined>(undefined);
|
|
20
|
+
|
|
21
|
+
export function useFieldConfig(field: string) {
|
|
22
|
+
const { fieldConfigurations } = useConfig() as RegistrationConfig;
|
|
23
|
+
return fieldConfigurations[field];
|
|
24
|
+
}
|