@ampath/esm-patient-registration-app 9.2.0-next.14 → 9.2.0-next.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/2450.js +1 -0
- package/dist/2450.js.map +1 -0
- package/dist/2615.js +2 -0
- package/dist/2615.js.map +1 -0
- package/dist/3474.js +1 -1
- package/dist/3474.js.map +1 -1
- package/dist/5239.js +1 -1
- package/dist/5239.js.map +1 -1
- package/dist/6276.js +1 -1
- package/dist/6276.js.map +1 -1
- package/dist/8434.js +1 -1
- package/dist/8434.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-registration-app.js +1 -1
- package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +59 -59
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/patient-registration/client-registry/client-registry-search.component.tsx +233 -0
- package/src/patient-registration/client-registry/client-registry.resource.ts +79 -0
- package/src/patient-registration/client-registry/map-client-registry-to-form-utils.ts +190 -0
- package/src/patient-registration/patient-registration.component.tsx +25 -8
- package/dist/1909.js +0 -2
- package/dist/1909.js.map +0 -1
- package/dist/627.js +0 -1
- package/dist/627.js.map +0 -1
- /package/dist/{1909.js.LICENSE.txt → 2615.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
interface PatientData {
|
|
2
|
+
[key: string]: any;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface Dependant {
|
|
6
|
+
relationship: string;
|
|
7
|
+
result: Array<{
|
|
8
|
+
first_name: string;
|
|
9
|
+
middle_name: string;
|
|
10
|
+
last_name: string;
|
|
11
|
+
gender: string;
|
|
12
|
+
date_of_birth: string;
|
|
13
|
+
identification_number: string;
|
|
14
|
+
identification_type: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface AlternativeContact {
|
|
19
|
+
contact_type: string;
|
|
20
|
+
contact_id: string;
|
|
21
|
+
contact_name: string;
|
|
22
|
+
relationship: string;
|
|
23
|
+
remarks: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fieldMapping: Record<string, string | { path: string; transform?: (value: any) => any }> = {
|
|
27
|
+
givenName: 'first_name',
|
|
28
|
+
familyName: 'last_name',
|
|
29
|
+
middleName: 'middle_name',
|
|
30
|
+
birthdate: 'date_of_birth',
|
|
31
|
+
phone: 'phone',
|
|
32
|
+
email: 'email',
|
|
33
|
+
identificationNumber: 'identification_number',
|
|
34
|
+
identificationType: 'identification_type',
|
|
35
|
+
county: 'county',
|
|
36
|
+
subCounty: 'sub_county',
|
|
37
|
+
ward: 'ward',
|
|
38
|
+
village: 'village_estate',
|
|
39
|
+
postalAddress: 'postal_address',
|
|
40
|
+
address: 'address',
|
|
41
|
+
gender: {
|
|
42
|
+
path: 'gender',
|
|
43
|
+
transform: (value) => {
|
|
44
|
+
if (!value) return '';
|
|
45
|
+
const genderMap: Record<string, string> = {
|
|
46
|
+
male: 'male',
|
|
47
|
+
m: 'male',
|
|
48
|
+
female: 'female',
|
|
49
|
+
f: 'female',
|
|
50
|
+
other: 'other',
|
|
51
|
+
unknown: 'unknown',
|
|
52
|
+
};
|
|
53
|
+
return genderMap[value.toLowerCase()] || value.toLowerCase();
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
'academicOccupation.highestLevelEducation': {
|
|
57
|
+
path: 'employment_type',
|
|
58
|
+
transform: (value) => value || '',
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
// Occupation field - also using employment_type (you might want to separate these)
|
|
62
|
+
'academicOccupation.occupation': {
|
|
63
|
+
path: 'employment_type',
|
|
64
|
+
transform: (value) => value || '',
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
// Civil status/marital status
|
|
68
|
+
civilStatus: {
|
|
69
|
+
path: 'civil_status',
|
|
70
|
+
transform: (value) => value || '',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function mapNextOfKin(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
|
|
75
|
+
const alternativeContacts: AlternativeContact[] = patient.alternative_contacts || [];
|
|
76
|
+
|
|
77
|
+
// Find spouse as next of kin (highest priority)
|
|
78
|
+
const spouseContact = alternativeContacts.find((contact) => contact.relationship.toLowerCase() === 'spouse');
|
|
79
|
+
|
|
80
|
+
// If no spouse, find any next of kin
|
|
81
|
+
const nextOfKinContact =
|
|
82
|
+
spouseContact ||
|
|
83
|
+
alternativeContacts.find(
|
|
84
|
+
(contact) =>
|
|
85
|
+
contact.remarks.toLowerCase().includes('next of kin') ||
|
|
86
|
+
contact.relationship.toLowerCase().includes('next of kin'),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (nextOfKinContact) {
|
|
90
|
+
setFieldValue('nextOfKin.nextOfKinName', nextOfKinContact.contact_name);
|
|
91
|
+
setFieldValue('nextOfKin.nextOfKinRelationship', nextOfKinContact.relationship);
|
|
92
|
+
setFieldValue('nextOfKin.nextOfKinPhoneNumber', nextOfKinContact.contact_id);
|
|
93
|
+
setFieldValue('nextOfKin.nextOfKinResidence', patient.village_estate || patient.county || '');
|
|
94
|
+
} else {
|
|
95
|
+
setFieldValue('nextOfKin.nextOfKinName', '');
|
|
96
|
+
setFieldValue('nextOfKin.nextOfKinRelationship', '');
|
|
97
|
+
setFieldValue('nextOfKin.nextOfKinPhoneNumber', '');
|
|
98
|
+
setFieldValue('nextOfKin.nextOfKinResidence', '');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function mapRelationships(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
|
|
103
|
+
const dependants: Dependant[] = patient.dependants || [];
|
|
104
|
+
const relationships: any[] = [];
|
|
105
|
+
|
|
106
|
+
dependants.forEach((dependant, index) => {
|
|
107
|
+
if (dependant.result && dependant.result.length > 0) {
|
|
108
|
+
const person = dependant.result[0];
|
|
109
|
+
relationships.push({
|
|
110
|
+
relationshipType: dependant.relationship, // "Child"
|
|
111
|
+
relatedPersonName: `${person.first_name} ${person.middle_name || ''} ${person.last_name}`.trim(),
|
|
112
|
+
relatedPersonUuid: '', // Not available in CR
|
|
113
|
+
relationshipTypeUuid: '', // Will need to map to OpenMRS relationship type UUID
|
|
114
|
+
// Additional fields that might be needed
|
|
115
|
+
relativeName: `${person.first_name} ${person.middle_name || ''} ${person.last_name}`.trim(),
|
|
116
|
+
relativePhone: '', // Not available in dependants
|
|
117
|
+
relationship: dependant.relationship,
|
|
118
|
+
birthdate: person.date_of_birth,
|
|
119
|
+
gender: person.gender,
|
|
120
|
+
identifier: person.identification_number,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
setFieldValue('relationships', relationships);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function mapAddresses(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
|
|
128
|
+
// county
|
|
129
|
+
if (patient['county']) {
|
|
130
|
+
setFieldValue('address.countyDistrict', patient['county']);
|
|
131
|
+
}
|
|
132
|
+
//subcounty
|
|
133
|
+
if (patient['sub_county']) {
|
|
134
|
+
setFieldValue('address.stateProvince', patient['sub_county']);
|
|
135
|
+
}
|
|
136
|
+
if (patient['ward']) {
|
|
137
|
+
//ward
|
|
138
|
+
setFieldValue('address.address4', patient['ward']);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function mapContactDetails(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
|
|
143
|
+
if (patient['email']) {
|
|
144
|
+
setFieldValue('attributes.2f65dbcb-3e58-45a3-8be7-fd1dc9aa0faa', patient['email']);
|
|
145
|
+
}
|
|
146
|
+
if (patient['phone']) {
|
|
147
|
+
setFieldValue('attributes.72a759a8-1359-11df-a1f1-0026b9348838', patient['phone']);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function mapIdentifiers(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
|
|
152
|
+
if (patient['id']) {
|
|
153
|
+
const crIdentifier = {
|
|
154
|
+
identifierTypeUuid: 'e88dc246-3614-4ee3-8141-1f2a83054e72',
|
|
155
|
+
initialValue: patient['id'],
|
|
156
|
+
identifierValue: patient['id'],
|
|
157
|
+
identifierName: 'CR',
|
|
158
|
+
selectedSource: null,
|
|
159
|
+
autoGeneration: false,
|
|
160
|
+
preferred: false,
|
|
161
|
+
required: true,
|
|
162
|
+
};
|
|
163
|
+
setFieldValue('identifiers.cr', crIdentifier);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function applyClientRegistryMapping(patient: PatientData, setFieldValue: (field: string, value: any) => void) {
|
|
168
|
+
Object.entries(fieldMapping).forEach(([formField, mapping]) => {
|
|
169
|
+
let crField: string;
|
|
170
|
+
let transformFn: (value: any) => any = (v) => v;
|
|
171
|
+
|
|
172
|
+
if (typeof mapping === 'string') {
|
|
173
|
+
crField = mapping;
|
|
174
|
+
} else {
|
|
175
|
+
crField = mapping.path;
|
|
176
|
+
transformFn = mapping.transform || transformFn;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (crField && patient[crField] !== undefined && patient[crField] !== null && patient[crField] !== '') {
|
|
180
|
+
const value = transformFn(patient[crField]);
|
|
181
|
+
setFieldValue(formField, value);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
mapNextOfKin(patient, setFieldValue);
|
|
186
|
+
mapRelationships(patient, setFieldValue);
|
|
187
|
+
mapAddresses(patient, setFieldValue);
|
|
188
|
+
mapContactDetails(patient, setFieldValue);
|
|
189
|
+
mapIdentifiers(patient, setFieldValue);
|
|
190
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import { Button, InlineLoading, Link } from '@carbon/react';
|
|
3
|
+
import { Button, InlineLoading, Link, InlineNotification } from '@carbon/react';
|
|
4
4
|
import { XAxis } from '@carbon/react/icons';
|
|
5
5
|
import { useLocation, useParams } from 'react-router-dom';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
@@ -25,6 +25,7 @@ import { type SavePatientForm, SavePatientTransactionManager } from './form-mana
|
|
|
25
25
|
import { useInitialAddressFieldValues, useInitialFormValues, usePatientUuidMap } from './patient-registration-hooks';
|
|
26
26
|
import BeforeSavePrompt from './before-save-prompt';
|
|
27
27
|
import styles from './patient-registration.scss';
|
|
28
|
+
import ClientRegistryLookupSection from './client-registry/client-registry-search.component';
|
|
28
29
|
|
|
29
30
|
let exportedInitialFormValuesForTesting = {} as FormValues;
|
|
30
31
|
|
|
@@ -56,6 +57,7 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
56
57
|
const [patientUuidMap] = usePatientUuidMap({}, isLoadingPatientToEdit, patientToEdit, uuidOfPatientToEdit);
|
|
57
58
|
|
|
58
59
|
const [target, setTarget] = useState<undefined | string>();
|
|
60
|
+
const [isClientVerified, setIsClientVerified] = useState(false);
|
|
59
61
|
const [capturePhotoProps, setCapturePhotoProps] = useState<CapturePhotoProps | null>(null);
|
|
60
62
|
|
|
61
63
|
const location = currentSession?.sessionLocation?.uuid;
|
|
@@ -243,13 +245,28 @@ export const PatientRegistration: React.FC<PatientRegistrationProps> = ({ savePa
|
|
|
243
245
|
</div>
|
|
244
246
|
<div className={styles.infoGrid}>
|
|
245
247
|
<PatientRegistrationContextProvider value={createContextValue(props)}>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
<div>
|
|
249
|
+
{!isClientVerified && !inEditMode && (
|
|
250
|
+
<div className={styles.notificationSpacing} style={{ marginTop: '1rem' }}>
|
|
251
|
+
<InlineNotification
|
|
252
|
+
title="Verification required"
|
|
253
|
+
subtitle="Please complete Client Registry OTP verification before proceeding with registration."
|
|
254
|
+
kind="info"
|
|
255
|
+
lowContrast
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
)}
|
|
259
|
+
|
|
260
|
+
<ClientRegistryLookupSection onClientVerified={() => setIsClientVerified(true)} />
|
|
261
|
+
|
|
262
|
+
{sections.map((section, index) => (
|
|
263
|
+
<SectionWrapper
|
|
264
|
+
key={`registration-section-${section.id}`}
|
|
265
|
+
sectionDefinition={section}
|
|
266
|
+
index={index}
|
|
267
|
+
/>
|
|
268
|
+
))}
|
|
269
|
+
</div>
|
|
253
270
|
</PatientRegistrationContextProvider>
|
|
254
271
|
</div>
|
|
255
272
|
</div>
|