@kenyaemr/esm-patient-registration-app 8.1.1-pre.114 → 8.1.1-pre.116
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/.turbo/turbo-build.log +20 -20
- package/dist/108.js +1 -1
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/250.js +1 -0
- package/dist/250.js.map +1 -0
- package/dist/271.js +1 -1
- package/dist/319.js +1 -1
- package/dist/460.js +1 -1
- package/dist/471.js +1 -0
- package/dist/471.js.map +1 -0
- package/dist/574.js +1 -1
- package/dist/644.js +1 -1
- package/dist/662.js +1 -0
- package/dist/662.js.map +1 -0
- package/dist/757.js +1 -1
- package/dist/76.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/895.js.map +1 -0
- package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +115 -115
- 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/package.json +2 -2
- package/src/config-schema.ts +28 -2
- package/src/index.ts +1 -4
- package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +98 -0
- package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +84 -0
- package/src/patient-registration/field/dob/dob.component.tsx +21 -7
- package/src/patient-registration/field/field.component.tsx +11 -5
- package/src/patient-registration/field/field.resource.ts +11 -4
- package/src/patient-registration/field/field.scss +23 -1
- package/src/patient-registration/field/gender/gender-field.component.tsx +2 -1
- package/src/patient-registration/field/gender/gender-field.test.tsx +1 -0
- package/src/patient-registration/field/name/name-field.component.tsx +5 -1
- package/src/patient-registration/form-manager.test.ts +3 -0
- package/src/patient-registration/form-manager.ts +30 -15
- package/src/patient-registration/input/basic-input/input/input.component.tsx +5 -1
- 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 +122 -71
- package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +3 -0
- package/src/patient-registration/patient-registration-context.ts +4 -3
- package/src/patient-registration/patient-registration-hooks.ts +54 -5
- package/src/patient-registration/patient-registration-utils.ts +3 -7
- package/src/patient-registration/patient-registration.component.tsx +20 -13
- package/src/patient-registration/patient-registration.resource.ts +8 -0
- package/src/patient-registration/patient-registration.test.tsx +9 -3
- package/src/patient-registration/patient-registration.types.ts +4 -1
- package/src/patient-registration/section/death-info/death-info-section.component.tsx +22 -17
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +4 -14
- package/src/patient-registration/section/section.component.tsx +1 -1
- package/src/patient-registration/section/section.scss +5 -0
- package/src/patient-registration/validation/{patient-registration-validation.test.tsx → patient-registration-validation.test.ts} +26 -4
- package/src/patient-registration/validation/patient-registration-validation.ts +126 -0
- package/src/routes.json +10 -18
- package/src/widgets/cancel-patient-edit.modal.tsx +33 -0
- package/src/widgets/cancel-patient-edit.test.tsx +2 -3
- package/src/widgets/delete-identifier-confirmation.modal.tsx +22 -15
- package/src/widgets/delete-identifier-confirmation.test.tsx +2 -1
- package/translations/am.json +36 -25
- package/translations/ar.json +37 -26
- package/translations/en.json +37 -20
- package/translations/es.json +38 -26
- package/translations/fr.json +47 -35
- package/translations/he.json +37 -30
- package/translations/km.json +37 -30
- package/translations/zh.json +37 -20
- package/translations/zh_CN.json +37 -20
- package/dist/623.js +0 -1
- package/dist/623.js.map +0 -1
- package/dist/709.js +0 -2
- package/dist/709.js.map +0 -1
- package/dist/735.js +0 -1
- package/dist/735.js.map +0 -1
- package/dist/830.js +0 -1
- package/dist/830.js.map +0 -1
- package/src/patient-registration/validation/patient-registration-validation.tsx +0 -60
- package/src/widgets/cancel-patient-edit.component.tsx +0 -37
- package/src/widgets/delete-identifier-confirmation.scss +0 -34
- /package/dist/{709.js.LICENSE.txt → 895.js.LICENSE.txt} +0 -0
package/src/patient-registration/input/custom-input/identifier/identifier-input.component.tsx
CHANGED
|
@@ -100,10 +100,10 @@ const IdentifierInput: React.FC<IdentifierInputProps> = ({ patientIdentifier, fi
|
|
|
100
100
|
) : (
|
|
101
101
|
<div className={styles.textID}>
|
|
102
102
|
<p className={styles.label}>{identifierName}</p>
|
|
103
|
-
<p className={styles.bodyShort02}>
|
|
103
|
+
<p data-testid="identifier-label" className={styles.bodyShort02}>
|
|
104
104
|
{autoGeneration ? t('autoGeneratedPlaceholderText', 'Auto-generated') : identifierValue}
|
|
105
105
|
</p>
|
|
106
|
-
<input type="hidden" {...identifierField} disabled />
|
|
106
|
+
<input data-testid="identifier-input" type="hidden" {...identifierField} disabled />
|
|
107
107
|
{/* This is added for any error descriptions */}
|
|
108
108
|
{!!(identifierFieldMeta.touched && identifierFieldMeta.error) && (
|
|
109
109
|
<span className={styles.dangerLabel01}>{identifierFieldMeta.error && t(identifierFieldMeta.error)}</span>
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
/* eslint-disable testing-library/no-node-access */
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render, screen } from '@testing-library/react';
|
|
4
|
-
import {
|
|
5
|
-
import { type
|
|
6
|
-
import {
|
|
4
|
+
import { Form, Formik } from 'formik';
|
|
5
|
+
import { ResourcesContext, type Resources } from '../../../../offline.resources';
|
|
6
|
+
import {
|
|
7
|
+
PatientRegistrationContext,
|
|
8
|
+
type PatientRegistrationContextProps,
|
|
9
|
+
} from '../../../patient-registration-context';
|
|
10
|
+
import type { AddressTemplate, FormValues, PatientIdentifierValue } from '../../../patient-registration.types';
|
|
7
11
|
import IdentifierInput from './identifier-input.component';
|
|
12
|
+
import userEvent from '@testing-library/user-event';
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
const predefinedAddressTemplate = {
|
|
15
|
+
uuid: 'test-address-template-uuid',
|
|
16
|
+
property: 'layout.address.format',
|
|
17
|
+
description: 'Test Address Template',
|
|
18
|
+
display:
|
|
19
|
+
'Layout - Address Format = <org.openmrs.layout.address.AddressTemplate>\n <nameMappings class="properties">\n <property name="postalCode" value="Location.postalCode"/>\n <property name="address2" value="Location.address2"/>\n <property name="address1" value="Location.address1"/>\n <property name="country" value="Location.country"/>\n <property name="stateProvince" value="Location.stateProvince"/>\n <property name="cityVillage" value="Location.cityVillage"/>\n </nameMappings>\n <sizeMappings class="properties">\n <property name="postalCode" value="10"/>\n <property name="address2" value="40"/>\n <property name="address1" value="40"/>\n <property name="country" value="10"/>\n <property name="stateProvince" value="10"/>\n <property name="cityVillage" value="10"/>\n </sizeMappings>\n <lineByLineFormat>\n <string>address1</string>\n <string>address2</string>\n <string>cityVillage stateProvince country postalCode</string>\n </lineByLineFormat>\n </org.openmrs.layout.address.AddressTemplate>',
|
|
20
|
+
value:
|
|
21
|
+
'<org.openmrs.layout.address.AddressTemplate>\r\n <nameMappings class="properties">\r\n <property name="postalCode" value="Location.postalCode"/>\r\n <property name="address2" value="Location.address2"/>\r\n <property name="address1" value="Location.address1"/>\r\n <property name="country" value="Location.country"/>\r\n <property name="stateProvince" value="Location.stateProvince"/>\r\n <property name="cityVillage" value="Location.cityVillage"/>\r\n </nameMappings>\r\n <sizeMappings class="properties">\r\n <property name="postalCode" value="4"/>\r\n <property name="address1" value="40"/>\r\n <property name="address2" value="40"/>\r\n <property name="country" value="10"/>\r\n <property name="stateProvince" value="10"/>\r\n <property name="cityVillage" value="10"/>\r\n <asset name="cityVillage" value="10"/>\r\n </sizeMappings>\r\n <lineByLineFormat>\r\n <string>address1 address2</string>\r\n <string>cityVillage stateProvince postalCode</string>\r\n <string>country</string>\r\n </lineByLineFormat>\r\n <elementDefaults class="properties">\r\n <property name="country" value=""/>\r\n </elementDefaults>\r\n <elementRegex class="properties">\r\n <property name="address1" value="[a-zA-Z]+$"/>\r\n </elementRegex>\r\n <elementRegexFormats class="properties">\r\n <property name="address1" value="Countries can only be letters"/>\r\n </elementRegexFormats>\r\n </org.openmrs.layout.address.AddressTemplate>',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mockIdentifierTypes = [
|
|
25
|
+
{
|
|
13
26
|
fieldName: 'openMrsId',
|
|
14
|
-
|
|
15
|
-
uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
16
|
-
format: null,
|
|
17
|
-
isPrimary: true,
|
|
27
|
+
format: '',
|
|
18
28
|
identifierSources: [
|
|
19
|
-
{
|
|
20
|
-
uuid: '691eed12-c0f1-11e2-94be-8c13b969e334',
|
|
21
|
-
name: 'Generator 1 for OpenMRS ID',
|
|
22
|
-
autoGenerationOption: {
|
|
23
|
-
manualEntryEnabled: false,
|
|
24
|
-
automaticGenerationEnabled: true,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
29
|
{
|
|
28
30
|
uuid: '01af8526-cea4-4175-aa90-340acb411771',
|
|
29
31
|
name: 'Generator 2 for OpenMRS ID',
|
|
@@ -33,72 +35,121 @@ xdescribe('identifier input', () => {
|
|
|
33
35
|
},
|
|
34
36
|
},
|
|
35
37
|
],
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
isPrimary: true,
|
|
39
|
+
name: 'OpenMRS ID',
|
|
40
|
+
required: true,
|
|
41
|
+
uniquenessBehavior: 'UNIQUE' as const,
|
|
42
|
+
uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
const mockResourcesContextValue: Resources = {
|
|
47
|
+
addressTemplate: predefinedAddressTemplate as unknown as AddressTemplate,
|
|
48
|
+
currentSession: {
|
|
49
|
+
authenticated: true,
|
|
50
|
+
sessionId: 'JSESSION',
|
|
51
|
+
currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' },
|
|
52
|
+
},
|
|
53
|
+
relationshipTypes: [],
|
|
54
|
+
identifierTypes: [...mockIdentifierTypes],
|
|
55
|
+
};
|
|
41
56
|
|
|
57
|
+
const mockContextValues: PatientRegistrationContextProps = {
|
|
58
|
+
currentPhoto: '',
|
|
59
|
+
inEditMode: false,
|
|
60
|
+
identifierTypes: [],
|
|
61
|
+
initialFormValues: {} as FormValues,
|
|
62
|
+
isOffline: false,
|
|
63
|
+
setCapturePhotoProps: jest.fn(),
|
|
64
|
+
setFieldValue: jest.fn(),
|
|
65
|
+
setInitialFormValues: jest.fn(),
|
|
66
|
+
setFieldTouched: jest.fn(),
|
|
67
|
+
validationSchema: null,
|
|
68
|
+
values: {} as FormValues,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
describe('identifier input', () => {
|
|
72
|
+
const fieldName = 'openMrsId';
|
|
73
|
+
const openmrsID = {
|
|
74
|
+
identifierTypeUuid: '05a29f94-c0ed-11e2-94be-8c13b969e334',
|
|
75
|
+
initialValue: '',
|
|
76
|
+
identifierValue: '',
|
|
77
|
+
identifierName: 'OpenMRS ID',
|
|
78
|
+
selectedSource: {
|
|
79
|
+
uuid: '01af8526-cea4-4175-aa90-340acb411771',
|
|
80
|
+
name: 'Generator 2 for OpenMRS ID',
|
|
81
|
+
autoGenerationOption: {
|
|
82
|
+
manualEntryEnabled: true,
|
|
83
|
+
automaticGenerationEnabled: true,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
autoGeneration: false,
|
|
87
|
+
preferred: true,
|
|
88
|
+
required: true,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const setupIdentifierInput = async (patientIdentifier: PatientIdentifierValue) => {
|
|
42
92
|
render(
|
|
43
|
-
<
|
|
44
|
-
<
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
93
|
+
<ResourcesContext.Provider value={mockResourcesContextValue}>
|
|
94
|
+
<Formik initialValues={{}} onSubmit={jest.fn()}>
|
|
95
|
+
<Form>
|
|
96
|
+
<PatientRegistrationContext.Provider value={mockContextValues}>
|
|
97
|
+
<IdentifierInput patientIdentifier={patientIdentifier} fieldName={fieldName} />
|
|
98
|
+
</PatientRegistrationContext.Provider>
|
|
99
|
+
</Form>
|
|
100
|
+
</Formik>
|
|
101
|
+
</ResourcesContext.Provider>,
|
|
48
102
|
);
|
|
49
|
-
|
|
50
|
-
let
|
|
103
|
+
|
|
104
|
+
let identifierLabel: HTMLParagraphElement;
|
|
105
|
+
let identifierInput: HTMLInputElement;
|
|
106
|
+
if (patientIdentifier.autoGeneration) {
|
|
107
|
+
identifierLabel = screen.getByTestId('identifier-label');
|
|
108
|
+
identifierInput = screen.getByTestId('identifier-input');
|
|
109
|
+
} else {
|
|
110
|
+
identifierLabel = screen.getByText(patientIdentifier.identifierName);
|
|
111
|
+
identifierInput = screen.getByLabelText(patientIdentifier.identifierName);
|
|
112
|
+
}
|
|
51
113
|
return {
|
|
114
|
+
identifierLabel,
|
|
52
115
|
identifierInput,
|
|
53
|
-
identifierSourceSelectInput,
|
|
54
116
|
};
|
|
55
117
|
};
|
|
56
118
|
|
|
57
|
-
it('
|
|
58
|
-
const { identifierInput
|
|
59
|
-
|
|
60
|
-
expect(identifierInput.type).toBe('text');
|
|
61
|
-
expect(identifierSourceSelectInput.type).toBe('select-one');
|
|
119
|
+
it('shows the identifier input', async () => {
|
|
120
|
+
const { identifierInput } = await setupIdentifierInput(openmrsID);
|
|
121
|
+
expect(identifierInput).toBeInTheDocument();
|
|
62
122
|
});
|
|
63
123
|
|
|
64
|
-
|
|
65
|
-
|
|
124
|
+
describe('Auto-generated identifier', () => {
|
|
125
|
+
openmrsID.autoGeneration = true;
|
|
66
126
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
it('hides the input when the identifier is auto-generated', async () => {
|
|
128
|
+
const { identifierInput } = await setupIdentifierInput(openmrsID);
|
|
129
|
+
expect(identifierInput.type).toBe('hidden');
|
|
130
|
+
});
|
|
70
131
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
132
|
+
it("displays 'Auto-Generated' when the indentifier has auto generation", async () => {
|
|
133
|
+
const { identifierLabel, identifierInput } = await setupIdentifierInput(openmrsID);
|
|
134
|
+
expect(identifierLabel.innerHTML).toBe('Auto-generated');
|
|
135
|
+
expect(identifierInput.disabled).toBe(true);
|
|
136
|
+
});
|
|
76
137
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
138
|
+
it('displays an edit button when there is an initial value', async () => {
|
|
139
|
+
// setup
|
|
140
|
+
openmrsID.required = false;
|
|
141
|
+
openmrsID.initialValue = '1002UU9';
|
|
142
|
+
// replay
|
|
143
|
+
await setupIdentifierInput(openmrsID);
|
|
144
|
+
expect(screen.getByText('Edit')).toBeInTheDocument();
|
|
145
|
+
});
|
|
85
146
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
manualEntryEnabled: true,
|
|
94
|
-
automaticGenerationEnabled: false,
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
];
|
|
98
|
-
// replay
|
|
99
|
-
const { identifierInput, identifierSourceSelectInput } = await setupIdentifierInput(openmrsID);
|
|
100
|
-
expect(identifierInput.placeholder).toBe('Enter identifier');
|
|
101
|
-
expect(identifierInput.disabled).toBe(false);
|
|
102
|
-
expect(identifierSourceSelectInput).toBe(undefined);
|
|
147
|
+
it('displays a delete button when the identifier is not a default type', async () => {
|
|
148
|
+
// setup
|
|
149
|
+
openmrsID.required = false;
|
|
150
|
+
// replay
|
|
151
|
+
await setupIdentifierInput(openmrsID);
|
|
152
|
+
expect(screen.getByText('Delete')).toBeInTheDocument();
|
|
153
|
+
});
|
|
103
154
|
});
|
|
104
155
|
});
|
|
@@ -25,7 +25,10 @@ export const dummyFormValues: FormValues = {
|
|
|
25
25
|
telephoneNumber: '0800001066',
|
|
26
26
|
isDead: false,
|
|
27
27
|
deathDate: '',
|
|
28
|
+
deathTime: '',
|
|
29
|
+
deathTimeFormat: 'AM',
|
|
28
30
|
deathCause: '',
|
|
31
|
+
nonCodedCauseOfDeath: '',
|
|
29
32
|
relationships: [],
|
|
30
33
|
address: {
|
|
31
34
|
address1: 'Bom Jesus Street',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useConfig } from '@openmrs/esm-framework';
|
|
2
2
|
import { createContext, type SetStateAction } from 'react';
|
|
3
3
|
import { type RegistrationConfig } from '../config-schema';
|
|
4
|
-
import { type
|
|
4
|
+
import { type CapturePhotoProps, type FormValues } from './patient-registration.types';
|
|
5
5
|
|
|
6
6
|
export interface PatientRegistrationContextProps {
|
|
7
7
|
currentPhoto: string;
|
|
@@ -9,11 +9,12 @@ export interface PatientRegistrationContextProps {
|
|
|
9
9
|
inEditMode: boolean;
|
|
10
10
|
initialFormValues: FormValues;
|
|
11
11
|
isOffline: boolean;
|
|
12
|
-
setCapturePhotoProps(value: SetStateAction<CapturePhotoProps>): void;
|
|
13
|
-
setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
|
|
14
12
|
setInitialFormValues?: React.Dispatch<SetStateAction<FormValues>>;
|
|
15
13
|
validationSchema: any;
|
|
16
14
|
values: FormValues;
|
|
15
|
+
setCapturePhotoProps(value: SetStateAction<CapturePhotoProps>): void;
|
|
16
|
+
setFieldValue(field: string, value: any, shouldValidate?: boolean): void;
|
|
17
|
+
setFieldTouched(field: string, isTouched?: any, shouldValidate?: boolean): void;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export const PatientRegistrationContext = createContext<PatientRegistrationContextProps | undefined>(undefined);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type FetchResponse,
|
|
3
|
-
type OpenmrsResource,
|
|
4
3
|
getSynchronizationItems,
|
|
5
4
|
openmrsFetch,
|
|
5
|
+
type OpenmrsResource,
|
|
6
|
+
restBaseUrl,
|
|
6
7
|
useConfig,
|
|
7
8
|
usePatient,
|
|
8
|
-
restBaseUrl,
|
|
9
9
|
} from '@openmrs/esm-framework';
|
|
10
10
|
import last from 'lodash-es/last';
|
|
11
11
|
import camelCase from 'lodash-es/camelCase';
|
|
@@ -19,12 +19,12 @@ import {
|
|
|
19
19
|
useGlobalProperties,
|
|
20
20
|
} from '../client-registry/patient-verification/patient-verification-hook';
|
|
21
21
|
import {
|
|
22
|
+
type Encounter,
|
|
22
23
|
type FormValues,
|
|
24
|
+
type PatientIdentifierResponse,
|
|
23
25
|
type PatientRegistration,
|
|
24
26
|
type PatientUuidMapType,
|
|
25
27
|
type PersonAttributeResponse,
|
|
26
|
-
type PatientIdentifierResponse,
|
|
27
|
-
type Encounter,
|
|
28
28
|
type ConceptAnswers,
|
|
29
29
|
type ObsResponse,
|
|
30
30
|
} from './patient-registration.types';
|
|
@@ -39,8 +39,10 @@ import { useInitialPatientRelationships } from './section/patient-relationships/
|
|
|
39
39
|
import dayjs from 'dayjs';
|
|
40
40
|
|
|
41
41
|
export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch<FormValues>] {
|
|
42
|
+
const { freeTextFieldConceptUuid } = useConfig<RegistrationConfig>();
|
|
42
43
|
const { martialStatus, education, occupation, educationLoad } = useConcepts();
|
|
43
44
|
const { isLoading: isLoadingPatientToEdit, patient: patientToEdit } = usePatient(patientUuid);
|
|
45
|
+
const { data: deathInfo, isLoading: isLoadingDeathInfo } = useInitialPersonDeathInfo(patientUuid);
|
|
44
46
|
const { data: attributes, isLoading: isLoadingAttributes } = useInitialPersonAttributes(patientUuid);
|
|
45
47
|
const { data: identifiers, isLoading: isLoadingIdentifiers } = useInitialPatientIdentifiers(patientUuid);
|
|
46
48
|
const { data: relationships, isLoading: isLoadingRelationships } = useInitialPatientRelationships(patientUuid);
|
|
@@ -62,8 +64,11 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
|
|
|
62
64
|
birthdateEstimated: false,
|
|
63
65
|
telephoneNumber: '',
|
|
64
66
|
isDead: false,
|
|
65
|
-
deathDate:
|
|
67
|
+
deathDate: undefined,
|
|
68
|
+
deathTime: undefined,
|
|
69
|
+
deathTimeFormat: 'AM',
|
|
66
70
|
deathCause: '',
|
|
71
|
+
nonCodedCauseOfDeath: '',
|
|
67
72
|
relationships: [],
|
|
68
73
|
identifiers: {},
|
|
69
74
|
address: {},
|
|
@@ -104,6 +109,24 @@ export function useInitialFormValues(patientUuid: string): [FormValues, Dispatch
|
|
|
104
109
|
})();
|
|
105
110
|
}, [isLoadingPatientToEdit, patientToEdit, patientUuid]);
|
|
106
111
|
|
|
112
|
+
// Set initial patient death info
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (!isLoadingDeathInfo && deathInfo?.dead) {
|
|
115
|
+
const deathDatetime = deathInfo.deathDate || null;
|
|
116
|
+
const deathDate = deathDatetime ? new Date(deathDatetime) : undefined;
|
|
117
|
+
const time = deathDate ? dayjs(deathDate).format('hh:mm') : undefined;
|
|
118
|
+
const timeFormat = deathDate ? (dayjs(deathDate).hour() >= 12 ? 'PM' : 'AM') : 'AM';
|
|
119
|
+
setInitialFormValues((initialFormValues) => ({
|
|
120
|
+
...initialFormValues,
|
|
121
|
+
isDead: deathInfo.dead || false,
|
|
122
|
+
deathDate: deathDate,
|
|
123
|
+
deathTime: time,
|
|
124
|
+
deathTimeFormat: timeFormat,
|
|
125
|
+
deathCause: deathInfo.causeOfDeathNonCoded ? freeTextFieldConceptUuid : deathInfo.causeOfDeath?.uuid,
|
|
126
|
+
nonCodedCauseOfDeath: deathInfo.causeOfDeathNonCoded,
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
}, [isLoadingDeathInfo, deathInfo, setInitialFormValues]);
|
|
107
130
|
// Setting authentication token
|
|
108
131
|
|
|
109
132
|
useEffect(() => {
|
|
@@ -304,6 +327,32 @@ function useInitialPersonAttributes(personUuid: string) {
|
|
|
304
327
|
return result;
|
|
305
328
|
}
|
|
306
329
|
|
|
330
|
+
interface DeathInfoResults {
|
|
331
|
+
uuid: string;
|
|
332
|
+
display: string;
|
|
333
|
+
causeOfDeath: OpenmrsResource | null;
|
|
334
|
+
dead: boolean;
|
|
335
|
+
deathDate: string;
|
|
336
|
+
causeOfDeathNonCoded: string | null;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function useInitialPersonDeathInfo(personUuid: string) {
|
|
340
|
+
const { data, error, isLoading } = useSWR<FetchResponse<DeathInfoResults>, Error>(
|
|
341
|
+
!!personUuid
|
|
342
|
+
? `${restBaseUrl}/person/${personUuid}?v=custom:(uuid,display,causeOfDeath,dead,deathDate,causeOfDeathNonCoded)`
|
|
343
|
+
: null,
|
|
344
|
+
openmrsFetch,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const result = useMemo(() => {
|
|
348
|
+
return {
|
|
349
|
+
data: data?.data,
|
|
350
|
+
isLoading,
|
|
351
|
+
};
|
|
352
|
+
}, [data, error]);
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
|
|
307
356
|
function getPatientAttributeUuidMapForPatient(attributes: Array<PersonAttributeResponse>) {
|
|
308
357
|
const attributeUuidMap = {};
|
|
309
358
|
attributes.forEach((attribute) => {
|
|
@@ -3,11 +3,11 @@ import camelCase from 'lodash-es/camelCase';
|
|
|
3
3
|
import { parseDate } from '@openmrs/esm-framework';
|
|
4
4
|
import {
|
|
5
5
|
type AddressValidationSchemaType,
|
|
6
|
+
type Encounter,
|
|
6
7
|
type FormValues,
|
|
7
8
|
type PatientIdentifier,
|
|
8
|
-
type PatientUuidMapType,
|
|
9
9
|
type PatientIdentifierValue,
|
|
10
|
-
type
|
|
10
|
+
type PatientUuidMapType,
|
|
11
11
|
} from './patient-registration.types';
|
|
12
12
|
|
|
13
13
|
export function parseAddressTemplateXml(addressTemplate: string) {
|
|
@@ -47,6 +47,7 @@ export function parseAddressTemplateXml(addressTemplate: string) {
|
|
|
47
47
|
addressValidationSchema,
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
+
|
|
50
51
|
export function parseAddressTemplateXmlOld(addressTemplate: string) {
|
|
51
52
|
const templateXmlDoc = new DOMParser().parseFromString(addressTemplate, 'text/xml');
|
|
52
53
|
const nameMappings = templateXmlDoc.querySelector('nameMappings').querySelectorAll('property');
|
|
@@ -123,11 +124,6 @@ export function getFormValuesFromFhirPatient(patient: fhir.Patient) {
|
|
|
123
124
|
result.birthdate = patient.birthDate ? parseDate(patient.birthDate) : undefined;
|
|
124
125
|
result.telephoneNumber = patient.telecom ? patient.telecom[0].value : '';
|
|
125
126
|
|
|
126
|
-
if (patient.deceasedBoolean || patient.deceasedDateTime) {
|
|
127
|
-
result.isDead = true;
|
|
128
|
-
result.deathDate = patient.deceasedDateTime ? patient.deceasedDateTime.split('T')[0] : '';
|
|
129
|
-
}
|
|
130
|
-
|
|
131
127
|
return {
|
|
132
128
|
...result,
|
|
133
129
|
...patient.identifier.map((identifier) => {
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
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 {
|
|
7
|
+
import { Form, Formik, type FormikHelpers } from 'formik';
|
|
8
8
|
import {
|
|
9
9
|
createErrorHandler,
|
|
10
|
+
interpolateUrl,
|
|
10
11
|
showSnackbar,
|
|
11
12
|
useConfig,
|
|
12
|
-
interpolateUrl,
|
|
13
13
|
usePatient,
|
|
14
14
|
usePatientPhoto,
|
|
15
15
|
useFeatureFlag,
|
|
16
16
|
} from '@openmrs/esm-framework';
|
|
17
17
|
import { getValidationSchema } from './validation/patient-registration-validation';
|
|
18
|
-
import { type
|
|
18
|
+
import { type CapturePhotoProps, type FormValues } from './patient-registration.types';
|
|
19
19
|
import { PatientRegistrationContext } from './patient-registration-context';
|
|
20
20
|
import { type SavePatientForm, SavePatientTransactionManager } from './form-manager';
|
|
21
21
|
import { DummyDataInput } from './input/dummy-data/dummy-data-input.component';
|
|
@@ -161,18 +161,24 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
161
161
|
}
|
|
162
162
|
};
|
|
163
163
|
|
|
164
|
+
const getDescription = (errors) => {
|
|
165
|
+
return (
|
|
166
|
+
<ul style={{ listStyle: 'inside' }}>
|
|
167
|
+
{Object.keys(errors).map((error, index) => {
|
|
168
|
+
return <li key={index}>{t(`${error}LabelText`, error)}</li>;
|
|
169
|
+
})}
|
|
170
|
+
</ul>
|
|
171
|
+
);
|
|
172
|
+
};
|
|
164
173
|
const enableRegistryButton = healthInformationExchangeFlag ? false : !enableClientRegistry;
|
|
165
174
|
|
|
166
175
|
const displayErrors = (errors) => {
|
|
167
176
|
if (errors && typeof errors === 'object' && !!Object.keys(errors).length) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
isLowContrast: true,
|
|
174
|
-
timeoutInMs: 5000,
|
|
175
|
-
});
|
|
177
|
+
showSnackbar({
|
|
178
|
+
isLowContrast: true,
|
|
179
|
+
kind: 'warning',
|
|
180
|
+
title: t('fieldsWithErrors', 'The following fields have errors:'),
|
|
181
|
+
subtitle: <>{getDescription(errors)}</>,
|
|
176
182
|
});
|
|
177
183
|
}
|
|
178
184
|
};
|
|
@@ -185,7 +191,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
185
191
|
onSubmit={onFormSubmit}>
|
|
186
192
|
{(props) => (
|
|
187
193
|
<Form className={styles.form}>
|
|
188
|
-
<BeforeSavePrompt when={props.
|
|
194
|
+
<BeforeSavePrompt when={Object.keys(props.touched).length > 0} redirect={target} />
|
|
189
195
|
<div className={styles.formContainer}>
|
|
190
196
|
<div>
|
|
191
197
|
<div className={styles.stickyColumn}>
|
|
@@ -250,6 +256,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
250
256
|
values: props.values,
|
|
251
257
|
inEditMode,
|
|
252
258
|
setFieldValue: props.setFieldValue,
|
|
259
|
+
setFieldTouched: props.setFieldTouched,
|
|
253
260
|
setCapturePhotoProps,
|
|
254
261
|
currentPhoto: photo?.imageSrc,
|
|
255
262
|
isOffline,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
2
2
|
import { type Patient, type Relationship, type PatientIdentifier, type Encounter } from './patient-registration.types';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
3
4
|
|
|
4
5
|
export const uuidIdentifier = '05a29f94-c0ed-11e2-94be-8c13b969e334';
|
|
5
6
|
export const uuidTelephoneNumber = '14d4f066-15f5-102d-96e4-000c29c2a5d7';
|
|
@@ -188,3 +189,10 @@ export async function deletePatientIdentifier(patientUuid: string, patientIdenti
|
|
|
188
189
|
signal: abortController.signal,
|
|
189
190
|
});
|
|
190
191
|
}
|
|
192
|
+
|
|
193
|
+
export function getDatetime(date: Date | string, time: string, timeFormat: 'AM' | 'PM') {
|
|
194
|
+
const datetime = new Date(date);
|
|
195
|
+
const [hours, minutes] = time.split(':').map(Number);
|
|
196
|
+
const fullHours = timeFormat === 'PM' ? (hours % 12) + 12 : hours % 12;
|
|
197
|
+
return dayjs(datetime).hour(fullHours).minute(minutes).second(0).millisecond(0).toDate();
|
|
198
|
+
}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { mockedAddressTemplate } from '__mocks__';
|
|
15
15
|
import { mockPatient } from 'tools';
|
|
16
16
|
import { saveEncounter, savePatient } from './patient-registration.resource';
|
|
17
|
-
import { type RegistrationConfig
|
|
17
|
+
import { esmPatientRegistrationSchema, type RegistrationConfig } from '../config-schema';
|
|
18
18
|
import type { AddressTemplate, Encounter } from './patient-registration.types';
|
|
19
19
|
import { ResourcesContext } from '../offline.resources';
|
|
20
20
|
import { FormManager } from './form-manager';
|
|
@@ -167,6 +167,9 @@ let mockOpenmrsConfig: RegistrationConfig = {
|
|
|
167
167
|
searchAddressByLevel: true,
|
|
168
168
|
},
|
|
169
169
|
},
|
|
170
|
+
causeOfDeath: {
|
|
171
|
+
conceptUuid: 'cause-of-death-concept-uuid',
|
|
172
|
+
},
|
|
170
173
|
},
|
|
171
174
|
links: {
|
|
172
175
|
submitButton: '#',
|
|
@@ -407,7 +410,7 @@ describe('Updating an existing patient record', () => {
|
|
|
407
410
|
const givenNameInput: HTMLInputElement = screen.getByLabelText(/First Name/);
|
|
408
411
|
const familyNameInput: HTMLInputElement = screen.getByLabelText(/Family Name/);
|
|
409
412
|
const middleNameInput: HTMLInputElement = screen.getByLabelText(/Middle Name/);
|
|
410
|
-
const dateOfBirthInput: HTMLInputElement = screen.getByLabelText(
|
|
413
|
+
const dateOfBirthInput: HTMLInputElement = screen.getByLabelText(/Date of Birth/i);
|
|
411
414
|
const genderInput: HTMLInputElement = screen.getByLabelText(/Male/);
|
|
412
415
|
|
|
413
416
|
// assert initial values
|
|
@@ -443,7 +446,10 @@ describe('Updating an existing patient record', () => {
|
|
|
443
446
|
birthdate: new Date('1972-04-04T00:00:00.000Z'),
|
|
444
447
|
birthdateEstimated: false,
|
|
445
448
|
deathCause: '',
|
|
446
|
-
|
|
449
|
+
nonCodedCauseOfDeath: '',
|
|
450
|
+
deathDate: undefined,
|
|
451
|
+
deathTime: undefined,
|
|
452
|
+
deathTimeFormat: 'AM',
|
|
447
453
|
familyName: 'Smith',
|
|
448
454
|
gender: expect.stringMatching(/male/i),
|
|
449
455
|
givenName: 'Eric',
|
|
@@ -167,7 +167,9 @@ export interface FormValues {
|
|
|
167
167
|
birthdate: Date | string;
|
|
168
168
|
birthdateEstimated: boolean;
|
|
169
169
|
deathCause: string;
|
|
170
|
-
deathDate: string;
|
|
170
|
+
deathDate: string | Date;
|
|
171
|
+
deathTime: string;
|
|
172
|
+
deathTimeFormat: 'AM' | 'PM';
|
|
171
173
|
familyName: string;
|
|
172
174
|
gender: string;
|
|
173
175
|
givenName: string;
|
|
@@ -177,6 +179,7 @@ export interface FormValues {
|
|
|
177
179
|
isDead: boolean;
|
|
178
180
|
middleName: string;
|
|
179
181
|
monthsEstimated: number;
|
|
182
|
+
nonCodedCauseOfDeath: string;
|
|
180
183
|
obs?: {
|
|
181
184
|
[conceptUuid: string]: string;
|
|
182
185
|
};
|
|
@@ -1,30 +1,35 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import classNames from 'classnames';
|
|
1
|
+
import React, { useContext } from 'react';
|
|
3
2
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { Checkbox, Layer } from '@carbon/react';
|
|
4
|
+
import { useField } from 'formik';
|
|
5
|
+
import { Field } from '../../field/field.component';
|
|
6
6
|
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
7
7
|
import styles from './../section.scss';
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
|
|
9
|
+
export interface DeathInfoSectionProps {
|
|
10
|
+
fields: Array<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const DeathInfoSection: React.FC<DeathInfoSectionProps> = ({ fields }) => {
|
|
11
14
|
const { t } = useTranslation();
|
|
15
|
+
const { values, setFieldValue } = useContext(PatientRegistrationContext);
|
|
16
|
+
const [deathDate, deathDateMeta] = useField('deathDate');
|
|
17
|
+
const today = new Date();
|
|
12
18
|
|
|
13
19
|
return (
|
|
14
20
|
<section className={styles.formSection} aria-label="Death Info Section">
|
|
15
|
-
<h5 className={classNames('omrs-type-title-5', styles.formSectionTitle)}>Death Info</h5>
|
|
16
21
|
<section className={styles.fieldGroup}>
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
name="deathCause"
|
|
22
|
+
<Layer>
|
|
23
|
+
<div className={styles.isDeadFieldContainer}>
|
|
24
|
+
<Checkbox
|
|
25
|
+
checked={values.isDead}
|
|
26
|
+
id="isDead"
|
|
27
|
+
labelText={t('isDeadInputLabel', 'Is dead')}
|
|
28
|
+
onChange={(event, { checked, id }) => setFieldValue(id, checked)}
|
|
25
29
|
/>
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
</div>
|
|
31
|
+
</Layer>
|
|
32
|
+
{values.isDead ? fields.map((field) => <Field key={`death-info-${field}`} name={field} />) : null}
|
|
28
33
|
</section>
|
|
29
34
|
</section>
|
|
30
35
|
);
|