@kenyaemr/esm-patient-registration-app 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +7 -0
  2. package/__mocks__/autogenerationoptions.mock.ts +34 -0
  3. package/__mocks__/react-i18next.js +49 -0
  4. package/dist/144.js +2 -0
  5. package/dist/144.js.LICENSE.txt +27 -0
  6. package/dist/144.js.map +1 -0
  7. package/dist/207.js +1 -0
  8. package/dist/207.js.map +1 -0
  9. package/dist/317.js +2 -0
  10. package/dist/317.js.LICENSE.txt +6 -0
  11. package/dist/317.js.map +1 -0
  12. package/dist/330.js +1 -0
  13. package/dist/330.js.map +1 -0
  14. package/dist/574.js +1 -0
  15. package/dist/59.js +1 -0
  16. package/dist/59.js.map +1 -0
  17. package/dist/591.js +2 -0
  18. package/dist/591.js.LICENSE.txt +32 -0
  19. package/dist/591.js.map +1 -0
  20. package/dist/62.js +1 -0
  21. package/dist/62.js.map +1 -0
  22. package/dist/635.js +1 -0
  23. package/dist/635.js.map +1 -0
  24. package/dist/68.js +1 -0
  25. package/dist/68.js.map +1 -0
  26. package/dist/735.js +1 -0
  27. package/dist/735.js.map +1 -0
  28. package/dist/757.js +1 -0
  29. package/dist/784.js +2 -0
  30. package/dist/784.js.LICENSE.txt +9 -0
  31. package/dist/784.js.map +1 -0
  32. package/dist/805.js +1 -0
  33. package/dist/805.js.map +1 -0
  34. package/dist/807.js +1 -0
  35. package/dist/821.js +1 -0
  36. package/dist/821.js.map +1 -0
  37. package/dist/822.js +1 -0
  38. package/dist/822.js.map +1 -0
  39. package/dist/858.js +2 -0
  40. package/dist/858.js.LICENSE.txt +3 -0
  41. package/dist/858.js.map +1 -0
  42. package/dist/887.js +1 -0
  43. package/dist/887.js.map +1 -0
  44. package/dist/9.js +2 -0
  45. package/dist/9.js.LICENSE.txt +9 -0
  46. package/dist/9.js.map +1 -0
  47. package/dist/975.js +1 -0
  48. package/dist/975.js.map +1 -0
  49. package/dist/main.js +2 -0
  50. package/dist/main.js.LICENSE.txt +9 -0
  51. package/dist/main.js.map +1 -0
  52. package/dist/openmrs-esm-patient-registration-app.js +1 -0
  53. package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +623 -0
  54. package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
  55. package/dist/openmrs-esm-patient-registration-app.old +1 -0
  56. package/docs/images/patient-registration-hierarchy.png +0 -0
  57. package/package.json +55 -0
  58. package/src/add-patient-link.scss +3 -0
  59. package/src/add-patient-link.tsx +21 -0
  60. package/src/config-schema.ts +405 -0
  61. package/src/constants.ts +14 -0
  62. package/src/declarations.d.tsx +4 -0
  63. package/src/index.ts +131 -0
  64. package/src/nav-link.tsx +10 -0
  65. package/src/offline.resources.ts +109 -0
  66. package/src/offline.ts +90 -0
  67. package/src/patient-registration/before-save-prompt.tsx +72 -0
  68. package/src/patient-registration/date-util.ts +52 -0
  69. package/src/patient-registration/field/__mocks__/field.resource.ts +60 -0
  70. package/src/patient-registration/field/address/address-field.component.tsx +31 -0
  71. package/src/patient-registration/field/address/address-hierarchy.component.tsx +143 -0
  72. package/src/patient-registration/field/address/address-hierarchy.test.tsx +181 -0
  73. package/src/patient-registration/field/address/address-search.component.tsx +98 -0
  74. package/src/patient-registration/field/address/address-search.scss +53 -0
  75. package/src/patient-registration/field/custom-field.component.tsx +25 -0
  76. package/src/patient-registration/field/dob/dob.component.tsx +143 -0
  77. package/src/patient-registration/field/dob/dob.test.tsx +73 -0
  78. package/src/patient-registration/field/field.component.tsx +44 -0
  79. package/src/patient-registration/field/field.resource.ts +35 -0
  80. package/src/patient-registration/field/field.scss +127 -0
  81. package/src/patient-registration/field/gender/gender-field.component.tsx +49 -0
  82. package/src/patient-registration/field/gender/gender-field.test.tsx +66 -0
  83. package/src/patient-registration/field/id/id-field.component.tsx +142 -0
  84. package/src/patient-registration/field/id/identifier-selection-overlay.tsx +194 -0
  85. package/src/patient-registration/field/id/identifier-selection.scss +37 -0
  86. package/src/patient-registration/field/name/name-field.component.tsx +109 -0
  87. package/src/patient-registration/field/obs/obs-field.component.tsx +185 -0
  88. package/src/patient-registration/field/obs/obs-field.test.tsx +127 -0
  89. package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +59 -0
  90. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +68 -0
  91. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +81 -0
  92. package/src/patient-registration/field/person-attributes/person-attributes.resource.tsx +20 -0
  93. package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +57 -0
  94. package/src/patient-registration/form-manager.test.ts +68 -0
  95. package/src/patient-registration/form-manager.ts +413 -0
  96. package/src/patient-registration/input/basic-input/input/input.component.tsx +59 -0
  97. package/src/patient-registration/input/basic-input/input/input.test.tsx +170 -0
  98. package/src/patient-registration/input/basic-input/select/select-input.component.tsx +32 -0
  99. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +32 -0
  100. package/src/patient-registration/input/combo-input/combo-input.component.tsx +76 -0
  101. package/src/patient-registration/input/combo-input/combo-input.test.tsx +43 -0
  102. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +84 -0
  103. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.scss +53 -0
  104. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +109 -0
  105. package/src/patient-registration/input/custom-input/estimated-age/estimated-age-input.component.tsx +32 -0
  106. package/src/patient-registration/input/custom-input/estimated-age/estimated-age-input.test.tsx +36 -0
  107. package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +156 -0
  108. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +110 -0
  109. package/src/patient-registration/input/custom-input/identifier/utils.ts +19 -0
  110. package/src/patient-registration/input/custom-input/unidentified-patient/unidentified-patient-input.component.tsx +24 -0
  111. package/src/patient-registration/input/custom-input/unidentified-patient/unidentified-patient-input.test.tsx +39 -0
  112. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +53 -0
  113. package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +43 -0
  114. package/src/patient-registration/input/input.scss +108 -0
  115. package/src/patient-registration/patient-registration-context.ts +24 -0
  116. package/src/patient-registration/patient-registration-hooks.ts +320 -0
  117. package/src/patient-registration/patient-registration-types.tsx +271 -0
  118. package/src/patient-registration/patient-registration-utils.ts +219 -0
  119. package/src/patient-registration/patient-registration.component.tsx +250 -0
  120. package/src/patient-registration/patient-registration.resource.test.tsx +26 -0
  121. package/src/patient-registration/patient-registration.resource.tsx +296 -0
  122. package/src/patient-registration/patient-registration.scss +94 -0
  123. package/src/patient-registration/patient-registration.test.tsx +436 -0
  124. package/src/patient-registration/section/death-info/death-info-section.component.tsx +30 -0
  125. package/src/patient-registration/section/death-info/death-info-section.test.tsx +73 -0
  126. package/src/patient-registration/section/demographics/demographics-section.component.tsx +30 -0
  127. package/src/patient-registration/section/demographics/demographics-section.test.tsx +84 -0
  128. package/src/patient-registration/section/generic-section.component.tsx +17 -0
  129. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +226 -0
  130. package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +78 -0
  131. package/src/patient-registration/section/patient-relationships/relationships.scss +35 -0
  132. package/src/patient-registration/section/section-wrapper.component.tsx +40 -0
  133. package/src/patient-registration/section/section.component.tsx +23 -0
  134. package/src/patient-registration/section/section.scss +1 -0
  135. package/src/patient-registration/ui-components/overlay/index.tsx +51 -0
  136. package/src/patient-registration/ui-components/overlay/overlay.scss +63 -0
  137. package/src/patient-registration/validation/patient-registration-validation.test.tsx +129 -0
  138. package/src/patient-registration/validation/patient-registration-validation.tsx +46 -0
  139. package/src/patient-verification/assets/counties.json +236 -0
  140. package/src/patient-verification/assets/verification-assets.ts +11 -0
  141. package/src/patient-verification/patient-verification-hook.tsx +156 -0
  142. package/src/patient-verification/patient-verification-utils.ts +173 -0
  143. package/src/patient-verification/patient-verification.component.tsx +118 -0
  144. package/src/patient-verification/patient-verification.scss +30 -0
  145. package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +69 -0
  146. package/src/patient-verification/verification-modal/empty-prompt.component.tsx +35 -0
  147. package/src/patient-verification/verification-types.ts +50 -0
  148. package/src/resource.ts +12 -0
  149. package/src/root.component.tsx +66 -0
  150. package/src/root.scss +7 -0
  151. package/src/root.test.tsx +32 -0
  152. package/src/widgets/cancel-patient-edit.component.tsx +37 -0
  153. package/src/widgets/delete-identifier-confirmation-modal.tsx +41 -0
  154. package/src/widgets/delete-identifier-modal.scss +34 -0
  155. package/src/widgets/display-photo.component.tsx +30 -0
  156. package/src/widgets/edit-patient-details-button.component.tsx +34 -0
  157. package/src/widgets/edit-patient-details-button.scss +3 -0
  158. package/translations/en.json +108 -0
  159. package/translations/fr.json +89 -0
  160. package/translations/km.json +89 -0
  161. package/tsconfig.json +5 -0
  162. 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 { 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="danger--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,110 @@
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 { 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 = undefined;
56
+ try {
57
+ identifierSourceSelectInput = screen.getByLabelText('source-for-' + identifierType.fieldName) as HTMLInputElement;
58
+ } catch (e) {}
59
+ return {
60
+ identifierInput,
61
+ identifierSourceSelectInput,
62
+ };
63
+ };
64
+
65
+ it('exists', async () => {
66
+ const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
67
+ expect(identifierInput.type).toBe('text');
68
+ expect(identifierSourceSelectInput.type).toBe('select-one');
69
+ });
70
+
71
+ it('has correct props for identifier source select input', async () => {
72
+ const { identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
73
+ expect(identifierSourceSelectInput.childElementCount).toBe(3);
74
+ expect(identifierSourceSelectInput.value).toBe('Generator 1 for OpenMRS ID');
75
+ });
76
+
77
+ it('has correct props for identifier input', async () => {
78
+ const { identifierInput } = await setupIdentifierInput(openmrsID);
79
+ expect(identifierInput.placeholder).toBe('Auto-generated');
80
+ expect(identifierInput.disabled).toBe(true);
81
+ });
82
+
83
+ it('text input should not be disabled if manual entry is enabled', async () => {
84
+ // setup
85
+ openmrsID.identifierSources[0].autoGenerationOption.manualEntryEnabled = true;
86
+ // replay
87
+ const { identifierInput } = await setupIdentifierInput(openmrsID);
88
+ expect(identifierInput.placeholder).toBe('Auto-generated');
89
+ expect(identifierInput.disabled).toBe(false);
90
+ });
91
+
92
+ it('should not render select widget if auto-entry is false', async () => {
93
+ // setup
94
+ openmrsID.identifierSources = [
95
+ {
96
+ uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
97
+ name: 'Generator 1 for OpenMRS ID',
98
+ autoGenerationOption: {
99
+ manualEntryEnabled: true,
100
+ automaticGenerationEnabled: false,
101
+ },
102
+ },
103
+ ];
104
+ // replay
105
+ const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
106
+ expect(identifierInput.placeholder).toBe('Enter identifier');
107
+ expect(identifierInput.disabled).toBe(false);
108
+ expect(identifierSourceSelectInput).toBe(undefined);
109
+ });
110
+ });
@@ -0,0 +1,19 @@
1
+ import { FetchedPatientIdentifierType, 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,24 @@
1
+ import React, { useEffect } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { useField } from 'formik';
4
+ import { Input } from '../../basic-input/input/input.component';
5
+
6
+ interface UnidentifiedPatientInputProps {
7
+ name: string;
8
+ setName(field: string, value: any, shouldValidate?: boolean): void;
9
+ }
10
+
11
+ export const UnidentifiedPatientInput: React.FC<UnidentifiedPatientInputProps> = ({ setName, name }) => {
12
+ const [field] = useField({ name });
13
+ const { t } = useTranslation();
14
+
15
+ useEffect(() => {
16
+ if (field.value) {
17
+ setName('givenName', 'UNKNOWN');
18
+ setName('middleName', 'UNKNOWN');
19
+ setName('familyName', 'UNKNOWN');
20
+ }
21
+ }, [field.value, setName]);
22
+
23
+ return <Input id="checkbox" labelText={t('unidentifiedPatient', 'Unidentified Patient')} name={name} />;
24
+ };
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { Formik, Form } from 'formik';
4
+ import { UnidentifiedPatientInput } from './unidentified-patient-input.component';
5
+
6
+ describe.skip('unidentified patient input', () => {
7
+ const mockSetName = jest.fn();
8
+
9
+ const setupInput = async (unidentifiedPatient) => {
10
+ render(
11
+ <Formik initialValues={{ unidentifiedPatient: unidentifiedPatient }} onSubmit={null}>
12
+ <Form>
13
+ <UnidentifiedPatientInput name="unidentifiedPatient" setName={mockSetName} />
14
+ </Form>
15
+ </Formik>,
16
+ );
17
+ return screen.getByLabelText('Unidentified Patient') as HTMLInputElement;
18
+ };
19
+
20
+ it('exists', async () => {
21
+ const input = await setupInput(false);
22
+ expect(input.type).toEqual('checkbox');
23
+ });
24
+
25
+ it('sets givenName, middleName and familyName to "UNKNOWN" if patient is marked as unidentified', async () => {
26
+ mockSetName.mockReset();
27
+ await setupInput(true);
28
+ expect(mockSetName.mock.calls.length).toBe(3);
29
+ expect(mockSetName).toHaveBeenCalledWith('givenName', 'UNKNOWN');
30
+ expect(mockSetName).toHaveBeenCalledWith('middleName', 'UNKNOWN');
31
+ expect(mockSetName).toHaveBeenCalledWith('familyName', 'UNKNOWN');
32
+ });
33
+
34
+ it('does not override givenName, middleName and familyName if patient is identified', async () => {
35
+ mockSetName.mockReset();
36
+ await setupInput(false);
37
+ expect(mockSetName.mock.calls.length).toBe(0);
38
+ });
39
+ });
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { v4 } from 'uuid';
3
+ import { FormValues } from '../../patient-registration-types';
4
+ import styles from './../input.scss';
5
+
6
+ interface DummyDataInputProps {
7
+ setValues(values: FormValues, shouldValidate?: boolean): void;
8
+ }
9
+
10
+ export const dummyFormValues: FormValues = {
11
+ patientUuid: v4(),
12
+ givenName: 'John',
13
+ middleName: '',
14
+ familyName: 'Smith',
15
+ unidentifiedPatient: false,
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
+ onClick={() => setValues(dummyFormValues)}
46
+ className={`omrs-btn omrs-filled-neutral ${styles.dummyData}`}
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, fireEvent, screen, waitFor } from '@testing-library/react';
3
+ import { DummyDataInput, dummyFormValues } from './dummy-data-input.component';
4
+ import { initialFormValues } from '../../patient-registration.component';
5
+ import { FormValues } from '../../patient-registration-types';
6
+
7
+ jest.mock('@openmrs/esm-framework', () => {
8
+ const originalModule = jest.requireActual('@openmrs/esm-framework');
9
+
10
+ return {
11
+ ...originalModule,
12
+ validator: jest.fn(),
13
+ };
14
+ });
15
+
16
+ describe('dummy data input', () => {
17
+ let formValues: FormValues = initialFormValues;
18
+
19
+ const setupInput = async () => {
20
+ render(
21
+ <DummyDataInput
22
+ setValues={(values) => {
23
+ formValues = values;
24
+ }}
25
+ />,
26
+ );
27
+ return screen.getByLabelText('Dummy Data Input') as HTMLButtonElement;
28
+ };
29
+
30
+ it('exists', async () => {
31
+ const input = await setupInput();
32
+ expect(input.type).toEqual('button');
33
+ });
34
+
35
+ it('can input data on button click', async () => {
36
+ const input = await setupInput();
37
+
38
+ fireEvent.click(input);
39
+ waitFor(() => {
40
+ expect(formValues).toEqual(dummyFormValues);
41
+ });
42
+ });
43
+ });
@@ -0,0 +1,108 @@
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
+ margin-bottom: spacing.$spacing-05;
23
+ }
24
+
25
+ .requiredField {
26
+ color: $danger;
27
+ }
28
+
29
+ .input {
30
+ margin-right: 5px;
31
+ }
32
+
33
+ .checkboxField {
34
+ display: flex;
35
+ flex-direction: row;
36
+ justify-content: flex-start;
37
+ width: 400px;
38
+ margin-left: 10px;
39
+ }
40
+
41
+ .textInput {
42
+ width: 250px !important;
43
+ height: 40px !important;
44
+ }
45
+
46
+ .numberInput {
47
+ width: 200px !important;
48
+ height: 40px !important;
49
+ }
50
+
51
+ .checkboxInput {
52
+ height: 1.5rem !important;
53
+ width: 1.5rem !important;
54
+ margin-top: 8px;
55
+ }
56
+
57
+ .selectInput {
58
+ width: 150px !important;
59
+ height: 40px !important;
60
+ }
61
+
62
+ .dateInput {
63
+ width: 200px !important;
64
+ height: 40px !important;
65
+ font-size: large !important;
66
+ }
67
+
68
+ .telInput {
69
+ width: 250px !important;
70
+ height: 40px !important;
71
+ }
72
+
73
+ .errorInput {
74
+ border: 2px solid $danger !important;
75
+ padding-left: 0.875rem !important;
76
+ }
77
+
78
+ .errorMessage {
79
+ color: $danger;
80
+ align-self: center;
81
+ }
82
+
83
+ .dummyData {
84
+ cursor: pointer;
85
+ margin-left: 5px;
86
+ }
87
+
88
+ .IDInput {
89
+ display: grid;
90
+ grid-template-columns: 1fr auto;
91
+ align-items: end;
92
+ }
93
+
94
+ .trashCan {
95
+ color: $danger;
96
+ }
97
+
98
+ :global(.omrs-breakpoint-lt-desktop) {
99
+ .IDInput {
100
+ max-width: unset;
101
+ width: 100%;
102
+ }
103
+ }
104
+
105
+ .dangerLabel01 {
106
+ @include type.type-style('label-01');
107
+ color: $danger;
108
+ }
@@ -0,0 +1,24 @@
1
+ import { useConfig } from '@openmrs/esm-framework';
2
+ import { createContext, SetStateAction } from 'react';
3
+ import { RegistrationConfig } from '../config-schema';
4
+ import { FormValues, CapturePhotoProps } from './patient-registration-types';
5
+
6
+ export interface PatientRegistrationContextProps {
7
+ identifierTypes: Array<any>;
8
+ values: FormValues;
9
+ validationSchema: any;
10
+ setValidationSchema(value: any): void;
11
+ inEditMode: boolean;
12
+ setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
13
+ setCapturePhotoProps(value: SetStateAction<CapturePhotoProps>): void;
14
+ currentPhoto: string;
15
+ isOffline: boolean;
16
+ initialFormValues: 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
+ }