@kenyaemr/esm-patient-registration-app 4.3.0 → 4.4.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/574.js +1 -1
- package/dist/68.js +1 -1
- package/dist/68.js.map +1 -1
- package/dist/822.js +1 -1
- package/dist/822.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +12 -12
- package/package.json +2 -2
- package/src/index.ts +0 -18
- package/src/offline.resources.ts +8 -1
- package/src/patient-registration/field/address/address-field.component.tsx +0 -1
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +13 -11
- package/src/patient-registration/form-manager.ts +27 -10
- package/src/patient-registration/input/basic-input/input/input.test.tsx +47 -10
- package/src/patient-registration/input/basic-input/select/select-input.test.tsx +16 -3
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +25 -14
- package/src/patient-registration/patient-registration-hooks.ts +21 -75
- package/src/patient-registration/patient-registration-types.tsx +2 -8
- package/src/patient-registration/patient-registration.component.tsx +3 -26
- package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
- package/src/root.component.tsx +1 -5
- package/translations/en.json +0 -15
- package/src/patient-registration/input/custom-input/estimated-age/estimated-age-input.component.tsx +0 -32
- package/src/patient-registration/input/custom-input/estimated-age/estimated-age-input.test.tsx +0 -36
- package/src/patient-registration/input/custom-input/unidentified-patient/unidentified-patient-input.component.tsx +0 -24
- package/src/patient-registration/input/custom-input/unidentified-patient/unidentified-patient-input.test.tsx +0 -39
- package/src/patient-verification/assets/counties.json +0 -236
- package/src/patient-verification/assets/verification-assets.ts +0 -11
- package/src/patient-verification/patient-verification-hook.tsx +0 -156
- package/src/patient-verification/patient-verification-utils.ts +0 -173
- package/src/patient-verification/patient-verification.component.tsx +0 -118
- package/src/patient-verification/patient-verification.scss +0 -30
- package/src/patient-verification/verification-modal/confirm-prompt.component.tsx +0 -69
- package/src/patient-verification/verification-modal/empty-prompt.component.tsx +0 -35
- package/src/patient-verification/verification-types.ts +0 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-patient-registration-app",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"description": "Patient registration microfrontend for the OpenMRS SPA",
|
|
5
5
|
"browser": "dist/openmrs-esm-patient-registration-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"serve": "webpack serve --mode=development",
|
|
13
13
|
"debug": "npm run serve",
|
|
14
14
|
"build": "webpack --mode production",
|
|
15
|
-
"analyze": "webpack --mode=production --env
|
|
15
|
+
"analyze": "webpack --mode=production --env.analyze=true",
|
|
16
16
|
"lint": "eslint src --ext ts,tsx",
|
|
17
17
|
"typescript": "tsc",
|
|
18
18
|
"extract-translations": "i18next 'src/**/*.component.tsx'"
|
package/src/index.ts
CHANGED
|
@@ -106,24 +106,6 @@ function setupOpenMRS() {
|
|
|
106
106
|
online: true,
|
|
107
107
|
offline: true,
|
|
108
108
|
},
|
|
109
|
-
{
|
|
110
|
-
id: 'empty-client-registry-modal',
|
|
111
|
-
load: getAsyncLifecycle(
|
|
112
|
-
() => import('./patient-verification/verification-modal/empty-prompt.component'),
|
|
113
|
-
options,
|
|
114
|
-
),
|
|
115
|
-
online: true,
|
|
116
|
-
offline: true,
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
id: 'confirm-client-registry-modal',
|
|
120
|
-
load: getAsyncLifecycle(
|
|
121
|
-
() => import('./patient-verification/verification-modal/confirm-prompt.component'),
|
|
122
|
-
options,
|
|
123
|
-
),
|
|
124
|
-
online: true,
|
|
125
|
-
offline: true,
|
|
126
|
-
},
|
|
127
109
|
],
|
|
128
110
|
};
|
|
129
111
|
}
|
package/src/offline.resources.ts
CHANGED
|
@@ -64,7 +64,14 @@ async function fetchPatientIdentifierTypes(): Promise<Array<FetchedPatientIdenti
|
|
|
64
64
|
|
|
65
65
|
const primaryIdentifierTypeUuid = primaryIdentifierTypeResponse?.data?.results?.[0]?.metadataUuid;
|
|
66
66
|
|
|
67
|
-
let identifierTypes =
|
|
67
|
+
let identifierTypes = primaryIdentifierTypeResponse?.ok
|
|
68
|
+
? [
|
|
69
|
+
mapPatientIdentifierType(
|
|
70
|
+
patientIdentifierTypes?.find((type) => type.uuid === primaryIdentifierTypeUuid),
|
|
71
|
+
true,
|
|
72
|
+
),
|
|
73
|
+
]
|
|
74
|
+
: [];
|
|
68
75
|
|
|
69
76
|
patientIdentifierTypes.forEach((type) => {
|
|
70
77
|
if (type.uuid !== primaryIdentifierTypeUuid) {
|
|
@@ -21,7 +21,6 @@ export const AddressField: React.FC<AddressFieldProps> = ({ fieldDefinition }) =
|
|
|
21
21
|
id={fieldDefinition.id}
|
|
22
22
|
labelText={t(`${fieldDefinition.label}`, `${fieldDefinition.label}`)}
|
|
23
23
|
{...field}
|
|
24
|
-
required={fieldDefinition.validation.required}
|
|
25
24
|
/>
|
|
26
25
|
);
|
|
27
26
|
}}
|
package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx
CHANGED
|
@@ -31,17 +31,19 @@ export function CodedPersonAttributeField({
|
|
|
31
31
|
<Field name={fieldName}>
|
|
32
32
|
{({ field, form: { touched, errors }, meta }) => {
|
|
33
33
|
return (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
<>
|
|
35
|
+
<Select
|
|
36
|
+
id={id}
|
|
37
|
+
name={`person-attribute-${personAttributeType.uuid}`}
|
|
38
|
+
labelText={label ?? personAttributeType?.display}
|
|
39
|
+
invalid={errors[fieldName] && touched[fieldName]}
|
|
40
|
+
{...field}>
|
|
41
|
+
<SelectItem value={''} text={t('selectAnOption', 'Select an option')} />
|
|
42
|
+
{conceptAnswers.map((answer) => (
|
|
43
|
+
<SelectItem key={answer.uuid} value={answer.uuid} text={answer.display} />
|
|
44
|
+
))}
|
|
45
|
+
</Select>
|
|
46
|
+
</>
|
|
45
47
|
);
|
|
46
48
|
}}
|
|
47
49
|
</Field>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FetchResponse, queueSynchronizationItem, Session
|
|
1
|
+
import { FetchResponse, openmrsFetch, queueSynchronizationItem, Session } from '@openmrs/esm-framework';
|
|
2
2
|
import { patientRegistration } from '../constants';
|
|
3
3
|
import {
|
|
4
4
|
FormValues,
|
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
updatePatientIdentifier,
|
|
25
25
|
saveEncounter,
|
|
26
26
|
} from './patient-registration.resource';
|
|
27
|
-
import isEqual from 'lodash-es/isEqual';
|
|
28
27
|
import { RegistrationConfig } from '../config-schema';
|
|
29
28
|
|
|
30
29
|
export type SavePatientForm = (
|
|
@@ -54,7 +53,7 @@ export class FormManager {
|
|
|
54
53
|
) => {
|
|
55
54
|
const syncItem: PatientRegistration = {
|
|
56
55
|
fhirPatient: FormManager.mapPatientToFhirPatient(
|
|
57
|
-
FormManager.getPatientToCreate(values, patientUuidMap, initialAddressFieldValues, []),
|
|
56
|
+
FormManager.getPatientToCreate(isNewPatient, values, patientUuidMap, initialAddressFieldValues, []),
|
|
58
57
|
),
|
|
59
58
|
_patientRegistrationData: {
|
|
60
59
|
isNewPatient,
|
|
@@ -102,6 +101,7 @@ export class FormManager {
|
|
|
102
101
|
);
|
|
103
102
|
|
|
104
103
|
const createdPatient = FormManager.getPatientToCreate(
|
|
104
|
+
isNewPatient,
|
|
105
105
|
values,
|
|
106
106
|
patientUuidMap,
|
|
107
107
|
initialAddressFieldValues,
|
|
@@ -180,7 +180,7 @@ export class FormManager {
|
|
|
180
180
|
);
|
|
181
181
|
} else {
|
|
182
182
|
const encounterToSave: Encounter = {
|
|
183
|
-
encounterDatetime:
|
|
183
|
+
encounterDatetime: new Date(),
|
|
184
184
|
patient: savePatientResponse.data.uuid,
|
|
185
185
|
encounterType: config.registrationObs.encounterTypeUuid,
|
|
186
186
|
location: currentLocation,
|
|
@@ -282,6 +282,7 @@ export class FormManager {
|
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
static getPatientToCreate(
|
|
285
|
+
isNewPatient: boolean,
|
|
285
286
|
values: FormValues,
|
|
286
287
|
patientUuidMap: PatientUuidMapType,
|
|
287
288
|
initialAddressFieldValues: Record<string, any>,
|
|
@@ -304,7 +305,7 @@ export class FormManager {
|
|
|
304
305
|
gender: values.gender.charAt(0),
|
|
305
306
|
birthdate,
|
|
306
307
|
birthdateEstimated: values.birthdateEstimated,
|
|
307
|
-
attributes: FormManager.getPatientAttributes(values),
|
|
308
|
+
attributes: FormManager.getPatientAttributes(isNewPatient, values, patientUuidMap),
|
|
308
309
|
addresses: [values.address],
|
|
309
310
|
...FormManager.getPatientDeathInfo(values),
|
|
310
311
|
},
|
|
@@ -336,16 +337,32 @@ export class FormManager {
|
|
|
336
337
|
return names;
|
|
337
338
|
}
|
|
338
339
|
|
|
339
|
-
static getPatientAttributes(values: FormValues) {
|
|
340
|
+
static getPatientAttributes(isNewPatient: boolean, values: FormValues, patientUuidMap: PatientUuidMapType) {
|
|
340
341
|
const attributes: Array<AttributeValue> = [];
|
|
341
342
|
if (values.attributes) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
343
|
+
Object.entries(values.attributes)
|
|
344
|
+
.filter(([, value]) => !!value)
|
|
345
|
+
.forEach(([key, value]) => {
|
|
346
|
+
attributes.push({
|
|
347
|
+
attributeType: key,
|
|
348
|
+
value,
|
|
349
|
+
});
|
|
346
350
|
});
|
|
351
|
+
|
|
352
|
+
if (!isNewPatient && values.patientUuid) {
|
|
353
|
+
Object.entries(values.attributes)
|
|
354
|
+
.filter(([, value]) => !value)
|
|
355
|
+
.forEach(async ([key]) => {
|
|
356
|
+
const attributeUuid = patientUuidMap[`attribute.${key}`];
|
|
357
|
+
await openmrsFetch(`/ws/rest/v1/person/${values.patientUuid}/attribute/${attributeUuid}`, {
|
|
358
|
+
method: 'DELETE',
|
|
359
|
+
}).catch((err) => {
|
|
360
|
+
console.error(err);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
347
363
|
}
|
|
348
364
|
}
|
|
365
|
+
|
|
349
366
|
if (values.unidentifiedPatient) {
|
|
350
367
|
attributes.push({
|
|
351
368
|
// The UUID of the 'Unknown Patient' attribute-type will always be static across all implementations of OpenMRS
|
|
@@ -9,11 +9,11 @@ describe.skip('number input', () => {
|
|
|
9
9
|
render(
|
|
10
10
|
<Formik initialValues={{ number: 0 }} onSubmit={null}>
|
|
11
11
|
<Form>
|
|
12
|
-
<Input id="number" labelText="Number" name="number" />
|
|
12
|
+
<Input id="number" type="number" labelText="Number" name="number" />
|
|
13
13
|
</Form>
|
|
14
14
|
</Formik>,
|
|
15
15
|
);
|
|
16
|
-
return screen.getByLabelText('
|
|
16
|
+
return screen.getByLabelText('Number (optional)') as HTMLInputElement;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
it('exists', async () => {
|
|
@@ -34,16 +34,27 @@ describe.skip('number input', () => {
|
|
|
34
34
|
});
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
describe
|
|
37
|
+
describe('text input', () => {
|
|
38
38
|
const setupInput = async () => {
|
|
39
39
|
render(
|
|
40
|
-
<Formik initialValues={{ text: '' }} onSubmit={
|
|
40
|
+
<Formik initialValues={{ text: '' }} onSubmit={() => {}}>
|
|
41
41
|
<Form>
|
|
42
|
-
<Input
|
|
42
|
+
<Input
|
|
43
|
+
id="text"
|
|
44
|
+
labelText="Text"
|
|
45
|
+
name="text"
|
|
46
|
+
placeholder="Enter text"
|
|
47
|
+
required
|
|
48
|
+
checkWarning={(value) => {
|
|
49
|
+
if (value.length > 5) {
|
|
50
|
+
return 'name should be of 5 char';
|
|
51
|
+
}
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
43
54
|
</Form>
|
|
44
55
|
</Formik>,
|
|
45
56
|
);
|
|
46
|
-
return screen.getByLabelText('
|
|
57
|
+
return screen.getByLabelText('Text') as HTMLInputElement;
|
|
47
58
|
};
|
|
48
59
|
|
|
49
60
|
it('exists', async () => {
|
|
@@ -51,16 +62,42 @@ describe.skip('text input', () => {
|
|
|
51
62
|
expect(input.type).toEqual('text');
|
|
52
63
|
});
|
|
53
64
|
|
|
54
|
-
it('can input data', async () => {
|
|
65
|
+
it('can input valid data without warning', async () => {
|
|
55
66
|
const user = userEvent.setup();
|
|
56
67
|
|
|
57
68
|
const input = await setupInput();
|
|
58
|
-
const
|
|
69
|
+
const userInput = 'text';
|
|
59
70
|
|
|
60
|
-
await user.type(input,
|
|
71
|
+
await user.type(input, userInput);
|
|
61
72
|
await user.tab();
|
|
62
73
|
|
|
63
|
-
expect(input.value).toEqual(
|
|
74
|
+
expect(input.value).toEqual(userInput);
|
|
75
|
+
expect(screen.queryByText('name should be of 5 char')).not.toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should show a warning when the invalid input is entered', async () => {
|
|
79
|
+
const user = userEvent.setup();
|
|
80
|
+
|
|
81
|
+
const input = await setupInput();
|
|
82
|
+
const userInput = 'Hello World';
|
|
83
|
+
|
|
84
|
+
await userEvent.clear(input);
|
|
85
|
+
|
|
86
|
+
await user.type(input, userInput);
|
|
87
|
+
await user.tab();
|
|
88
|
+
|
|
89
|
+
expect(screen.getByText('name should be of 5 char')).toBeInTheDocument();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should show the correct label text if the field is not required', () => {
|
|
93
|
+
render(
|
|
94
|
+
<Formik initialValues={{ text: '' }} onSubmit={() => {}}>
|
|
95
|
+
<Form>
|
|
96
|
+
<Input id="text" labelText="Text" name="text" placeholder="Enter text" />
|
|
97
|
+
</Form>
|
|
98
|
+
</Formik>,
|
|
99
|
+
);
|
|
100
|
+
expect(screen.getByLabelText('Text (optional)')).toBeInTheDocument();
|
|
64
101
|
});
|
|
65
102
|
});
|
|
66
103
|
|
|
@@ -3,16 +3,16 @@ import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
|
|
3
3
|
import { Formik, Form } from 'formik';
|
|
4
4
|
import { SelectInput } from './select-input.component';
|
|
5
5
|
|
|
6
|
-
describe
|
|
6
|
+
describe('the select input', () => {
|
|
7
7
|
const setupSelect = async () => {
|
|
8
8
|
render(
|
|
9
9
|
<Formik initialValues={{ select: '' }} onSubmit={null}>
|
|
10
10
|
<Form>
|
|
11
|
-
<SelectInput label="Select" name="select" options={['A Option', 'B Option']} />
|
|
11
|
+
<SelectInput label="Select" name="select" options={['A Option', 'B Option']} required />
|
|
12
12
|
</Form>
|
|
13
13
|
</Formik>,
|
|
14
14
|
);
|
|
15
|
-
return screen.getByLabelText('
|
|
15
|
+
return screen.getByLabelText('Select') as HTMLInputElement;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
it('exists', async () => {
|
|
@@ -29,4 +29,17 @@ describe.skip('select input', () => {
|
|
|
29
29
|
|
|
30
30
|
await waitFor(() => expect(input.value).toEqual(expected));
|
|
31
31
|
});
|
|
32
|
+
|
|
33
|
+
it('should show optional label if the input is not required', () => {
|
|
34
|
+
render(
|
|
35
|
+
<Formik initialValues={{ select: '' }} onSubmit={null}>
|
|
36
|
+
<Form>
|
|
37
|
+
<SelectInput label="Select" name="select" options={['A Option', 'B Option']} />
|
|
38
|
+
</Form>
|
|
39
|
+
</Formik>,
|
|
40
|
+
);
|
|
41
|
+
const selectInput = screen.getByRole('combobox', { name: 'Select (optional)' }) as HTMLSelectElement;
|
|
42
|
+
expect(selectInput.labels).toHaveLength(1);
|
|
43
|
+
expect(selectInput.labels[0]).toHaveTextContent('Select (optional)');
|
|
44
|
+
});
|
|
32
45
|
});
|
|
@@ -55,7 +55,7 @@ describe('autosuggest', () => {
|
|
|
55
55
|
expect(screen.queryByRole('list')).toBeNull();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
-
it('
|
|
58
|
+
it('should show the search results in a list', async () => {
|
|
59
59
|
setup();
|
|
60
60
|
const searchbox = screen.getByRole('searchbox');
|
|
61
61
|
fireEvent.change(searchbox, { target: { value: 'john' } });
|
|
@@ -64,7 +64,7 @@ describe('autosuggest', () => {
|
|
|
64
64
|
expect(list.children).toHaveLength(2);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
it('creates li items whose inner text is gotten through getDisplayValue', async () => {
|
|
67
|
+
it('should creates the li items whose inner text is gotten through getDisplayValue', async () => {
|
|
68
68
|
setup();
|
|
69
69
|
const searchbox = screen.getByRole('searchbox');
|
|
70
70
|
fireEvent.change(searchbox, { target: { value: 'john' } });
|
|
@@ -73,7 +73,7 @@ describe('autosuggest', () => {
|
|
|
73
73
|
expect(list[1].textContent).toBe('John Smith');
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
it('should trigger the onSuggestionSelected with correct values when li is clicked', async () => {
|
|
77
77
|
setup();
|
|
78
78
|
const searchbox = screen.getByRole('searchbox');
|
|
79
79
|
fireEvent.change(searchbox, { target: { value: 'john' } });
|
|
@@ -82,28 +82,39 @@ describe('autosuggest', () => {
|
|
|
82
82
|
expect(handleSuggestionSelected).toHaveBeenNthCalledWith(1, 'person', 'randomuuid1');
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it
|
|
85
|
+
it('should clear the suggestions when a suggestion is selected', async () => {
|
|
86
86
|
setup();
|
|
87
|
-
let
|
|
87
|
+
let list = screen.queryByRole('list');
|
|
88
|
+
expect(list).toBeNull();
|
|
89
|
+
const searchbox = screen.getByRole('searchbox');
|
|
88
90
|
fireEvent.change(searchbox, { target: { value: 'john' } });
|
|
89
|
-
|
|
91
|
+
list = await waitFor(() => screen.getByRole('list'));
|
|
92
|
+
expect(list).toBeInTheDocument();
|
|
93
|
+
const listitems = screen.getAllByRole('listitem');
|
|
90
94
|
fireEvent.click(listitems[0]);
|
|
91
|
-
|
|
92
|
-
expect(
|
|
95
|
+
list = screen.queryByRole('list');
|
|
96
|
+
expect(list).toBeNull();
|
|
93
97
|
});
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
it('should change suggestions when a search input is changed', async () => {
|
|
96
100
|
setup();
|
|
97
|
-
// screen.getByRole('x');
|
|
98
101
|
let list = screen.queryByRole('list');
|
|
99
102
|
expect(list).toBeNull();
|
|
100
103
|
const searchbox = screen.getByRole('searchbox');
|
|
101
104
|
fireEvent.change(searchbox, { target: { value: 'john' } });
|
|
102
|
-
|
|
103
|
-
expect(
|
|
104
|
-
|
|
105
|
-
fireEvent.click(listitems[0]);
|
|
105
|
+
const suggestion = await screen.findByText('John Doe');
|
|
106
|
+
expect(suggestion).toBeInTheDocument();
|
|
107
|
+
fireEvent.change(searchbox, { target: { value: '' } });
|
|
106
108
|
list = screen.queryByRole('list');
|
|
107
109
|
expect(list).toBeNull();
|
|
108
110
|
});
|
|
111
|
+
|
|
112
|
+
it('should hide suggestions when clicked outside of component', async () => {
|
|
113
|
+
setup();
|
|
114
|
+
const input = screen.getByRole('searchbox');
|
|
115
|
+
fireEvent.change(input, { target: { value: 'john' } });
|
|
116
|
+
await screen.findByText('John Doe');
|
|
117
|
+
fireEvent.mouseDown(document.body);
|
|
118
|
+
expect(screen.queryByText('John Doe')).not.toBeInTheDocument();
|
|
119
|
+
});
|
|
109
120
|
});
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { FetchResponse, getSynchronizationItems, openmrsFetch, useConfig, usePatient } from '@openmrs/esm-framework';
|
|
2
|
-
import last from 'lodash-es/last';
|
|
3
2
|
import camelCase from 'lodash-es/camelCase';
|
|
4
3
|
import { Dispatch, useEffect, useMemo, useState } from 'react';
|
|
5
4
|
import useSWR from 'swr';
|
|
6
5
|
import { v4 } from 'uuid';
|
|
7
6
|
import { RegistrationConfig } from '../config-schema';
|
|
8
7
|
import { patientRegistration } from '../constants';
|
|
9
|
-
import { useConceptAnswers, useGlobalProperties } from '../patient-verification/patient-verification-hook';
|
|
10
8
|
import {
|
|
11
9
|
FormValues,
|
|
12
10
|
PatientRegistration,
|
|
13
11
|
PatientUuidMapType,
|
|
14
12
|
PersonAttributeResponse,
|
|
15
13
|
PatientIdentifierResponse,
|
|
16
|
-
ObsResponse,
|
|
17
|
-
ConceptAnswers,
|
|
18
14
|
Encounter,
|
|
19
15
|
} from './patient-registration-types';
|
|
20
16
|
import {
|
|
@@ -27,13 +23,12 @@ import {
|
|
|
27
23
|
import { useInitialPatientRelationships } from './section/patient-relationships/relationships.resource';
|
|
28
24
|
|
|
29
25
|
export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
|
|
30
|
-
const { martialStatus, education, occupation, educationLoad, loadingStatus } = useConcepts();
|
|
31
26
|
const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
|
|
32
27
|
const { data: attributes, isLoading: isLoadingAttributes } = useInitialPersonAttributes(patientUuid);
|
|
33
28
|
const { data: identifiers, isLoading: isLoadingIdentifiers } = useInitialPatientIdentifiers(patientUuid);
|
|
34
29
|
const { data: relationships, isLoading: isLoadingRelationships } = useInitialPatientRelationships(patientUuid);
|
|
35
|
-
const { data:
|
|
36
|
-
|
|
30
|
+
const { data: encounters } = useInitialEncounters(patientUuid, patientToEdit);
|
|
31
|
+
|
|
37
32
|
const [initialFormValues, setInitialFormValues] = useState<FormValues>({
|
|
38
33
|
patientUuid: v4(),
|
|
39
34
|
givenName: '',
|
|
@@ -82,14 +77,6 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
|
|
|
82
77
|
})();
|
|
83
78
|
}, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
|
|
84
79
|
|
|
85
|
-
// Setting authentication token
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
if (!isLoadingToken && token) {
|
|
89
|
-
setInitialFormValues((initialFormValues) => ({ ...initialFormValues, token: String(token.access_token) }));
|
|
90
|
-
}
|
|
91
|
-
}, [isLoadingToken, token]);
|
|
92
|
-
|
|
93
80
|
// Set initial patient relationships
|
|
94
81
|
useEffect(() => {
|
|
95
82
|
if (!isLoadingRelationships && relationships) {
|
|
@@ -127,24 +114,15 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
|
|
|
127
114
|
}
|
|
128
115
|
}, [attributes, setInitialFormValues, isLoadingAttributes]);
|
|
129
116
|
|
|
130
|
-
// Set
|
|
131
|
-
|
|
117
|
+
// Set Initial registration encounters
|
|
132
118
|
useEffect(() => {
|
|
133
|
-
if (
|
|
134
|
-
setInitialFormValues((initialFormValues) => ({ ...initialFormValues, obs: obs, observation: observations }));
|
|
135
|
-
}
|
|
136
|
-
}, [isLoadingObs]);
|
|
137
|
-
|
|
138
|
-
// Set Initial encounter
|
|
139
|
-
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
if (!educationLoad || !loadingStatus) {
|
|
119
|
+
if (patientToEdit && encounters) {
|
|
142
120
|
setInitialFormValues((initialFormValues) => ({
|
|
143
121
|
...initialFormValues,
|
|
144
|
-
|
|
122
|
+
obs: encounters as Record<string, string>,
|
|
145
123
|
}));
|
|
146
124
|
}
|
|
147
|
-
}, [
|
|
125
|
+
}, [encounters, patientToEdit]);
|
|
148
126
|
|
|
149
127
|
return [initialFormValues, setInitialFormValues];
|
|
150
128
|
}
|
|
@@ -175,6 +153,7 @@ export function usePatientUuidMap(
|
|
|
175
153
|
fallback: PatientUuidMapType = {},
|
|
176
154
|
): [PatientUuidMapType, Dispatch<PatientUuidMapType>] {
|
|
177
155
|
const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
|
|
156
|
+
const { data: attributes } = useInitialPersonAttributes(patientUuid);
|
|
178
157
|
const [patientUuidMap, setPatientUuidMap] = useState(fallback);
|
|
179
158
|
|
|
180
159
|
useEffect(() => {
|
|
@@ -187,6 +166,15 @@ export function usePatientUuidMap(
|
|
|
187
166
|
}
|
|
188
167
|
}, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
|
|
189
168
|
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (attributes) {
|
|
171
|
+
setPatientUuidMap((prevPatientUuidMap) => ({
|
|
172
|
+
...prevPatientUuidMap,
|
|
173
|
+
...getPatientAttributeUuidMapForPatient(attributes),
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
}, [attributes]);
|
|
177
|
+
|
|
190
178
|
return [patientUuidMap, setPatientUuidMap];
|
|
191
179
|
}
|
|
192
180
|
|
|
@@ -269,52 +257,10 @@ function useInitialPersonAttributes(personUuid: string) {
|
|
|
269
257
|
return result;
|
|
270
258
|
}
|
|
271
259
|
|
|
272
|
-
|
|
273
|
-
const {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const url = `/ws/rest/v1/encounter?patient=${patientUuid}&encounterType=${encounterTypeUuid}&v=custom:(obs:(uuid,display,concept:(uuid,display),value:(uuid,display)))`;
|
|
277
|
-
const { data, isLoading, error } = useSWR<{ data: ObsResponse }>(patientUuid ? url : null, openmrsFetch);
|
|
278
|
-
let obsObject = {};
|
|
279
|
-
const patientObs = last(data?.data?.results)?.obs?.forEach((ob) => {
|
|
280
|
-
Object.assign(obsObject, { [ob.concept.uuid]: ob.value.uuid });
|
|
260
|
+
function getPatientAttributeUuidMapForPatient(attributes: Array<PersonAttributeResponse>) {
|
|
261
|
+
const attributeUuidMap = {};
|
|
262
|
+
attributes.forEach((attribute) => {
|
|
263
|
+
attributeUuidMap[`attribute.${attribute?.attributeType?.uuid}`] = attribute?.uuid;
|
|
281
264
|
});
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function useConcepts() {
|
|
286
|
-
const { data: martialStatus, isLoading: loadingStatus } = useConceptAnswers('1054AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
|
|
287
|
-
const { data: education, isLoading: educationLoad } = useConceptAnswers('1712AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
|
|
288
|
-
const occupation: Array<ConceptAnswers> = [
|
|
289
|
-
{
|
|
290
|
-
uuid: '1538AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
291
|
-
display: 'Farmer',
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
uuid: '1540AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
295
|
-
display: 'Employee',
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
uuid: '1539AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
299
|
-
display: 'Trader',
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
uuid: '159465AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
303
|
-
display: 'Student',
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
uuid: '159466AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
307
|
-
display: 'Driver',
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
uuid: '1107AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
311
|
-
display: 'None',
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
uuid: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
315
|
-
display: 'Other',
|
|
316
|
-
},
|
|
317
|
-
];
|
|
318
|
-
|
|
319
|
-
return { martialStatus, education, occupation, loadingStatus, educationLoad };
|
|
265
|
+
return attributeUuidMap;
|
|
320
266
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Session } from '@openmrs/esm-framework';
|
|
2
2
|
import { RegistrationConfig } from '../config-schema';
|
|
3
3
|
import { SavePatientTransactionManager } from './form-manager';
|
|
4
4
|
|
|
@@ -118,7 +118,7 @@ export type Patient = {
|
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
export interface Encounter {
|
|
121
|
-
encounterDatetime: Date
|
|
121
|
+
encounterDatetime: Date;
|
|
122
122
|
patient: string;
|
|
123
123
|
encounterType: string;
|
|
124
124
|
location: string;
|
|
@@ -185,9 +185,6 @@ export interface FormValues {
|
|
|
185
185
|
address: {
|
|
186
186
|
[addressField: string]: string;
|
|
187
187
|
};
|
|
188
|
-
observation?: ObsResponse;
|
|
189
|
-
concepts?: Array<ConceptAnswers>;
|
|
190
|
-
token?: string;
|
|
191
188
|
}
|
|
192
189
|
|
|
193
190
|
export interface PatientUuidMapType {
|
|
@@ -266,6 +263,3 @@ export interface ConceptAnswers {
|
|
|
266
263
|
display: string;
|
|
267
264
|
uuid: string;
|
|
268
265
|
}
|
|
269
|
-
export interface ObsResponse {
|
|
270
|
-
results: Array<{ obs: Array<{ uuid: string; display: string; value: OpenmrsResource; concept: OpenmrsResource }> }>;
|
|
271
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect, useContext, useMemo, useRef } from 'react';
|
|
2
2
|
import { Button, Link } from '@carbon/react';
|
|
3
|
-
import { XAxis
|
|
3
|
+
import { XAxis } from '@carbon/react/icons';
|
|
4
4
|
import { Router, useLocation, useParams } from 'react-router-dom';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
import { Formik, Form, FormikHelpers } from 'formik';
|
|
@@ -17,19 +17,12 @@ import {
|
|
|
17
17
|
parseAddressTemplateXml,
|
|
18
18
|
scrollIntoView,
|
|
19
19
|
} from './patient-registration-utils';
|
|
20
|
-
import {
|
|
21
|
-
useInitialAddressFieldValues,
|
|
22
|
-
useInitialFormValues,
|
|
23
|
-
usePatientObs,
|
|
24
|
-
usePatientUuidMap,
|
|
25
|
-
} from './patient-registration-hooks';
|
|
20
|
+
import { useInitialAddressFieldValues, useInitialFormValues, usePatientUuidMap } from './patient-registration-hooks';
|
|
26
21
|
import { ResourcesContext } from '../offline.resources';
|
|
27
22
|
import { builtInSections, RegistrationConfig, SectionDefinition } from '../config-schema';
|
|
28
23
|
import { SectionWrapper } from './section/section-wrapper.component';
|
|
29
24
|
import BeforeSavePrompt from './before-save-prompt';
|
|
30
25
|
import styles from './patient-registration.scss';
|
|
31
|
-
import PatientVerification from '../patient-verification/patient-verification.component';
|
|
32
|
-
import { handleSavePatientToClientRegistry } from '../patient-verification/patient-verification-hook';
|
|
33
26
|
|
|
34
27
|
let exportedInitialFormValuesForTesting = {} as FormValues;
|
|
35
28
|
|
|
@@ -57,9 +50,6 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
57
50
|
const { data: photo } = usePatientPhoto(patientToEdit?.id);
|
|
58
51
|
const savePatientTransactionManager = useRef(new SavePatientTransactionManager());
|
|
59
52
|
const fieldDefinition = config?.fieldDefinitions?.filter((def) => def.type === 'address');
|
|
60
|
-
const [enableClientRegistry, setEnableClientRegistry] = useState(
|
|
61
|
-
inEditMode ? initialFormValues.identifiers['nationalUniquePatientIdentifier']?.identifierValue : false,
|
|
62
|
-
);
|
|
63
53
|
|
|
64
54
|
useEffect(() => {
|
|
65
55
|
exportedInitialFormValuesForTesting = initialFormValues;
|
|
@@ -185,18 +175,6 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
185
175
|
</Link>
|
|
186
176
|
</div>
|
|
187
177
|
))}
|
|
188
|
-
<Button
|
|
189
|
-
renderIcon={ShareKnowledge}
|
|
190
|
-
disabled={!currentSession || !identifierTypes}
|
|
191
|
-
onClick={() => {
|
|
192
|
-
setEnableClientRegistry(true);
|
|
193
|
-
props.isValid
|
|
194
|
-
? handleSavePatientToClientRegistry(props.values, props.setValues, inEditMode)
|
|
195
|
-
: props.validateForm().then((errors) => displayErrors(errors));
|
|
196
|
-
}}
|
|
197
|
-
className={styles.submitButton}>
|
|
198
|
-
{t('postToRegistry', 'Post to registry')}
|
|
199
|
-
</Button>
|
|
200
178
|
<Button
|
|
201
179
|
className={styles.submitButton}
|
|
202
180
|
type="submit"
|
|
@@ -204,7 +182,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
204
182
|
// Current session and identifiers are required for patient registration.
|
|
205
183
|
// If currentSession or identifierTypes are not available, then the
|
|
206
184
|
// user should be blocked to register the patient.
|
|
207
|
-
disabled={!
|
|
185
|
+
disabled={!currentSession || !identifierTypes}>
|
|
208
186
|
{inEditMode ? t('updatePatient', 'Update Patient') : t('registerPatient', 'Register Patient')}
|
|
209
187
|
</Button>
|
|
210
188
|
<Button className={styles.cancelButton} kind="tertiary" onClick={cancelRegistration}>
|
|
@@ -226,7 +204,6 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
226
204
|
isOffline,
|
|
227
205
|
initialFormValues: props.initialValues,
|
|
228
206
|
}}>
|
|
229
|
-
<PatientVerification props={props} />
|
|
230
207
|
{sections.map((section, index) => (
|
|
231
208
|
<SectionWrapper
|
|
232
209
|
key={`registration-section-${section.id}`}
|