@olaboot/esm-patient-registration-app 9.2.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.
- package/ADDRESS_CONFIGURATION.md +152 -0
- package/IDENTIFIER_CONFIGURATION.md +142 -0
- package/IMPLEMENTATION_SUMMARY.md +111 -0
- package/QUICK_START.md +95 -0
- package/README.md +7 -0
- package/address-required-fields-config.json +26 -0
- package/dist/126.js +1 -0
- package/dist/15.js +1 -0
- package/dist/1564.js +1 -0
- package/dist/1567.js +1 -0
- package/dist/1845.js +1 -0
- package/dist/1953.js +1 -0
- package/dist/200.js +1 -0
- package/dist/200.js.map +1 -0
- package/dist/215.js +1 -0
- package/dist/2178.js +1 -0
- package/dist/250.js +1 -0
- package/dist/250.js.map +1 -0
- package/dist/2523.js +1 -0
- package/dist/2523.js.map +1 -0
- package/dist/2566.js +1 -0
- package/dist/2586.js +1 -0
- package/dist/2586.js.map +1 -0
- package/dist/2716.js +1 -0
- package/dist/2716.js.map +1 -0
- package/dist/2759.js +1 -0
- package/dist/2821.js +6 -0
- package/dist/2821.js.map +1 -0
- package/dist/3089.js +1 -0
- package/dist/3089.js.map +1 -0
- package/dist/3230.js +1 -0
- package/dist/3441.js +1 -0
- package/dist/3565.js +1 -0
- package/dist/3571.js +1 -0
- package/dist/3571.js.map +1 -0
- package/dist/3746.js +1 -0
- package/dist/3925.js +1 -0
- package/dist/3946.js +1 -0
- package/dist/4024.js +1 -0
- package/dist/4024.js.map +1 -0
- package/dist/4744.js +1 -0
- package/dist/4744.js.map +1 -0
- package/dist/4809.js +1 -0
- package/dist/4894.js +1 -0
- package/dist/4970.js +1 -0
- package/dist/4970.js.map +1 -0
- package/dist/5130.js +1 -0
- package/dist/5187.js +1 -0
- package/dist/5491.js +1 -0
- package/dist/5491.js.map +1 -0
- package/dist/5595.js +1 -0
- package/dist/5961.js +1 -0
- package/dist/6133.js +1 -0
- package/dist/634.js +1 -0
- package/dist/634.js.map +1 -0
- package/dist/6456.js +1 -0
- package/dist/6466.js +1 -0
- package/dist/6613.js +1 -0
- package/dist/6783.js +1 -0
- package/dist/7073.js +38 -0
- package/dist/7073.js.map +1 -0
- package/dist/7154.js +1 -0
- package/dist/7154.js.map +1 -0
- package/dist/7348.js +1 -0
- package/dist/7439.js +1 -0
- package/dist/7439.js.map +1 -0
- package/dist/7543.js +1 -0
- package/dist/7607.js +1 -0
- package/dist/772.js +1 -0
- package/dist/7984.js +1 -0
- package/dist/7984.js.map +1 -0
- package/dist/8538.js +1 -0
- package/dist/8538.js.map +1 -0
- package/dist/8599.js +1 -0
- package/dist/8727.js +1 -0
- package/dist/8847.js +1 -0
- package/dist/9015.js +1 -0
- package/dist/906.js +1 -0
- package/dist/9065.js +1 -0
- package/dist/9182.js +1 -0
- package/dist/9339.js +1 -0
- package/dist/9453.js +1 -0
- package/dist/9833.js +1 -0
- package/dist/9833.js.map +1 -0
- package/dist/9856.js +1 -0
- package/dist/9856.js.map +1 -0
- package/dist/9920.js +1 -0
- package/dist/9938.js +1 -0
- package/dist/9943.js +1 -0
- package/dist/9943.js.map +1 -0
- package/dist/main.js +10 -0
- package/dist/main.js.map +1 -0
- package/dist/olaboot-esm-patient-registration-app.js +5 -0
- package/dist/olaboot-esm-patient-registration-app.js.buildmanifest.json +1627 -0
- package/dist/olaboot-esm-patient-registration-app.js.map +1 -0
- package/dist/routes.json +1 -0
- package/docs/images/patient-registration-hierarchy.png +0 -0
- package/example-config.json +14 -0
- package/jest.config.js +3 -0
- package/package.json +60 -0
- package/rspack.config.js +1 -0
- package/src/add-patient-link.extension.tsx +21 -0
- package/src/add-patient-link.scss +3 -0
- package/src/add-patient-link.test.tsx +16 -0
- package/src/config-schema.ts +507 -0
- package/src/constants.ts +14 -0
- package/src/declarations.d.ts +4 -0
- package/src/index.ts +59 -0
- package/src/nav-link.test.tsx +13 -0
- package/src/nav-link.tsx +10 -0
- package/src/offline.resources.ts +157 -0
- package/src/offline.ts +93 -0
- package/src/patient-photo.extension.tsx +11 -0
- package/src/patient-registration/before-save-prompt.component.tsx +72 -0
- package/src/patient-registration/field/__mocks__/field.resource.ts +60 -0
- package/src/patient-registration/field/address/address-field.component.tsx +186 -0
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +71 -0
- package/src/patient-registration/field/address/address-hierarchy.resource.tsx +157 -0
- package/src/patient-registration/field/address/address-hierarchy.test.tsx +296 -0
- package/src/patient-registration/field/address/address-search.component.tsx +87 -0
- package/src/patient-registration/field/address/address-search.scss +53 -0
- package/src/patient-registration/field/address/address-search.test.tsx +141 -0
- package/src/patient-registration/field/address/custom-address-field.component.tsx +32 -0
- package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +98 -0
- package/src/patient-registration/field/custom-field.component.tsx +25 -0
- package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +79 -0
- package/src/patient-registration/field/dob/dob.component.tsx +167 -0
- package/src/patient-registration/field/dob/dob.test.tsx +90 -0
- package/src/patient-registration/field/field.component.tsx +53 -0
- package/src/patient-registration/field/field.resource.ts +42 -0
- package/src/patient-registration/field/field.scss +171 -0
- package/src/patient-registration/field/field.test.tsx +330 -0
- package/src/patient-registration/field/gender/gender-field.component.tsx +54 -0
- package/src/patient-registration/field/gender/gender-field.test.tsx +99 -0
- package/src/patient-registration/field/id/id-field.component.tsx +136 -0
- package/src/patient-registration/field/id/id-field.test.tsx +121 -0
- package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +200 -0
- package/src/patient-registration/field/id/identifier-selection.scss +41 -0
- package/src/patient-registration/field/name/name-field.component.tsx +148 -0
- package/src/patient-registration/field/obs/obs-field.component.tsx +261 -0
- package/src/patient-registration/field/obs/obs-field.test.tsx +299 -0
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +120 -0
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +141 -0
- package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +105 -0
- package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +48 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +100 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +193 -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 +90 -0
- package/src/patient-registration/field/phone/phone-field.component.tsx +17 -0
- package/src/patient-registration/form-manager.test.ts +91 -0
- package/src/patient-registration/form-manager.ts +443 -0
- package/src/patient-registration/input/basic-input/input/input.component.tsx +183 -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 +130 -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 +164 -0
- package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +193 -0
- package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +335 -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 +56 -0
- package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +34 -0
- package/src/patient-registration/input/input.scss +122 -0
- package/src/patient-registration/patient-registration-context.ts +35 -0
- package/src/patient-registration/patient-registration-hooks.ts +376 -0
- package/src/patient-registration/patient-registration-utils.test.ts +33 -0
- package/src/patient-registration/patient-registration-utils.ts +214 -0
- package/src/patient-registration/patient-registration.component.tsx +266 -0
- package/src/patient-registration/patient-registration.resource.test.tsx +22 -0
- package/src/patient-registration/patient-registration.resource.ts +198 -0
- package/src/patient-registration/patient-registration.scss +103 -0
- package/src/patient-registration/patient-registration.test.tsx +580 -0
- package/src/patient-registration/patient-registration.types.ts +322 -0
- package/src/patient-registration/section/death-info/death-info-section.component.tsx +36 -0
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +47 -0
- package/src/patient-registration/section/demographics/demographics-section.component.tsx +30 -0
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +98 -0
- package/src/patient-registration/section/generic-section.component.tsx +17 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +234 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +113 -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 +21 -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.ts +205 -0
- package/src/patient-registration/validation/patient-registration-validation.ts +123 -0
- package/src/resource.ts +12 -0
- package/src/resources-context.ts +14 -0
- package/src/root.component.tsx +63 -0
- package/src/root.scss +7 -0
- package/src/routes.json +61 -0
- package/src/widgets/cancel-patient-edit.modal.tsx +33 -0
- package/src/widgets/cancel-patient-edit.test.tsx +22 -0
- package/src/widgets/delete-identifier-confirmation.modal.tsx +48 -0
- package/src/widgets/delete-identifier-confirmation.test.tsx +32 -0
- package/src/widgets/edit-patient-details-button.component.tsx +33 -0
- package/src/widgets/edit-patient-details-button.scss +3 -0
- package/src/widgets/edit-patient-details-button.test.tsx +35 -0
- package/translations/am.json +120 -0
- package/translations/ar.json +120 -0
- package/translations/ar_SY.json +120 -0
- package/translations/bn.json +120 -0
- package/translations/cs.json +120 -0
- package/translations/de.json +120 -0
- package/translations/en.json +120 -0
- package/translations/en_US.json +120 -0
- package/translations/es.json +120 -0
- package/translations/es_MX.json +120 -0
- package/translations/fr.json +120 -0
- package/translations/he.json +120 -0
- package/translations/hi.json +120 -0
- package/translations/hi_IN.json +120 -0
- package/translations/id.json +120 -0
- package/translations/it.json +120 -0
- package/translations/ka.json +120 -0
- package/translations/km.json +120 -0
- package/translations/ku.json +120 -0
- package/translations/ky.json +120 -0
- package/translations/lg.json +120 -0
- package/translations/ne.json +120 -0
- package/translations/pl.json +120 -0
- package/translations/pt.json +120 -0
- package/translations/pt_BR.json +120 -0
- package/translations/qu.json +120 -0
- package/translations/ro_RO.json +120 -0
- package/translations/ru_RU.json +120 -0
- package/translations/si.json +120 -0
- package/translations/sq.json +120 -0
- package/translations/sw.json +120 -0
- package/translations/sw_KE.json +120 -0
- package/translations/tr.json +120 -0
- package/translations/tr_TR.json +120 -0
- package/translations/uk.json +120 -0
- package/translations/uz.json +120 -0
- package/translations/uz@Latn.json +120 -0
- package/translations/uz_UZ.json +120 -0
- package/translations/vi.json +120 -0
- package/translations/zh.json +120 -0
- package/translations/zh_CN.json +120 -0
- package/translations/zh_TW.json +120 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Form, Formik } from 'formik';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { type FieldDefinition } from '../../../config-schema';
|
|
5
|
+
import { usePersonAttributeType } from './person-attributes.resource';
|
|
6
|
+
import { useConceptAnswers } from '../field.resource';
|
|
7
|
+
import { PersonAttributeField } from './person-attribute-field.component';
|
|
8
|
+
|
|
9
|
+
jest.mock('./person-attributes.resource', () => ({
|
|
10
|
+
...jest.requireActual('./person-attributes.resource'),
|
|
11
|
+
usePersonAttributeType: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock('../field.resource', () => ({
|
|
15
|
+
...jest.requireActual('../field.resource'),
|
|
16
|
+
useConceptAnswers: jest.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
const mockUsePersonAttributeType = jest.mocked(usePersonAttributeType);
|
|
20
|
+
const mockUseConceptAnswers = jest.mocked(useConceptAnswers);
|
|
21
|
+
|
|
22
|
+
const mockPersonAttributeType = {
|
|
23
|
+
format: 'java.lang.String',
|
|
24
|
+
display: 'Referred by',
|
|
25
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
26
|
+
name: 'Referred by',
|
|
27
|
+
description: 'The person who referred the patient',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let fieldDefinition: FieldDefinition = {
|
|
31
|
+
id: 'referredby',
|
|
32
|
+
label: 'Referred by',
|
|
33
|
+
type: 'person attribute',
|
|
34
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
35
|
+
answerConceptSetUuid: '6682d17f-0777-45e4-a39b-93f77eb3531c',
|
|
36
|
+
validation: {
|
|
37
|
+
matches: '',
|
|
38
|
+
required: true,
|
|
39
|
+
},
|
|
40
|
+
showHeading: true,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe('PersonAttributeField', () => {
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
46
|
+
data: mockPersonAttributeType,
|
|
47
|
+
isLoading: false,
|
|
48
|
+
error: null,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders the text input field for String format', () => {
|
|
53
|
+
render(
|
|
54
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
55
|
+
<Form>
|
|
56
|
+
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
57
|
+
</Form>
|
|
58
|
+
</Formik>,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const input = screen.getByLabelText(/Referred by/i) as HTMLInputElement;
|
|
62
|
+
expect(screen.getByRole('heading')).toBeInTheDocument();
|
|
63
|
+
expect(input).toBeInTheDocument();
|
|
64
|
+
expect(input.type).toBe('text');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should not show heading if showHeading is false', () => {
|
|
68
|
+
fieldDefinition = {
|
|
69
|
+
...fieldDefinition,
|
|
70
|
+
showHeading: false,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
render(
|
|
74
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
75
|
+
<Form>
|
|
76
|
+
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
77
|
+
</Form>
|
|
78
|
+
</Formik>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(screen.queryByRole('heading')).not.toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('renders the coded attribute field for Concept format', () => {
|
|
85
|
+
fieldDefinition = {
|
|
86
|
+
id: 'referredby',
|
|
87
|
+
...fieldDefinition,
|
|
88
|
+
label: 'Referred by',
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
92
|
+
data: { ...mockPersonAttributeType, format: 'org.openmrs.Concept' },
|
|
93
|
+
isLoading: false,
|
|
94
|
+
error: null,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
mockUseConceptAnswers.mockReturnValueOnce({
|
|
98
|
+
data: [
|
|
99
|
+
{ uuid: '1', display: 'Option 1' },
|
|
100
|
+
{ uuid: '2', display: 'Option 2' },
|
|
101
|
+
],
|
|
102
|
+
error: null,
|
|
103
|
+
isLoading: false,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
render(
|
|
107
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
108
|
+
<Form>
|
|
109
|
+
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
110
|
+
</Form>
|
|
111
|
+
</Formik>,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const input = screen.getByLabelText(/Referred by/i) as HTMLInputElement;
|
|
115
|
+
expect(input).toBeInTheDocument();
|
|
116
|
+
expect(input.type).toBe('select-one');
|
|
117
|
+
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
|
118
|
+
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('renders an error notification if attribute type has unknown format', () => {
|
|
122
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
123
|
+
data: { ...mockPersonAttributeType, format: 'unknown' },
|
|
124
|
+
isLoading: false,
|
|
125
|
+
error: null,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
render(
|
|
129
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
130
|
+
<Form>
|
|
131
|
+
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
132
|
+
</Form>
|
|
133
|
+
</Formik>,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(screen.getByText('Error')).toBeInTheDocument();
|
|
137
|
+
expect(screen.getByText(/Patient attribute type has unknown format/i)).toBeInTheDocument();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('renders an error notification if unable to fetch attribute type', () => {
|
|
141
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
142
|
+
data: null,
|
|
143
|
+
isLoading: false,
|
|
144
|
+
error: new Error('Failed to fetch attribute type'),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
fieldDefinition = {
|
|
148
|
+
id: 'referredBy',
|
|
149
|
+
uuid: 'attribute-uuid',
|
|
150
|
+
label: 'Attribute',
|
|
151
|
+
showHeading: false,
|
|
152
|
+
type: 'person attribute',
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
render(
|
|
156
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
157
|
+
<Form>
|
|
158
|
+
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
159
|
+
</Form>
|
|
160
|
+
</Formik>,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(screen.getByText('Error')).toBeInTheDocument();
|
|
164
|
+
expect(screen.getByText(/Unable to fetch person attribute type/i)).toBeInTheDocument();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('renders a skeleton if attribute type is loading', async () => {
|
|
168
|
+
mockUsePersonAttributeType.mockReturnValue({
|
|
169
|
+
data: null,
|
|
170
|
+
isLoading: true,
|
|
171
|
+
error: null,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
fieldDefinition = {
|
|
175
|
+
id: 'referredBy',
|
|
176
|
+
uuid: 'attribute-uuid',
|
|
177
|
+
label: 'Attribute',
|
|
178
|
+
showHeading: true,
|
|
179
|
+
type: 'person attribute',
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
render(
|
|
183
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
184
|
+
<Form>
|
|
185
|
+
<PersonAttributeField fieldDefinition={fieldDefinition} />
|
|
186
|
+
</Form>
|
|
187
|
+
</Formik>,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
await screen.findByRole('heading', { name: /attribute/i });
|
|
191
|
+
expect(screen.queryByLabelText(/referred by/i)).not.toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
|
+
import useSWRImmutable from 'swr/immutable';
|
|
3
|
+
import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
4
|
+
|
|
5
|
+
export function usePersonAttributeType(personAttributeTypeUuid: string): {
|
|
6
|
+
data: PersonAttributeTypeResponse;
|
|
7
|
+
isLoading: boolean;
|
|
8
|
+
error: any;
|
|
9
|
+
} {
|
|
10
|
+
const { data, error, isLoading } = useSWRImmutable<FetchResponse<PersonAttributeTypeResponse>>(
|
|
11
|
+
`${restBaseUrl}/personattributetype/${personAttributeTypeUuid}`,
|
|
12
|
+
openmrsFetch,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
data: data?.data,
|
|
17
|
+
isLoading,
|
|
18
|
+
error,
|
|
19
|
+
};
|
|
20
|
+
}
|
package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Field } from 'formik';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { Input } from '../../input/basic-input/input/input.component';
|
|
6
|
+
import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
7
|
+
import styles from './../field.scss';
|
|
8
|
+
|
|
9
|
+
export interface TextPersonAttributeFieldProps {
|
|
10
|
+
id: string;
|
|
11
|
+
personAttributeType: PersonAttributeTypeResponse;
|
|
12
|
+
validationRegex?: string;
|
|
13
|
+
label?: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function TextPersonAttributeField({
|
|
18
|
+
id,
|
|
19
|
+
personAttributeType,
|
|
20
|
+
validationRegex,
|
|
21
|
+
label,
|
|
22
|
+
required,
|
|
23
|
+
}: TextPersonAttributeFieldProps) {
|
|
24
|
+
const { t } = useTranslation();
|
|
25
|
+
|
|
26
|
+
const validateInput = (value: string) => {
|
|
27
|
+
if (!value || !validationRegex || validationRegex === '' || typeof validationRegex !== 'string' || value === '') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const regex = new RegExp(validationRegex);
|
|
31
|
+
if (regex.test(value)) {
|
|
32
|
+
return;
|
|
33
|
+
} else {
|
|
34
|
+
return t('invalidInput', 'Invalid Input');
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const fieldName = `attributes.${personAttributeType.uuid}`;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
|
|
42
|
+
<Field name={fieldName} validate={validateInput}>
|
|
43
|
+
{({ field, form: { touched, errors }, meta }) => {
|
|
44
|
+
return (
|
|
45
|
+
<Input
|
|
46
|
+
id={id}
|
|
47
|
+
name={`person-attribute-${personAttributeType.uuid}`}
|
|
48
|
+
labelText={label ?? personAttributeType?.display}
|
|
49
|
+
invalid={errors[fieldName] && touched[fieldName]}
|
|
50
|
+
{...field}
|
|
51
|
+
required={required}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}}
|
|
55
|
+
</Field>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { Form, Formik } from 'formik';
|
|
5
|
+
import { TextPersonAttributeField } from './text-person-attribute-field.component';
|
|
6
|
+
|
|
7
|
+
describe('TextPersonAttributeField', () => {
|
|
8
|
+
const mockPersonAttributeType = {
|
|
9
|
+
format: 'java.lang.String',
|
|
10
|
+
display: 'Referred by',
|
|
11
|
+
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
12
|
+
description: 'Referred by',
|
|
13
|
+
name: 'Referred by',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
it('renders the input field with a label', () => {
|
|
17
|
+
render(
|
|
18
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
19
|
+
<Form>
|
|
20
|
+
<TextPersonAttributeField
|
|
21
|
+
id="attributeId"
|
|
22
|
+
personAttributeType={mockPersonAttributeType}
|
|
23
|
+
label="Custom Label"
|
|
24
|
+
/>
|
|
25
|
+
</Form>
|
|
26
|
+
</Formik>,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(screen.getByRole('textbox', { name: /custom label \(optional\)/i })).toBeInTheDocument();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders the input field with the default label if label prop is not provided', () => {
|
|
33
|
+
render(
|
|
34
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
35
|
+
<Form>
|
|
36
|
+
<TextPersonAttributeField id="attributeId" personAttributeType={mockPersonAttributeType} />
|
|
37
|
+
</Form>
|
|
38
|
+
</Formik>,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
expect(screen.getByRole('textbox', { name: /referred by \(optional\)/i })).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('validates the input with the provided validationRegex', async () => {
|
|
45
|
+
const user = userEvent.setup();
|
|
46
|
+
const validationRegex = '^[A-Z]+$'; // Accepts only uppercase letters
|
|
47
|
+
|
|
48
|
+
render(
|
|
49
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
50
|
+
<Form>
|
|
51
|
+
<TextPersonAttributeField
|
|
52
|
+
id="attributeId"
|
|
53
|
+
personAttributeType={mockPersonAttributeType}
|
|
54
|
+
validationRegex={validationRegex}
|
|
55
|
+
/>
|
|
56
|
+
</Form>
|
|
57
|
+
</Formik>,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const textbox = screen.getByRole('textbox', { name: /referred by \(optional\)/i });
|
|
61
|
+
expect(textbox).toBeInTheDocument();
|
|
62
|
+
|
|
63
|
+
// Valid input: "ABC"
|
|
64
|
+
await user.type(textbox, 'ABC');
|
|
65
|
+
await user.tab();
|
|
66
|
+
|
|
67
|
+
expect(screen.queryByText(/invalid input/i)).not.toBeInTheDocument();
|
|
68
|
+
await user.clear(textbox);
|
|
69
|
+
|
|
70
|
+
// // Invalid input: "abc" (contains lowercase letters)
|
|
71
|
+
await user.type(textbox, 'abc');
|
|
72
|
+
await user.tab();
|
|
73
|
+
expect(screen.getByText(/invalid input/i)).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('renders the input field as required when required prop is true', () => {
|
|
77
|
+
render(
|
|
78
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
79
|
+
<Form>
|
|
80
|
+
<TextPersonAttributeField id="attributeId" personAttributeType={mockPersonAttributeType} required />
|
|
81
|
+
</Form>
|
|
82
|
+
</Formik>,
|
|
83
|
+
);
|
|
84
|
+
const textbox = screen.getByRole('textbox', { name: /referred by/i });
|
|
85
|
+
|
|
86
|
+
// Required attribute should be truthy on the input element
|
|
87
|
+
expect(textbox).toBeInTheDocument();
|
|
88
|
+
expect(textbox).toBeRequired();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PersonAttributeField } from '../person-attributes/person-attribute-field.component';
|
|
3
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
4
|
+
import { type RegistrationConfig } from '../../../config-schema';
|
|
5
|
+
|
|
6
|
+
export function PhoneField() {
|
|
7
|
+
const config = useConfig<RegistrationConfig>();
|
|
8
|
+
|
|
9
|
+
const fieldDefinition = {
|
|
10
|
+
id: 'phone',
|
|
11
|
+
type: 'person attribute',
|
|
12
|
+
uuid: config.fieldConfigurations.phone.personAttributeUuid,
|
|
13
|
+
validation: config.fieldConfigurations.phone.validation,
|
|
14
|
+
showHeading: false,
|
|
15
|
+
};
|
|
16
|
+
return <PersonAttributeField fieldDefinition={fieldDefinition} />;
|
|
17
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { FormManager } from './form-manager';
|
|
2
|
+
import { type FormValues } from './patient-registration.types';
|
|
3
|
+
import { generateIdentifier } from './patient-registration.resource';
|
|
4
|
+
|
|
5
|
+
jest.mock('./patient-registration.resource', () => ({
|
|
6
|
+
...jest.requireActual('./patient-registration.resource'),
|
|
7
|
+
generateIdentifier: jest.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
const mockGenerateIdentifier = generateIdentifier as jest.Mock;
|
|
11
|
+
|
|
12
|
+
const formValues: FormValues = {
|
|
13
|
+
patientUuid: '',
|
|
14
|
+
givenName: '',
|
|
15
|
+
middleName: '',
|
|
16
|
+
familyName: '',
|
|
17
|
+
additionalGivenName: '',
|
|
18
|
+
additionalMiddleName: '',
|
|
19
|
+
additionalFamilyName: '',
|
|
20
|
+
addNameInLocalLanguage: false,
|
|
21
|
+
gender: '',
|
|
22
|
+
birthdate: '',
|
|
23
|
+
yearsEstimated: 1000,
|
|
24
|
+
monthsEstimated: 11,
|
|
25
|
+
birthdateEstimated: false,
|
|
26
|
+
telephoneNumber: '',
|
|
27
|
+
isDead: false,
|
|
28
|
+
deathDate: 'string',
|
|
29
|
+
deathTime: '',
|
|
30
|
+
deathTimeFormat: 'AM',
|
|
31
|
+
deathCause: 'string',
|
|
32
|
+
nonCodedCauseOfDeath: '',
|
|
33
|
+
relationships: [],
|
|
34
|
+
address: {
|
|
35
|
+
address1: '',
|
|
36
|
+
address2: '',
|
|
37
|
+
cityVillage: '',
|
|
38
|
+
stateProvince: 'New York',
|
|
39
|
+
country: 'string',
|
|
40
|
+
postalCode: 'string',
|
|
41
|
+
},
|
|
42
|
+
identifiers: {
|
|
43
|
+
foo: {
|
|
44
|
+
identifierUuid: 'aUuid',
|
|
45
|
+
identifierName: 'Foo',
|
|
46
|
+
required: false,
|
|
47
|
+
initialValue: 'foo',
|
|
48
|
+
identifierValue: 'foo',
|
|
49
|
+
identifierTypeUuid: 'identifierType',
|
|
50
|
+
preferred: true,
|
|
51
|
+
autoGeneration: false,
|
|
52
|
+
selectedSource: {
|
|
53
|
+
uuid: 'some-uuid',
|
|
54
|
+
name: 'unique',
|
|
55
|
+
autoGenerationOption: { manualEntryEnabled: true, automaticGenerationEnabled: false },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
describe('FormManager', () => {
|
|
62
|
+
describe('createIdentifiers', () => {
|
|
63
|
+
it('uses the uuid of a field name if it exists', async () => {
|
|
64
|
+
const result = await FormManager.savePatientIdentifiers(true, undefined, formValues.identifiers, {}, 'Nyc');
|
|
65
|
+
expect(result).toEqual([
|
|
66
|
+
{
|
|
67
|
+
uuid: 'aUuid',
|
|
68
|
+
identifier: 'foo',
|
|
69
|
+
identifierType: 'identifierType',
|
|
70
|
+
location: 'Nyc',
|
|
71
|
+
preferred: true,
|
|
72
|
+
},
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should generate identifier if it has autoGeneration and manual entry disabled', async () => {
|
|
77
|
+
formValues.identifiers.foo.autoGeneration = true;
|
|
78
|
+
formValues.identifiers.foo.selectedSource.autoGenerationOption.manualEntryEnabled = false;
|
|
79
|
+
mockGenerateIdentifier.mockResolvedValue({ data: { identifier: '10001V' } });
|
|
80
|
+
await FormManager.savePatientIdentifiers(true, undefined, formValues.identifiers, {}, 'Nyc');
|
|
81
|
+
expect(mockGenerateIdentifier.mock.calls).toHaveLength(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should not generate identifiers if manual entry enabled and identifier value given', async () => {
|
|
85
|
+
formValues.identifiers.foo.autoGeneration = true;
|
|
86
|
+
formValues.identifiers.foo.selectedSource.autoGenerationOption.manualEntryEnabled = true;
|
|
87
|
+
await FormManager.savePatientIdentifiers(true, undefined, formValues.identifiers, {}, 'Nyc');
|
|
88
|
+
expect(mockGenerateIdentifier.mock.calls).toHaveLength(0);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|