@kenyaemr/esm-patient-registration-app 4.5.4 → 4.5.5
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/117.js +2 -0
- package/dist/117.js.map +1 -0
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/208.js +1 -1
- package/dist/218.js +1 -0
- package/dist/218.js.map +1 -0
- package/dist/275.js +1 -0
- package/dist/275.js.map +1 -0
- package/dist/319.js +1 -1
- package/dist/{821.js → 348.js} +1 -1
- package/dist/{821.js.map → 348.js.map} +1 -1
- package/dist/574.js +1 -1
- package/dist/68.js +1 -1
- package/dist/68.js.map +1 -1
- package/dist/693.js +1 -0
- package/dist/693.js.map +1 -0
- package/dist/757.js +1 -1
- package/dist/788.js +1 -1
- package/dist/807.js +1 -1
- package/dist/833.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +147 -122
- package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/jest.config.js +3 -0
- package/package.json +5 -2
- package/src/index.ts +9 -2
- package/src/offline.resources.ts +8 -4
- package/src/offline.ts +1 -1
- package/src/patient-registration/field/__mocks__/field.resource.ts +1 -1
- package/src/patient-registration/field/address/address-field.component.tsx +25 -70
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +11 -9
- package/src/patient-registration/field/address/address-hierarchy.resource.tsx +57 -3
- package/src/patient-registration/field/address/address-search.component.tsx +1 -1
- package/src/patient-registration/field/address/tests/address-hierarchy.test.tsx +137 -63
- package/src/patient-registration/field/address/tests/address-search-component.test.tsx +128 -0
- package/src/patient-registration/field/address/tests/mocks.ts +93 -99
- package/src/patient-registration/field/dob/dob.component.tsx +34 -32
- package/src/patient-registration/field/field.resource.ts +1 -1
- package/src/patient-registration/field/id/id-field.component.tsx +1 -1
- package/src/patient-registration/field/id/identifier-selection-overlay.tsx +1 -1
- package/src/patient-registration/field/name/name-field.component.tsx +0 -1
- package/src/patient-registration/field/obs/obs-field.component.tsx +14 -13
- 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 +1 -1
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +103 -0
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +187 -0
- package/src/patient-registration/field/person-attributes/person-attributes.resource.tsx +1 -1
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.component.tsx +3 -3
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +88 -0
- package/src/patient-registration/form-manager.test.ts +1 -1
- package/src/patient-registration/form-manager.ts +1 -1
- package/src/patient-registration/input/basic-input/input/input.test.tsx +0 -135
- package/src/patient-registration/input/basic-input/select/select-input.test.tsx +8 -4
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +8 -6
- package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx +1 -1
- package/src/patient-registration/input/custom-input/identifier/utils.test.ts +81 -0
- 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/patient-registration-context.ts +1 -1
- package/src/patient-registration/patient-registration-hooks.ts +1 -1
- package/src/patient-registration/patient-registration-utils.ts +1 -1
- package/src/patient-registration/patient-registration.component.tsx +1 -12
- package/src/patient-registration/patient-registration.resource.tsx +1 -72
- package/src/patient-registration/patient-registration.test.tsx +250 -247
- package/src/patient-registration/{patient-registration-types.tsx → patient-registration.types.tsx} +45 -0
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +83 -79
- package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +88 -0
- package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +1 -1
- package/src/patient-registration/validation/patient-registration-validation.tsx +1 -1
- package/src/patient-verification/patient-verification-hook.tsx +12 -1
- package/src/patient-verification/patient-verification-utils.ts +1 -1
- package/src/patient-verification/patient-verification.component.tsx +1 -1
- package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +11 -0
- package/src/root.component.tsx +1 -1
- package/src/routes.json +62 -51
- package/src/widgets/display-photo.test.tsx +37 -0
- package/src/widgets/edit-patient-details-button.test.tsx +36 -0
- package/translations/am.json +0 -7
- package/translations/en.json +2 -5
- package/translations/es.json +0 -7
- package/translations/fr.json +0 -7
- package/translations/he.json +0 -7
- package/translations/km.json +0 -7
- package/__mocks__/react-i18next.js +0 -49
- package/dist/196.js +0 -1
- package/dist/196.js.map +0 -1
- package/dist/59.js +0 -1
- package/dist/59.js.map +0 -1
- package/dist/9.js +0 -2
- package/dist/9.js.map +0 -1
- /package/dist/{9.js.LICENSE.txt → 117.js.LICENSE.txt} +0 -0
package/src/patient-registration/{patient-registration-types.tsx → patient-registration.types.tsx}
RENAMED
|
@@ -265,6 +265,51 @@ export interface ConceptAnswers {
|
|
|
265
265
|
display: string;
|
|
266
266
|
uuid: string;
|
|
267
267
|
}
|
|
268
|
+
|
|
269
|
+
export type AddressProperties =
|
|
270
|
+
| 'cityVillage'
|
|
271
|
+
| 'stateProvince'
|
|
272
|
+
| 'countyDistrict'
|
|
273
|
+
| 'postalCode'
|
|
274
|
+
| 'country'
|
|
275
|
+
| 'address1'
|
|
276
|
+
| 'address2'
|
|
277
|
+
| 'address3'
|
|
278
|
+
| 'address4'
|
|
279
|
+
| 'address5'
|
|
280
|
+
| 'address6'
|
|
281
|
+
| 'address7'
|
|
282
|
+
| 'address8'
|
|
283
|
+
| 'address9'
|
|
284
|
+
| 'address10'
|
|
285
|
+
| 'address11'
|
|
286
|
+
| 'address12'
|
|
287
|
+
| 'address13'
|
|
288
|
+
| 'address14'
|
|
289
|
+
| 'address15';
|
|
290
|
+
|
|
291
|
+
export type ExtensibleAddressProperties = { [p in AddressProperties]?: string } | null;
|
|
292
|
+
|
|
293
|
+
export interface AddressTemplate {
|
|
294
|
+
displayName: string | null;
|
|
295
|
+
codeName: string | null;
|
|
296
|
+
country: string | null;
|
|
297
|
+
lines: Array<
|
|
298
|
+
Array<{
|
|
299
|
+
isToken: 'IS_NOT_ADDR_TOKEN' | 'IS_ADDR_TOKEN';
|
|
300
|
+
displayText: string;
|
|
301
|
+
codeName?: AddressProperties;
|
|
302
|
+
displaySize?: string;
|
|
303
|
+
}>
|
|
304
|
+
> | null;
|
|
305
|
+
lineByLineFormat: Array<string> | null;
|
|
306
|
+
nameMappings: ExtensibleAddressProperties;
|
|
307
|
+
sizeMappings: ExtensibleAddressProperties;
|
|
308
|
+
elementDefaults: ExtensibleAddressProperties;
|
|
309
|
+
elementRegex: ExtensibleAddressProperties;
|
|
310
|
+
elementRegexFormats: ExtensibleAddressProperties;
|
|
311
|
+
requiredElements: Array<AddressProperties> | null;
|
|
312
|
+
}
|
|
268
313
|
export interface ObsResponse {
|
|
269
314
|
results: Array<{ obs: Array<{ uuid: string; display: string; value: OpenmrsResource; concept: OpenmrsResource }> }>;
|
|
270
315
|
}
|
package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx
CHANGED
|
@@ -15,7 +15,7 @@ import { Autosuggest } from '../../input/custom-input/autosuggest/autosuggest.co
|
|
|
15
15
|
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
16
16
|
import { ResourcesContext } from '../../../offline.resources';
|
|
17
17
|
import { fetchPerson } from '../../patient-registration.resource';
|
|
18
|
-
import { RelationshipValue } from '../../patient-registration
|
|
18
|
+
import { RelationshipValue } from '../../patient-registration.types';
|
|
19
19
|
import sectionStyles from '../section.scss';
|
|
20
20
|
import styles from './relationships.scss';
|
|
21
21
|
|
|
@@ -25,82 +25,6 @@ interface RelationshipType {
|
|
|
25
25
|
direction: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export const RelationshipsSection = () => {
|
|
29
|
-
const { relationshipTypes } = useContext(ResourcesContext);
|
|
30
|
-
const [displayRelationshipTypes, setDisplayRelationshipTypes] = useState<RelationshipType[]>([]);
|
|
31
|
-
const { t } = useTranslation();
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
if (relationshipTypes) {
|
|
35
|
-
const tmp: RelationshipType[] = [];
|
|
36
|
-
relationshipTypes.results.forEach((type) => {
|
|
37
|
-
const aIsToB = {
|
|
38
|
-
display: type.aIsToB,
|
|
39
|
-
uuid: type.uuid,
|
|
40
|
-
direction: 'aIsToB',
|
|
41
|
-
};
|
|
42
|
-
const bIsToA = {
|
|
43
|
-
display: type.bIsToA,
|
|
44
|
-
uuid: type.uuid,
|
|
45
|
-
direction: 'bIsToA',
|
|
46
|
-
};
|
|
47
|
-
aIsToB.display === bIsToA.display ? tmp.push(aIsToB) : tmp.push(aIsToB, bIsToA);
|
|
48
|
-
});
|
|
49
|
-
setDisplayRelationshipTypes(tmp);
|
|
50
|
-
}
|
|
51
|
-
}, [relationshipTypes]);
|
|
52
|
-
|
|
53
|
-
if (!relationshipTypes) {
|
|
54
|
-
return (
|
|
55
|
-
<section aria-label="Relationships Section">
|
|
56
|
-
<SkeletonText />
|
|
57
|
-
</section>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<section aria-label="Relationships Section">
|
|
63
|
-
<FieldArray name="relationships">
|
|
64
|
-
{({
|
|
65
|
-
push,
|
|
66
|
-
remove,
|
|
67
|
-
form: {
|
|
68
|
-
values: { relationships },
|
|
69
|
-
},
|
|
70
|
-
}) => (
|
|
71
|
-
<div>
|
|
72
|
-
{relationships && relationships.length > 0
|
|
73
|
-
? relationships.map((relationship: RelationshipValue, index) => (
|
|
74
|
-
<div key={index} className={sectionStyles.formSection}>
|
|
75
|
-
<RelationshipView
|
|
76
|
-
relationship={relationship}
|
|
77
|
-
index={index}
|
|
78
|
-
displayRelationshipTypes={displayRelationshipTypes}
|
|
79
|
-
key={index}
|
|
80
|
-
remove={remove}
|
|
81
|
-
/>
|
|
82
|
-
</div>
|
|
83
|
-
))
|
|
84
|
-
: null}
|
|
85
|
-
<div className={styles.actions}>
|
|
86
|
-
<Button
|
|
87
|
-
kind="ghost"
|
|
88
|
-
onClick={() =>
|
|
89
|
-
push({
|
|
90
|
-
relatedPersonUuid: '',
|
|
91
|
-
action: 'ADD',
|
|
92
|
-
})
|
|
93
|
-
}>
|
|
94
|
-
{t('addRelationshipButtonText', 'Add Relationship')}
|
|
95
|
-
</Button>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
)}
|
|
99
|
-
</FieldArray>
|
|
100
|
-
</section>
|
|
101
|
-
);
|
|
102
|
-
};
|
|
103
|
-
|
|
104
28
|
interface RelationshipViewProps {
|
|
105
29
|
relationship: RelationshipValue;
|
|
106
30
|
index: number;
|
|
@@ -205,8 +129,12 @@ const RelationshipView: React.FC<RelationshipViewProps> = ({
|
|
|
205
129
|
value="placeholder-item"
|
|
206
130
|
text={t('relationshipToPatient', 'Relationship to patient')}
|
|
207
131
|
/>
|
|
208
|
-
{displayRelationshipTypes.map((
|
|
209
|
-
<SelectItem
|
|
132
|
+
{displayRelationshipTypes.map((relationshipType, index) => (
|
|
133
|
+
<SelectItem
|
|
134
|
+
text={relationshipType.display}
|
|
135
|
+
value={`${relationshipType.uuid}/${relationshipType.direction}`}
|
|
136
|
+
key={`relationship-${relationshipType.uuid}-${index}`}
|
|
137
|
+
/>
|
|
210
138
|
))}
|
|
211
139
|
</Select>
|
|
212
140
|
</Layer>
|
|
@@ -224,3 +152,79 @@ const RelationshipView: React.FC<RelationshipViewProps> = ({
|
|
|
224
152
|
/>
|
|
225
153
|
);
|
|
226
154
|
};
|
|
155
|
+
|
|
156
|
+
export const RelationshipsSection = () => {
|
|
157
|
+
const { relationshipTypes } = useContext(ResourcesContext);
|
|
158
|
+
const [displayRelationshipTypes, setDisplayRelationshipTypes] = useState<RelationshipType[]>([]);
|
|
159
|
+
const { t } = useTranslation();
|
|
160
|
+
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (relationshipTypes) {
|
|
163
|
+
const tmp: RelationshipType[] = [];
|
|
164
|
+
relationshipTypes.results.forEach((type) => {
|
|
165
|
+
const aIsToB = {
|
|
166
|
+
display: type.aIsToB,
|
|
167
|
+
uuid: type.uuid,
|
|
168
|
+
direction: 'aIsToB',
|
|
169
|
+
};
|
|
170
|
+
const bIsToA = {
|
|
171
|
+
display: type.bIsToA,
|
|
172
|
+
uuid: type.uuid,
|
|
173
|
+
direction: 'bIsToA',
|
|
174
|
+
};
|
|
175
|
+
aIsToB.display === bIsToA.display ? tmp.push(aIsToB) : tmp.push(aIsToB, bIsToA);
|
|
176
|
+
});
|
|
177
|
+
setDisplayRelationshipTypes(tmp);
|
|
178
|
+
}
|
|
179
|
+
}, [relationshipTypes]);
|
|
180
|
+
|
|
181
|
+
if (!relationshipTypes) {
|
|
182
|
+
return (
|
|
183
|
+
<section aria-label="Loading relationships section">
|
|
184
|
+
<SkeletonText role="progressbar" />
|
|
185
|
+
</section>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<section aria-label="Relationships section">
|
|
191
|
+
<FieldArray name="relationships">
|
|
192
|
+
{({
|
|
193
|
+
push,
|
|
194
|
+
remove,
|
|
195
|
+
form: {
|
|
196
|
+
values: { relationships },
|
|
197
|
+
},
|
|
198
|
+
}) => (
|
|
199
|
+
<div>
|
|
200
|
+
{relationships && relationships.length > 0
|
|
201
|
+
? relationships.map((relationship: RelationshipValue, index) => (
|
|
202
|
+
<div key={index} className={sectionStyles.formSection}>
|
|
203
|
+
<RelationshipView
|
|
204
|
+
relationship={relationship}
|
|
205
|
+
index={index}
|
|
206
|
+
displayRelationshipTypes={displayRelationshipTypes}
|
|
207
|
+
key={index}
|
|
208
|
+
remove={remove}
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
))
|
|
212
|
+
: null}
|
|
213
|
+
<div className={styles.actions}>
|
|
214
|
+
<Button
|
|
215
|
+
kind="ghost"
|
|
216
|
+
onClick={() =>
|
|
217
|
+
push({
|
|
218
|
+
relatedPersonUuid: '',
|
|
219
|
+
action: 'ADD',
|
|
220
|
+
})
|
|
221
|
+
}>
|
|
222
|
+
{t('addRelationshipButtonText', 'Add Relationship')}
|
|
223
|
+
</Button>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
</FieldArray>
|
|
228
|
+
</section>
|
|
229
|
+
);
|
|
230
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Form, Formik } from 'formik';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
5
|
+
import { Resources, ResourcesContext } from '../../../offline.resources';
|
|
6
|
+
import { RelationshipsSection } from './relationships-section.component';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../patient-registration.resource', () => ({
|
|
9
|
+
fetchPerson: jest.fn().mockResolvedValue({
|
|
10
|
+
data: {
|
|
11
|
+
results: [
|
|
12
|
+
{ uuid: '42ae5ce0-d64b-11ea-9064-5adc43bbdd24', display: 'Person 1' },
|
|
13
|
+
{ uuid: '691eed12-c0f1-11e2-94be-8c13b969e334', display: 'Person 2' },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
}),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
let mockResourcesContextValue = {
|
|
20
|
+
addressTemplate: [],
|
|
21
|
+
currentSession: {
|
|
22
|
+
authenticated: true,
|
|
23
|
+
sessionId: 'JSESSION',
|
|
24
|
+
currentProvider: { uuid: '45ce6c2e-dd5a-11e6-9d9c-0242ac150002', identifier: 'PRO-123' },
|
|
25
|
+
},
|
|
26
|
+
identifierTypes: [],
|
|
27
|
+
relationshipTypes: null,
|
|
28
|
+
} as Resources;
|
|
29
|
+
|
|
30
|
+
describe('RelationshipsSection', () => {
|
|
31
|
+
it('renders a loader when relationshipTypes are not available', () => {
|
|
32
|
+
render(
|
|
33
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
34
|
+
<Formik initialValues={{}} onSubmit={null}>
|
|
35
|
+
<Form>
|
|
36
|
+
<RelationshipsSection />
|
|
37
|
+
</Form>
|
|
38
|
+
</Formik>
|
|
39
|
+
</ResourcesContext.Provider>,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(screen.getByLabelText(/loading relationships section/i)).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByRole(/progressbar/i)).toBeInTheDocument();
|
|
44
|
+
expect(screen.queryByText(/add relationship/i)).not.toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders relationships when relationshipTypes are available', () => {
|
|
48
|
+
const relationshipTypes = {
|
|
49
|
+
results: [
|
|
50
|
+
{ aIsToB: 'Mother', bIsToA: 'Child', uuid: '42ae5ce0-d64b-11ea-9064-5adc43bbdd34' },
|
|
51
|
+
{ aIsToB: 'Father', bIsToA: 'Child', uuid: '52ae5ce0-d64b-11ea-9064-5adc43bbdd24' },
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
mockResourcesContextValue = {
|
|
55
|
+
...mockResourcesContextValue,
|
|
56
|
+
relationshipTypes: relationshipTypes,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
render(
|
|
60
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
61
|
+
<Formik
|
|
62
|
+
initialValues={{
|
|
63
|
+
relationships: [{ action: 'ADD', relatedPersonUuid: '11524ae7-3ef6-4ab6-aff6-804ffc58704a' }],
|
|
64
|
+
}}
|
|
65
|
+
onSubmit={null}>
|
|
66
|
+
<Form>
|
|
67
|
+
<PatientRegistrationContext.Provider
|
|
68
|
+
value={{
|
|
69
|
+
setFieldValue: jest.fn(),
|
|
70
|
+
setInitialFormValues: jest.fn(),
|
|
71
|
+
}}>
|
|
72
|
+
<RelationshipsSection />
|
|
73
|
+
</PatientRegistrationContext.Provider>
|
|
74
|
+
</Form>
|
|
75
|
+
</Formik>
|
|
76
|
+
</ResourcesContext.Provider>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(screen.getByLabelText(/relationships section/i)).toBeInTheDocument();
|
|
80
|
+
expect(screen.getByRole('heading', { name: /relationship/i })).toBeInTheDocument();
|
|
81
|
+
expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
|
|
82
|
+
expect(screen.getByRole('button', { name: /add relationship/i })).toBeInTheDocument();
|
|
83
|
+
expect(screen.getByRole('searchbox', { name: /full name/i })).toBeInTheDocument();
|
|
84
|
+
expect(screen.getByRole('option', { name: /mother/i })).toBeInTheDocument();
|
|
85
|
+
expect(screen.getByRole('option', { name: /father/i })).toBeInTheDocument();
|
|
86
|
+
expect(screen.getAllByRole('option', { name: /child/i }).length).toEqual(2);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FetchResponse, openmrsFetch, showToast } from '@openmrs/esm-framework';
|
|
2
|
-
import { RelationshipValue } from '../../patient-registration
|
|
2
|
+
import { RelationshipValue } from '../../patient-registration.types';
|
|
3
3
|
import useSWR from 'swr';
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
5
|
import { personRelationshipRepresentation } from '../../../constants';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Yup from 'yup';
|
|
2
2
|
import mapValues from 'lodash/mapValues';
|
|
3
|
-
import { FormValues } from '../patient-registration
|
|
3
|
+
import { FormValues } from '../patient-registration.types';
|
|
4
4
|
|
|
5
5
|
export const validationSchema = Yup.object({
|
|
6
6
|
givenName: Yup.string().required('givenNameRequired'),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { FetchResponse, openmrsFetch, showNotification, showToast } from '@openmrs/esm-framework';
|
|
2
|
-
import { ConceptAnswers, ConceptResponse, FormValues } from '../patient-registration/patient-registration-types';
|
|
3
2
|
import { generateNUPIPayload, handleClientRegistryResponse } from './patient-verification-utils';
|
|
4
3
|
import useSWR from 'swr';
|
|
5
4
|
import useSWRImmutable from 'swr/immutable';
|
|
5
|
+
import { ConceptAnswers, ConceptResponse, FormValues } from '../patient-registration/patient-registration.types';
|
|
6
6
|
|
|
7
7
|
export function searchClientRegistry(identifierType: string, searchTerm: string, token: string) {
|
|
8
8
|
const url = `https://afyakenyaapi.health.go.ke/partners/registry/search/KE/${identifierType}/${searchTerm}`;
|
|
@@ -154,3 +154,14 @@ async function postToRegistry(
|
|
|
154
154
|
showNotification({ kind: 'error', title: 'NUPI Post failed', description: JSON.stringify(error) });
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
+
|
|
158
|
+
export const useFacilityName = (facilityCode) => {
|
|
159
|
+
const apiUrl = `/ws/rest/v1/kenyaemr/facilityName?facilityCode=${facilityCode}`;
|
|
160
|
+
const { data, error, isLoading } = useSWRImmutable<FetchResponse>(apiUrl, openmrsFetch);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
facilityName: data ? data?.data : null,
|
|
164
|
+
isLoading: isLoading,
|
|
165
|
+
isError: error,
|
|
166
|
+
};
|
|
167
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { showModal } from '@openmrs/esm-framework';
|
|
2
2
|
import { FormikProps } from 'formik';
|
|
3
|
-
import { FormValues } from '../patient-registration/patient-registration-types';
|
|
4
3
|
import { ClientRegistryPatient, RegistryPatient } from './verification-types';
|
|
5
4
|
import counties from './assets/counties.json';
|
|
5
|
+
import { FormValues } from '../patient-registration/patient-registration.types';
|
|
6
6
|
|
|
7
7
|
export function handleClientRegistryResponse(
|
|
8
8
|
clientResponse: ClientRegistryPatient,
|
|
@@ -6,8 +6,8 @@ import { countries, verificationIdentifierTypes } from './assets/verification-as
|
|
|
6
6
|
import { searchClientRegistry, useGlobalProperties } from './patient-verification-hook';
|
|
7
7
|
import { showToast } from '@openmrs/esm-framework';
|
|
8
8
|
import { handleClientRegistryResponse } from './patient-verification-utils';
|
|
9
|
-
import { FormValues } from '../patient-registration/patient-registration-types';
|
|
10
9
|
import { FormikProps } from 'formik';
|
|
10
|
+
import { FormValues } from '../patient-registration/patient-registration.types';
|
|
11
11
|
|
|
12
12
|
interface PatientVerificationProps {
|
|
13
13
|
props: FormikProps<FormValues>;
|
|
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
3
3
|
import { Button } from '@carbon/react';
|
|
4
4
|
import { age, ExtensionSlot, formatDate } from '@openmrs/esm-framework';
|
|
5
5
|
import capitalize from 'lodash-es/capitalize';
|
|
6
|
+
import { useFacilityName } from '../patient-verification-hook';
|
|
6
7
|
|
|
7
8
|
const PatientInfo: React.FC<{ label: string; value: string }> = ({ label, value }) => {
|
|
8
9
|
return (
|
|
@@ -21,6 +22,8 @@ interface ConfirmPromptProps {
|
|
|
21
22
|
|
|
22
23
|
const ConfirmPrompt: React.FC<ConfirmPromptProps> = ({ close, onConfirm, patient }) => {
|
|
23
24
|
const { t } = useTranslation();
|
|
25
|
+
const { facilityName, isLoading } = useFacilityName(patient?.originFacilityKmflCode);
|
|
26
|
+
|
|
24
27
|
return (
|
|
25
28
|
<>
|
|
26
29
|
<div className="cds--modal-header">
|
|
@@ -54,6 +57,14 @@ const ConfirmPrompt: React.FC<ConfirmPromptProps> = ({ close, onConfirm, patient
|
|
|
54
57
|
<PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.dateOfBirth))} />
|
|
55
58
|
<PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
|
|
56
59
|
<PatientInfo label={t('nascopNumber', 'Nascop facility no')} value={capitalize(patient?.nascopCCCNumber)} />
|
|
60
|
+
<PatientInfo
|
|
61
|
+
label={t('originFacilityCode', 'Origin facility code')}
|
|
62
|
+
value={patient?.originFacilityKmflCode}
|
|
63
|
+
/>
|
|
64
|
+
<PatientInfo
|
|
65
|
+
label={t('originFacilityName', 'Origin facility name')}
|
|
66
|
+
value={isLoading ? '--' : facilityName?.name}
|
|
67
|
+
/>
|
|
57
68
|
</div>
|
|
58
69
|
</div>
|
|
59
70
|
</div>
|
package/src/root.component.tsx
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
fetchPatientIdentifierTypesWithSources,
|
|
10
10
|
} from './offline.resources';
|
|
11
11
|
import { FormManager } from './patient-registration/form-manager';
|
|
12
|
-
import { PatientRegistration
|
|
12
|
+
import { PatientRegistration } from './patient-registration/patient-registration.component';
|
|
13
13
|
import useSWRImmutable from 'swr/immutable';
|
|
14
14
|
import styles from './root.scss';
|
|
15
15
|
|
package/src/routes.json
CHANGED
|
@@ -3,54 +3,65 @@
|
|
|
3
3
|
"backendDependencies": {
|
|
4
4
|
"webservices.rest": "^2.24.0"
|
|
5
5
|
},
|
|
6
|
-
"pages": [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
6
|
+
"pages": [
|
|
7
|
+
{
|
|
8
|
+
"component": "root",
|
|
9
|
+
"route": "patient-registration",
|
|
10
|
+
"online": true,
|
|
11
|
+
"offline": true
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"component": "editPatient",
|
|
15
|
+
"routeRegex": "patient\\/([a-zA-Z0-9\\-]+)\\/edit",
|
|
16
|
+
"online": true,
|
|
17
|
+
"offline": true
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"extensions": [
|
|
21
|
+
{
|
|
22
|
+
"component": "addPatientLink",
|
|
23
|
+
"name": "add-patient-action",
|
|
24
|
+
"slot": "top-nav-actions-slot",
|
|
25
|
+
"online": true,
|
|
26
|
+
"offline": true
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"component": "cancelPatientEditModal",
|
|
30
|
+
"name": "cancel-patient-edit-modal",
|
|
31
|
+
"online": true,
|
|
32
|
+
"offline": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"component": "patientPhoto",
|
|
36
|
+
"name": "patient-photo-widget",
|
|
37
|
+
"slot": "patient-photo-slot",
|
|
38
|
+
"online": true,
|
|
39
|
+
"offline": true
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"component": "editPatientDetailsButton",
|
|
43
|
+
"name": "edit-patient-details-button",
|
|
44
|
+
"slot": "patient-actions-slot",
|
|
45
|
+
"online": true,
|
|
46
|
+
"offline": true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"component": "deleteIdentifierConfirmationModal",
|
|
50
|
+
"name": "delete-identifier-confirmation-modal",
|
|
51
|
+
"online": true,
|
|
52
|
+
"offline": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"component": "emptyClientRegistryModal",
|
|
56
|
+
"name": "empty-client-registry-modal",
|
|
57
|
+
"online": true,
|
|
58
|
+
"offline": true
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"component": "confirmClientRegistryModal",
|
|
62
|
+
"name": "confirm-client-registry-modal",
|
|
63
|
+
"online": true,
|
|
64
|
+
"offline": true
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import DisplayPatientPhoto from './display-photo.component';
|
|
4
|
+
import { mockPatient } from '../../../../__mocks__/appointments.mock';
|
|
5
|
+
|
|
6
|
+
jest.mock('../patient-registration/patient-registration.resource', () => ({
|
|
7
|
+
usePatientPhoto: jest.fn().mockReturnValue({ data: { imageSrc: 'test-image-src' } }),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
jest.mock('geopattern', () => ({
|
|
11
|
+
generate: jest.fn().mockReturnValue({
|
|
12
|
+
toDataUri: jest.fn().mockReturnValue('https://example.com'),
|
|
13
|
+
}),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const patientUuid = mockPatient.uuid;
|
|
17
|
+
const patientName = mockPatient.name;
|
|
18
|
+
|
|
19
|
+
describe('DisplayPatientPhoto Component', () => {
|
|
20
|
+
it('should render the component with the patient photo and size should not be small', () => {
|
|
21
|
+
render(<DisplayPatientPhoto patientUuid={patientUuid} patientName={patientName} />);
|
|
22
|
+
|
|
23
|
+
const avatarImage = screen.getByTitle(`${patientName}`);
|
|
24
|
+
|
|
25
|
+
expect(avatarImage).toBeInTheDocument();
|
|
26
|
+
expect(avatarImage).toHaveAttribute('style', expect.stringContaining('width: 80px; height: 80px'));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should render the component with the patient photo and size should be small i.e. 48px', () => {
|
|
30
|
+
render(<DisplayPatientPhoto patientUuid={patientUuid} patientName={patientName} size="small" />);
|
|
31
|
+
|
|
32
|
+
const avatarImage = screen.getByTitle(`${patientName}`);
|
|
33
|
+
|
|
34
|
+
expect(avatarImage).toBeInTheDocument();
|
|
35
|
+
expect(avatarImage).toHaveAttribute('style', expect.stringContaining('width: 48px; height: 48px'));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, screen } from '@testing-library/react';
|
|
3
|
+
import EditPatientDetailsButton from './edit-patient-details-button.component';
|
|
4
|
+
import { navigate } from '@openmrs/esm-framework';
|
|
5
|
+
import { mockPatient } from '../../../../__mocks__/appointments.mock';
|
|
6
|
+
|
|
7
|
+
describe('EditPatientDetailsButton', () => {
|
|
8
|
+
const patientUuid = mockPatient.uuid;
|
|
9
|
+
it('should navigate to the edit page when clicked', () => {
|
|
10
|
+
const mockNavigate = navigate as jest.Mock;
|
|
11
|
+
|
|
12
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
13
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
14
|
+
return {
|
|
15
|
+
...originalModule,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
render(<EditPatientDetailsButton patientUuid={patientUuid} />);
|
|
20
|
+
|
|
21
|
+
const button = screen.getByRole('menuitem');
|
|
22
|
+
fireEvent.click(button);
|
|
23
|
+
|
|
24
|
+
expect(mockNavigate).toHaveBeenCalledWith({ to: expect.stringContaining(`/patient/${patientUuid}/edit`) });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should call the onTransition function when provided', () => {
|
|
28
|
+
const onTransitionMock = jest.fn();
|
|
29
|
+
render(<EditPatientDetailsButton patientUuid={patientUuid} onTransition={onTransitionMock} />);
|
|
30
|
+
|
|
31
|
+
const button = screen.getByRole('menuitem');
|
|
32
|
+
fireEvent.click(button);
|
|
33
|
+
|
|
34
|
+
expect(onTransitionMock).toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
});
|
package/translations/am.json
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"addRelationshipButtonText": "Add Relationship",
|
|
3
|
-
"address1": "Address line 1",
|
|
4
|
-
"address2": "Address line 2",
|
|
5
3
|
"addressHeader": "Address",
|
|
6
4
|
"allFieldsRequiredText": "All fields are required unless marked optional",
|
|
7
5
|
"autoGeneratedPlaceholderText": "Auto-generated",
|
|
@@ -10,11 +8,8 @@
|
|
|
10
8
|
"birthFieldLabelText": "Birth",
|
|
11
9
|
"cancel": "Cancel",
|
|
12
10
|
"causeOfDeathInputLabel": "Cause of Death",
|
|
13
|
-
"cityVillage": "city",
|
|
14
11
|
"configure": "Configure",
|
|
15
12
|
"contactSection": "Contact Details",
|
|
16
|
-
"country": "Country",
|
|
17
|
-
"countyDistrict": "District",
|
|
18
13
|
"createNew": "Create New",
|
|
19
14
|
"dateOfBirthLabelText": "Date of Birth",
|
|
20
15
|
"deathDateInputLabel": "Date of Death",
|
|
@@ -63,7 +58,6 @@
|
|
|
63
58
|
"other": "Other",
|
|
64
59
|
"patient": "Patient",
|
|
65
60
|
"patientNameKnown": "Patient's Name is Known?",
|
|
66
|
-
"postalCode": "Postal code",
|
|
67
61
|
"registerPatient": "Register Patient",
|
|
68
62
|
"registrationSuccessToastDescription": "The patient can now be found by searching for them using their name or ID number",
|
|
69
63
|
"registrationSuccessToastTitle": "New Patient Created",
|
|
@@ -79,7 +73,6 @@
|
|
|
79
73
|
"searchAddress": "Search address",
|
|
80
74
|
"selectAnOption": "Select an option",
|
|
81
75
|
"sexFieldLabelText": "Sex",
|
|
82
|
-
"stateProvince": "State",
|
|
83
76
|
"stroke": "Stroke",
|
|
84
77
|
"unableToFetch": "Unable to fetch person attribute type - {personattributetype}",
|
|
85
78
|
"unknown": "Unknown",
|