@kenyaemr/esm-patient-registration-app 8.1.1-pre.114 → 8.1.1-pre.118
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/dist/routes.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true},{"component":"
|
|
1
|
+
{"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"webservices.rest":"^2.24.0"},"pages":[{"component":"root","route":"patient-registration","online":true,"offline":true},{"component":"editPatient","routeRegex":"patient\\/([a-zA-Z0-9\\-]+)\\/edit","online":true,"offline":true}],"extensions":[{"component":"addPatientLink","name":"add-patient-action","slot":"top-nav-actions-slot","online":true,"offline":true},{"component":"patientPhotoExtension","name":"patient-photo-widget","slot":"patient-photo-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-actions-slot","online":true,"offline":true},{"component":"editPatientDetailsButton","name":"edit-patient-details-button","slot":"patient-search-actions-slot","online":true,"offline":true}],"modals":[{"name":"cancel-patient-edit-modal","component":"cancelPatientEditModal"},{"name":"delete-identifier-confirmation-modal","component":"deleteIdentifierConfirmationModal"},{"component":"emptyClientRegistryModal","name":"empty-client-registry-modal"},{"component":"confirmClientRegistryModal","name":"confirm-client-registry-modal"},{"component":"hieConfirmationModal","name":"hie-confirmation-modal"}],"version":"8.1.1-pre.118"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-patient-registration-app",
|
|
3
|
-
"version": "8.1.1-pre.
|
|
3
|
+
"version": "8.1.1-pre.118",
|
|
4
4
|
"description": "Patient registration microfrontend for the OpenMRS SPA",
|
|
5
5
|
"browser": "dist/kenyaemr-esm-patient-registration-app.js",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"test:watch": "cross-env TZ=UTC jest --watch --config jest.config.js --color",
|
|
19
19
|
"coverage": "yarn test --coverage",
|
|
20
20
|
"typescript": "tsc",
|
|
21
|
-
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*modal.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' --config ../../tools/i18next-parser.config.js"
|
|
21
|
+
"extract-translations": "i18next 'src/**/*.component.tsx' 'src/**/*.extension.tsx' 'src/**/*modal.tsx' 'src/**/*.workspace.tsx' 'src/index.ts' 'src/patient-registration/validation/patient-registration-validation.ts' --config ../../tools/i18next-parser.config.js"
|
|
22
22
|
},
|
|
23
23
|
"browserslist": [
|
|
24
24
|
"extends browserslist-config-openmrs"
|
package/src/config-schema.ts
CHANGED
|
@@ -42,6 +42,10 @@ export interface RegistrationConfig {
|
|
|
42
42
|
sectionDefinitions: Array<SectionDefinition>;
|
|
43
43
|
fieldDefinitions: Array<FieldDefinition>;
|
|
44
44
|
fieldConfigurations: {
|
|
45
|
+
causeOfDeath: {
|
|
46
|
+
conceptUuid: string;
|
|
47
|
+
required?: boolean;
|
|
48
|
+
};
|
|
45
49
|
name: {
|
|
46
50
|
displayMiddleName: boolean;
|
|
47
51
|
allowUnidentifiedPatients: boolean;
|
|
@@ -83,6 +87,7 @@ export interface RegistrationConfig {
|
|
|
83
87
|
encounterProviderRoleUuid: string;
|
|
84
88
|
registrationFormUuid: string | null;
|
|
85
89
|
};
|
|
90
|
+
freeTextFieldConceptUuid: string;
|
|
86
91
|
hieClientRegistry: {
|
|
87
92
|
identifierTypes: Array<{ identifierType: string; identifierValue: string }>;
|
|
88
93
|
baseUrl: string;
|
|
@@ -97,12 +102,21 @@ export const builtInSections: Array<SectionDefinition> = [
|
|
|
97
102
|
fields: ['name', 'gender', 'dob', 'id'],
|
|
98
103
|
},
|
|
99
104
|
{ id: 'contact', name: 'Contact Details', fields: ['address', 'phone'] },
|
|
100
|
-
{ id: 'death', name: 'Death Info', fields: [] },
|
|
105
|
+
{ id: 'death', name: 'Death Info', fields: ['dateAndTimeOfDeath', 'causeOfDeath'] },
|
|
101
106
|
{ id: 'relationships', name: 'Relationships', fields: [] },
|
|
102
107
|
];
|
|
103
108
|
|
|
104
109
|
// These fields are handled specially in field.component.tsx
|
|
105
|
-
export const builtInFields = [
|
|
110
|
+
export const builtInFields = [
|
|
111
|
+
'name',
|
|
112
|
+
'gender',
|
|
113
|
+
'dob',
|
|
114
|
+
'id',
|
|
115
|
+
'address',
|
|
116
|
+
'phone',
|
|
117
|
+
'causeOfDeath',
|
|
118
|
+
'dateAndTimeOfDeath',
|
|
119
|
+
] as const;
|
|
106
120
|
|
|
107
121
|
export const esmPatientRegistrationSchema = {
|
|
108
122
|
sections: {
|
|
@@ -209,6 +223,14 @@ export const esmPatientRegistrationSchema = {
|
|
|
209
223
|
'Definitions for custom fields that can be used in sectionDefinitions. Can also be used to override built-in fields.',
|
|
210
224
|
},
|
|
211
225
|
fieldConfigurations: {
|
|
226
|
+
causeOfDeath: {
|
|
227
|
+
conceptUuid: {
|
|
228
|
+
_type: Type.ConceptUuid,
|
|
229
|
+
_description: 'The concept UUID to get cause of death answers',
|
|
230
|
+
_default: '9272a14b-7260-4353-9e5b-5787b5dead9d',
|
|
231
|
+
},
|
|
232
|
+
required: { _type: Type.Boolean, _default: false },
|
|
233
|
+
},
|
|
212
234
|
name: {
|
|
213
235
|
displayMiddleName: { _type: Type.Boolean, _default: true },
|
|
214
236
|
allowUnidentifiedPatients: {
|
|
@@ -369,6 +391,10 @@ export const esmPatientRegistrationSchema = {
|
|
|
369
391
|
'The form UUID to associate with the registration encounter. By default no form will be associated.',
|
|
370
392
|
},
|
|
371
393
|
},
|
|
394
|
+
freeTextFieldConceptUuid: {
|
|
395
|
+
_default: '5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
396
|
+
_type: Type.ConceptUuid,
|
|
397
|
+
},
|
|
372
398
|
hieClientRegistry: {
|
|
373
399
|
identifierTypes: {
|
|
374
400
|
_type: Type.Array,
|
package/src/index.ts
CHANGED
|
@@ -51,10 +51,7 @@ export const editPatient = getSyncLifecycle(rootComponent, {
|
|
|
51
51
|
|
|
52
52
|
export const addPatientLink = getSyncLifecycle(addPatientLinkComponent, options);
|
|
53
53
|
|
|
54
|
-
export const cancelPatientEditModal = getAsyncLifecycle(
|
|
55
|
-
() => import('./widgets/cancel-patient-edit.component'),
|
|
56
|
-
options,
|
|
57
|
-
);
|
|
54
|
+
export const cancelPatientEditModal = getAsyncLifecycle(() => import('./widgets/cancel-patient-edit.modal'), options);
|
|
58
55
|
|
|
59
56
|
export const patientPhotoExtension = getSyncLifecycle(PatientPhotoExtension, options);
|
|
60
57
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Field, useField } from 'formik';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { InlineNotification, Layer, Select, SelectItem, SelectSkeleton, TextInput } from '@carbon/react';
|
|
6
|
+
import { useConfig } from '@openmrs/esm-framework';
|
|
7
|
+
import { type RegistrationConfig } from '../../../config-schema';
|
|
8
|
+
import { useConceptAnswers } from '../field.resource';
|
|
9
|
+
import styles from '../field.scss';
|
|
10
|
+
|
|
11
|
+
export const CauseOfDeathField: React.FC = () => {
|
|
12
|
+
const { t } = useTranslation();
|
|
13
|
+
const { fieldConfigurations, freeTextFieldConceptUuid } = useConfig<RegistrationConfig>();
|
|
14
|
+
const [deathCause, deathCauseMeta] = useField('deathCause');
|
|
15
|
+
|
|
16
|
+
const conceptUuid = fieldConfigurations?.causeOfDeath?.conceptUuid;
|
|
17
|
+
const required = fieldConfigurations?.causeOfDeath?.required;
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
data: conceptAnswers,
|
|
21
|
+
isLoading: isLoadingConceptAnswers,
|
|
22
|
+
error: errorLoadingConceptAnswers,
|
|
23
|
+
} = useConceptAnswers(conceptUuid);
|
|
24
|
+
|
|
25
|
+
const answers = useMemo(() => {
|
|
26
|
+
if (!isLoadingConceptAnswers && conceptAnswers) {
|
|
27
|
+
return conceptAnswers.map((answer) => ({ ...answer, label: answer.display }));
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
}, [conceptAnswers, isLoadingConceptAnswers]);
|
|
31
|
+
|
|
32
|
+
if (isLoadingConceptAnswers) {
|
|
33
|
+
return (
|
|
34
|
+
<div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
|
|
35
|
+
<h4 className={styles.productiveHeading02Light}>{t('causeOfDeathInputLabel', 'Cause of death')}</h4>
|
|
36
|
+
<SelectSkeleton />
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className={classNames(styles.customField, styles.halfWidthInDesktopView)}>
|
|
43
|
+
<h4 className={styles.productiveHeading02Light}>{t('causeOfDeathInputLabel', 'Cause of death')}</h4>
|
|
44
|
+
{errorLoadingConceptAnswers || !conceptUuid ? (
|
|
45
|
+
<InlineNotification
|
|
46
|
+
hideCloseButton
|
|
47
|
+
kind="error"
|
|
48
|
+
title={t('errorFetchingCodedCausesOfDeath', 'Error fetching coded causes of death')}
|
|
49
|
+
subtitle={t('refreshOrContactAdmin', 'Try refreshing the page or contact your system administrator')}
|
|
50
|
+
/>
|
|
51
|
+
) : (
|
|
52
|
+
<>
|
|
53
|
+
<Field name="deathCause">
|
|
54
|
+
{({ field, form: { touched, errors }, meta }) => {
|
|
55
|
+
return (
|
|
56
|
+
<Layer>
|
|
57
|
+
<Select
|
|
58
|
+
{...field}
|
|
59
|
+
id="deathCause"
|
|
60
|
+
invalid={errors.deathCause && touched.deathCause}
|
|
61
|
+
invalidText={errors.deathCause?.message}
|
|
62
|
+
labelText={t('causeOfDeathInputLabel', 'Cause of Death')}
|
|
63
|
+
name="deathCause"
|
|
64
|
+
required={required}>
|
|
65
|
+
<SelectItem id="empty-default-option" value={null} text={t('selectAnOption', 'Select an option')} />
|
|
66
|
+
{answers.map((answer) => (
|
|
67
|
+
<SelectItem id={answer.uuid} key={answer.uuid} text={answer.label} value={answer.uuid} />
|
|
68
|
+
))}
|
|
69
|
+
</Select>
|
|
70
|
+
</Layer>
|
|
71
|
+
);
|
|
72
|
+
}}
|
|
73
|
+
</Field>
|
|
74
|
+
{deathCause.value === freeTextFieldConceptUuid && (
|
|
75
|
+
<div className={styles.nonCodedCauseOfDeath}>
|
|
76
|
+
<Field name="nonCodedCauseOfDeath">
|
|
77
|
+
{({ field, form: { touched, errors }, meta }) => {
|
|
78
|
+
return (
|
|
79
|
+
<Layer>
|
|
80
|
+
<TextInput
|
|
81
|
+
{...field}
|
|
82
|
+
id="nonCodedCauseOfDeath"
|
|
83
|
+
invalid={errors?.nonCodedCauseOfDeath && touched.nonCodedCauseOfDeath}
|
|
84
|
+
invalidText={errors?.nonCodedCauseOfDeath?.message}
|
|
85
|
+
labelText={t('nonCodedCauseOfDeath', 'Non-coded cause of death')}
|
|
86
|
+
placeholder={t('enterNonCodedCauseOfDeath', 'Enter non-coded cause of death')}
|
|
87
|
+
/>
|
|
88
|
+
</Layer>
|
|
89
|
+
);
|
|
90
|
+
}}
|
|
91
|
+
</Field>
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
</>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useCallback, useContext } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import { Layer, SelectItem, TimePicker, TimePickerSelect } from '@carbon/react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { useField } from 'formik';
|
|
7
|
+
import { OpenmrsDatePicker } from '@openmrs/esm-framework';
|
|
8
|
+
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
9
|
+
import type { FormValues } from '../../patient-registration.types';
|
|
10
|
+
import styles from '../field.scss';
|
|
11
|
+
|
|
12
|
+
export const DateAndTimeOfDeathField: React.FC = () => {
|
|
13
|
+
const { t } = useTranslation();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className={classNames(styles.dodField, styles.halfWidthInDesktopView)}>
|
|
17
|
+
<h4 className={styles.productiveHeading02Light}>{t('deathDateInputLabel', 'Date of Death')}</h4>
|
|
18
|
+
<span>
|
|
19
|
+
<DeathDateField />
|
|
20
|
+
<DeathTimeField />
|
|
21
|
+
</span>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function DeathDateField() {
|
|
27
|
+
const { values, setFieldValue } = useContext(PatientRegistrationContext);
|
|
28
|
+
const [deathDate, deathDateMeta] = useField<keyof FormValues>('deathDate');
|
|
29
|
+
const { t } = useTranslation();
|
|
30
|
+
const today = dayjs().hour(23).minute(59).second(59).toDate();
|
|
31
|
+
const onDateChange = useCallback(
|
|
32
|
+
(selectedDate: Date) => {
|
|
33
|
+
setFieldValue(
|
|
34
|
+
'deathDate',
|
|
35
|
+
selectedDate ? dayjs(selectedDate).hour(0).minute(0).second(0).millisecond(0).toDate() : undefined,
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
[deathDate],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Layer>
|
|
43
|
+
<OpenmrsDatePicker
|
|
44
|
+
{...deathDate}
|
|
45
|
+
id="deathDate"
|
|
46
|
+
invalidText={t(deathDateMeta.error)}
|
|
47
|
+
invalid={!!(deathDateMeta.touched && deathDateMeta.error)}
|
|
48
|
+
isRequired={values.isDead}
|
|
49
|
+
labelText={t('deathDateInputLabel', 'Date of death')}
|
|
50
|
+
maxDate={today}
|
|
51
|
+
onChange={onDateChange}
|
|
52
|
+
/>
|
|
53
|
+
</Layer>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function DeathTimeField() {
|
|
58
|
+
const { t } = useTranslation();
|
|
59
|
+
const [deathTimeField, deathTimeMeta] = useField<keyof FormValues>('deathTime');
|
|
60
|
+
const [deathTimeFormatField, deathTimeFormatMeta] = useField<keyof FormValues>('deathTimeFormat');
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Layer>
|
|
64
|
+
<TimePicker
|
|
65
|
+
{...deathTimeField}
|
|
66
|
+
id="time-picker"
|
|
67
|
+
labelText={t('timeOfDeathInputLabel', 'Time of death (hh:mm)')}
|
|
68
|
+
className={styles.timeOfDeathField}
|
|
69
|
+
pattern="^(1[0-2]|0?[1-9]):([0-5]?[0-9])$"
|
|
70
|
+
invalid={!!(deathTimeMeta.touched && deathTimeMeta.error)}
|
|
71
|
+
invalidText={t(deathTimeMeta.error)}>
|
|
72
|
+
<TimePickerSelect
|
|
73
|
+
{...deathTimeFormatField}
|
|
74
|
+
id="time-format-picker"
|
|
75
|
+
aria-label={t('timeFormat', 'Time Format')}
|
|
76
|
+
invalid={!!deathTimeFormatMeta.touched && deathTimeFormatMeta.error}
|
|
77
|
+
invalidText={t(deathTimeFormatMeta.error)}>
|
|
78
|
+
<SelectItem value="AM" text="AM" />
|
|
79
|
+
<SelectItem value="PM" text="PM" />
|
|
80
|
+
</TimePickerSelect>
|
|
81
|
+
</TimePicker>
|
|
82
|
+
</Layer>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -30,7 +30,7 @@ export const DobField: React.FC = () => {
|
|
|
30
30
|
const [birthdate, birthdateMeta] = useField('birthdate');
|
|
31
31
|
const [yearsEstimated, yearsEstimateMeta] = useField('yearsEstimated');
|
|
32
32
|
const [monthsEstimated, monthsEstimateMeta] = useField('monthsEstimated');
|
|
33
|
-
const { setFieldValue } = useContext(PatientRegistrationContext);
|
|
33
|
+
const { setFieldValue, setFieldTouched } = useContext(PatientRegistrationContext);
|
|
34
34
|
const today = new Date();
|
|
35
35
|
|
|
36
36
|
const onToggle = useCallback(
|
|
@@ -39,6 +39,7 @@ export const DobField: React.FC = () => {
|
|
|
39
39
|
setFieldValue('birthdate', '');
|
|
40
40
|
setFieldValue('yearsEstimated', 0);
|
|
41
41
|
setFieldValue('monthsEstimated', '');
|
|
42
|
+
setFieldTouched('birthdateEstimated', true, false);
|
|
42
43
|
},
|
|
43
44
|
[setFieldValue],
|
|
44
45
|
);
|
|
@@ -46,8 +47,9 @@ export const DobField: React.FC = () => {
|
|
|
46
47
|
const onDateChange = useCallback(
|
|
47
48
|
(birthdate: Date) => {
|
|
48
49
|
setFieldValue('birthdate', birthdate);
|
|
50
|
+
setFieldTouched('birthdate', true, false);
|
|
49
51
|
},
|
|
50
|
-
[setFieldValue],
|
|
52
|
+
[setFieldValue, setFieldTouched],
|
|
51
53
|
);
|
|
52
54
|
|
|
53
55
|
const onEstimatedYearsChange = useCallback(
|
|
@@ -80,7 +82,10 @@ export const DobField: React.FC = () => {
|
|
|
80
82
|
setFieldValue('yearsEstimated', years);
|
|
81
83
|
setFieldValue('monthsEstimated', months > 0 ? months : '');
|
|
82
84
|
setFieldValue('birthdate', calcBirthdate(years, months, dateOfBirth));
|
|
83
|
-
|
|
85
|
+
setFieldTouched('yearsEstimated', true, false);
|
|
86
|
+
setFieldTouched('monthsEstimated', true, false);
|
|
87
|
+
setFieldTouched('birthdate', true, false);
|
|
88
|
+
}, [setFieldValue, setFieldTouched, monthsEstimateMeta, yearsEstimateMeta, dateOfBirth]);
|
|
84
89
|
|
|
85
90
|
return (
|
|
86
91
|
<div className={styles.halfWidthInDesktopView}>
|
|
@@ -88,7 +93,7 @@ export const DobField: React.FC = () => {
|
|
|
88
93
|
{(allowEstimatedBirthDate || dobUnknown) && (
|
|
89
94
|
<div className={styles.dobField}>
|
|
90
95
|
<div className={styles.dobContentSwitcherLabel}>
|
|
91
|
-
<span className={styles.label01}>{t('dobToggleLabelText', 'Date of
|
|
96
|
+
<span className={styles.label01}>{t('dobToggleLabelText', 'Date of birth known?')}</span>
|
|
92
97
|
</div>
|
|
93
98
|
<ContentSwitcher onChange={onToggle} selectedIndex={dobUnknown ? 1 : 0}>
|
|
94
99
|
<Switch name="known" text={t('yes', 'Yes')} />
|
|
@@ -103,8 +108,9 @@ export const DobField: React.FC = () => {
|
|
|
103
108
|
id="birthdate"
|
|
104
109
|
{...birthdate}
|
|
105
110
|
onChange={onDateChange}
|
|
111
|
+
onBlur={() => setFieldTouched('birthdate', true, false)}
|
|
106
112
|
maxDate={today}
|
|
107
|
-
labelText={t('dateOfBirthLabelText', 'Date of
|
|
113
|
+
labelText={t('dateOfBirthLabelText', 'Date of birth')}
|
|
108
114
|
isInvalid={!!(birthdateMeta.touched && birthdateMeta.error)}
|
|
109
115
|
invalidText={t(birthdateMeta.error)}
|
|
110
116
|
value={birthdate.value}
|
|
@@ -125,7 +131,11 @@ export const DobField: React.FC = () => {
|
|
|
125
131
|
min={0}
|
|
126
132
|
required
|
|
127
133
|
{...yearsEstimated}
|
|
128
|
-
onBlur={
|
|
134
|
+
onBlur={(e) => {
|
|
135
|
+
yearsEstimated.onBlur(e);
|
|
136
|
+
setFieldTouched('yearsEstimated', true, false);
|
|
137
|
+
updateBirthdate();
|
|
138
|
+
}}
|
|
129
139
|
/>
|
|
130
140
|
</div>
|
|
131
141
|
<div className={styles.dobField}>
|
|
@@ -141,7 +151,11 @@ export const DobField: React.FC = () => {
|
|
|
141
151
|
min={0}
|
|
142
152
|
{...monthsEstimated}
|
|
143
153
|
required={!yearsEstimateMeta.value}
|
|
144
|
-
onBlur={
|
|
154
|
+
onBlur={(e) => {
|
|
155
|
+
monthsEstimated.onBlur(e);
|
|
156
|
+
setFieldTouched('monthsEstimated', true, false);
|
|
157
|
+
updateBirthdate();
|
|
158
|
+
}}
|
|
145
159
|
/>
|
|
146
160
|
</div>
|
|
147
161
|
</div>
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { NameField } from './name/name-field.component';
|
|
3
|
-
import { GenderField } from './gender/gender-field.component';
|
|
4
|
-
import { Identifiers } from './id/id-field.component';
|
|
5
|
-
import { DobField } from './dob/dob.component';
|
|
6
2
|
import { reportError, useConfig } from '@openmrs/esm-framework';
|
|
7
3
|
import { builtInFields, type RegistrationConfig } from '../../config-schema';
|
|
8
|
-
import { CustomField } from './custom-field.component';
|
|
9
4
|
import { AddressComponent } from './address/address-field.component';
|
|
5
|
+
import { CauseOfDeathField } from './cause-of-death/cause-of-death.component';
|
|
6
|
+
import { CustomField } from './custom-field.component';
|
|
7
|
+
import { DateAndTimeOfDeathField } from './date-and-time-of-death/date-and-time-of-death.component';
|
|
8
|
+
import { DobField } from './dob/dob.component';
|
|
9
|
+
import { GenderField } from './gender/gender-field.component';
|
|
10
|
+
import { Identifiers } from './id/id-field.component';
|
|
11
|
+
import { NameField } from './name/name-field.component';
|
|
10
12
|
import { PhoneField } from './phone/phone-field.component';
|
|
11
13
|
|
|
12
14
|
export interface FieldProps {
|
|
@@ -35,6 +37,10 @@ export function Field({ name }: FieldProps) {
|
|
|
35
37
|
return <GenderField />;
|
|
36
38
|
case 'dob':
|
|
37
39
|
return <DobField />;
|
|
40
|
+
case 'dateAndTimeOfDeath':
|
|
41
|
+
return <DateAndTimeOfDeathField />;
|
|
42
|
+
case 'causeOfDeath':
|
|
43
|
+
return <CauseOfDeathField />;
|
|
38
44
|
case 'address':
|
|
39
45
|
return <AddressComponent />;
|
|
40
46
|
case 'id':
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type FetchResponse, openmrsFetch,
|
|
1
|
+
import { type FetchResponse, openmrsFetch, restBaseUrl, showSnackbar } from '@openmrs/esm-framework';
|
|
2
2
|
import useSWRImmutable from 'swr/immutable';
|
|
3
3
|
import { type ConceptAnswers, type ConceptResponse } from '../patient-registration.types';
|
|
4
|
+
import { useMemo } from 'react';
|
|
4
5
|
|
|
5
6
|
export function useConcept(conceptUuid: string): { data: ConceptResponse; isLoading: boolean } {
|
|
6
7
|
const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== '';
|
|
@@ -15,10 +16,15 @@ export function useConcept(conceptUuid: string): { data: ConceptResponse; isLoad
|
|
|
15
16
|
kind: 'error',
|
|
16
17
|
});
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
const results = useMemo(() => ({ data: data?.data, isLoading }), [data, isLoading]);
|
|
20
|
+
return results;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export function useConceptAnswers(conceptUuid: string): {
|
|
23
|
+
export function useConceptAnswers(conceptUuid: string): {
|
|
24
|
+
data: Array<ConceptAnswers>;
|
|
25
|
+
isLoading: boolean;
|
|
26
|
+
error: Error;
|
|
27
|
+
} {
|
|
22
28
|
const shouldFetch = typeof conceptUuid === 'string' && conceptUuid !== '';
|
|
23
29
|
const { data, error, isLoading } = useSWRImmutable<FetchResponse<ConceptResponse>, Error>(
|
|
24
30
|
shouldFetch ? `${restBaseUrl}/concept/${conceptUuid}` : null,
|
|
@@ -31,5 +37,6 @@ export function useConceptAnswers(conceptUuid: string): { data: Array<ConceptAns
|
|
|
31
37
|
kind: 'error',
|
|
32
38
|
});
|
|
33
39
|
}
|
|
34
|
-
|
|
40
|
+
const results = useMemo(() => ({ data: data?.data?.answers, isLoading, error }), [isLoading, error, data]);
|
|
41
|
+
return results;
|
|
35
42
|
}
|
|
@@ -64,8 +64,30 @@
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
.sexField,
|
|
67
|
-
.dobField
|
|
67
|
+
.dobField,
|
|
68
|
+
.dodField {
|
|
68
69
|
margin-bottom: layout.$spacing-05;
|
|
70
|
+
|
|
71
|
+
span {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-flow: row nowrap;
|
|
74
|
+
justify-content: space-between;
|
|
75
|
+
align-items: start;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.nonCodedCauseOfDeath {
|
|
80
|
+
margin-top: layout.$spacing-04;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.timeOfDeathContainer {
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.timeOfDeathField {
|
|
89
|
+
flex: none;
|
|
90
|
+
margin-left: layout.$spacing-02;
|
|
69
91
|
}
|
|
70
92
|
|
|
71
93
|
.dobContentSwitcherLabel {
|
|
@@ -11,11 +11,12 @@ export const GenderField: React.FC = () => {
|
|
|
11
11
|
const { fieldConfigurations } = useConfig<RegistrationConfig>();
|
|
12
12
|
const { t } = useTranslation();
|
|
13
13
|
const [field, meta] = useField('gender');
|
|
14
|
-
const { setFieldValue } = useContext(PatientRegistrationContext);
|
|
14
|
+
const { setFieldValue, setFieldTouched } = useContext(PatientRegistrationContext);
|
|
15
15
|
const fieldConfigs = fieldConfigurations?.gender;
|
|
16
16
|
|
|
17
17
|
const setGender = (gender: string) => {
|
|
18
18
|
setFieldValue('gender', gender);
|
|
19
|
+
setFieldTouched('gender', true, false);
|
|
19
20
|
};
|
|
20
21
|
/**
|
|
21
22
|
* DO NOT REMOVE THIS COMMENT HERE, ADDS TRANSLATION FOR SEX OPTIONS
|
|
@@ -21,7 +21,7 @@ function checkNumber(value: string) {
|
|
|
21
21
|
|
|
22
22
|
export const NameField = () => {
|
|
23
23
|
const { t } = useTranslation();
|
|
24
|
-
const { setCapturePhotoProps, currentPhoto, setFieldValue } = useContext(PatientRegistrationContext);
|
|
24
|
+
const { setCapturePhotoProps, currentPhoto, setFieldValue, setFieldTouched } = useContext(PatientRegistrationContext);
|
|
25
25
|
const {
|
|
26
26
|
fieldConfigurations: {
|
|
27
27
|
name: {
|
|
@@ -48,6 +48,7 @@ export const NameField = () => {
|
|
|
48
48
|
imageData: dataUri,
|
|
49
49
|
dateTime: photoDateTime,
|
|
50
50
|
});
|
|
51
|
+
setFieldTouched('photo', true, false);
|
|
51
52
|
}
|
|
52
53
|
},
|
|
53
54
|
[setCapturePhotoProps],
|
|
@@ -63,6 +64,9 @@ export const NameField = () => {
|
|
|
63
64
|
setFieldValue('familyName', defaultUnknownFamilyName);
|
|
64
65
|
setUnknownPatient('true');
|
|
65
66
|
}
|
|
67
|
+
setFieldTouched('givenName', true);
|
|
68
|
+
setFieldTouched('familyName', true);
|
|
69
|
+
setFieldTouched(`attributes.${unidentifiedPatientAttributeTypeUuid}`, true, false);
|
|
66
70
|
};
|
|
67
71
|
|
|
68
72
|
const firstNameField = (
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type FetchResponse,
|
|
3
|
-
type Session,
|
|
4
|
-
type StyleguideConfigObject,
|
|
5
3
|
getConfig,
|
|
6
4
|
openmrsFetch,
|
|
7
5
|
queueSynchronizationItem,
|
|
8
6
|
restBaseUrl,
|
|
7
|
+
type Session,
|
|
8
|
+
type StyleguideConfigObject,
|
|
9
|
+
toOmrsIsoString,
|
|
9
10
|
} from '@openmrs/esm-framework';
|
|
10
11
|
import { patientRegistration } from '../constants';
|
|
11
12
|
import {
|
|
12
|
-
type FormValues,
|
|
13
13
|
type AttributeValue,
|
|
14
|
-
type PatientUuidMapType,
|
|
15
|
-
type Patient,
|
|
16
14
|
type CapturePhotoProps,
|
|
15
|
+
type Encounter,
|
|
16
|
+
type FormValues,
|
|
17
|
+
type Patient,
|
|
17
18
|
type PatientIdentifier,
|
|
18
19
|
type PatientRegistration,
|
|
20
|
+
type PatientUuidMapType,
|
|
19
21
|
type RelationshipValue,
|
|
20
|
-
type Encounter,
|
|
21
22
|
} from './patient-registration.types';
|
|
22
23
|
import {
|
|
23
24
|
addPatientIdentifier,
|
|
@@ -25,14 +26,16 @@ import {
|
|
|
25
26
|
deletePersonName,
|
|
26
27
|
deleteRelationship,
|
|
27
28
|
generateIdentifier,
|
|
29
|
+
getDatetime,
|
|
30
|
+
saveEncounter,
|
|
28
31
|
savePatient,
|
|
29
32
|
savePatientPhoto,
|
|
30
33
|
saveRelationship,
|
|
31
|
-
updateRelationship,
|
|
32
34
|
updatePatientIdentifier,
|
|
33
|
-
|
|
35
|
+
updateRelationship,
|
|
34
36
|
} from './patient-registration.resource';
|
|
35
37
|
import { type RegistrationConfig } from '../config-schema';
|
|
38
|
+
import dayjs from 'dayjs';
|
|
36
39
|
|
|
37
40
|
export type SavePatientForm = (
|
|
38
41
|
isNewPatient: boolean,
|
|
@@ -62,7 +65,7 @@ export class FormManager {
|
|
|
62
65
|
) => {
|
|
63
66
|
const syncItem: PatientRegistration = {
|
|
64
67
|
fhirPatient: FormManager.mapPatientToFhirPatient(
|
|
65
|
-
FormManager.getPatientToCreate(isNewPatient, values, patientUuidMap, initialAddressFieldValues, []),
|
|
68
|
+
FormManager.getPatientToCreate(isNewPatient, values, patientUuidMap, initialAddressFieldValues, [], config),
|
|
66
69
|
),
|
|
67
70
|
_patientRegistrationData: {
|
|
68
71
|
isNewPatient,
|
|
@@ -115,6 +118,7 @@ export class FormManager {
|
|
|
115
118
|
patientUuidMap,
|
|
116
119
|
initialAddressFieldValues,
|
|
117
120
|
patientIdentifiers,
|
|
121
|
+
config,
|
|
118
122
|
);
|
|
119
123
|
|
|
120
124
|
FormManager.getDeletedNames(values.patientUuid, patientUuidMap).forEach(async (name) => {
|
|
@@ -296,6 +300,7 @@ export class FormManager {
|
|
|
296
300
|
patientUuidMap: PatientUuidMapType,
|
|
297
301
|
initialAddressFieldValues: Record<string, any>,
|
|
298
302
|
identifiers: Array<PatientIdentifier>,
|
|
303
|
+
config?: RegistrationConfig,
|
|
299
304
|
): Patient {
|
|
300
305
|
let birthdate;
|
|
301
306
|
if (values.birthdate instanceof Date) {
|
|
@@ -316,7 +321,7 @@ export class FormManager {
|
|
|
316
321
|
birthdateEstimated: values.birthdateEstimated,
|
|
317
322
|
attributes: FormManager.getPatientAttributes(isNewPatient, values, patientUuidMap),
|
|
318
323
|
addresses: [values.address],
|
|
319
|
-
...FormManager.getPatientDeathInfo(values),
|
|
324
|
+
...FormManager.getPatientDeathInfo(values, config),
|
|
320
325
|
},
|
|
321
326
|
identifiers,
|
|
322
327
|
};
|
|
@@ -375,12 +380,22 @@ export class FormManager {
|
|
|
375
380
|
return attributes;
|
|
376
381
|
}
|
|
377
382
|
|
|
378
|
-
static getPatientDeathInfo(values: FormValues) {
|
|
379
|
-
const { isDead, deathDate, deathCause } = values;
|
|
383
|
+
static getPatientDeathInfo(values: FormValues, config?: RegistrationConfig) {
|
|
384
|
+
const { isDead, deathDate, deathTime, deathTimeFormat, deathCause, nonCodedCauseOfDeath } = values;
|
|
385
|
+
|
|
386
|
+
if (!isDead) {
|
|
387
|
+
return {
|
|
388
|
+
dead: false,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const dateTimeOfDeath = toOmrsIsoString(getDatetime(deathDate, deathTime, deathTimeFormat));
|
|
392
|
+
|
|
380
393
|
return {
|
|
381
|
-
dead:
|
|
382
|
-
deathDate:
|
|
383
|
-
|
|
394
|
+
dead: true,
|
|
395
|
+
deathDate: dateTimeOfDeath,
|
|
396
|
+
...(deathCause === config?.freeTextFieldConceptUuid
|
|
397
|
+
? { causeOfDeathNonCoded: nonCodedCauseOfDeath, causeOfDeath: null }
|
|
398
|
+
: { causeOfDeath: deathCause, causeOfDeathNonCoded: null }),
|
|
384
399
|
};
|
|
385
400
|
}
|
|
386
401
|
|
|
@@ -66,7 +66,7 @@ export interface TextInputProps
|
|
|
66
66
|
* `true` to use the light version. For use on $ui-01 backgrounds only.
|
|
67
67
|
* Don't use this to make tile background color same as container background color.
|
|
68
68
|
* 'The `light` prop for `TextInput` has ' +
|
|
69
|
-
|
|
69
|
+
'been deprecated in favor of the new `Layer` component. It will be removed in the next major release.'
|
|
70
70
|
*/
|
|
71
71
|
light?: boolean;
|
|
72
72
|
|
|
@@ -145,6 +145,10 @@ export const Input: React.FC<InputProps> = ({ checkWarning, ...props }) => {
|
|
|
145
145
|
t('invalidEmail')
|
|
146
146
|
t('numberInNameDubious')
|
|
147
147
|
t('yearsEstimateRequired')
|
|
148
|
+
t('deathdayIsRequired', 'Death date is required when the patient is marked as deceased.')
|
|
149
|
+
t('deathdayInvalidDate', 'Date of death is invalid')
|
|
150
|
+
t('deathCauseRequired', 'Cause of death is required')
|
|
151
|
+
t('nonCodedCauseOfDeathRequired', 'Non-coded cause of death is required')
|
|
148
152
|
*/
|
|
149
153
|
|
|
150
154
|
const value = field.value || '';
|