@kenyaemr/esm-patient-registration-app 8.1.1-pre.129 → 8.1.2-pre.152
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 +21 -23
- package/dist/108.js +1 -1
- package/dist/130.js +1 -1
- package/dist/130.js.map +1 -1
- package/dist/173.js +2 -0
- package/dist/{895.js.LICENSE.txt → 173.js.LICENSE.txt} +25 -0
- package/dist/173.js.map +1 -0
- package/dist/236.js +1 -0
- package/dist/240.js +1 -0
- package/dist/261.js +1 -0
- package/dist/271.js +1 -1
- package/dist/272.js +1 -0
- package/dist/319.js +1 -1
- package/dist/336.js +1 -0
- package/dist/371.js +1 -0
- package/dist/371.js.map +1 -0
- package/dist/378.js +1 -0
- package/dist/460.js +1 -1
- package/dist/501.js +1 -1
- package/dist/501.js.map +1 -1
- package/dist/539.js +1 -0
- package/dist/566.js +1 -0
- package/dist/574.js +1 -1
- package/dist/623.js +1 -0
- package/dist/623.js.map +1 -0
- package/dist/644.js +1 -1
- package/dist/652.js +1 -0
- package/dist/657.js +1 -0
- package/dist/657.js.map +1 -0
- package/dist/673.js +1 -0
- package/dist/705.js +1 -0
- package/dist/711.js +1 -0
- package/dist/727.js +1 -0
- package/dist/737.js +1 -0
- package/dist/744.js +1 -0
- package/dist/757.js +1 -1
- package/dist/759.js +1 -0
- package/dist/759.js.map +1 -0
- 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/899.js +1 -0
- package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +445 -93
- package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +25 -0
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package-lock.json +2052 -1699
- package/package.json +4 -4
- package/src/client-registry/hie-client-registry/dependants/dependants.component.tsx +46 -0
- package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +38 -8
- package/src/client-registry/hie-client-registry/hie-resource.ts +126 -21
- package/src/client-registry/hie-client-registry/hie-types.ts +102 -0
- package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +78 -62
- package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +55 -3
- package/src/client-registry/hie-client-registry/modal/hie-otp-verification-form.component.tsx +88 -0
- package/src/client-registry/hie-client-registry/modal/hie-patient-detail-preview.component.tsx +77 -0
- package/src/client-registry/hie-client-registry/patient-info/patient-info.component.tsx +17 -0
- package/src/config-schema.ts +30 -2
- package/src/patient-registration/field/address/address-search.component.tsx +5 -2
- package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
- package/src/patient-registration/field/dob/dob.component.tsx +1 -1
- package/src/patient-registration/field/gender/gender-field.component.tsx +6 -2
- package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +3 -3
- package/src/patient-registration/field/name/name-field.component.tsx +2 -2
- package/src/patient-registration/field/obs/obs-field.component.tsx +9 -5
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx +3 -3
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +22 -11
- package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +1 -1
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +12 -4
- package/src/patient-registration/form-manager.test.ts +4 -1
- package/src/patient-registration/form-manager.ts +0 -1
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +52 -62
- package/src/patient-registration/mpi/mpi-patient.resource.ts +21 -0
- package/src/patient-registration/patient-registration-hooks.ts +90 -25
- package/src/patient-registration/patient-registration-utils.test.ts +33 -0
- package/src/patient-registration/patient-registration-utils.ts +63 -13
- package/src/patient-registration/patient-registration.component.tsx +17 -2
- package/src/patient-registration/patient-registration.test.tsx +442 -56
- package/src/patient-registration/section/demographics/demographics-section.component.tsx +3 -3
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +1 -1
- package/src/patient-registration/section/patient-relationships/relationships.resource.tsx +28 -28
- package/src/widgets/cancel-patient-edit.modal.tsx +2 -0
- package/src/widgets/cancel-patient-edit.scss +29 -0
- package/src/widgets/delete-identifier-confirmation.modal.tsx +2 -0
- package/src/widgets/delete-identifier-confirmation.scss +29 -0
- package/translations/am.json +1 -0
- package/translations/ar.json +6 -4
- package/translations/de.json +118 -0
- package/translations/en.json +17 -0
- package/translations/es.json +2 -0
- package/translations/fr.json +1 -0
- package/translations/he.json +1 -0
- package/translations/hi.json +118 -0
- package/translations/hi_IN.json +118 -0
- package/translations/id.json +118 -0
- package/translations/it.json +118 -0
- package/translations/km.json +1 -0
- package/translations/ne.json +118 -0
- package/translations/pt.json +118 -0
- package/translations/pt_BR.json +118 -0
- package/translations/qu.json +118 -0
- package/translations/si.json +118 -0
- package/translations/sw.json +118 -0
- package/translations/sw_KE.json +118 -0
- package/translations/tr.json +118 -0
- package/translations/tr_TR.json +118 -0
- package/translations/uk.json +118 -0
- package/translations/vi.json +118 -0
- package/translations/zh.json +3 -1
- package/translations/zh_CN.json +2 -0
- package/dist/250.js +0 -1
- package/dist/250.js.map +0 -1
- package/dist/66.js +0 -1
- package/dist/66.js.map +0 -1
- package/dist/662.js +0 -1
- package/dist/662.js.map +0 -1
- package/dist/753.js +0 -1
- package/dist/753.js.map +0 -1
- package/dist/895.js +0 -2
- package/dist/895.js.map +0 -1
|
@@ -1,10 +1,62 @@
|
|
|
1
1
|
@use '@carbon/type';
|
|
2
|
-
@use '@
|
|
2
|
+
@use '@carbon/layout';
|
|
3
|
+
@use '@carbon/colors';
|
|
3
4
|
|
|
4
5
|
.header {
|
|
5
6
|
@include type.type-style('heading-03');
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
.
|
|
9
|
-
|
|
9
|
+
.patientInfo {
|
|
10
|
+
display: grid;
|
|
11
|
+
grid-template-columns: 0.25fr 0.75fr;
|
|
12
|
+
margin: 0.25rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.patientInfoLabel {
|
|
16
|
+
min-width: 5rem;
|
|
17
|
+
font-weight: bold;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.dependentInfo {
|
|
21
|
+
margin-bottom: 1rem;
|
|
22
|
+
padding: 0.5rem;
|
|
23
|
+
border: 1px solid #ddd;
|
|
24
|
+
border-radius: 5px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.patientDetails {
|
|
28
|
+
display: flex;
|
|
29
|
+
margin: 1rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.patientPhotoContainer {
|
|
33
|
+
display: flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.patientInfoContainer {
|
|
38
|
+
width: 100%;
|
|
39
|
+
margin-left: 0.625rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.patientNameValue {
|
|
43
|
+
display: flex;
|
|
44
|
+
gap: layout.$spacing-03;
|
|
45
|
+
align-items: center;
|
|
46
|
+
|
|
47
|
+
& > span {
|
|
48
|
+
color: colors.$gray-40;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.grid {
|
|
53
|
+
margin: 0 layout.$spacing-05;
|
|
54
|
+
padding: layout.$spacing-05 0rem 0rem orem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.otpInputRow {
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-direction: row;
|
|
60
|
+
gap: layout.$spacing-03;
|
|
61
|
+
align-items: flex-end;
|
|
10
62
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Button, Column, Row, Stack, Tag, TextInput, InlineLoading } from '@carbon/react';
|
|
2
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { type authorizationFormSchema, generateOTP, persistOTP, sendOtp } from '../hie-resource';
|
|
7
|
+
import styles from './confirm-hie.scss';
|
|
8
|
+
import { type z } from 'zod';
|
|
9
|
+
|
|
10
|
+
type HIEOTPVerficationFormProps = {
|
|
11
|
+
name: string;
|
|
12
|
+
patientId: string;
|
|
13
|
+
status?: 'loadingOtp' | 'otpSendSuccessfull' | 'otpFetchError';
|
|
14
|
+
setStatus: React.Dispatch<React.SetStateAction<'loadingOtp' | 'otpSendSuccessfull' | 'otpFetchError'>>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const HIEOTPVerficationForm: React.FC<HIEOTPVerficationFormProps> = ({ name, patientId, setStatus, status }) => {
|
|
18
|
+
const form = useFormContext<z.infer<typeof authorizationFormSchema>>();
|
|
19
|
+
const { t } = useTranslation();
|
|
20
|
+
|
|
21
|
+
const handleGetOTP = async () => {
|
|
22
|
+
try {
|
|
23
|
+
setStatus('loadingOtp');
|
|
24
|
+
const otp = generateOTP(5);
|
|
25
|
+
await sendOtp({ otp, receiver: form.watch('receiver') }, name);
|
|
26
|
+
setStatus('otpSendSuccessfull');
|
|
27
|
+
persistOTP(otp, patientId);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
setStatus('otpFetchError');
|
|
30
|
+
showSnackbar({ title: t('error', 'Error'), kind: 'error', subtitle: error?.message });
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Stack gap={4} className={styles.grid}>
|
|
36
|
+
<Column>
|
|
37
|
+
<Controller
|
|
38
|
+
control={form.control}
|
|
39
|
+
name="receiver"
|
|
40
|
+
render={({ field }) => (
|
|
41
|
+
<TextInput
|
|
42
|
+
invalid={form.formState.errors[field.name]?.message}
|
|
43
|
+
invalidText={form.formState.errors[field.name]?.message}
|
|
44
|
+
{...field}
|
|
45
|
+
placeholder={t('patientPhoneNUmber', 'Patient Phone number')}
|
|
46
|
+
labelText={t('patientPhoneNUmber', 'Patient Phone number')}
|
|
47
|
+
helperText={t('phoneNumberHelper', 'Patient will receive OTP on this number')}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
/>
|
|
51
|
+
</Column>
|
|
52
|
+
|
|
53
|
+
<Column>
|
|
54
|
+
<Controller
|
|
55
|
+
control={form.control}
|
|
56
|
+
name="otp"
|
|
57
|
+
render={({ field }) => (
|
|
58
|
+
<Row className={styles.otpInputRow}>
|
|
59
|
+
<TextInput
|
|
60
|
+
invalid={form.formState.errors[field.name]?.message}
|
|
61
|
+
invalidText={form.formState.errors[field.name]?.message}
|
|
62
|
+
{...field}
|
|
63
|
+
placeholder={t('otpCode', 'OTP Authorization code')}
|
|
64
|
+
labelText={t('otpCode', 'OTP Authorization code')}
|
|
65
|
+
/>
|
|
66
|
+
<Button
|
|
67
|
+
onClick={handleGetOTP}
|
|
68
|
+
role="button"
|
|
69
|
+
type="blue"
|
|
70
|
+
kind="tertiary"
|
|
71
|
+
disabled={['loadingOtp', 'otpSendSuccessfull'].includes(status)}>
|
|
72
|
+
{status === 'loadingOtp' ? (
|
|
73
|
+
<InlineLoading status="active" iconDescription="Loading" description="Loading data..." />
|
|
74
|
+
) : status === 'otpFetchError' ? (
|
|
75
|
+
t('retry', 'Retry')
|
|
76
|
+
) : (
|
|
77
|
+
t('verifyOTP', 'Verify with OTP')
|
|
78
|
+
)}
|
|
79
|
+
</Button>
|
|
80
|
+
</Row>
|
|
81
|
+
)}
|
|
82
|
+
/>
|
|
83
|
+
</Column>
|
|
84
|
+
</Stack>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export default HIEOTPVerficationForm;
|
package/src/client-registry/hie-client-registry/modal/hie-patient-detail-preview.component.tsx
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Accordion, AccordionItem, CodeSnippet } from '@carbon/react';
|
|
2
|
+
import { age, ExtensionSlot, formatDate } from '@openmrs/esm-framework';
|
|
3
|
+
import capitalize from 'lodash-es/capitalize';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import DependentInfo from '../dependants/dependants.component';
|
|
7
|
+
import { getPatientName, maskData } from '../hie-resource';
|
|
8
|
+
import PatientInfo from '../patient-info/patient-info.component';
|
|
9
|
+
import styles from './confirm-hie.scss';
|
|
10
|
+
import { type HIEPatientResponse } from '../hie-types';
|
|
11
|
+
|
|
12
|
+
type HIEPatientDetailPreviewProps = {
|
|
13
|
+
patient: HIEPatientResponse;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const HIEPatientDetailPreview: React.FC<HIEPatientDetailPreviewProps> = ({ patient }) => {
|
|
17
|
+
const { familyName, givenName, middleName } = getPatientName(patient);
|
|
18
|
+
const { t } = useTranslation();
|
|
19
|
+
const getidentifier = (code: string) =>
|
|
20
|
+
patient?.entry[0]?.resource.identifier?.find(
|
|
21
|
+
(identifier) => identifier?.type?.coding?.some((coding) => coding?.code === code),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<div className={styles.patientDetails}>
|
|
27
|
+
<ExtensionSlot
|
|
28
|
+
className={styles.patientPhotoContainer}
|
|
29
|
+
name="patient-photo-slot"
|
|
30
|
+
state={{ patientName: `${maskData(givenName)} . ${maskData(middleName)} . ${maskData(familyName)}` }}
|
|
31
|
+
/>
|
|
32
|
+
<div className={styles.patientInfoContainer}>
|
|
33
|
+
<PatientInfo label={t('healthID', 'HealthID')} value={getidentifier('sha-number')?.value} />
|
|
34
|
+
<PatientInfo
|
|
35
|
+
label={t('patientName', 'Patient name')}
|
|
36
|
+
customValue={
|
|
37
|
+
<span className={styles.patientNameValue}>
|
|
38
|
+
<p>{maskData(givenName)}</p>
|
|
39
|
+
<span>•</span>
|
|
40
|
+
<p>{maskData(middleName)}</p>
|
|
41
|
+
<span>•</span>
|
|
42
|
+
<p>{maskData(familyName)}</p>
|
|
43
|
+
</span>
|
|
44
|
+
}
|
|
45
|
+
/>
|
|
46
|
+
|
|
47
|
+
<PatientInfo label={t('age', 'Age')} value={age(patient?.entry[0]?.resource.birthDate)} />
|
|
48
|
+
<PatientInfo
|
|
49
|
+
label={t('dateOfBirth', 'Date of birth')}
|
|
50
|
+
value={formatDate(new Date(patient?.entry[0]?.resource?.birthDate))}
|
|
51
|
+
/>
|
|
52
|
+
<PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.entry[0]?.resource.gender)} />
|
|
53
|
+
<PatientInfo
|
|
54
|
+
label={t('maritalStatus', 'Marital status')}
|
|
55
|
+
value={patient?.entry[0]?.resource.maritalStatus?.coding?.map((m) => m.code).join('')}
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
{!patient?.entry[0]?.resource.contact && <PatientInfo label={t('dependents', 'Dependents')} value="--" />}
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<DependentInfo dependents={patient?.entry[0]?.resource.contact} />
|
|
63
|
+
|
|
64
|
+
<div>
|
|
65
|
+
<Accordion>
|
|
66
|
+
<AccordionItem title={t('viewFullResponse', 'View full response')}>
|
|
67
|
+
<CodeSnippet type="multi" feedback="Copied to clipboard">
|
|
68
|
+
{JSON.stringify(patient, null, 2)}
|
|
69
|
+
</CodeSnippet>
|
|
70
|
+
</AccordionItem>
|
|
71
|
+
</Accordion>
|
|
72
|
+
</div>
|
|
73
|
+
</>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default HIEPatientDetailPreview;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styles from '../modal/confirm-hie.scss';
|
|
3
|
+
|
|
4
|
+
const PatientInfo: React.FC<{ label: string; value?: string; customValue?: JSX.Element }> = ({
|
|
5
|
+
label,
|
|
6
|
+
value,
|
|
7
|
+
customValue,
|
|
8
|
+
}) => {
|
|
9
|
+
return (
|
|
10
|
+
<div className={styles.patientInfo}>
|
|
11
|
+
<span className={styles.patientInfoLabel}>{label}</span>
|
|
12
|
+
<span>{value || customValue || '--'}</span>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default PatientInfo;
|
package/src/config-schema.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type, validator, validators } from '@openmrs/esm-framework';
|
|
2
|
+
import _default from 'yup/lib/locale';
|
|
2
3
|
|
|
3
4
|
export interface SectionDefinition {
|
|
4
5
|
id: string;
|
|
@@ -12,7 +13,8 @@ export interface FieldDefinition {
|
|
|
12
13
|
label?: string;
|
|
13
14
|
uuid: string;
|
|
14
15
|
placeholder?: string;
|
|
15
|
-
|
|
16
|
+
allowFutureDates?: boolean;
|
|
17
|
+
allowPastDates?: boolean;
|
|
16
18
|
showHeading: boolean;
|
|
17
19
|
validation?: {
|
|
18
20
|
required: boolean;
|
|
@@ -71,6 +73,7 @@ export interface RegistrationConfig {
|
|
|
71
73
|
month: number;
|
|
72
74
|
};
|
|
73
75
|
};
|
|
76
|
+
identifierMappings: [{ fhirIdentifierSystem: string; openmrsIdentifierTypeUuid: string }];
|
|
74
77
|
phone: {
|
|
75
78
|
personAttributeUuid: string;
|
|
76
79
|
validation?: {
|
|
@@ -186,6 +189,16 @@ export const esmPatientRegistrationSchema = {
|
|
|
186
189
|
_default: '',
|
|
187
190
|
_description: 'Placeholder that will appear in the input.',
|
|
188
191
|
},
|
|
192
|
+
allowFutureDates: {
|
|
193
|
+
_type: Type.Boolean,
|
|
194
|
+
_default: true,
|
|
195
|
+
_description: 'Indicates whether the date input field should allow the selection of future dates or not.',
|
|
196
|
+
},
|
|
197
|
+
allowPastDates: {
|
|
198
|
+
_type: Type.Boolean,
|
|
199
|
+
_default: true,
|
|
200
|
+
_description: 'Indicates whether the date input field should allow the selection of past dates or not.',
|
|
201
|
+
},
|
|
189
202
|
validation: {
|
|
190
203
|
required: { _type: Type.Boolean, _default: false },
|
|
191
204
|
matches: {
|
|
@@ -349,6 +362,21 @@ export const esmPatientRegistrationSchema = {
|
|
|
349
362
|
},
|
|
350
363
|
},
|
|
351
364
|
},
|
|
365
|
+
identifierMappings: {
|
|
366
|
+
_type: Type.Array,
|
|
367
|
+
_elements: {
|
|
368
|
+
fhirIdentifierSystem: {
|
|
369
|
+
_type: Type.String,
|
|
370
|
+
_description: 'Identifier system from the fhir server',
|
|
371
|
+
},
|
|
372
|
+
openmrsIdentifierTypeUuid: {
|
|
373
|
+
_type: Type.String,
|
|
374
|
+
_default: null,
|
|
375
|
+
_description: 'Identifier type uuid of OpenMRS to map the identifier system',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
_default: [],
|
|
379
|
+
},
|
|
352
380
|
phone: {
|
|
353
381
|
personAttributeUuid: {
|
|
354
382
|
_type: Type.UUID,
|
|
@@ -416,7 +444,7 @@ export const esmPatientRegistrationSchema = {
|
|
|
416
444
|
},
|
|
417
445
|
},
|
|
418
446
|
_default: [
|
|
419
|
-
{ identifierType: 'National ID', identifierValue: '
|
|
447
|
+
{ identifierType: 'National ID', identifierValue: 'National ID' },
|
|
420
448
|
{ identifierType: 'Passport Number', identifierValue: 'passport-number' },
|
|
421
449
|
{ identifierType: 'Birth Certificate Number', identifierValue: 'birth-certificate-number' },
|
|
422
450
|
{ identifierType: 'Alien ID Number', identifierValue: 'alien-id-number' },
|
|
@@ -14,8 +14,11 @@ const AddressSearchComponent: React.FC<AddressSearchComponentProps> = ({ address
|
|
|
14
14
|
const separator = ' > ';
|
|
15
15
|
const searchBox = useRef(null);
|
|
16
16
|
const wrapper = useRef(null);
|
|
17
|
-
|
|
18
|
-
const
|
|
17
|
+
|
|
18
|
+
const [searchString, setSearchString] = useState('');
|
|
19
|
+
|
|
20
|
+
const { addresses } = useAddressHierarchy(searchString, separator);
|
|
21
|
+
|
|
19
22
|
const addressOptions: Array<string> = useMemo(() => {
|
|
20
23
|
const options: Set<string> = new Set();
|
|
21
24
|
addresses.forEach((address) => {
|
|
@@ -30,8 +30,12 @@ export const GenderField: React.FC = () => {
|
|
|
30
30
|
<div className={styles.halfWidthInDesktopView}>
|
|
31
31
|
<h4 className={styles.productiveHeading02Light}>{t('sexFieldLabelText', 'Sex')}</h4>
|
|
32
32
|
<div className={styles.sexField}>
|
|
33
|
-
<
|
|
34
|
-
|
|
33
|
+
<RadioButtonGroup
|
|
34
|
+
name="gender"
|
|
35
|
+
legendText={t('genderLabelText', 'Sex')}
|
|
36
|
+
orientation="vertical"
|
|
37
|
+
onChange={setGender}
|
|
38
|
+
valueSelected={field.value}>
|
|
35
39
|
{fieldConfigs.map((option) => (
|
|
36
40
|
<RadioButton
|
|
37
41
|
key={option.label ?? option.value}
|
|
@@ -23,7 +23,7 @@ const PatientIdentifierOverlay: React.FC<PatientIdentifierOverlayProps> = ({ clo
|
|
|
23
23
|
const { identifierTypes } = useContext(ResourcesContext);
|
|
24
24
|
const { isOffline, values, initialFormValues } = useContext(PatientRegistrationContext);
|
|
25
25
|
const [unsavedIdentifierTypes, setUnsavedIdentifierTypes] = useState<FormValues['identifiers']>(values.identifiers);
|
|
26
|
-
const [searchString, setSearchString] = useState
|
|
26
|
+
const [searchString, setSearchString] = useState('');
|
|
27
27
|
const { t } = useTranslation();
|
|
28
28
|
const { defaultPatientIdentifierTypes } = useConfig();
|
|
29
29
|
const defaultPatientIdentifierTypesMap = useMemo(() => {
|
|
@@ -107,11 +107,11 @@ const PatientIdentifierOverlay: React.FC<PatientIdentifierOverlayProps> = ({ clo
|
|
|
107
107
|
/>
|
|
108
108
|
{patientIdentifier &&
|
|
109
109
|
identifierType?.identifierSources?.length > 0 &&
|
|
110
|
-
/*
|
|
110
|
+
/*
|
|
111
111
|
This check are for the cases when there's an initialValue identifier is assigned
|
|
112
112
|
to the patient
|
|
113
113
|
The corresponding flow is like:
|
|
114
|
-
1. If there's no change to the actual initial identifier, then the source remains null,
|
|
114
|
+
1. If there's no change to the actual initial identifier, then the source remains null,
|
|
115
115
|
hence the list of the identifier sources shouldn't be displayed.
|
|
116
116
|
2. If user wants to edit the patient identifier's value, hence there will be an initialValue,
|
|
117
117
|
along with a source assigned to itself(only if the identifierType has sources, else there's nothing to worry about), which by
|
|
@@ -3,9 +3,9 @@ import { useTranslation } from 'react-i18next';
|
|
|
3
3
|
import { ContentSwitcher, Switch } from '@carbon/react';
|
|
4
4
|
import { useField } from 'formik';
|
|
5
5
|
import { ExtensionSlot, useConfig } from '@openmrs/esm-framework';
|
|
6
|
+
import { type RegistrationConfig } from '../../../config-schema';
|
|
6
7
|
import { Input } from '../../input/basic-input/input/input.component';
|
|
7
8
|
import { PatientRegistrationContext } from '../../patient-registration-context';
|
|
8
|
-
import { type RegistrationConfig } from '../../../config-schema';
|
|
9
9
|
import styles from '../field.scss';
|
|
10
10
|
|
|
11
11
|
export const unidentifiedPatientAttributeTypeUuid = '8b56eac7-5c76-4b9c-8c6f-1deab8d3fc47';
|
|
@@ -51,7 +51,7 @@ export const NameField = () => {
|
|
|
51
51
|
setFieldTouched('photo', true, false);
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
|
-
[setCapturePhotoProps],
|
|
54
|
+
[setCapturePhotoProps, setFieldTouched],
|
|
55
55
|
);
|
|
56
56
|
|
|
57
57
|
const toggleNameKnown = (e) => {
|
|
@@ -57,8 +57,8 @@ export function ObsField({ fieldDefinition }: ObsFieldProps) {
|
|
|
57
57
|
concept={concept}
|
|
58
58
|
label={fieldDefinition.label}
|
|
59
59
|
required={fieldDefinition.validation.required}
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
allowPastDates={fieldDefinition.allowPastDates}
|
|
61
|
+
allowFutureDates={fieldDefinition.allowFutureDates}
|
|
62
62
|
/>
|
|
63
63
|
);
|
|
64
64
|
case 'Coded':
|
|
@@ -159,14 +159,16 @@ interface DateObsFieldProps {
|
|
|
159
159
|
concept: ConceptResponse;
|
|
160
160
|
label: string;
|
|
161
161
|
required?: boolean;
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
allowPastDates?: boolean;
|
|
163
|
+
allowFutureDates?: boolean;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
function DateObsField({ concept, label, required,
|
|
166
|
+
function DateObsField({ concept, label, required, allowPastDates, allowFutureDates }: DateObsFieldProps) {
|
|
167
167
|
const { t } = useTranslation();
|
|
168
168
|
const fieldName = `obs.${concept.uuid}`;
|
|
169
169
|
const { setFieldValue } = useContext(PatientRegistrationContext);
|
|
170
|
+
const futureDatesAllowed = allowFutureDates ?? true;
|
|
171
|
+
const pastDatesAllowed = allowPastDates ?? true;
|
|
170
172
|
|
|
171
173
|
const onDateChange = (date: Date) => {
|
|
172
174
|
setFieldValue(fieldName, date);
|
|
@@ -188,6 +190,8 @@ function DateObsField({ concept, label, required, placeholder }: DateObsFieldPro
|
|
|
188
190
|
isInvalid={errors[fieldName] && touched[fieldName]}
|
|
189
191
|
invalidText={t(meta.error)}
|
|
190
192
|
value={field.value}
|
|
193
|
+
minDate={!pastDatesAllowed ? new Date() : undefined}
|
|
194
|
+
maxDate={!futureDatesAllowed ? new Date() : undefined}
|
|
191
195
|
/>
|
|
192
196
|
</>
|
|
193
197
|
);
|
package/src/patient-registration/field/person-attributes/coded-person-attribute-field.component.tsx
CHANGED
|
@@ -3,10 +3,10 @@ import classNames from 'classnames';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { Field } from 'formik';
|
|
5
5
|
import { Layer, Select, SelectItem } from '@carbon/react';
|
|
6
|
+
import { reportError } from '@openmrs/esm-framework';
|
|
6
7
|
import { type PersonAttributeTypeResponse } from '../../patient-registration.types';
|
|
7
8
|
import { useConceptAnswers } from '../field.resource';
|
|
8
9
|
import styles from './../field.scss';
|
|
9
|
-
import { reportError } from '@openmrs/esm-framework';
|
|
10
10
|
|
|
11
11
|
export interface CodedPersonAttributeFieldProps {
|
|
12
12
|
id: string;
|
|
@@ -44,7 +44,7 @@ export function CodedPersonAttributeField({
|
|
|
44
44
|
);
|
|
45
45
|
setError(true);
|
|
46
46
|
}
|
|
47
|
-
}, [answerConceptSetUuid, customConceptAnswers]);
|
|
47
|
+
}, [answerConceptSetUuid, customConceptAnswers, id, t]);
|
|
48
48
|
|
|
49
49
|
useEffect(() => {
|
|
50
50
|
if (!isLoadingConceptAnswers && !customConceptAnswers.length) {
|
|
@@ -72,7 +72,7 @@ export function CodedPersonAttributeField({
|
|
|
72
72
|
setError(true);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
}, [isLoadingConceptAnswers, conceptAnswers, customConceptAnswers]);
|
|
75
|
+
}, [isLoadingConceptAnswers, conceptAnswers, customConceptAnswers, t, id, answerConceptSetUuid]);
|
|
76
76
|
|
|
77
77
|
const answers = useMemo(() => {
|
|
78
78
|
if (customConceptAnswers.length) {
|
package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx
CHANGED
|
@@ -4,19 +4,19 @@ import { render, screen } from '@testing-library/react';
|
|
|
4
4
|
import { useConceptAnswers } from '../field.resource';
|
|
5
5
|
import { CodedPersonAttributeField } from './coded-person-attribute-field.component';
|
|
6
6
|
|
|
7
|
-
jest.mock('formik', () => ({
|
|
8
|
-
...jest.requireActual('formik'),
|
|
9
|
-
}));
|
|
10
|
-
|
|
11
|
-
jest.mock('../field.resource');
|
|
12
|
-
|
|
13
7
|
const mockUseConceptAnswers = jest.mocked(useConceptAnswers);
|
|
14
8
|
|
|
9
|
+
jest.mock('../field.resource', () => ({
|
|
10
|
+
...jest.requireActual('../field.resource'),
|
|
11
|
+
useConceptAnswers: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
15
14
|
describe('CodedPersonAttributeField', () => {
|
|
16
15
|
const conceptAnswers = [
|
|
17
16
|
{ uuid: '1', display: 'Option 1' },
|
|
18
17
|
{ uuid: '2', display: 'Option 2' },
|
|
19
18
|
];
|
|
19
|
+
|
|
20
20
|
const personAttributeType = {
|
|
21
21
|
format: 'org.openmrs.Concept',
|
|
22
22
|
display: 'Referred by',
|
|
@@ -24,26 +24,35 @@ describe('CodedPersonAttributeField', () => {
|
|
|
24
24
|
name: '',
|
|
25
25
|
description: '',
|
|
26
26
|
};
|
|
27
|
+
|
|
27
28
|
const answerConceptSetUuid = '6682d17f-0777-45e4-a39b-93f77eb3531c';
|
|
29
|
+
let consoleSpy: jest.SpyInstance;
|
|
28
30
|
|
|
29
31
|
beforeEach(() => {
|
|
30
32
|
mockUseConceptAnswers.mockReturnValue({
|
|
31
33
|
data: conceptAnswers,
|
|
32
34
|
isLoading: false,
|
|
35
|
+
error: null,
|
|
33
36
|
});
|
|
37
|
+
|
|
38
|
+
consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
34
39
|
});
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
consoleSpy.mockRestore();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders an error if there is no concept answer set provided', () => {
|
|
37
46
|
expect(() => {
|
|
38
47
|
render(
|
|
39
48
|
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
40
49
|
<Form>
|
|
41
50
|
<CodedPersonAttributeField
|
|
42
|
-
id="attributeId"
|
|
43
|
-
personAttributeType={personAttributeType}
|
|
44
51
|
answerConceptSetUuid={null}
|
|
45
|
-
label={personAttributeType.display}
|
|
46
52
|
customConceptAnswers={[]}
|
|
53
|
+
id="attributeId"
|
|
54
|
+
label={personAttributeType.display}
|
|
55
|
+
personAttributeType={personAttributeType}
|
|
47
56
|
required={false}
|
|
48
57
|
/>
|
|
49
58
|
</Form>
|
|
@@ -52,11 +61,13 @@ describe('CodedPersonAttributeField', () => {
|
|
|
52
61
|
}).toThrow(expect.stringMatching(/has been defined without an answer concept set UUID/i));
|
|
53
62
|
});
|
|
54
63
|
|
|
55
|
-
it('
|
|
64
|
+
it('renders an error if the concept answer set does not have any concept answers', () => {
|
|
56
65
|
mockUseConceptAnswers.mockReturnValue({
|
|
57
66
|
data: [],
|
|
58
67
|
isLoading: false,
|
|
68
|
+
error: null,
|
|
59
69
|
});
|
|
70
|
+
|
|
60
71
|
expect(() => {
|
|
61
72
|
render(
|
|
62
73
|
<Formik initialValues={{}} onSubmit={() => {}}>
|
|
@@ -25,7 +25,7 @@ export function LocationPersonAttributeField({
|
|
|
25
25
|
const { t } = useTranslation();
|
|
26
26
|
const fieldName = `attributes.${personAttributeType.uuid}`;
|
|
27
27
|
const [field, meta, { setValue }] = useField(`attributes.${personAttributeType.uuid}`);
|
|
28
|
-
const [searchQuery, setSearchQuery] = useState
|
|
28
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
29
29
|
const { locations, isLoading, loadingNewData } = useLocations(locationTag || null, searchQuery);
|
|
30
30
|
const prevLocationOptions = useRef([]);
|
|
31
31
|
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Form, Formik } from 'formik';
|
|
3
3
|
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { type FieldDefinition } from '../../../config-schema';
|
|
4
5
|
import { usePersonAttributeType } from './person-attributes.resource';
|
|
5
6
|
import { useConceptAnswers } from '../field.resource';
|
|
6
|
-
import { type FieldDefinition } from '../../../config-schema';
|
|
7
7
|
import { PersonAttributeField } from './person-attribute-field.component';
|
|
8
8
|
|
|
9
|
-
jest.mock('./person-attributes.resource')
|
|
10
|
-
jest.
|
|
9
|
+
jest.mock('./person-attributes.resource', () => ({
|
|
10
|
+
...jest.requireActual('./person-attributes.resource'),
|
|
11
|
+
usePersonAttributeType: jest.fn(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock('../field.resource', () => ({
|
|
15
|
+
...jest.requireActual('../field.resource'),
|
|
16
|
+
useConceptAnswers: jest.fn(),
|
|
17
|
+
}));
|
|
11
18
|
|
|
12
19
|
const mockUsePersonAttributeType = jest.mocked(usePersonAttributeType);
|
|
13
20
|
const mockUseConceptAnswers = jest.mocked(useConceptAnswers);
|
|
@@ -92,6 +99,7 @@ describe('PersonAttributeField', () => {
|
|
|
92
99
|
{ uuid: '1', display: 'Option 1' },
|
|
93
100
|
{ uuid: '2', display: 'Option 2' },
|
|
94
101
|
],
|
|
102
|
+
error: null,
|
|
95
103
|
isLoading: false,
|
|
96
104
|
});
|
|
97
105
|
|
|
@@ -180,6 +188,6 @@ describe('PersonAttributeField', () => {
|
|
|
180
188
|
);
|
|
181
189
|
|
|
182
190
|
await screen.findByRole('heading', { name: /attribute/i });
|
|
183
|
-
expect(screen.queryByLabelText(/
|
|
191
|
+
expect(screen.queryByLabelText(/referred by/i)).not.toBeInTheDocument();
|
|
184
192
|
});
|
|
185
193
|
});
|
|
@@ -2,7 +2,10 @@ import { FormManager } from './form-manager';
|
|
|
2
2
|
import { type FormValues } from './patient-registration.types';
|
|
3
3
|
import { generateIdentifier } from './patient-registration.resource';
|
|
4
4
|
|
|
5
|
-
jest.mock('./patient-registration.resource')
|
|
5
|
+
jest.mock('./patient-registration.resource', () => ({
|
|
6
|
+
...jest.requireActual('./patient-registration.resource'),
|
|
7
|
+
generateIdentifier: jest.fn(),
|
|
8
|
+
}));
|
|
6
9
|
|
|
7
10
|
const mockGenerateIdentifier = generateIdentifier as jest.Mock;
|
|
8
11
|
|