@ampath/esm-patient-registration-app 9.2.0-next.17 → 9.2.0-next.19
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/2498.js +2 -0
- package/dist/2498.js.map +1 -0
- package/dist/2523.js +1 -0
- package/dist/2523.js.map +1 -0
- 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/6583.js +1 -0
- package/dist/6583.js.map +1 -0
- package/dist/7821.js +1 -1
- package/dist/7821.js.map +1 -1
- package/dist/8414.js +1 -1
- package/dist/8414.js.map +1 -1
- package/dist/8434.js +1 -1
- package/dist/8434.js.map +1 -1
- package/dist/9853.js +1 -0
- package/dist/9853.js.map +1 -0
- 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 +109 -109
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/patient-registration/client-registry/client-registry-search.component.tsx +164 -68
- package/src/patient-registration/client-registry/client-registry-search.scss +26 -0
- package/src/patient-registration/client-registry/existing-client/client-dependants-comparison/client-dependants-comparison.component.tsx +26 -0
- package/src/patient-registration/client-registry/existing-client/client-dependants-comparison/dependant-comparison-rows.component.tsx +153 -0
- package/src/patient-registration/client-registry/existing-client/client-details-comparison/client-details-comparison.component.tsx +181 -0
- package/src/patient-registration/client-registry/existing-client/client-details-comparison/comparison-table-row.component.tsx +42 -0
- package/src/patient-registration/client-registry/existing-client/client-registry-verification-tag.component.tsx +54 -0
- package/src/patient-registration/client-registry/existing-client/existing-client-tab.component.tsx +85 -0
- package/src/patient-registration/client-registry/existing-client/existing-client.resource.ts +78 -0
- package/src/patient-registration/client-registry/existing-client/mapper-utils.ts +419 -0
- package/src/patient-registration/client-registry/existing-client/types/index.ts +185 -0
- package/src/patient-registration/client-registry/hie-client-adapter.ts +56 -0
- package/src/patient-registration/client-registry/new-client/client-dependants/list/client-depandants.component.tsx +55 -0
- package/src/patient-registration/client-registry/new-client/client-details/client-details.tsx +42 -0
- package/src/patient-registration/client-registry/new-client/new-client-tab.component.tsx +30 -0
- package/src/patient-registration/client-registry/types/index.ts +210 -0
- package/src/patient-registration/custom-patient-registration.scss +6 -0
- package/src/patient-registration/patient-registration.component.tsx +24 -12
- package/src/widgets/client-registry-verification.modal.tsx +13 -0
- package/dist/4395.js +0 -1
- package/dist/4395.js.map +0 -1
- package/dist/6996.js +0 -1
- package/dist/6996.js.map +0 -1
- package/dist/8882.js +0 -1
- package/dist/8882.js.map +0 -1
- package/dist/9933.js +0 -2
- package/dist/9933.js.map +0 -1
- /package/dist/{9933.js.LICENSE.txt → 2498.js.LICENSE.txt} +0 -0
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
TextInput,
|
|
5
|
+
InlineLoading,
|
|
6
|
+
InlineNotification,
|
|
7
|
+
Dropdown,
|
|
8
|
+
Modal,
|
|
9
|
+
ModalBody,
|
|
10
|
+
Tabs,
|
|
11
|
+
TabList,
|
|
12
|
+
Tab,
|
|
13
|
+
TabPanels,
|
|
14
|
+
TabPanel,
|
|
15
|
+
} from '@carbon/react';
|
|
3
16
|
import { showSnackbar, useSession } from '@openmrs/esm-framework';
|
|
4
17
|
import { useFormikContext } from 'formik';
|
|
5
|
-
import styles from '
|
|
18
|
+
import styles from './client-registry-search.scss';
|
|
6
19
|
import { requestCustomOtp, validateCustomOtp, fetchClientRegistryData } from './client-registry.resource';
|
|
7
20
|
import { applyClientRegistryMapping } from './map-client-registry-to-form-utils';
|
|
21
|
+
import ClientDetails from './new-client/client-details/client-details';
|
|
22
|
+
import { type HieClient } from './types';
|
|
23
|
+
import ClientDependantList from './new-client/client-dependants/list/client-depandants.component';
|
|
24
|
+
import NewClientTab from './new-client/new-client-tab.component';
|
|
25
|
+
import ExistingClientTab from './existing-client/existing-client-tab.component';
|
|
8
26
|
|
|
9
27
|
export interface ClientRegistryLookupSectionProps {
|
|
10
28
|
onClientVerified?: () => void;
|
|
29
|
+
onModalClose: () => void;
|
|
30
|
+
open: boolean;
|
|
31
|
+
isNewClient: boolean;
|
|
11
32
|
}
|
|
12
33
|
|
|
13
34
|
export type IdentifierType = 'National ID' | 'Alien ID' | 'Passport' | 'Mandate Number' | 'Refugee ID';
|
|
@@ -20,7 +41,12 @@ export const IDENTIFIER_TYPES: IdentifierType[] = [
|
|
|
20
41
|
'Refugee ID',
|
|
21
42
|
];
|
|
22
43
|
|
|
23
|
-
const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> = ({
|
|
44
|
+
const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> = ({
|
|
45
|
+
onClientVerified,
|
|
46
|
+
open,
|
|
47
|
+
onModalClose,
|
|
48
|
+
isNewClient = true,
|
|
49
|
+
}) => {
|
|
24
50
|
const { setFieldValue } = useFormikContext<any>();
|
|
25
51
|
const [identifierType, setIdentifierType] = useState<IdentifierType>('National ID');
|
|
26
52
|
const [identifierValue, setIdentifierValue] = useState('');
|
|
@@ -31,6 +57,7 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
|
|
|
31
57
|
const [sessionId, setSessionId] = useState('');
|
|
32
58
|
const [error, setError] = useState<string>('');
|
|
33
59
|
const { sessionLocation } = useSession();
|
|
60
|
+
const [client, setClient] = useState<HieClient>();
|
|
34
61
|
const locationUuid = sessionLocation?.uuid;
|
|
35
62
|
|
|
36
63
|
async function withTimeout<T>(promise: Promise<T>, ms = 10000): Promise<T> {
|
|
@@ -64,8 +91,7 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
|
|
|
64
91
|
if (patients.length === 0) throw new Error('No matching patient found in Client Registry.');
|
|
65
92
|
|
|
66
93
|
const patient = patients[0];
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
setClient(patient);
|
|
69
95
|
showSnackbar({
|
|
70
96
|
kind: 'success',
|
|
71
97
|
title: 'Client Data Loaded',
|
|
@@ -156,77 +182,147 @@ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> =
|
|
|
156
182
|
}
|
|
157
183
|
};
|
|
158
184
|
|
|
185
|
+
const useHieData = () => {
|
|
186
|
+
applyClientRegistryMapping(client, setFieldValue);
|
|
187
|
+
onModalClose();
|
|
188
|
+
};
|
|
189
|
+
const registerOnAfyaYangu = () => {
|
|
190
|
+
window.open('https://afyayangu.go.ke/', '_blank');
|
|
191
|
+
};
|
|
192
|
+
|
|
159
193
|
return (
|
|
160
|
-
<
|
|
161
|
-
|
|
194
|
+
<Modal
|
|
195
|
+
open={open}
|
|
196
|
+
size="lg"
|
|
197
|
+
onSecondarySubmit={onModalClose}
|
|
198
|
+
onRequestClose={onModalClose}
|
|
199
|
+
onRequestSubmit={registerOnAfyaYangu}
|
|
200
|
+
primaryButtonText="Register on Afya Yangu"
|
|
201
|
+
secondaryButtonText="Cancel">
|
|
202
|
+
<ModalBody>
|
|
203
|
+
<div className={styles.modalVerificationLayout}>
|
|
204
|
+
<h4 className={styles.sectionTitle}>Client Registry Verification</h4>
|
|
162
205
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
</div>
|
|
167
|
-
)}
|
|
168
|
-
|
|
169
|
-
<div className={styles.fieldGroup}>
|
|
170
|
-
<Dropdown
|
|
171
|
-
id="identifier-type-dropdown"
|
|
172
|
-
label="Identifier Type"
|
|
173
|
-
titleText="Select Identifier Type"
|
|
174
|
-
items={IDENTIFIER_TYPES}
|
|
175
|
-
selectedItem={identifierType}
|
|
176
|
-
onChange={({ selectedItem }) => setIdentifierType(selectedItem as IdentifierType)}
|
|
177
|
-
disabled={otpSent}
|
|
178
|
-
/>
|
|
179
|
-
</div>
|
|
180
|
-
|
|
181
|
-
<div className={styles.fieldGroup}>
|
|
182
|
-
<TextInput
|
|
183
|
-
id="identifier-value"
|
|
184
|
-
labelText={`${identifierType} Value`}
|
|
185
|
-
value={identifierValue}
|
|
186
|
-
onChange={(e) => setIdentifierValue(e.target.value)}
|
|
187
|
-
disabled={otpSent}
|
|
188
|
-
placeholder={`Enter ${identifierType.toLowerCase()} value`}
|
|
189
|
-
/>
|
|
190
|
-
</div>
|
|
191
|
-
|
|
192
|
-
<div style={{ marginTop: '0.75rem' }}>
|
|
193
|
-
{!otpSent ? (
|
|
194
|
-
<Button kind="secondary" onClick={handleSendOtp} disabled={loading}>
|
|
195
|
-
{loading ? <InlineLoading description="Sending..." /> : 'Send OTP'}
|
|
196
|
-
</Button>
|
|
197
|
-
) : (
|
|
198
|
-
<>
|
|
199
|
-
<div style={{ marginTop: '0.75rem' }}>
|
|
200
|
-
<TextInput
|
|
201
|
-
id="otp-input"
|
|
202
|
-
labelText="Enter OTP"
|
|
203
|
-
value={otp}
|
|
204
|
-
onChange={(e) => setOtp(e.target.value)}
|
|
205
|
-
disabled={otpVerified}
|
|
206
|
-
placeholder="Enter the code sent to your phone"
|
|
207
|
-
/>
|
|
206
|
+
{error && (
|
|
207
|
+
<div className={styles.notificationSpacing}>
|
|
208
|
+
<InlineNotification title="Error" subtitle={error} kind="error" lowContrast />
|
|
208
209
|
</div>
|
|
210
|
+
)}
|
|
211
|
+
{!client ? (
|
|
212
|
+
<div className={styles.formSection}>
|
|
213
|
+
{!otpSent ? (
|
|
214
|
+
<>
|
|
215
|
+
<div className={styles.formRow}>
|
|
216
|
+
<div className={styles.formControl}>
|
|
217
|
+
<Dropdown
|
|
218
|
+
id="identifier-type-dropdown"
|
|
219
|
+
label="Identifier Type"
|
|
220
|
+
titleText="Select Identifier Type"
|
|
221
|
+
items={IDENTIFIER_TYPES}
|
|
222
|
+
selectedItem={identifierType}
|
|
223
|
+
onChange={({ selectedItem }) => setIdentifierType(selectedItem as IdentifierType)}
|
|
224
|
+
disabled={otpSent}
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
209
227
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
228
|
+
<div className={styles.formControl}>
|
|
229
|
+
<TextInput
|
|
230
|
+
id="identifier-value"
|
|
231
|
+
labelText={`${identifierType} Value`}
|
|
232
|
+
value={identifierValue}
|
|
233
|
+
onChange={(e) => setIdentifierValue(e.target.value)}
|
|
234
|
+
disabled={otpSent}
|
|
235
|
+
placeholder={`Enter ${identifierType.toLowerCase()} value`}
|
|
236
|
+
/>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
<div className={styles.formRow}>
|
|
240
|
+
<div className={styles.formControl}>
|
|
241
|
+
<Button kind="primary" onClick={handleSendOtp} disabled={loading}>
|
|
242
|
+
{loading ? <InlineLoading description="Sending..." /> : 'Send OTP'}
|
|
243
|
+
</Button>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</>
|
|
215
247
|
) : (
|
|
216
|
-
|
|
217
|
-
{
|
|
218
|
-
|
|
248
|
+
<>
|
|
249
|
+
{!otpVerified ? (
|
|
250
|
+
<>
|
|
251
|
+
<div className={styles.formRow}>
|
|
252
|
+
<div className={styles.formControl}>
|
|
253
|
+
<TextInput
|
|
254
|
+
id="otp-input"
|
|
255
|
+
labelText="Enter OTP"
|
|
256
|
+
value={otp}
|
|
257
|
+
onChange={(e) => setOtp(e.target.value)}
|
|
258
|
+
disabled={otpVerified}
|
|
259
|
+
placeholder="Enter the code sent to your phone"
|
|
260
|
+
/>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
<div className={styles.formRow}>
|
|
264
|
+
<div className={styles.actionBtn}>
|
|
265
|
+
<Button kind="primary" onClick={handleVerifyOtp} disabled={loading}>
|
|
266
|
+
{loading ? <InlineLoading description="Verifying..." /> : 'Verify OTP'}
|
|
267
|
+
</Button>
|
|
268
|
+
</div>
|
|
269
|
+
<div className={styles.actionBtn}>
|
|
270
|
+
<Button kind="secondary" onClick={handleSendOtp} disabled={loading}>
|
|
271
|
+
{loading ? <InlineLoading description="Resending OTP..." /> : 'Resend OTP'}
|
|
272
|
+
</Button>
|
|
273
|
+
</div>
|
|
274
|
+
<div className={styles.actionBtn}>
|
|
275
|
+
<Button kind="tertiary" onClick={() => setOtpSent(false)}>
|
|
276
|
+
Back
|
|
277
|
+
</Button>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
</>
|
|
281
|
+
) : (
|
|
282
|
+
<>
|
|
283
|
+
<div className={styles.formRow}>
|
|
284
|
+
<div className={styles.formControl}>
|
|
285
|
+
<Button kind="primary" onClick={handleFetchCR} disabled={loading}>
|
|
286
|
+
{loading ? <InlineLoading description="Fetching..." /> : 'Fetch Client Registry Data'}
|
|
287
|
+
</Button>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
</>
|
|
291
|
+
)}
|
|
292
|
+
</>
|
|
219
293
|
)}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
294
|
+
</div>
|
|
295
|
+
) : (
|
|
296
|
+
<></>
|
|
297
|
+
)}
|
|
298
|
+
|
|
299
|
+
{otpVerified && client ? (
|
|
300
|
+
<div className="clientDataSection">
|
|
301
|
+
{isNewClient ? (
|
|
302
|
+
<NewClientTab client={client} useHieData={useHieData} />
|
|
303
|
+
) : (
|
|
304
|
+
<ExistingClientTab hieClient={client} />
|
|
224
305
|
)}
|
|
306
|
+
{/* <Tabs>
|
|
307
|
+
<TabList contained>
|
|
308
|
+
<Tab>Patient</Tab>
|
|
309
|
+
<Tab>Dependants</Tab>
|
|
310
|
+
</TabList>
|
|
311
|
+
<TabPanels>
|
|
312
|
+
<TabPanel>{client ? <ClientDetails client={client} /> : <></>}</TabPanel>
|
|
313
|
+
<TabPanel>
|
|
314
|
+
{client.dependants ? <ClientDependantList hieDependants={client.dependants} /> : <></>}
|
|
315
|
+
</TabPanel>
|
|
316
|
+
</TabPanels>
|
|
317
|
+
</Tabs>
|
|
318
|
+
<Button onClick={useHieData}>Use Data</Button> */}
|
|
225
319
|
</div>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
320
|
+
) : (
|
|
321
|
+
<></>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
</ModalBody>
|
|
325
|
+
</Modal>
|
|
230
326
|
);
|
|
231
327
|
};
|
|
232
328
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
.modalVerificationLayout {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
row-gap: 10px;
|
|
5
|
+
width: 100%;
|
|
6
|
+
}
|
|
7
|
+
.formSection {
|
|
8
|
+
display: flex;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
width: 100%;
|
|
11
|
+
}
|
|
12
|
+
.formControl {
|
|
13
|
+
width: 45%;
|
|
14
|
+
}
|
|
15
|
+
.formRow {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: row;
|
|
18
|
+
width: 100%;
|
|
19
|
+
column-gap: 10px;
|
|
20
|
+
padding-top: 5px;
|
|
21
|
+
padding-bottom: 5px;
|
|
22
|
+
}
|
|
23
|
+
.actionBtn {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Accordion } from '@carbon/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { type ClientDependantsComparisonProps } from '../types';
|
|
4
|
+
import ClientDependantComparisonRows from './dependant-comparison-rows.component';
|
|
5
|
+
|
|
6
|
+
const ClientDependantsComparison: React.FC<ClientDependantsComparisonProps> = ({
|
|
7
|
+
hieDependants,
|
|
8
|
+
amrsClient,
|
|
9
|
+
patientRelationships,
|
|
10
|
+
}) => {
|
|
11
|
+
return (
|
|
12
|
+
<div>
|
|
13
|
+
<Accordion size="lg">
|
|
14
|
+
{hieDependants.map((dependant) => (
|
|
15
|
+
<ClientDependantComparisonRows
|
|
16
|
+
hieDependant={dependant}
|
|
17
|
+
amrsClient={amrsClient}
|
|
18
|
+
patientRelationships={patientRelationships}
|
|
19
|
+
/>
|
|
20
|
+
))}
|
|
21
|
+
</Accordion>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default ClientDependantsComparison;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { AccordionItem, Row, Column, Select, SelectItem, Button } from '@carbon/react';
|
|
2
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { createPerson, createRelationship, fetchAmrsPersonData } from '../existing-client.resource';
|
|
6
|
+
import {
|
|
7
|
+
patientObjFields,
|
|
8
|
+
mapFieldValue,
|
|
9
|
+
getPatientAttributes,
|
|
10
|
+
personSyncFields,
|
|
11
|
+
nameFields,
|
|
12
|
+
addressFields,
|
|
13
|
+
getPatientRelationshipPayload,
|
|
14
|
+
} from '../mapper-utils';
|
|
15
|
+
import { type AmrsClient, type ClientDependantComparisonRowsProps } from '../types';
|
|
16
|
+
import { type HieDependant } from '../../types';
|
|
17
|
+
import ClientDetailsComparison from '../client-details-comparison/client-details-comparison.component';
|
|
18
|
+
|
|
19
|
+
const ClientDependantComparisonRows: React.FC<ClientDependantComparisonRowsProps> = ({
|
|
20
|
+
hieDependant,
|
|
21
|
+
patientRelationships,
|
|
22
|
+
amrsClient,
|
|
23
|
+
}) => {
|
|
24
|
+
const { t } = useTranslation();
|
|
25
|
+
const [amrsRelationExists, setAmrsRelationExists] = useState(false);
|
|
26
|
+
const [amrsDependantData, setAmrsDependantData] = useState<AmrsClient>();
|
|
27
|
+
const [selectedRelationship, setSelectedRelationship] = useState('');
|
|
28
|
+
|
|
29
|
+
const handleCreateDependant = async (dependantBody: HieDependant) => {
|
|
30
|
+
try {
|
|
31
|
+
const syncFields = {};
|
|
32
|
+
patientObjFields.forEach((field) => {
|
|
33
|
+
let fieldArr = mapFieldValue(field, dependantBody.result[0], amrsClient);
|
|
34
|
+
const hieFieldValue = fieldArr[1];
|
|
35
|
+
syncFields[field] = hieFieldValue;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Person
|
|
39
|
+
const patientPayload = {};
|
|
40
|
+
const names = {};
|
|
41
|
+
const addresses = {};
|
|
42
|
+
const otherFields = {};
|
|
43
|
+
const attributes = getPatientAttributes(dependantBody.result[0]);
|
|
44
|
+
Object.entries(syncFields).forEach(([k, v]) => {
|
|
45
|
+
if (personSyncFields.includes(k)) {
|
|
46
|
+
// names
|
|
47
|
+
if (nameFields.includes(k)) {
|
|
48
|
+
names[k] = v;
|
|
49
|
+
}
|
|
50
|
+
// addresses
|
|
51
|
+
else if (addressFields.includes(k)) {
|
|
52
|
+
if (v) {
|
|
53
|
+
addresses[k] = v;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
otherFields[k] = v;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
Object.assign(
|
|
61
|
+
patientPayload,
|
|
62
|
+
otherFields,
|
|
63
|
+
{ addresses: [addresses] },
|
|
64
|
+
{ names: [names] },
|
|
65
|
+
{ attributes: attributes },
|
|
66
|
+
);
|
|
67
|
+
const response = await createPerson(patientPayload);
|
|
68
|
+
if (response && response.data) {
|
|
69
|
+
showSnackbar({
|
|
70
|
+
kind: 'success',
|
|
71
|
+
title: 'Dependant created successfully.',
|
|
72
|
+
});
|
|
73
|
+
const relationshipPayload = getPatientRelationshipPayload(
|
|
74
|
+
amrsClient,
|
|
75
|
+
dependantBody.relationship,
|
|
76
|
+
response.data.uuid,
|
|
77
|
+
);
|
|
78
|
+
await createRelationship(relationshipPayload);
|
|
79
|
+
showSnackbar({
|
|
80
|
+
kind: 'success',
|
|
81
|
+
title: 'Dependant relationship created successfully.',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
showSnackbar({
|
|
86
|
+
kind: 'error',
|
|
87
|
+
title: 'Error syncing patient data.',
|
|
88
|
+
subtitle: JSON.stringify(err),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleSelectAmrsDependant = async (e: any) => {
|
|
94
|
+
const uuid = e.target.value;
|
|
95
|
+
if (!uuid) {
|
|
96
|
+
setAmrsRelationExists(false);
|
|
97
|
+
setAmrsDependantData(null);
|
|
98
|
+
}
|
|
99
|
+
const response = await fetchAmrsPersonData(uuid);
|
|
100
|
+
if (response && response.data) {
|
|
101
|
+
setAmrsRelationExists(true);
|
|
102
|
+
setAmrsDependantData((prev) => ({ ...prev, person: response.data }));
|
|
103
|
+
} else {
|
|
104
|
+
setAmrsRelationExists(false);
|
|
105
|
+
setAmrsDependantData(null);
|
|
106
|
+
const relationshipUuid = patientRelationships.find((v) => v.relatedPersonUuid === uuid);
|
|
107
|
+
if (relationshipUuid && relationshipUuid.relationshipType) {
|
|
108
|
+
setSelectedRelationship(relationshipUuid.relationshipType);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const getDependantName = () => {
|
|
114
|
+
const name = hieDependant.result[0];
|
|
115
|
+
return `${name.first_name} ${name.middle_name} ${name.last_name} (${hieDependant.relationship})`;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<>
|
|
120
|
+
<AccordionItem title={getDependantName()}>
|
|
121
|
+
<Row>
|
|
122
|
+
<Column>
|
|
123
|
+
<Select labelText={t('amrsDependants', 'AMRS dependants')} onChange={handleSelectAmrsDependant}>
|
|
124
|
+
<SelectItem text={t('selectAmrsRelation', 'Select AMRS relation')} value="" />
|
|
125
|
+
{patientRelationships.map((relationship) => (
|
|
126
|
+
<SelectItem
|
|
127
|
+
text={`${relationship.display} (${relationship.relationshipType})`}
|
|
128
|
+
value={relationship.relatedPerson.uuid}
|
|
129
|
+
/>
|
|
130
|
+
))}
|
|
131
|
+
</Select>
|
|
132
|
+
</Column>
|
|
133
|
+
{!amrsRelationExists ? (
|
|
134
|
+
<Column>
|
|
135
|
+
<Button onClick={() => handleCreateDependant(hieDependant)}>
|
|
136
|
+
{t('createDependant', 'Create dependant in AMRS')}
|
|
137
|
+
</Button>
|
|
138
|
+
</Column>
|
|
139
|
+
) : null}
|
|
140
|
+
</Row>
|
|
141
|
+
<Row>
|
|
142
|
+
<ClientDetailsComparison
|
|
143
|
+
hieClient={hieDependant.result[0]}
|
|
144
|
+
amrsClient={amrsDependantData}
|
|
145
|
+
fromDependant={true}
|
|
146
|
+
/>
|
|
147
|
+
</Row>
|
|
148
|
+
</AccordionItem>
|
|
149
|
+
</>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export default ClientDependantComparisonRows;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { type ClientDetailsComparisonProps } from '../types';
|
|
3
|
+
import {
|
|
4
|
+
Row,
|
|
5
|
+
Button,
|
|
6
|
+
InlineLoading,
|
|
7
|
+
Table,
|
|
8
|
+
TableHead,
|
|
9
|
+
TableRow,
|
|
10
|
+
TableHeader,
|
|
11
|
+
Checkbox,
|
|
12
|
+
TableBody,
|
|
13
|
+
} from '@carbon/react';
|
|
14
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
|
15
|
+
import { HieIdentificationType } from '../../types';
|
|
16
|
+
import {
|
|
17
|
+
personSyncFields,
|
|
18
|
+
nameFields,
|
|
19
|
+
addressFields,
|
|
20
|
+
identifiersSyncFields,
|
|
21
|
+
getIdentifierUuid,
|
|
22
|
+
patientObjFields,
|
|
23
|
+
mapFieldValue,
|
|
24
|
+
} from '../mapper-utils';
|
|
25
|
+
import ComparisonTableRow from './comparison-table-row.component';
|
|
26
|
+
import { updateAmrsPersonIdentifiers, updatePerson } from '../existing-client.resource';
|
|
27
|
+
|
|
28
|
+
const ClientDetailsComparison: React.FC<ClientDetailsComparisonProps> = ({ hieClient, amrsClient, fromDependant }) => {
|
|
29
|
+
const [syncFields, setSyncFields] = useState<Array<Record<string, string>>>([]);
|
|
30
|
+
const [allChecked, setAllChecked] = useState(false);
|
|
31
|
+
const [loading, setLoading] = useState(false);
|
|
32
|
+
const locationUuid = '18c343eb-b353-462a-9139-b16606e6b6c2';
|
|
33
|
+
const randomString = Math.random().toString(10).substring(2, 6).toUpperCase();
|
|
34
|
+
|
|
35
|
+
const handleFieldChange = (checked: boolean, field: string, value: string, multiple: boolean) => {
|
|
36
|
+
if (multiple) {
|
|
37
|
+
if (checked) {
|
|
38
|
+
setSyncFields((prev) => [...prev, { [field]: value }]);
|
|
39
|
+
} else {
|
|
40
|
+
setSyncFields([]);
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
if (checked) {
|
|
44
|
+
setSyncFields([...syncFields, { [field]: value }]);
|
|
45
|
+
} else {
|
|
46
|
+
setSyncFields((prev) => prev.filter((p) => !Object.keys(p).includes(field)));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleCheckAll = (e) => {
|
|
52
|
+
setAllChecked(e.target.checked);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const handleSync = async () => {
|
|
56
|
+
try {
|
|
57
|
+
const payload = {};
|
|
58
|
+
syncFields.forEach((field) => {
|
|
59
|
+
let key = Object.keys(field)[0];
|
|
60
|
+
payload[key] = field[key];
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Person
|
|
64
|
+
const patientPayload = {};
|
|
65
|
+
const names = {};
|
|
66
|
+
const addresses = {};
|
|
67
|
+
const otherFields = {};
|
|
68
|
+
Object.entries(payload).forEach(([k, v]) => {
|
|
69
|
+
if (personSyncFields.includes(k)) {
|
|
70
|
+
// names
|
|
71
|
+
if (nameFields.includes(k)) {
|
|
72
|
+
names[k] = v;
|
|
73
|
+
}
|
|
74
|
+
// addresses
|
|
75
|
+
else if (addressFields.includes(k)) {
|
|
76
|
+
if (v) {
|
|
77
|
+
addresses[k] = v;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
otherFields[k] = v;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
Object.assign(patientPayload, otherFields, { addresses: [addresses] }, { names: [names] });
|
|
85
|
+
await updatePerson(amrsClient.person.uuid, patientPayload);
|
|
86
|
+
showSnackbar({
|
|
87
|
+
kind: 'success',
|
|
88
|
+
title: 'Patient successfully synced.',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Identifiers
|
|
92
|
+
Object.entries(payload).forEach(async ([k, v]) => {
|
|
93
|
+
if (identifiersSyncFields().includes(k)) {
|
|
94
|
+
if (v) {
|
|
95
|
+
const identifierUuid = getIdentifierUuid(HieIdentificationType[k]);
|
|
96
|
+
const identifierPayload = {
|
|
97
|
+
identifier: v,
|
|
98
|
+
location: locationUuid,
|
|
99
|
+
identifierType: identifierUuid,
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
// Check if the identifier exists
|
|
103
|
+
if (amrsClient?.person?.identifiers?.find((i) => i.identifierType.uuid === identifierUuid)) {
|
|
104
|
+
// update to have the selected identifier
|
|
105
|
+
await updateAmrsPersonIdentifiers(
|
|
106
|
+
amrsClient.person.uuid,
|
|
107
|
+
identifierUuid + '',
|
|
108
|
+
identifierPayload,
|
|
109
|
+
fromDependant,
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
// create to have the blank identifier
|
|
113
|
+
await updateAmrsPersonIdentifiers(amrsClient.person.uuid, '', identifierPayload, fromDependant);
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
showSnackbar({
|
|
117
|
+
kind: 'error',
|
|
118
|
+
title: 'Error syncing patient identifiers.',
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
showSnackbar({
|
|
125
|
+
kind: 'success',
|
|
126
|
+
title: 'Patient identifiers successfully synced.',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
setSyncFields([]);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
showSnackbar({
|
|
132
|
+
kind: 'error',
|
|
133
|
+
title: 'Error syncing patient data.',
|
|
134
|
+
subtitle: JSON.stringify(err?.error?.message),
|
|
135
|
+
});
|
|
136
|
+
} finally {
|
|
137
|
+
setLoading(false);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<>
|
|
143
|
+
<Table>
|
|
144
|
+
<TableHead>
|
|
145
|
+
<TableRow>
|
|
146
|
+
<TableHeader>Field</TableHeader>
|
|
147
|
+
<TableHeader>AMRS Person</TableHeader>
|
|
148
|
+
<TableHeader>HIE Patient</TableHeader>
|
|
149
|
+
<TableHeader>
|
|
150
|
+
<Checkbox id={`cbox-multiple-${randomString}`} onChange={(e) => handleCheckAll(e)} />
|
|
151
|
+
</TableHeader>
|
|
152
|
+
</TableRow>
|
|
153
|
+
</TableHead>
|
|
154
|
+
<TableBody>
|
|
155
|
+
{patientObjFields.map((field) => {
|
|
156
|
+
const fieldValue = mapFieldValue(field, hieClient, amrsClient);
|
|
157
|
+
const amrsField = fieldValue[0];
|
|
158
|
+
const hieField = fieldValue[1];
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<ComparisonTableRow
|
|
162
|
+
label={field}
|
|
163
|
+
field={field}
|
|
164
|
+
amrsValue={amrsField}
|
|
165
|
+
hieValue={hieField}
|
|
166
|
+
onChange={handleFieldChange}
|
|
167
|
+
allChecked={allChecked}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</TableBody>
|
|
172
|
+
</Table>
|
|
173
|
+
<Row>
|
|
174
|
+
{syncFields.length ? <Button onClick={handleSync}>Sync data</Button> : null}
|
|
175
|
+
{loading ? <InlineLoading description="Syncing patient details..." /> : null}
|
|
176
|
+
</Row>
|
|
177
|
+
</>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export default ClientDetailsComparison;
|