@kenyaemr/esm-patient-registration-app 5.2.2 → 6.0.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/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/271.js +1 -0
- package/dist/319.js +1 -1
- package/dist/330.js +1 -1
- package/dist/460.js +1 -1
- package/dist/537.js +1 -1
- package/dist/574.js +1 -1
- package/dist/59.js +1 -1
- package/dist/59.js.map +1 -1
- package/dist/619.js +1 -0
- package/dist/619.js.map +1 -0
- package/dist/644.js +1 -0
- package/dist/735.js +1 -1
- package/dist/757.js +1 -1
- package/dist/784.js +1 -1
- package/dist/788.js +1 -1
- package/dist/807.js +1 -1
- package/dist/833.js +1 -1
- package/dist/895.js +2 -0
- package/dist/{388.js.LICENSE.txt → 895.js.LICENSE.txt} +4 -2
- package/dist/895.js.map +1 -0
- package/dist/{openmrs-esm-patient-registration-app.js → kenyaemr-esm-patient-registration-app.js} +1 -1
- package/dist/{openmrs-esm-patient-registration-app.js.buildmanifest.json → kenyaemr-esm-patient-registration-app.js.buildmanifest.json} +117 -73
- package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -0
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +4 -2
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +6 -5
- package/src/add-patient-link.test.tsx +9 -7
- package/src/config-schema.ts +31 -38
- package/src/constants.ts +1 -1
- package/src/offline.resources.ts +13 -18
- package/src/offline.ts +8 -6
- package/src/patient-registration/before-save-prompt.tsx +2 -1
- package/src/patient-registration/field/__mocks__/field.resource.ts +1 -1
- package/src/patient-registration/field/address/address-field.component.tsx +5 -4
- package/src/patient-registration/field/address/address-hierarchy.resource.tsx +2 -2
- package/src/patient-registration/field/address/address-search.component.tsx +1 -14
- package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -1
- package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +3 -3
- package/src/patient-registration/field/address/tests/address-search-component.test.tsx +14 -7
- package/src/patient-registration/field/custom-field.component.tsx +1 -1
- package/src/patient-registration/field/dob/dob.component.tsx +2 -2
- package/src/patient-registration/field/dob/dob.test.tsx +0 -3
- package/src/patient-registration/field/field.component.tsx +4 -1
- package/src/patient-registration/field/field.resource.ts +4 -4
- package/src/patient-registration/field/field.test.tsx +98 -95
- package/src/patient-registration/field/gender/gender-field.component.tsx +4 -4
- package/src/patient-registration/field/gender/gender-field.test.tsx +7 -14
- package/src/patient-registration/field/id/id-field.component.tsx +6 -6
- package/src/patient-registration/field/id/id-field.test.tsx +9 -7
- package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +1 -1
- package/src/patient-registration/field/name/name-field.component.tsx +1 -1
- package/src/patient-registration/field/obs/obs-field.component.tsx +35 -33
- package/src/patient-registration/field/obs/obs-field.test.tsx +106 -28
- package/src/patient-registration/field/person-attributes/coded-attributes.component.tsx +1 -1
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +70 -24
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +54 -30
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +4 -3
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +1 -1
- package/src/patient-registration/field/person-attributes/{person-attributes.resource.tsx → person-attributes.resource.ts} +3 -3
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +1 -1
- package/src/patient-registration/field/phone/phone-field.component.tsx +16 -0
- package/src/patient-registration/form-manager.test.ts +1 -1
- package/src/patient-registration/form-manager.ts +22 -24
- package/src/patient-registration/input/basic-input/select/select-input.test.tsx +3 -3
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +5 -5
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +70 -58
- package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +2 -2
- package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +2 -5
- package/src/patient-registration/input/custom-input/identifier/utils.ts +1 -1
- package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +1 -1
- package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +6 -6
- package/src/patient-registration/patient-registration-context.ts +3 -4
- package/src/patient-registration/patient-registration-hooks.ts +25 -20
- package/src/patient-registration/patient-registration-utils.ts +7 -7
- package/src/patient-registration/patient-registration.component.tsx +20 -10
- package/src/patient-registration/patient-registration.resource.test.tsx +3 -3
- package/src/patient-registration/{patient-registration.resource.tsx → patient-registration.resource.ts} +15 -15
- package/src/patient-registration/patient-registration.test.tsx +270 -251
- package/src/patient-registration/{patient-registration.types.tsx → patient-registration.types.ts} +12 -3
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +33 -45
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +1 -2
- package/src/patient-registration/section/generic-section.component.tsx +1 -1
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +3 -3
- package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +17 -5
- package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +4 -4
- package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
- package/src/patient-registration/section/section.component.tsx +1 -1
- package/src/patient-registration/validation/patient-registration-validation.test.tsx +140 -126
- package/src/patient-registration/validation/patient-registration-validation.tsx +54 -46
- package/src/patient-verification/patient-verification-hook.tsx +13 -4
- package/src/patient-verification/patient-verification-utils.ts +20 -12
- package/src/patient-verification/patient-verification.component.tsx +13 -6
- package/src/patient-verification/patient-verification.scss +0 -1
- package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +2 -11
- package/src/routes.json +1 -0
- package/src/widgets/cancel-patient-edit.test.tsx +7 -4
- package/src/widgets/delete-identifier-confirmation-modal.test.tsx +7 -4
- package/src/widgets/display-photo.test.tsx +1 -1
- package/src/widgets/edit-patient-details-button.test.tsx +12 -7
- package/translations/am.json +30 -14
- package/translations/ar.json +30 -14
- package/translations/en.json +11 -11
- package/translations/es.json +34 -22
- package/translations/fr.json +48 -40
- package/translations/he.json +22 -2
- package/translations/km.json +22 -2
- package/translations/zh.json +97 -0
- package/translations/zh_CN.json +97 -0
- package/tsconfig.json +1 -1
- package/__mocks__/autogenerationoptions.mock.ts +0 -34
- package/dist/388.js +0 -2
- package/dist/388.js.map +0 -1
- package/dist/598.js +0 -1
- package/dist/598.js.map +0 -1
- package/dist/openmrs-esm-patient-registration-app.js.map +0 -1
- package/src/patient-registration/field/__mocks__/identifier-types.mock.ts +0 -76
- package/src/patient-registration/field/__mocks__/identifiers.mock.ts +0 -27
- package/src/patient-registration/field/address/tests/mocks.ts +0 -98
|
@@ -2,17 +2,86 @@ import React from 'react';
|
|
|
2
2
|
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
4
|
import { useConfig } from '@openmrs/esm-framework';
|
|
5
|
-
import { FieldDefinition } from '../../../config-schema';
|
|
5
|
+
import { type FieldDefinition } from '../../../config-schema';
|
|
6
|
+
import { useConcept, useConceptAnswers } from '../field.resource';
|
|
6
7
|
import { ObsField } from './obs-field.component';
|
|
7
8
|
|
|
8
9
|
const mockUseConfig = useConfig as jest.Mock;
|
|
9
10
|
|
|
10
|
-
//
|
|
11
|
-
|
|
11
|
+
jest.mock('../field.resource'); // Mock the useConceptAnswers hook
|
|
12
|
+
|
|
13
|
+
const mockedUseConcept = useConcept as jest.Mock;
|
|
14
|
+
const mockedUseConceptAnswers = useConceptAnswers as jest.Mock;
|
|
15
|
+
|
|
16
|
+
const useConceptMockImpl = (uuid: string) => {
|
|
17
|
+
let data;
|
|
18
|
+
if (uuid == 'weight-uuid') {
|
|
19
|
+
data = {
|
|
20
|
+
uuid: 'weight-uuid',
|
|
21
|
+
display: 'Weight (kg)',
|
|
22
|
+
datatype: { display: 'Numeric', uuid: 'num' },
|
|
23
|
+
answers: [],
|
|
24
|
+
setMembers: [],
|
|
25
|
+
};
|
|
26
|
+
} else if (uuid == 'chief-complaint-uuid') {
|
|
27
|
+
data = {
|
|
28
|
+
uuid: 'chief-complaint-uuid',
|
|
29
|
+
display: 'Chief Complaint',
|
|
30
|
+
datatype: { display: 'Text', uuid: 'txt' },
|
|
31
|
+
answers: [],
|
|
32
|
+
setMembers: [],
|
|
33
|
+
};
|
|
34
|
+
} else if (uuid == 'nationality-uuid') {
|
|
35
|
+
data = {
|
|
36
|
+
uuid: 'nationality-uuid',
|
|
37
|
+
display: 'Nationality',
|
|
38
|
+
datatype: { display: 'Coded', uuid: 'cdd' },
|
|
39
|
+
answers: [
|
|
40
|
+
{ display: 'USA', uuid: 'usa' },
|
|
41
|
+
{ display: 'Mexico', uuid: 'mex' },
|
|
42
|
+
],
|
|
43
|
+
setMembers: [],
|
|
44
|
+
};
|
|
45
|
+
} else {
|
|
46
|
+
throw Error(`Programming error, you probably didn't mean to do this: unknown concept uuid '${uuid}'`);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
data: data ?? null,
|
|
50
|
+
isLoading: !data,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const useConceptAnswersMockImpl = (uuid: string) => {
|
|
55
|
+
if (uuid == 'nationality-uuid') {
|
|
56
|
+
return {
|
|
57
|
+
data: [
|
|
58
|
+
{ display: 'USA', uuid: 'usa' },
|
|
59
|
+
{ display: 'Mexico', uuid: 'mex' },
|
|
60
|
+
],
|
|
61
|
+
isLoading: false,
|
|
62
|
+
};
|
|
63
|
+
} else if (uuid == 'other-countries-uuid') {
|
|
64
|
+
return {
|
|
65
|
+
data: [
|
|
66
|
+
{ display: 'Kenya', uuid: 'ke' },
|
|
67
|
+
{ display: 'Uganda', uuid: 'ug' },
|
|
68
|
+
],
|
|
69
|
+
isLoading: false,
|
|
70
|
+
};
|
|
71
|
+
} else if (uuid == '') {
|
|
72
|
+
return {
|
|
73
|
+
data: [],
|
|
74
|
+
isLoading: false,
|
|
75
|
+
};
|
|
76
|
+
} else {
|
|
77
|
+
throw Error(`Programming error, you probably didn't mean to do this: unknown concept answer set uuid '${uuid}'`);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
12
80
|
|
|
13
81
|
type FieldProps = {
|
|
14
82
|
children: ({ field, form: { touched, errors } }) => React.ReactNode;
|
|
15
83
|
};
|
|
84
|
+
|
|
16
85
|
jest.mock('formik', () => ({
|
|
17
86
|
...(jest.requireActual('formik') as object),
|
|
18
87
|
Field: jest.fn(({ children }: FieldProps) => <>{children({ field: {}, form: { touched: {}, errors: {} } })}</>),
|
|
@@ -24,18 +93,14 @@ const textFieldDef: FieldDefinition = {
|
|
|
24
93
|
type: 'obs',
|
|
25
94
|
label: '',
|
|
26
95
|
placeholder: '',
|
|
96
|
+
showHeading: false,
|
|
27
97
|
uuid: 'chief-complaint-uuid',
|
|
28
98
|
validation: {
|
|
29
99
|
required: false,
|
|
30
100
|
matches: null,
|
|
31
101
|
},
|
|
32
102
|
answerConceptSetUuid: null,
|
|
33
|
-
customConceptAnswers: [
|
|
34
|
-
{
|
|
35
|
-
uuid: 'concept-uuid',
|
|
36
|
-
label: '',
|
|
37
|
-
},
|
|
38
|
-
],
|
|
103
|
+
customConceptAnswers: [],
|
|
39
104
|
};
|
|
40
105
|
|
|
41
106
|
const numberFieldDef: FieldDefinition = {
|
|
@@ -43,18 +108,14 @@ const numberFieldDef: FieldDefinition = {
|
|
|
43
108
|
type: 'obs',
|
|
44
109
|
label: '',
|
|
45
110
|
placeholder: '',
|
|
111
|
+
showHeading: false,
|
|
46
112
|
uuid: 'weight-uuid',
|
|
47
113
|
validation: {
|
|
48
114
|
required: false,
|
|
49
115
|
matches: null,
|
|
50
116
|
},
|
|
51
117
|
answerConceptSetUuid: null,
|
|
52
|
-
customConceptAnswers: [
|
|
53
|
-
{
|
|
54
|
-
uuid: 'concept-uuid',
|
|
55
|
-
label: '',
|
|
56
|
-
},
|
|
57
|
-
],
|
|
118
|
+
customConceptAnswers: [],
|
|
58
119
|
};
|
|
59
120
|
|
|
60
121
|
const codedFieldDef: FieldDefinition = {
|
|
@@ -62,30 +123,30 @@ const codedFieldDef: FieldDefinition = {
|
|
|
62
123
|
type: 'obs',
|
|
63
124
|
label: '',
|
|
64
125
|
placeholder: '',
|
|
126
|
+
showHeading: false,
|
|
65
127
|
uuid: 'nationality-uuid',
|
|
66
128
|
validation: {
|
|
67
129
|
required: false,
|
|
68
130
|
matches: null,
|
|
69
131
|
},
|
|
70
132
|
answerConceptSetUuid: null,
|
|
71
|
-
customConceptAnswers: [
|
|
72
|
-
{
|
|
73
|
-
uuid: 'concept-uuid',
|
|
74
|
-
label: 'Kenya',
|
|
75
|
-
},
|
|
76
|
-
],
|
|
133
|
+
customConceptAnswers: [],
|
|
77
134
|
};
|
|
78
135
|
|
|
79
136
|
describe('ObsField', () => {
|
|
80
137
|
beforeEach(() => {
|
|
81
138
|
mockUseConfig.mockReturnValue({ registrationObs: { encounterTypeUuid: 'reg-enc-uuid' } });
|
|
139
|
+
mockedUseConcept.mockImplementation(useConceptMockImpl);
|
|
140
|
+
mockedUseConceptAnswers.mockImplementation(useConceptAnswersMockImpl);
|
|
82
141
|
});
|
|
83
142
|
|
|
84
143
|
it("logs an error and doesn't render if no registration encounter type is provided", () => {
|
|
85
144
|
mockUseConfig.mockReturnValue({ registrationObs: { encounterTypeUuid: null } });
|
|
86
145
|
console.error = jest.fn();
|
|
87
146
|
render(<ObsField fieldDefinition={textFieldDef} />);
|
|
88
|
-
expect(console.error).toHaveBeenCalledWith(
|
|
147
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
148
|
+
expect.stringMatching(/no registration encounter type has been configured/i),
|
|
149
|
+
);
|
|
89
150
|
expect(screen.queryByRole('textbox')).toBeNull();
|
|
90
151
|
});
|
|
91
152
|
|
|
@@ -107,15 +168,10 @@ describe('ObsField', () => {
|
|
|
107
168
|
// expect(screen.getByLabelText("Nationality")).toBeInTheDocument();
|
|
108
169
|
const select = screen.getByRole('combobox');
|
|
109
170
|
expect(select).toBeInTheDocument();
|
|
110
|
-
expect(select).toHaveDisplayValue('');
|
|
171
|
+
expect(select).toHaveDisplayValue('Select an option');
|
|
111
172
|
});
|
|
112
173
|
|
|
113
174
|
it('select uses answerConcept for answers when it is provided', async () => {
|
|
114
|
-
mockUseConfig.mockReturnValue({
|
|
115
|
-
registrationObs: { encounterTypeUuid: 'reg-enc-uuid' },
|
|
116
|
-
fieldDefinitions: [codedFieldDef],
|
|
117
|
-
});
|
|
118
|
-
|
|
119
175
|
const user = userEvent.setup();
|
|
120
176
|
|
|
121
177
|
render(<ObsField fieldDefinition={{ ...codedFieldDef, answerConceptSetUuid: 'other-countries-uuid' }} />);
|
|
@@ -124,4 +180,26 @@ describe('ObsField', () => {
|
|
|
124
180
|
expect(select).toBeInTheDocument();
|
|
125
181
|
await user.selectOptions(select, 'Kenya');
|
|
126
182
|
});
|
|
183
|
+
|
|
184
|
+
it('select uses customConceptAnswers for answers when provided', async () => {
|
|
185
|
+
const user = userEvent.setup();
|
|
186
|
+
|
|
187
|
+
render(
|
|
188
|
+
<ObsField
|
|
189
|
+
fieldDefinition={{
|
|
190
|
+
...codedFieldDef,
|
|
191
|
+
customConceptAnswers: [
|
|
192
|
+
{
|
|
193
|
+
uuid: 'mozambique-uuid',
|
|
194
|
+
label: 'Mozambique',
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
}}
|
|
198
|
+
/>,
|
|
199
|
+
);
|
|
200
|
+
// expect(screen.getByLabelText("Nationality")).toBeInTheDocument();
|
|
201
|
+
const select = screen.getByRole('combobox');
|
|
202
|
+
expect(select).toBeInTheDocument();
|
|
203
|
+
await user.selectOptions(select, 'Mozambique');
|
|
204
|
+
});
|
|
127
205
|
});
|
|
@@ -3,7 +3,7 @@ import classNames from 'classnames';
|
|
|
3
3
|
import { Layer, Select, SelectItem } from '@carbon/react';
|
|
4
4
|
import { useConfig } from '@openmrs/esm-framework';
|
|
5
5
|
import { Input } from '../../input/basic-input/input/input.component';
|
|
6
|
-
import { CodedPersonAttributeConfig } from '../../patient-registration.types';
|
|
6
|
+
import { type CodedPersonAttributeConfig } from '../../patient-registration.types';
|
|
7
7
|
import { useConceptAnswers } from '../field.resource';
|
|
8
8
|
import { usePersonAttributeType } from './person-attributes.resource';
|
|
9
9
|
import styles from './../field.scss';
|
package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Field } from 'formik';
|
|
5
5
|
import { Layer, Select, SelectItem } from '@carbon/react';
|
|
6
|
-
import {
|
|
7
|
-
import { PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
6
|
+
import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
8
7
|
import { useConceptAnswers } from '../field.resource';
|
|
9
8
|
import styles from './../field.scss';
|
|
9
|
+
import { reportError } from '@openmrs/esm-framework';
|
|
10
10
|
|
|
11
11
|
export interface CodedPersonAttributeFieldProps {
|
|
12
12
|
id: string;
|
|
13
13
|
personAttributeType: PersonAttributeTypeResponse;
|
|
14
14
|
answerConceptSetUuid: string;
|
|
15
15
|
label?: string;
|
|
16
|
+
customConceptAnswers: Array<{ uuid: string; label?: string }>;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function CodedPersonAttributeField({
|
|
@@ -20,14 +21,75 @@ export function CodedPersonAttributeField({
|
|
|
20
21
|
personAttributeType,
|
|
21
22
|
answerConceptSetUuid,
|
|
22
23
|
label,
|
|
24
|
+
customConceptAnswers,
|
|
23
25
|
}: CodedPersonAttributeFieldProps) {
|
|
24
|
-
const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(
|
|
26
|
+
const { data: conceptAnswers, isLoading: isLoadingConceptAnswers } = useConceptAnswers(
|
|
27
|
+
customConceptAnswers.length ? '' : answerConceptSetUuid,
|
|
28
|
+
);
|
|
29
|
+
|
|
25
30
|
const { t } = useTranslation();
|
|
26
31
|
const fieldName = `attributes.${personAttributeType.uuid}`;
|
|
32
|
+
const [error, setError] = useState(false);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!answerConceptSetUuid && !customConceptAnswers.length) {
|
|
36
|
+
reportError(
|
|
37
|
+
t(
|
|
38
|
+
'codedPersonAttributeNoAnswerSet',
|
|
39
|
+
`The person attribute field '{{codedPersonAttributeFieldId}}' is of type 'coded' but has been defined without an answer concept set UUID. The 'answerConceptSetUuid' key is required.`,
|
|
40
|
+
{ codedPersonAttributeFieldId: id },
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
setError(true);
|
|
44
|
+
}
|
|
45
|
+
}, [answerConceptSetUuid, customConceptAnswers]);
|
|
46
|
+
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!isLoadingConceptAnswers && !customConceptAnswers.length) {
|
|
49
|
+
if (!conceptAnswers) {
|
|
50
|
+
reportError(
|
|
51
|
+
t(
|
|
52
|
+
'codedPersonAttributeAnswerSetInvalid',
|
|
53
|
+
`The coded person attribute field '{{codedPersonAttributeFieldId}}' has been defined with an invalid answer concept set UUID '{{answerConceptSetUuid}}'.`,
|
|
54
|
+
{ codedPersonAttributeFieldId: id, answerConceptSetUuid },
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
setError(true);
|
|
58
|
+
}
|
|
59
|
+
if (conceptAnswers?.length == 0) {
|
|
60
|
+
reportError(
|
|
61
|
+
t(
|
|
62
|
+
'codedPersonAttributeAnswerSetEmpty',
|
|
63
|
+
`The coded person attribute field '{{codedPersonAttributeFieldId}}' has been defined with an answer concept set UUID '{{answerConceptSetUuid}}' that does not have any concept answers.`,
|
|
64
|
+
{
|
|
65
|
+
codedPersonAttributeFieldId: id,
|
|
66
|
+
answerConceptSetUuid,
|
|
67
|
+
},
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
setError(true);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}, [isLoadingConceptAnswers, conceptAnswers, customConceptAnswers]);
|
|
74
|
+
|
|
75
|
+
const answers = useMemo(() => {
|
|
76
|
+
if (customConceptAnswers.length) {
|
|
77
|
+
return customConceptAnswers;
|
|
78
|
+
}
|
|
79
|
+
return isLoadingConceptAnswers || !conceptAnswers
|
|
80
|
+
? []
|
|
81
|
+
: conceptAnswers
|
|
82
|
+
.map((answer) => ({ ...answer, label: answer.display }))
|
|
83
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
84
|
+
}, [customConceptAnswers, conceptAnswers, isLoadingConceptAnswers]);
|
|
85
|
+
|
|
86
|
+
if (error) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
27
89
|
|
|
28
90
|
return (
|
|
29
91
|
<div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
|
|
30
|
-
{!isLoadingConceptAnswers
|
|
92
|
+
{!isLoadingConceptAnswers ? (
|
|
31
93
|
<Layer>
|
|
32
94
|
<Field name={fieldName}>
|
|
33
95
|
{({ field, form: { touched, errors }, meta }) => {
|
|
@@ -40,8 +102,8 @@ export function CodedPersonAttributeField({
|
|
|
40
102
|
invalid={errors[fieldName] && touched[fieldName]}
|
|
41
103
|
{...field}>
|
|
42
104
|
<SelectItem value={''} text={t('selectAnOption', 'Select an option')} />
|
|
43
|
-
{
|
|
44
|
-
<SelectItem key={answer.uuid} value={answer.uuid} text={answer.
|
|
105
|
+
{answers.map((answer) => (
|
|
106
|
+
<SelectItem key={answer.uuid} value={answer.uuid} text={answer.label} />
|
|
45
107
|
))}
|
|
46
108
|
</Select>
|
|
47
109
|
</>
|
|
@@ -49,23 +111,7 @@ export function CodedPersonAttributeField({
|
|
|
49
111
|
}}
|
|
50
112
|
</Field>
|
|
51
113
|
</Layer>
|
|
52
|
-
) :
|
|
53
|
-
<Layer>
|
|
54
|
-
<Field name={fieldName}>
|
|
55
|
-
{({ field, form: { touched, errors }, meta }) => {
|
|
56
|
-
return (
|
|
57
|
-
<Input
|
|
58
|
-
id={id}
|
|
59
|
-
name={`person-attribute-${personAttributeType.uuid}`}
|
|
60
|
-
labelText={label ?? personAttributeType?.display}
|
|
61
|
-
invalid={errors[fieldName] && touched[fieldName]}
|
|
62
|
-
{...field}
|
|
63
|
-
/>
|
|
64
|
-
);
|
|
65
|
-
}}
|
|
66
|
-
</Field>
|
|
67
|
-
</Layer>
|
|
68
|
-
)}
|
|
114
|
+
) : null}
|
|
69
115
|
</div>
|
|
70
116
|
);
|
|
71
117
|
}
|
package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx
CHANGED
|
@@ -21,6 +21,8 @@ describe('CodedPersonAttributeField', () => {
|
|
|
21
21
|
format: 'org.openmrs.Concept',
|
|
22
22
|
display: 'Referred by',
|
|
23
23
|
uuid: '4dd56a75-14ab-4148-8700-1f4f704dc5b0',
|
|
24
|
+
name: '',
|
|
25
|
+
description: '',
|
|
24
26
|
};
|
|
25
27
|
const answerConceptSetUuid = '6682d17f-0777-45e4-a39b-93f77eb3531c';
|
|
26
28
|
|
|
@@ -32,31 +34,47 @@ describe('CodedPersonAttributeField', () => {
|
|
|
32
34
|
});
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
it('
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
expect
|
|
51
|
-
expect(screen.getByText(/Option 2/i)).toBeInTheDocument();
|
|
37
|
+
it('shows error if there is no concept answer set provided', () => {
|
|
38
|
+
expect(() => {
|
|
39
|
+
render(
|
|
40
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
41
|
+
<Form>
|
|
42
|
+
<CodedPersonAttributeField
|
|
43
|
+
id="attributeId"
|
|
44
|
+
personAttributeType={personAttributeType}
|
|
45
|
+
answerConceptSetUuid={null}
|
|
46
|
+
label={personAttributeType.display}
|
|
47
|
+
customConceptAnswers={[]}
|
|
48
|
+
/>
|
|
49
|
+
</Form>
|
|
50
|
+
</Formik>,
|
|
51
|
+
);
|
|
52
|
+
}).toThrow(expect.stringMatching(/has been defined without an answer concept set UUID/i));
|
|
52
53
|
});
|
|
53
54
|
|
|
54
|
-
it('
|
|
55
|
-
mockedUseConceptAnswers.
|
|
56
|
-
data:
|
|
55
|
+
it('shows error if the concept answer set does not have any concept answers', () => {
|
|
56
|
+
mockedUseConceptAnswers.mockReturnValue({
|
|
57
|
+
data: [],
|
|
57
58
|
isLoading: false,
|
|
58
59
|
});
|
|
60
|
+
expect(() => {
|
|
61
|
+
render(
|
|
62
|
+
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
63
|
+
<Form>
|
|
64
|
+
<CodedPersonAttributeField
|
|
65
|
+
id="attributeId"
|
|
66
|
+
personAttributeType={personAttributeType}
|
|
67
|
+
answerConceptSetUuid={answerConceptSetUuid}
|
|
68
|
+
label={personAttributeType.display}
|
|
69
|
+
customConceptAnswers={[]}
|
|
70
|
+
/>
|
|
71
|
+
</Form>
|
|
72
|
+
</Formik>,
|
|
73
|
+
);
|
|
74
|
+
}).toThrow(expect.stringMatching(/does not have any concept answers/i));
|
|
75
|
+
});
|
|
59
76
|
|
|
77
|
+
it('renders the conceptAnswers as select options', () => {
|
|
60
78
|
render(
|
|
61
79
|
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
62
80
|
<Form>
|
|
@@ -65,23 +83,18 @@ describe('CodedPersonAttributeField', () => {
|
|
|
65
83
|
personAttributeType={personAttributeType}
|
|
66
84
|
answerConceptSetUuid={answerConceptSetUuid}
|
|
67
85
|
label={personAttributeType.display}
|
|
86
|
+
customConceptAnswers={[]}
|
|
68
87
|
/>
|
|
69
88
|
</Form>
|
|
70
89
|
</Formik>,
|
|
71
90
|
);
|
|
72
91
|
|
|
73
92
|
expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
|
|
74
|
-
expect(screen.
|
|
75
|
-
expect(screen.
|
|
76
|
-
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
93
|
+
expect(screen.getByText(/Option 1/i)).toBeInTheDocument();
|
|
94
|
+
expect(screen.getByText(/Option 2/i)).toBeInTheDocument();
|
|
77
95
|
});
|
|
78
96
|
|
|
79
|
-
it('renders
|
|
80
|
-
mockedUseConceptAnswers.mockReturnValueOnce({
|
|
81
|
-
data: null,
|
|
82
|
-
isLoading: true,
|
|
83
|
-
});
|
|
84
|
-
|
|
97
|
+
it('renders customConceptAnswers as select options when they are provided', () => {
|
|
85
98
|
render(
|
|
86
99
|
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
87
100
|
<Form>
|
|
@@ -90,14 +103,25 @@ describe('CodedPersonAttributeField', () => {
|
|
|
90
103
|
personAttributeType={personAttributeType}
|
|
91
104
|
answerConceptSetUuid={answerConceptSetUuid}
|
|
92
105
|
label={personAttributeType.display}
|
|
106
|
+
customConceptAnswers={[
|
|
107
|
+
{
|
|
108
|
+
uuid: 'A',
|
|
109
|
+
label: 'Special Option A',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
uuid: 'B',
|
|
113
|
+
label: 'Special Option B',
|
|
114
|
+
},
|
|
115
|
+
]}
|
|
93
116
|
/>
|
|
94
117
|
</Form>
|
|
95
118
|
</Formik>,
|
|
96
119
|
);
|
|
97
120
|
|
|
98
121
|
expect(screen.getByLabelText(/Referred by/i)).toBeInTheDocument();
|
|
122
|
+
expect(screen.getByText(/Special Option A/i)).toBeInTheDocument();
|
|
123
|
+
expect(screen.getByText(/Special Option B/i)).toBeInTheDocument();
|
|
99
124
|
expect(screen.queryByText(/Option 1/i)).not.toBeInTheDocument();
|
|
100
125
|
expect(screen.queryByText(/Option 2/i)).not.toBeInTheDocument();
|
|
101
|
-
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
102
126
|
});
|
|
103
127
|
});
|
package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
2
|
import { InlineNotification, TextInputSkeleton, SkeletonText } from '@carbon/react';
|
|
3
|
-
import { FieldDefinition } from '../../../config-schema';
|
|
3
|
+
import { type FieldDefinition } from '../../../config-schema';
|
|
4
4
|
import { CodedPersonAttributeField } from './coded-person-attribute-field.component';
|
|
5
5
|
import { usePersonAttributeType } from './person-attributes.resource';
|
|
6
6
|
import { TextPersonAttributeField } from './text-person-attribute-field.component';
|
|
@@ -24,9 +24,9 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
|
|
|
24
24
|
return (
|
|
25
25
|
<TextPersonAttributeField
|
|
26
26
|
personAttributeType={personAttributeType}
|
|
27
|
-
validationRegex={fieldDefinition.validation
|
|
27
|
+
validationRegex={fieldDefinition.validation?.matches ?? ''}
|
|
28
28
|
label={fieldDefinition.label}
|
|
29
|
-
required={fieldDefinition.validation
|
|
29
|
+
required={fieldDefinition.validation?.required ?? false}
|
|
30
30
|
id={fieldDefinition?.id}
|
|
31
31
|
/>
|
|
32
32
|
);
|
|
@@ -37,6 +37,7 @@ export function PersonAttributeField({ fieldDefinition }: PersonAttributeFieldPr
|
|
|
37
37
|
answerConceptSetUuid={fieldDefinition.answerConceptSetUuid}
|
|
38
38
|
label={fieldDefinition.label}
|
|
39
39
|
id={fieldDefinition?.id}
|
|
40
|
+
customConceptAnswers={fieldDefinition.customConceptAnswers ?? []}
|
|
40
41
|
/>
|
|
41
42
|
);
|
|
42
43
|
default:
|
|
@@ -4,7 +4,7 @@ import { usePersonAttributeType } from './person-attributes.resource';
|
|
|
4
4
|
import { PersonAttributeField } from './person-attribute-field.component';
|
|
5
5
|
import { useConceptAnswers } from '../field.resource';
|
|
6
6
|
import { Form, Formik } from 'formik';
|
|
7
|
-
import { FieldDefinition } from '../../../config-schema';
|
|
7
|
+
import { type FieldDefinition } from '../../../config-schema';
|
|
8
8
|
|
|
9
9
|
jest.mock('./person-attributes.resource'); // Mock the usePersonAttributeType hook
|
|
10
10
|
jest.mock('../field.resource'); // Mock the useConceptAnswers hook
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { FetchResponse, openmrsFetch } from '@openmrs/esm-framework';
|
|
1
|
+
import { type FetchResponse, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
2
|
import useSWRImmutable from 'swr/immutable';
|
|
3
|
-
import { PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
3
|
+
import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
4
4
|
|
|
5
5
|
export function usePersonAttributeType(personAttributeTypeUuid: string): {
|
|
6
6
|
data: PersonAttributeTypeResponse;
|
|
@@ -8,7 +8,7 @@ export function usePersonAttributeType(personAttributeTypeUuid: string): {
|
|
|
8
8
|
error: any;
|
|
9
9
|
} {
|
|
10
10
|
const { data, error, isLoading } = useSWRImmutable<FetchResponse<PersonAttributeTypeResponse>>(
|
|
11
|
-
|
|
11
|
+
`${restBaseUrl}/personattributetype/${personAttributeTypeUuid}`,
|
|
12
12
|
openmrsFetch,
|
|
13
13
|
);
|
|
14
14
|
|
package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import classNames from 'classnames';
|
|
|
3
3
|
import { Field } from 'formik';
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Input } from '../../input/basic-input/input/input.component';
|
|
6
|
-
import { PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
6
|
+
import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
7
7
|
import styles from './../field.scss';
|
|
8
8
|
|
|
9
9
|
export interface TextPersonAttributeFieldProps {
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
showHeading: false,
|
|
14
|
+
};
|
|
15
|
+
return <PersonAttributeField fieldDefinition={fieldDefinition} />;
|
|
16
|
+
}
|