@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
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type FetchResponse,
|
|
3
|
+
openmrsFetch,
|
|
4
|
+
queueSynchronizationItem,
|
|
5
|
+
type Session,
|
|
6
|
+
restBaseUrl,
|
|
7
|
+
} from '@openmrs/esm-framework';
|
|
2
8
|
import { patientRegistration } from '../constants';
|
|
3
9
|
import {
|
|
4
|
-
FormValues,
|
|
5
|
-
AttributeValue,
|
|
6
|
-
PatientUuidMapType,
|
|
7
|
-
Patient,
|
|
8
|
-
CapturePhotoProps,
|
|
9
|
-
PatientIdentifier,
|
|
10
|
-
PatientRegistration,
|
|
11
|
-
RelationshipValue,
|
|
12
|
-
Encounter,
|
|
10
|
+
type FormValues,
|
|
11
|
+
type AttributeValue,
|
|
12
|
+
type PatientUuidMapType,
|
|
13
|
+
type Patient,
|
|
14
|
+
type CapturePhotoProps,
|
|
15
|
+
type PatientIdentifier,
|
|
16
|
+
type PatientRegistration,
|
|
17
|
+
type RelationshipValue,
|
|
18
|
+
type Encounter,
|
|
13
19
|
} from './patient-registration.types';
|
|
14
20
|
import {
|
|
15
21
|
addPatientIdentifier,
|
|
@@ -24,7 +30,7 @@ import {
|
|
|
24
30
|
updatePatientIdentifier,
|
|
25
31
|
saveEncounter,
|
|
26
32
|
} from './patient-registration.resource';
|
|
27
|
-
import { RegistrationConfig } from '../config-schema';
|
|
33
|
+
import { type RegistrationConfig } from '../config-schema';
|
|
28
34
|
|
|
29
35
|
export type SavePatientForm = (
|
|
30
36
|
isNewPatient: boolean,
|
|
@@ -128,7 +134,7 @@ export class FormManager {
|
|
|
128
134
|
await savePatientPhoto(
|
|
129
135
|
savePatientResponse.data.uuid,
|
|
130
136
|
capturePhotoProps.imageData,
|
|
131
|
-
|
|
137
|
+
`${restBaseUrl}/obs`,
|
|
132
138
|
capturePhotoProps.dateTime || new Date().toISOString(),
|
|
133
139
|
config.concepts.patientPhotoUuid,
|
|
134
140
|
);
|
|
@@ -252,7 +258,7 @@ export class FormManager {
|
|
|
252
258
|
});
|
|
253
259
|
|
|
254
260
|
/*
|
|
255
|
-
If there was initially an identifier assigned to the patient,
|
|
261
|
+
If there was initially an identifier assigned to the patient,
|
|
256
262
|
which is now not present in the patientIdentifiers(values.identifiers),
|
|
257
263
|
this means that the identifier is meant to be deleted, hence we need
|
|
258
264
|
to delete the respective identifiers.
|
|
@@ -302,7 +308,7 @@ export class FormManager {
|
|
|
302
308
|
person: {
|
|
303
309
|
uuid: values.patientUuid,
|
|
304
310
|
names: FormManager.getNames(values, patientUuidMap),
|
|
305
|
-
gender: values.gender.charAt(0),
|
|
311
|
+
gender: values.gender.charAt(0).toUpperCase(),
|
|
306
312
|
birthdate,
|
|
307
313
|
birthdateEstimated: values.birthdateEstimated,
|
|
308
314
|
attributes: FormManager.getPatientAttributes(isNewPatient, values, patientUuidMap),
|
|
@@ -354,7 +360,7 @@ export class FormManager {
|
|
|
354
360
|
.filter(([, value]) => !value)
|
|
355
361
|
.forEach(async ([key]) => {
|
|
356
362
|
const attributeUuid = patientUuidMap[`attribute.${key}`];
|
|
357
|
-
await openmrsFetch(
|
|
363
|
+
await openmrsFetch(`${restBaseUrl}/person/${values.patientUuid}/attribute/${attributeUuid}`, {
|
|
358
364
|
method: 'DELETE',
|
|
359
365
|
}).catch((err) => {
|
|
360
366
|
console.error(err);
|
|
@@ -383,21 +389,13 @@ export class FormManager {
|
|
|
383
389
|
// provide a valid fhir.Patient object. The various patient chart modules should be able to handle
|
|
384
390
|
// such missing props correctly (and should be updated if they don't).
|
|
385
391
|
|
|
386
|
-
// Gender in the original object only uses a single letter. fhir.Patient expects a full string.
|
|
387
|
-
const genderMap = {
|
|
388
|
-
M: 'male',
|
|
389
|
-
F: 'female',
|
|
390
|
-
O: 'other',
|
|
391
|
-
U: 'unknown',
|
|
392
|
-
};
|
|
393
|
-
|
|
394
392
|
// Mapping inspired by:
|
|
395
393
|
// https://github.com/openmrs/openmrs-module-fhir/blob/669b3c52220bb9abc622f815f4dc0d8523687a57/api/src/main/java/org/openmrs/module/fhir/api/util/FHIRPatientUtil.java#L36
|
|
396
394
|
// https://github.com/openmrs/openmrs-esm-patient-management/blob/94e6f637fb37cf4984163c355c5981ea6b8ca38c/packages/esm-patient-search-app/src/patient-search-result/patient-search-result.component.tsx#L21
|
|
397
395
|
// Update as required.
|
|
398
396
|
return {
|
|
399
397
|
id: patient.uuid,
|
|
400
|
-
gender:
|
|
398
|
+
gender: patient.person?.gender,
|
|
401
399
|
birthDate: patient.person?.birthdate,
|
|
402
400
|
deceasedBoolean: patient.person.dead,
|
|
403
401
|
deceasedDateTime: patient.person.deathDate,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render,
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
4
|
import { Formik, Form } from 'formik';
|
|
5
5
|
import { SelectInput } from './select-input.component';
|
|
@@ -28,7 +28,7 @@ describe('the select input', () => {
|
|
|
28
28
|
|
|
29
29
|
await user.selectOptions(input, expected);
|
|
30
30
|
|
|
31
|
-
await
|
|
31
|
+
await expect(input.value).toEqual(expected);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it('should show optional label if the input is not required', async () => {
|
|
@@ -40,7 +40,7 @@ describe('the select input', () => {
|
|
|
40
40
|
</Formik>,
|
|
41
41
|
);
|
|
42
42
|
|
|
43
|
-
await
|
|
43
|
+
await expect(screen.findByRole('combobox'));
|
|
44
44
|
|
|
45
45
|
const selectInput = screen.getByRole('combobox', { name: 'Select (optional)' }) as HTMLSelectElement;
|
|
46
46
|
expect(selectInput.labels).toHaveLength(1);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React, { HTMLAttributes, useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { Layer, Search, SearchProps } from '@carbon/react';
|
|
1
|
+
import React, { type HTMLAttributes, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { Layer, Search, type SearchProps } from '@carbon/react';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import styles from './autosuggest.scss';
|
|
5
5
|
|
|
6
6
|
// FIXME Temporarily included types from Carbon
|
|
7
7
|
type InputPropsBase = Omit<HTMLAttributes<HTMLInputElement>, 'onChange'>;
|
|
8
|
+
|
|
8
9
|
interface SearchProps extends InputPropsBase {
|
|
9
10
|
/**
|
|
10
11
|
* Specify an optional value for the `autocomplete` property on the underlying
|
|
@@ -136,6 +137,7 @@ export const Autosuggest: React.FC<AutosuggestProps> = ({
|
|
|
136
137
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
137
138
|
const query = e.target.value;
|
|
138
139
|
onSuggestionSelected(name, undefined);
|
|
140
|
+
|
|
139
141
|
if (query) {
|
|
140
142
|
getSearchResults(query).then((suggestions) => {
|
|
141
143
|
setSuggestions(suggestions);
|
|
@@ -173,9 +175,7 @@ export const Autosuggest: React.FC<AutosuggestProps> = ({
|
|
|
173
175
|
{suggestions.length > 0 && (
|
|
174
176
|
<ul className={styles.suggestions}>
|
|
175
177
|
{suggestions.map((suggestion, index) => (
|
|
176
|
-
<li
|
|
177
|
-
key={index}
|
|
178
|
-
onClick={(e) => handleClick(index)}>
|
|
178
|
+
<li key={index} onClick={(e) => handleClick(index)}>
|
|
179
179
|
{getDisplayValue(suggestion)}
|
|
180
180
|
</li>
|
|
181
181
|
))}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Autosuggest } from './autosuggest.component';
|
|
3
|
-
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
4
3
|
import { BrowserRouter } from 'react-router-dom';
|
|
5
|
-
import '@testing-library/
|
|
6
|
-
import '@testing-library/
|
|
4
|
+
import { render, screen } from '@testing-library/react';
|
|
5
|
+
import userEvent from '@testing-library/user-event';
|
|
7
6
|
|
|
8
7
|
const mockPersons = [
|
|
9
8
|
{
|
|
@@ -24,97 +23,110 @@ const mockPersons = [
|
|
|
24
23
|
},
|
|
25
24
|
];
|
|
26
25
|
|
|
27
|
-
const
|
|
26
|
+
const mockedGetSearchResults = async (query: string) => {
|
|
28
27
|
return mockPersons.filter((person) => {
|
|
29
28
|
return person.display.toUpperCase().includes(query.toUpperCase());
|
|
30
29
|
});
|
|
31
30
|
};
|
|
32
31
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
describe('
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
id="person"
|
|
42
|
-
placeholder="Find Person"
|
|
43
|
-
onSuggestionSelected={handleSuggestionSelected}
|
|
44
|
-
getSearchResults={mockGetSearchResults}
|
|
45
|
-
getDisplayValue={(item) => item.display}
|
|
46
|
-
getFieldValue={(item) => item.uuid}
|
|
47
|
-
/>
|
|
48
|
-
</BrowserRouter>,
|
|
49
|
-
);
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
it('should render a search box', () => {
|
|
53
|
-
setup();
|
|
32
|
+
const mockedHandleSuggestionSelected = jest.fn((field, value) => [field, value]);
|
|
33
|
+
|
|
34
|
+
describe('Autosuggest', () => {
|
|
35
|
+
afterEach(() => mockedHandleSuggestionSelected.mockClear());
|
|
36
|
+
|
|
37
|
+
it('renders a search box', () => {
|
|
38
|
+
renderAutosuggest();
|
|
39
|
+
|
|
54
40
|
expect(screen.getByRole('searchbox')).toBeInTheDocument();
|
|
55
41
|
expect(screen.queryByRole('list')).toBeNull();
|
|
56
42
|
});
|
|
57
43
|
|
|
58
|
-
it('
|
|
59
|
-
setup();
|
|
44
|
+
it('renders matching search results in a list when the user types a query', async () => {
|
|
45
|
+
const user = userEvent.setup();
|
|
46
|
+
|
|
47
|
+
renderAutosuggest();
|
|
48
|
+
|
|
60
49
|
const searchbox = screen.getByRole('searchbox');
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
await user.type(searchbox, 'john');
|
|
51
|
+
|
|
52
|
+
const list = screen.getByRole('list');
|
|
53
|
+
|
|
63
54
|
expect(list).toBeInTheDocument();
|
|
64
55
|
expect(list.children).toHaveLength(2);
|
|
56
|
+
expect(screen.getAllByRole('listitem')[0]).toHaveTextContent('John Doe');
|
|
57
|
+
expect(screen.getAllByRole('listitem')[1]).toHaveTextContent('John Smith');
|
|
65
58
|
});
|
|
66
59
|
|
|
67
|
-
it('
|
|
68
|
-
setup();
|
|
69
|
-
const searchbox = screen.getByRole('searchbox');
|
|
70
|
-
fireEvent.change(searchbox, { target: { value: 'john' } });
|
|
71
|
-
const list = await waitFor(() => screen.getAllByRole('listitem'));
|
|
72
|
-
expect(list[0].textContent).toBe('John Doe');
|
|
73
|
-
expect(list[1].textContent).toBe('John Smith');
|
|
74
|
-
});
|
|
60
|
+
it('clears the list of suggestions when a suggestion is selected', async () => {
|
|
61
|
+
const user = userEvent.setup();
|
|
75
62
|
|
|
76
|
-
|
|
77
|
-
setup();
|
|
78
|
-
const searchbox = screen.getByRole('searchbox');
|
|
79
|
-
fireEvent.change(searchbox, { target: { value: 'john' } });
|
|
80
|
-
const listitems = await waitFor(() => screen.getAllByRole('listitem'));
|
|
81
|
-
fireEvent.click(listitems[0]);
|
|
82
|
-
expect(handleSuggestionSelected).toHaveBeenNthCalledWith(4, 'person', 'randomuuid1');
|
|
83
|
-
});
|
|
63
|
+
renderAutosuggest();
|
|
84
64
|
|
|
85
|
-
it('should clear the suggestions when a suggestion is selected', async () => {
|
|
86
|
-
setup();
|
|
87
65
|
let list = screen.queryByRole('list');
|
|
88
66
|
expect(list).toBeNull();
|
|
67
|
+
|
|
89
68
|
const searchbox = screen.getByRole('searchbox');
|
|
90
|
-
|
|
91
|
-
|
|
69
|
+
await user.type(searchbox, 'john');
|
|
70
|
+
|
|
71
|
+
list = screen.getByRole('list');
|
|
92
72
|
expect(list).toBeInTheDocument();
|
|
73
|
+
|
|
93
74
|
const listitems = screen.getAllByRole('listitem');
|
|
94
|
-
|
|
75
|
+
await user.click(listitems[0]);
|
|
76
|
+
|
|
77
|
+
expect(mockedHandleSuggestionSelected).toHaveBeenLastCalledWith('person', 'randomuuid1');
|
|
78
|
+
|
|
95
79
|
list = screen.queryByRole('list');
|
|
96
80
|
expect(list).toBeNull();
|
|
97
81
|
});
|
|
98
82
|
|
|
99
|
-
it('
|
|
100
|
-
setup();
|
|
83
|
+
it('changes suggestions when a search input is changed', async () => {
|
|
84
|
+
const user = userEvent.setup();
|
|
85
|
+
|
|
86
|
+
renderAutosuggest();
|
|
87
|
+
|
|
101
88
|
let list = screen.queryByRole('list');
|
|
102
89
|
expect(list).toBeNull();
|
|
90
|
+
|
|
103
91
|
const searchbox = screen.getByRole('searchbox');
|
|
104
|
-
|
|
92
|
+
await user.type(searchbox, 'john');
|
|
93
|
+
|
|
105
94
|
const suggestion = await screen.findByText('John Doe');
|
|
106
95
|
expect(suggestion).toBeInTheDocument();
|
|
107
|
-
|
|
96
|
+
|
|
97
|
+
await user.clear(searchbox);
|
|
98
|
+
|
|
108
99
|
list = screen.queryByRole('list');
|
|
109
100
|
expect(list).toBeNull();
|
|
110
101
|
});
|
|
111
102
|
|
|
112
|
-
it('
|
|
113
|
-
setup();
|
|
103
|
+
it('hides the list of suggestions when the user clicks outside of the component', async () => {
|
|
104
|
+
const user = userEvent.setup();
|
|
105
|
+
|
|
106
|
+
renderAutosuggest();
|
|
107
|
+
|
|
114
108
|
const input = screen.getByRole('searchbox');
|
|
115
|
-
|
|
109
|
+
|
|
110
|
+
await user.type(input, 'john');
|
|
116
111
|
await screen.findByText('John Doe');
|
|
117
|
-
|
|
112
|
+
await user.click(document.body);
|
|
113
|
+
|
|
118
114
|
expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
|
|
119
115
|
});
|
|
120
116
|
});
|
|
117
|
+
|
|
118
|
+
function renderAutosuggest() {
|
|
119
|
+
render(
|
|
120
|
+
<BrowserRouter>
|
|
121
|
+
<Autosuggest
|
|
122
|
+
getSearchResults={mockedGetSearchResults}
|
|
123
|
+
getDisplayValue={(item) => item.display}
|
|
124
|
+
getFieldValue={(item) => item.uuid}
|
|
125
|
+
id="person"
|
|
126
|
+
labelText=""
|
|
127
|
+
onSuggestionSelected={mockedHandleSuggestionSelected}
|
|
128
|
+
placeholder="Find Person"
|
|
129
|
+
/>
|
|
130
|
+
</BrowserRouter>,
|
|
131
|
+
);
|
|
132
|
+
}
|
package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx
CHANGED
|
@@ -7,7 +7,7 @@ import { ResourcesContext } from '../../../../offline.resources';
|
|
|
7
7
|
import { showModal, useConfig, UserHasAccess } from '@openmrs/esm-framework';
|
|
8
8
|
import { shouldBlockPatientIdentifierInOfflineMode } from './utils';
|
|
9
9
|
import { deleteIdentifierType, setIdentifierSource } from '../../../field/id/id-field.component';
|
|
10
|
-
import { PatientIdentifierValue } from '../../../patient-registration.types';
|
|
10
|
+
import { type PatientIdentifierValue } from '../../../patient-registration.types';
|
|
11
11
|
import { PatientRegistrationContext } from '../../../patient-registration-context';
|
|
12
12
|
import { Input } from '../../basic-input/input/input.component';
|
|
13
13
|
import styles from '../../input.scss';
|
|
@@ -141,7 +141,7 @@ export const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentif
|
|
|
141
141
|
<UserHasAccess privilege="Delete Patient Identifiers">
|
|
142
142
|
<Button
|
|
143
143
|
size="md"
|
|
144
|
-
kind="
|
|
144
|
+
kind="ghost"
|
|
145
145
|
onClick={handleDelete}
|
|
146
146
|
iconDescription={t('deleteIdentifierTooltip', 'Delete')}
|
|
147
147
|
disabled={disabled}
|
|
@@ -3,7 +3,7 @@ import { render, screen } from '@testing-library/react';
|
|
|
3
3
|
import { Formik, Form } from 'formik';
|
|
4
4
|
import { IdentifierInput } from './identifier-input.component';
|
|
5
5
|
import { initialFormValues } from '../../../patient-registration.component';
|
|
6
|
-
import { PatientIdentifierType } from '../../../patient-registration-types';
|
|
6
|
+
import { type PatientIdentifierType } from '../../../patient-registration-types';
|
|
7
7
|
|
|
8
8
|
jest.mock('@openmrs/esm-framework', () => {
|
|
9
9
|
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
@@ -52,10 +52,7 @@ describe.skip('identifier input', () => {
|
|
|
52
52
|
</Formik>,
|
|
53
53
|
);
|
|
54
54
|
const identifierInput = screen.getByLabelText(identifierType.fieldName) as HTMLInputElement;
|
|
55
|
-
let identifierSourceSelectInput =
|
|
56
|
-
try {
|
|
57
|
-
identifierSourceSelectInput = screen.getByLabelText('source-for-' + identifierType.fieldName) as HTMLInputElement;
|
|
58
|
-
} catch (e) {}
|
|
55
|
+
let identifierSourceSelectInput = screen.getByLabelText('source-for-' + identifierType.fieldName);
|
|
59
56
|
return {
|
|
60
57
|
identifierInput,
|
|
61
58
|
identifierSourceSelectInput,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FetchedPatientIdentifierType, PatientIdentifierType } from '../../../patient-registration.types';
|
|
1
|
+
import { type FetchedPatientIdentifierType, type PatientIdentifierType } from '../../../patient-registration.types';
|
|
2
2
|
|
|
3
3
|
export function shouldBlockPatientIdentifierInOfflineMode(identifierType: PatientIdentifierType) {
|
|
4
4
|
// Patient Identifiers which are unique and can be manually entered are prohibited while offline because
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { v4 } from 'uuid';
|
|
4
|
-
import { FormValues } from '../../patient-registration.types';
|
|
4
|
+
import { type FormValues } from '../../patient-registration.types';
|
|
5
5
|
import styles from './../input.scss';
|
|
6
6
|
|
|
7
7
|
interface DummyDataInputProps {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render,
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
3
4
|
import { DummyDataInput, dummyFormValues } from './dummy-data-input.component';
|
|
4
5
|
import { initialFormValues } from '../../patient-registration.component';
|
|
5
|
-
import { FormValues } from '../../patient-registration-types';
|
|
6
|
+
import { type FormValues } from '../../patient-registration-types';
|
|
6
7
|
|
|
7
8
|
jest.mock('@openmrs/esm-framework', () => {
|
|
8
9
|
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
@@ -33,11 +34,10 @@ describe('dummy data input', () => {
|
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
it('can input data on button click', async () => {
|
|
37
|
+
const user = userEvent.setup();
|
|
36
38
|
const input = await setupInput();
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
expect(formValues).toEqual(dummyFormValues);
|
|
41
|
-
});
|
|
40
|
+
await user.click(input);
|
|
41
|
+
expect(formValues).toEqual(dummyFormValues);
|
|
42
42
|
});
|
|
43
43
|
});
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { useConfig } from '@openmrs/esm-framework';
|
|
2
|
-
import { createContext, SetStateAction } from 'react';
|
|
3
|
-
import { RegistrationConfig } from '../config-schema';
|
|
4
|
-
import { FormValues, CapturePhotoProps } from './patient-registration.types';
|
|
2
|
+
import { createContext, type SetStateAction } from 'react';
|
|
3
|
+
import { type RegistrationConfig } from '../config-schema';
|
|
4
|
+
import { type FormValues, type CapturePhotoProps } from './patient-registration.types';
|
|
5
5
|
|
|
6
6
|
export interface PatientRegistrationContextProps {
|
|
7
7
|
identifierTypes: Array<any>;
|
|
8
8
|
values: FormValues;
|
|
9
9
|
validationSchema: any;
|
|
10
|
-
setValidationSchema(value: any): void;
|
|
11
10
|
inEditMode: boolean;
|
|
12
11
|
setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
|
|
13
12
|
setCapturePhotoProps(value: SetStateAction<CapturePhotoProps>): void;
|
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
|
-
FetchResponse,
|
|
3
|
-
OpenmrsResource,
|
|
2
|
+
type FetchResponse,
|
|
3
|
+
type OpenmrsResource,
|
|
4
4
|
getSynchronizationItems,
|
|
5
5
|
openmrsFetch,
|
|
6
6
|
useConfig,
|
|
7
7
|
usePatient,
|
|
8
|
+
restBaseUrl,
|
|
8
9
|
} from '@openmrs/esm-framework';
|
|
9
10
|
import last from 'lodash-es/last';
|
|
10
11
|
import camelCase from 'lodash-es/camelCase';
|
|
11
|
-
import { Dispatch, useEffect, useMemo, useState } from 'react';
|
|
12
|
+
import { type Dispatch, useEffect, useMemo, useState } from 'react';
|
|
12
13
|
import useSWR from 'swr';
|
|
13
14
|
import { v4 } from 'uuid';
|
|
14
|
-
import { RegistrationConfig } from '../config-schema';
|
|
15
|
+
import { type RegistrationConfig } from '../config-schema';
|
|
15
16
|
import { patientRegistration } from '../constants';
|
|
16
17
|
import { useConceptAnswers, useGlobalProperties } from '../patient-verification/patient-verification-hook';
|
|
17
18
|
import {
|
|
18
|
-
FormValues,
|
|
19
|
-
PatientRegistration,
|
|
20
|
-
PatientUuidMapType,
|
|
21
|
-
PersonAttributeResponse,
|
|
22
|
-
PatientIdentifierResponse,
|
|
23
|
-
|
|
24
|
-
ConceptAnswers,
|
|
25
|
-
|
|
19
|
+
type FormValues,
|
|
20
|
+
type PatientRegistration,
|
|
21
|
+
type PatientUuidMapType,
|
|
22
|
+
type PersonAttributeResponse,
|
|
23
|
+
type PatientIdentifierResponse,
|
|
24
|
+
type Encounter,
|
|
25
|
+
type ConceptAnswers,
|
|
26
|
+
type ObsResponse,
|
|
26
27
|
} from './patient-registration.types';
|
|
27
28
|
import {
|
|
28
29
|
getAddressFieldValuesFromFhirPatient,
|
|
@@ -35,7 +36,7 @@ import { useInitialPatientRelationships } from './section/patient-relationships/
|
|
|
35
36
|
import dayjs from 'dayjs';
|
|
36
37
|
|
|
37
38
|
export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
|
|
38
|
-
const { martialStatus, education, occupation, educationLoad
|
|
39
|
+
const { martialStatus, education, occupation, educationLoad } = useConcepts();
|
|
39
40
|
const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
|
|
40
41
|
const { data: attributes, isLoading: isLoadingAttributes } = useInitialPersonAttributes(patientUuid);
|
|
41
42
|
const { data: identifiers, isLoading: isLoadingIdentifiers } = useInitialPatientIdentifiers(patientUuid);
|
|
@@ -157,13 +158,13 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
|
|
|
157
158
|
// Set Initial encounter
|
|
158
159
|
|
|
159
160
|
useEffect(() => {
|
|
160
|
-
if (!educationLoad
|
|
161
|
+
if (!educationLoad) {
|
|
161
162
|
setInitialFormValues((initialFormValues) => ({
|
|
162
163
|
...initialFormValues,
|
|
163
164
|
concepts: [...occupation, ...martialStatus, ...education],
|
|
164
165
|
}));
|
|
165
166
|
}
|
|
166
|
-
}, [educationLoad
|
|
167
|
+
}, [educationLoad]);
|
|
167
168
|
|
|
168
169
|
return [initialFormValues, setInitialFormValues];
|
|
169
170
|
}
|
|
@@ -232,7 +233,7 @@ export function useInitialPatientIdentifiers(patientUuid: string): {
|
|
|
232
233
|
|
|
233
234
|
const { data, error, isLoading } = useSWR<FetchResponse<{ results: Array<PatientIdentifierResponse> }>, Error>(
|
|
234
235
|
shouldFetch
|
|
235
|
-
?
|
|
236
|
+
? `${restBaseUrl}/patient/${patientUuid}/identifier?v=custom:(uuid,identifier,identifierType:(uuid,required,name),preferred)`
|
|
236
237
|
: null,
|
|
237
238
|
openmrsFetch,
|
|
238
239
|
);
|
|
@@ -269,7 +270,7 @@ function useInitialEncounters(patientUuid: string, patientToEdit: fhir.Patient)
|
|
|
269
270
|
const { registrationObs } = useConfig() as RegistrationConfig;
|
|
270
271
|
const { data, error, isLoading } = useSWR<FetchResponse<{ results: Array<Encounter> }>>(
|
|
271
272
|
patientToEdit && registrationObs.encounterTypeUuid
|
|
272
|
-
?
|
|
273
|
+
? `${restBaseUrl}/encounter?patient=${patientUuid}&v=custom:(encounterDatetime,obs:(concept:ref,value:ref))&encounterType=${registrationObs.encounterTypeUuid}`
|
|
273
274
|
: null,
|
|
274
275
|
openmrsFetch,
|
|
275
276
|
);
|
|
@@ -287,7 +288,7 @@ function useInitialPersonAttributes(personUuid: string) {
|
|
|
287
288
|
const shouldFetch = !!personUuid;
|
|
288
289
|
const { data, error, isLoading } = useSWR<FetchResponse<{ results: Array<PersonAttributeResponse> }>, Error>(
|
|
289
290
|
shouldFetch
|
|
290
|
-
?
|
|
291
|
+
? `${restBaseUrl}/person/${personUuid}/attribute?v=custom:(uuid,display,attributeType:(uuid,display,format),value)`
|
|
291
292
|
: null,
|
|
292
293
|
openmrsFetch,
|
|
293
294
|
);
|
|
@@ -322,7 +323,7 @@ export function usePatientObs(patientUuid: string) {
|
|
|
322
323
|
}
|
|
323
324
|
|
|
324
325
|
function useConcepts() {
|
|
325
|
-
const
|
|
326
|
+
const config = useConfig<RegistrationConfig>();
|
|
326
327
|
const { data: education, isLoading: educationLoad } = useConceptAnswers('1712AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
|
|
327
328
|
const occupation: Array<ConceptAnswers> = [
|
|
328
329
|
{
|
|
@@ -355,5 +356,9 @@ function useConcepts() {
|
|
|
355
356
|
},
|
|
356
357
|
];
|
|
357
358
|
|
|
358
|
-
|
|
359
|
+
const martialStatus: Array<ConceptAnswers> = config.fieldDefinitions
|
|
360
|
+
.find((fieldDefinition) => fieldDefinition.id === 'maritalStatus')
|
|
361
|
+
.customConceptAnswers.map((concept) => ({ uuid: concept.uuid, display: concept.label }));
|
|
362
|
+
|
|
363
|
+
return { martialStatus, education, occupation, educationLoad };
|
|
359
364
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as Yup from 'yup';
|
|
2
2
|
import {
|
|
3
|
-
AddressValidationSchemaType,
|
|
4
|
-
FormValues,
|
|
5
|
-
PatientIdentifier,
|
|
6
|
-
PatientUuidMapType,
|
|
7
|
-
PatientIdentifierValue,
|
|
8
|
-
Encounter,
|
|
3
|
+
type AddressValidationSchemaType,
|
|
4
|
+
type FormValues,
|
|
5
|
+
type PatientIdentifier,
|
|
6
|
+
type PatientUuidMapType,
|
|
7
|
+
type PatientIdentifierValue,
|
|
8
|
+
type Encounter,
|
|
9
9
|
} from './patient-registration.types';
|
|
10
10
|
import { parseDate } from '@openmrs/esm-framework';
|
|
11
11
|
import camelCase from 'lodash-es/camelCase';
|
|
@@ -120,7 +120,7 @@ export function getFormValuesFromFhirPatient(patient: fhir.Patient) {
|
|
|
120
120
|
result.additionalMiddleName = additionalPatientName?.given[1];
|
|
121
121
|
result.additionalFamilyName = additionalPatientName?.family;
|
|
122
122
|
|
|
123
|
-
result.gender =
|
|
123
|
+
result.gender = patient.gender;
|
|
124
124
|
result.birthdate = patient.birthDate ? parseDate(patient.birthDate) : undefined;
|
|
125
125
|
result.telephoneNumber = patient.telecom ? patient.telecom[0].value : '';
|
|
126
126
|
|
|
@@ -4,18 +4,28 @@ import { Button, Link, InlineLoading } from '@carbon/react';
|
|
|
4
4
|
import { XAxis, ShareKnowledge } from '@carbon/react/icons';
|
|
5
5
|
import { useLocation, useParams } from 'react-router-dom';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
|
-
import { Formik, Form, FormikHelpers } from 'formik';
|
|
7
|
+
import { Formik, Form, type FormikHelpers } from 'formik';
|
|
8
8
|
import { createErrorHandler, showSnackbar, useConfig, interpolateUrl, usePatient } from '@openmrs/esm-framework';
|
|
9
|
-
import {
|
|
10
|
-
import { FormValues, CapturePhotoProps } from './patient-registration.types';
|
|
9
|
+
import { getValidationSchema } from './validation/patient-registration-validation';
|
|
10
|
+
import { type FormValues, type CapturePhotoProps } from './patient-registration.types';
|
|
11
11
|
import { PatientRegistrationContext } from './patient-registration-context';
|
|
12
|
-
import { SavePatientForm, SavePatientTransactionManager } from './form-manager';
|
|
12
|
+
import { type SavePatientForm, SavePatientTransactionManager } from './form-manager';
|
|
13
13
|
import { usePatientPhoto } from './patient-registration.resource';
|
|
14
14
|
import { DummyDataInput } from './input/dummy-data/dummy-data-input.component';
|
|
15
|
-
import {
|
|
16
|
-
|
|
15
|
+
import {
|
|
16
|
+
cancelRegistration,
|
|
17
|
+
filterUndefinedPatientIdenfier,
|
|
18
|
+
parseAddressTemplateXml,
|
|
19
|
+
scrollIntoView,
|
|
20
|
+
} from './patient-registration-utils';
|
|
21
|
+
import {
|
|
22
|
+
useInitialAddressFieldValues,
|
|
23
|
+
useInitialFormValues,
|
|
24
|
+
usePatientObs,
|
|
25
|
+
usePatientUuidMap,
|
|
26
|
+
} from './patient-registration-hooks';
|
|
17
27
|
import { ResourcesContext } from '../offline.resources';
|
|
18
|
-
import { builtInSections, RegistrationConfig, SectionDefinition } from '../config-schema';
|
|
28
|
+
import { builtInSections, type RegistrationConfig, type SectionDefinition } from '../config-schema';
|
|
19
29
|
import { SectionWrapper } from './section/section-wrapper.component';
|
|
20
30
|
import BeforeSavePrompt from './before-save-prompt';
|
|
21
31
|
import styles from './patient-registration.scss';
|
|
@@ -34,7 +44,6 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
34
44
|
const { search } = useLocation();
|
|
35
45
|
const config = useConfig() as RegistrationConfig;
|
|
36
46
|
const [target, setTarget] = useState<undefined | string>();
|
|
37
|
-
const [validationSchema, setValidationSchema] = useState(initialSchema);
|
|
38
47
|
const { patientUuid: uuidOfPatientToEdit } = useParams();
|
|
39
48
|
const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(uuidOfPatientToEdit);
|
|
40
49
|
const { t } = useTranslation();
|
|
@@ -48,6 +57,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
48
57
|
const { data: photo } = usePatientPhoto(patientToEdit?.id);
|
|
49
58
|
const savePatientTransactionManager = useRef(new SavePatientTransactionManager());
|
|
50
59
|
const fieldDefinition = config?.fieldDefinitions?.filter((def) => def.type === 'address');
|
|
60
|
+
const validationSchema = getValidationSchema(config);
|
|
51
61
|
const [enableClientRegistry, setEnableClientRegistry] = useState(
|
|
52
62
|
inEditMode ? initialFormValues.identifiers['nationalUniquePatientIdentifier']?.identifierValue : false,
|
|
53
63
|
);
|
|
@@ -139,6 +149,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
139
149
|
title: t('incompleteForm', 'The following field has errors:'),
|
|
140
150
|
kind: 'warning',
|
|
141
151
|
isLowContrast: true,
|
|
152
|
+
timeoutInMs: 5000,
|
|
142
153
|
});
|
|
143
154
|
});
|
|
144
155
|
}
|
|
@@ -211,7 +222,6 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
211
222
|
value={{
|
|
212
223
|
identifierTypes: identifierTypes,
|
|
213
224
|
validationSchema,
|
|
214
|
-
setValidationSchema,
|
|
215
225
|
values: props.values,
|
|
216
226
|
inEditMode,
|
|
217
227
|
setFieldValue: props.setFieldValue,
|
|
@@ -221,7 +231,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
221
231
|
initialFormValues: props.initialValues,
|
|
222
232
|
setInitialFormValues,
|
|
223
233
|
}}>
|
|
224
|
-
<PatientVerification props={props} />
|
|
234
|
+
<PatientVerification props={props} setInitialFormValues={setInitialFormValues} />
|
|
225
235
|
{sections.map((section, index) => (
|
|
226
236
|
<SectionWrapper
|
|
227
237
|
key={`registration-section-${section.id}`}
|