@kenyaemr/esm-patient-registration-app 8.0.3-pre.136 → 8.0.3-pre.140
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 +17 -17
- package/dist/108.js +1 -1
- package/dist/250.js +1 -1
- package/dist/574.js +1 -1
- package/dist/610.js +1 -0
- package/dist/610.js.map +1 -0
- package/dist/662.js +1 -1
- package/dist/76.js +1 -1
- package/dist/94.js +2 -0
- package/dist/94.js.map +1 -0
- package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
- package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +53 -53
- 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 +1 -1
- package/src/client-registry/hie-client-registry/dependants/dependants.component.tsx +48 -0
- package/src/client-registry/hie-client-registry/hie-resource.ts +78 -0
- package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +76 -72
- package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +43 -2
- 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 +73 -0
- package/src/client-registry/hie-client-registry/patient-info/patient-info.component.tsx +17 -0
- package/translations/en.json +12 -0
- package/dist/66.js +0 -1
- package/dist/66.js.map +0 -1
- package/dist/895.js +0 -2
- package/dist/895.js.map +0 -1
- /package/dist/{895.js.LICENSE.txt → 94.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":"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.0.3-pre.
|
|
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.0.3-pre.140"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenyaemr/esm-patient-registration-app",
|
|
3
|
-
"version": "8.0.3-pre.
|
|
3
|
+
"version": "8.0.3-pre.140",
|
|
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",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import styles from '../modal/confirm-hie.scss';
|
|
4
|
+
import PatientInfo from '../patient-info/patient-info.component';
|
|
5
|
+
|
|
6
|
+
const DependentInfo: React.FC<{ dependents: any[] }> = ({ dependents }) => {
|
|
7
|
+
const { t } = useTranslation();
|
|
8
|
+
|
|
9
|
+
if (dependents && dependents.length > 0) {
|
|
10
|
+
return (
|
|
11
|
+
<div>
|
|
12
|
+
<span className={styles.header}>{t('dependants', 'Dependants')}</span>
|
|
13
|
+
{dependents.map((dependent, index) => {
|
|
14
|
+
const name = dependent?.name?.text;
|
|
15
|
+
const relationship =
|
|
16
|
+
dependent?.relationship?.[0]?.coding?.[0]?.display || t('unknownRelationship', 'Unknown');
|
|
17
|
+
const nationalID = dependent?.extension?.find(
|
|
18
|
+
(ext) =>
|
|
19
|
+
ext.url === 'http://cr.tiberbu.app/fhir/StructureDefinition/dependants-id-number' &&
|
|
20
|
+
ext.valueIdentifier?.type?.coding?.[0]?.code === 'national-id',
|
|
21
|
+
)?.valueIdentifier?.value;
|
|
22
|
+
const birthCertificate = dependent?.extension?.find(
|
|
23
|
+
(ext) =>
|
|
24
|
+
ext.url === 'http://cr.tiberbu.app/fhir/StructureDefinition/dependants-id-number' &&
|
|
25
|
+
ext.valueIdentifier?.type?.coding?.[0]?.code === 'birth-certificate-number',
|
|
26
|
+
)?.valueIdentifier?.value;
|
|
27
|
+
|
|
28
|
+
const primaryIdentifier = nationalID || birthCertificate;
|
|
29
|
+
const identifierLabel = nationalID
|
|
30
|
+
? t('nationalID', 'National ID')
|
|
31
|
+
: t('birthCertificate', 'Birth Certificate');
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div key={index} className={styles.dependentInfo}>
|
|
35
|
+
<PatientInfo label={t('name', 'Name')} value={name} />
|
|
36
|
+
<PatientInfo label={t('relationship', 'Relationship')} value={relationship} />
|
|
37
|
+
<PatientInfo label={identifierLabel} value={primaryIdentifier} />
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
})}
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default DependentInfo;
|
|
@@ -3,6 +3,8 @@ import { type PatientIdentifierValue, type FormValues } from '../../patient-regi
|
|
|
3
3
|
import { type MapperConfig, type HIEPatient, type ErrorResponse } from './hie-types';
|
|
4
4
|
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
|
|
5
5
|
import { v4 } from 'uuid';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import dayjs from 'dayjs';
|
|
6
8
|
/**
|
|
7
9
|
* Represents a client for interacting with a Health Information Exchange (HIE) resource.
|
|
8
10
|
* @template T - The type of the resource being fetched.
|
|
@@ -182,3 +184,79 @@ export const getPatientName = (patient: fhir.Patient) => {
|
|
|
182
184
|
const middleName = patient.name[0]?.['given']?.[0]?.replace(givenName, '')?.trim() ?? '';
|
|
183
185
|
return { familyName, givenName, middleName };
|
|
184
186
|
};
|
|
187
|
+
|
|
188
|
+
export const authorizationFormSchema = z.object({
|
|
189
|
+
otp: z.string().min(1, 'Required'),
|
|
190
|
+
receiver: z
|
|
191
|
+
.string()
|
|
192
|
+
.regex(/^(\+?254|0)((7|1)\d{8})$/)
|
|
193
|
+
.optional(),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
export function generateOTP(length = 5) {
|
|
197
|
+
let otpNumbers = '0123456789';
|
|
198
|
+
let OTP = '';
|
|
199
|
+
const len = otpNumbers.length;
|
|
200
|
+
for (let i = 0; i < length; i++) {
|
|
201
|
+
OTP += otpNumbers[Math.floor(Math.random() * len)];
|
|
202
|
+
}
|
|
203
|
+
return OTP;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function persistOTP(otp: string, patientUuid: string) {
|
|
207
|
+
sessionStorage.setItem(
|
|
208
|
+
patientUuid,
|
|
209
|
+
JSON.stringify({
|
|
210
|
+
otp,
|
|
211
|
+
timestamp: new Date().toISOString(),
|
|
212
|
+
}),
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function sendOtp({ otp, receiver }: z.infer<typeof authorizationFormSchema>, patientName: string) {
|
|
217
|
+
const payload = parseMessage(
|
|
218
|
+
{ otp, patient_name: patientName, expiry_time: 5 },
|
|
219
|
+
'Dear {{patient_name}}, your OTP for accessing your Shared Health Records (SHR) is {{otp}}. Please enter this code to proceed. The code is valid for {{expiry_time}} minutes.',
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const url = `${restBaseUrl}/kenyaemr/send-kenyaemr-sms?message=${payload}&phone=${receiver}`;
|
|
223
|
+
|
|
224
|
+
const res = await openmrsFetch(url, {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
redirect: 'follow',
|
|
227
|
+
});
|
|
228
|
+
if (res.ok) {
|
|
229
|
+
return await res.json();
|
|
230
|
+
}
|
|
231
|
+
throw new Error('Error sending otp');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseMessage(object, template) {
|
|
235
|
+
const placeholderRegex = /{{(.*?)}}/g;
|
|
236
|
+
|
|
237
|
+
const parsedMessage = template.replace(placeholderRegex, (match, fieldName) => {
|
|
238
|
+
if (object.hasOwnProperty(fieldName)) {
|
|
239
|
+
return object[fieldName];
|
|
240
|
+
} else {
|
|
241
|
+
return match;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return parsedMessage;
|
|
246
|
+
}
|
|
247
|
+
export function verifyOtp(otp: string, patientUuid: string) {
|
|
248
|
+
const data = sessionStorage.getItem(patientUuid);
|
|
249
|
+
if (!data) {
|
|
250
|
+
throw new Error('Invalid OTP');
|
|
251
|
+
}
|
|
252
|
+
const { otp: storedOtp, timestamp } = JSON.parse(data);
|
|
253
|
+
const isExpired = dayjs(timestamp).add(5, 'minutes').isBefore(dayjs());
|
|
254
|
+
if (storedOtp !== otp) {
|
|
255
|
+
throw new Error('Invalid OTP');
|
|
256
|
+
}
|
|
257
|
+
if (isExpired) {
|
|
258
|
+
throw new Error('OTP Expired');
|
|
259
|
+
}
|
|
260
|
+
sessionStorage.removeItem(patientUuid);
|
|
261
|
+
return 'Verification success';
|
|
262
|
+
}
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Button, ModalBody, ModalFooter, ModalHeader } from '@carbon/react';
|
|
2
|
+
import React, { useState } from 'react';
|
|
2
3
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import { Button, ModalBody, ModalHeader, ModalFooter, Accordion, AccordionItem, CodeSnippet } from '@carbon/react';
|
|
4
|
-
import styles from './confirm-hie.scss';
|
|
5
|
-
import { age, ExtensionSlot, formatDate } from '@openmrs/esm-framework';
|
|
6
4
|
import { type HIEPatient } from '../hie-types';
|
|
7
|
-
import
|
|
8
|
-
import { getPatientName,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
17
|
-
};
|
|
5
|
+
import styles from './confirm-hie.scss';
|
|
6
|
+
import { authorizationFormSchema, generateOTP, getPatientName, persistOTP, sendOtp, verifyOtp } from '../hie-resource';
|
|
7
|
+
import HIEPatientDetailPreview from './hie-patient-detail-preview.component';
|
|
8
|
+
import HIEOTPVerficationForm from './hie-otp-verification-form.component';
|
|
9
|
+
import { Form } from '@carbon/react';
|
|
10
|
+
import { FormProvider, useForm } from 'react-hook-form';
|
|
11
|
+
import { type z } from 'zod';
|
|
12
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
13
|
+
import { showSnackbar } from '@openmrs/esm-framework';
|
|
18
14
|
|
|
19
15
|
interface HIEConfirmationModalProps {
|
|
20
16
|
closeModal: () => void;
|
|
@@ -24,70 +20,78 @@ interface HIEConfirmationModalProps {
|
|
|
24
20
|
|
|
25
21
|
const HIEConfirmationModal: React.FC<HIEConfirmationModalProps> = ({ closeModal, patient, onUseValues }) => {
|
|
26
22
|
const { t } = useTranslation();
|
|
27
|
-
const
|
|
23
|
+
const [mode, setMode] = useState<'authorization' | 'preview'>('preview');
|
|
24
|
+
const [status, setStatus] = useState<'loadingOtp' | 'otpSendSuccessfull' | 'otpFetchError'>();
|
|
25
|
+
const phoneNumber = patient?.telecom?.find((num) => num.value)?.value;
|
|
26
|
+
const getidentifier = (code: string) =>
|
|
27
|
+
patient?.identifier?.find((identifier) => identifier?.type?.coding?.some((coding) => coding?.code === code));
|
|
28
|
+
const patientId = patient?.id ?? getidentifier('SHA-number')?.value;
|
|
29
|
+
const form = useForm<z.infer<typeof authorizationFormSchema>>({
|
|
30
|
+
defaultValues: {
|
|
31
|
+
receiver: phoneNumber,
|
|
32
|
+
},
|
|
33
|
+
resolver: zodResolver(authorizationFormSchema),
|
|
34
|
+
});
|
|
35
|
+
const patientName = getPatientName(patient);
|
|
28
36
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
37
|
+
const onSubmit = async (values: z.infer<typeof authorizationFormSchema>) => {
|
|
38
|
+
try {
|
|
39
|
+
verifyOtp(values.otp, patientId);
|
|
40
|
+
showSnackbar({ title: 'Success', kind: 'success', subtitle: 'Access granted successfully' });
|
|
41
|
+
onUseValues();
|
|
42
|
+
closeModal();
|
|
43
|
+
} catch (error) {
|
|
44
|
+
showSnackbar({ title: 'Faulure', kind: 'error', subtitle: `${error}` });
|
|
45
|
+
}
|
|
32
46
|
};
|
|
33
47
|
|
|
34
48
|
return (
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<span className={styles.patientNameValue}>
|
|
52
|
-
<p>{maskData(givenName)}</p>
|
|
53
|
-
<span>•</span>
|
|
54
|
-
<p>{maskData(middleName)}</p>
|
|
55
|
-
<span>•</span>
|
|
56
|
-
<p>{maskData(familyName)}</p>
|
|
57
|
-
</span>
|
|
58
|
-
)}
|
|
49
|
+
<FormProvider {...form}>
|
|
50
|
+
<Form onSubmit={form.handleSubmit(onSubmit)}>
|
|
51
|
+
<ModalHeader closeModal={closeModal}>
|
|
52
|
+
<span className={styles.header}>
|
|
53
|
+
{mode === 'authorization'
|
|
54
|
+
? t('hiePatientVerification', 'HIE Patient Verification')
|
|
55
|
+
: t('hieModal', 'HIE Patient Record Found')}
|
|
56
|
+
</span>
|
|
57
|
+
</ModalHeader>
|
|
58
|
+
<ModalBody>
|
|
59
|
+
{mode === 'authorization' ? (
|
|
60
|
+
<HIEOTPVerficationForm
|
|
61
|
+
name={`${patientName.givenName} ${patientName.middleName}`}
|
|
62
|
+
patientId={patientId}
|
|
63
|
+
status={status}
|
|
64
|
+
setStatus={setStatus}
|
|
59
65
|
/>
|
|
60
|
-
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
<div>
|
|
71
|
-
<Accordion>
|
|
72
|
-
<AccordionItem title={t('viewFullResponse', 'View full response')}>
|
|
73
|
-
<CodeSnippet type="multi" feedback="Copied to clipboard">
|
|
74
|
-
{JSON.stringify(patient, null, 2)}
|
|
75
|
-
</CodeSnippet>
|
|
76
|
-
</AccordionItem>
|
|
77
|
-
</Accordion>
|
|
78
|
-
</div>
|
|
79
|
-
</ModalBody>
|
|
80
|
-
<ModalFooter>
|
|
81
|
-
<Button kind="secondary" onClick={closeModal}>
|
|
82
|
-
{t('cancel', 'Cancel')}
|
|
83
|
-
</Button>
|
|
66
|
+
) : (
|
|
67
|
+
<HIEPatientDetailPreview patient={patient} />
|
|
68
|
+
)}
|
|
69
|
+
</ModalBody>
|
|
70
|
+
<ModalFooter>
|
|
71
|
+
<Button kind="secondary" onClick={closeModal}>
|
|
72
|
+
{t('cancel', 'Cancel')}
|
|
73
|
+
</Button>
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
{mode === 'preview' && (
|
|
76
|
+
<Button onClick={() => setMode('authorization')} kind="primary">
|
|
77
|
+
{t('useValues', 'Use values')}
|
|
78
|
+
</Button>
|
|
79
|
+
)}
|
|
80
|
+
{mode === 'authorization' && (
|
|
81
|
+
<Button
|
|
82
|
+
kind="primary"
|
|
83
|
+
type="submit"
|
|
84
|
+
disabled={form.formState.isSubmitting || status !== 'otpSendSuccessfull'}>
|
|
85
|
+
{t('verifyAndUseValues', 'Verify & Use values')}
|
|
86
|
+
</Button>
|
|
87
|
+
)}
|
|
88
|
+
</ModalFooter>
|
|
89
|
+
</Form>
|
|
90
|
+
</FormProvider>
|
|
90
91
|
);
|
|
91
92
|
};
|
|
92
93
|
|
|
93
94
|
export default HIEConfirmationModal;
|
|
95
|
+
function onVerificationSuccesfull() {
|
|
96
|
+
throw new Error('Function not implemented.');
|
|
97
|
+
}
|
|
@@ -6,8 +6,37 @@
|
|
|
6
6
|
@include type.type-style('heading-03');
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
.
|
|
10
|
-
|
|
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;
|
|
11
40
|
}
|
|
12
41
|
|
|
13
42
|
.patientNameValue {
|
|
@@ -19,3 +48,15 @@
|
|
|
19
48
|
color: colors.$gray-40;
|
|
20
49
|
}
|
|
21
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;
|
|
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,73 @@
|
|
|
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
|
+
|
|
11
|
+
type HIEPatientDetailPreviewProps = {
|
|
12
|
+
patient: fhir.Patient;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const HIEPatientDetailPreview: React.FC<HIEPatientDetailPreviewProps> = ({ patient }) => {
|
|
16
|
+
const { familyName, givenName, middleName } = getPatientName(patient);
|
|
17
|
+
const { t } = useTranslation();
|
|
18
|
+
const getidentifier = (code: string) =>
|
|
19
|
+
patient?.identifier?.find((identifier) => identifier?.type?.coding?.some((coding) => coding?.code === code));
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<div className={styles.patientDetails}>
|
|
24
|
+
<ExtensionSlot
|
|
25
|
+
className={styles.patientPhotoContainer}
|
|
26
|
+
name="patient-photo-slot"
|
|
27
|
+
state={{ patientName: `${maskData(givenName)} . ${maskData(middleName)} . ${maskData(familyName)}` }}
|
|
28
|
+
/>
|
|
29
|
+
<div className={styles.patientInfoContainer}>
|
|
30
|
+
<PatientInfo label={t('healthID', 'HealthID')} value={getidentifier('SHA-number')?.value} />
|
|
31
|
+
<PatientInfo
|
|
32
|
+
label={t('patientName', 'Patient name')}
|
|
33
|
+
customValue={
|
|
34
|
+
<span className={styles.patientNameValue}>
|
|
35
|
+
<p>{maskData(givenName)}</p>
|
|
36
|
+
<span>•</span>
|
|
37
|
+
<p>{maskData(middleName)}</p>
|
|
38
|
+
<span>•</span>
|
|
39
|
+
<p>{maskData(familyName)}</p>
|
|
40
|
+
</span>
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
<PatientInfo label={t('age', 'Age')} value={age(patient?.birthDate)} />
|
|
45
|
+
<PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.birthDate))} />
|
|
46
|
+
<PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
|
|
47
|
+
<PatientInfo
|
|
48
|
+
label={t('maritalStatus', 'Marital status')}
|
|
49
|
+
value={patient?.maritalStatus?.coding?.map((m) => m.code).join('')}
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
{(!patient?.contact || patient?.contact.length === 0) && (
|
|
53
|
+
<PatientInfo label={t('dependents', 'Dependents')} value="--" />
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<DependentInfo dependents={patient?.contact} />
|
|
59
|
+
|
|
60
|
+
<div>
|
|
61
|
+
<Accordion>
|
|
62
|
+
<AccordionItem title={t('viewFullResponse', 'View full response')}>
|
|
63
|
+
<CodeSnippet type="multi" feedback="Copied to clipboard">
|
|
64
|
+
{JSON.stringify(patient, null, 2)}
|
|
65
|
+
</CodeSnippet>
|
|
66
|
+
</AccordionItem>
|
|
67
|
+
</Accordion>
|
|
68
|
+
</div>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
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/translations/en.json
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"age": "Age",
|
|
5
5
|
"allFieldsRequiredText": "All fields are required unless marked optional",
|
|
6
6
|
"autoGeneratedPlaceholderText": "Auto-generated",
|
|
7
|
+
"birthCertificate": "Birth Certificate",
|
|
7
8
|
"birthdayNotInTheFuture": "Birthday cannot be in future",
|
|
8
9
|
"birthdayNotOver140YearsAgo": "Birthday cannot be more than 140 years ago",
|
|
9
10
|
"birthdayRequired": "Birthday is required",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"deleteIdentifierTooltip": "Delete",
|
|
46
47
|
"deleteRelationshipTooltipText": "Delete",
|
|
47
48
|
"demographicsSection": "Basic Info",
|
|
49
|
+
"dependants": "Dependants",
|
|
48
50
|
"dependents": "Dependents",
|
|
49
51
|
"discard": "Discard",
|
|
50
52
|
"dobToggleLabelText": "Date of Birth Known?",
|
|
@@ -72,6 +74,7 @@
|
|
|
72
74
|
"givenNameRequired": "Given name is required",
|
|
73
75
|
"healthID": "HealthID",
|
|
74
76
|
"hieModal": "HIE Patient Record Found",
|
|
77
|
+
"hiePatientVerification": "HIE Patient Verification",
|
|
75
78
|
"identifierSearch": "Identifier search",
|
|
76
79
|
"identifierType": "Identifier type",
|
|
77
80
|
"identifierValueRequired": "Identifier value is required",
|
|
@@ -84,7 +87,9 @@
|
|
|
84
87
|
"male": "Male",
|
|
85
88
|
"maritalStatus": "Marital status",
|
|
86
89
|
"middleNameLabelText": "Middle Name",
|
|
90
|
+
"name": "Name",
|
|
87
91
|
"nationalId": "National ID",
|
|
92
|
+
"nationalID": "National ID",
|
|
88
93
|
"negativeMonths": "Estimated months cannot be negative",
|
|
89
94
|
"negativeYears": "Estimated years cannot be negative",
|
|
90
95
|
"no": "No",
|
|
@@ -97,12 +102,15 @@
|
|
|
97
102
|
"optional": "optional",
|
|
98
103
|
"optionalIdentifierLabel": "{{identifierName}} (optional)",
|
|
99
104
|
"other": "Other",
|
|
105
|
+
"otpCode": "OTP Authorization code",
|
|
100
106
|
"patientDetailsFound": "Patient information found in the registry, do you want to use the information to continue with registration?",
|
|
101
107
|
"patientName": "Patient name",
|
|
102
108
|
"patientNameKnown": "Patient's Name is Known?",
|
|
103
109
|
"patientNotFound": "The patient records could not be found in Client registry, do you want to continue to create and post patient to registry",
|
|
110
|
+
"patientPhoneNUmber": "Patient Phone number",
|
|
104
111
|
"patientRegistrationBreadcrumb": "Patient Registration",
|
|
105
112
|
"patientVerificationFromHIE": "Patient verification from HIE",
|
|
113
|
+
"phoneNumberHelper": "Patient will receive OTP on this number",
|
|
106
114
|
"postToRegistry": "Post to registry",
|
|
107
115
|
"refreshOrContactAdmin": "Try refreshing the page or contact your system administrator",
|
|
108
116
|
"registerPatient": "Register patient",
|
|
@@ -120,6 +128,7 @@
|
|
|
120
128
|
"removeIdentifierButton": "Remove identifier",
|
|
121
129
|
"resetIdentifierTooltip": "Reset",
|
|
122
130
|
"restoreRelationshipActionButton": "Undo",
|
|
131
|
+
"retry": "Retry",
|
|
123
132
|
"searchAddress": "Search address",
|
|
124
133
|
"searchClientRegistry": "Search client registry",
|
|
125
134
|
"searchIdentifierPlaceholder": "Search identifier",
|
|
@@ -138,12 +147,15 @@
|
|
|
138
147
|
"unableToFetch": "Unable to fetch person attribute type - {{personattributetype}}",
|
|
139
148
|
"unknown": "Unknown",
|
|
140
149
|
"unknownPatientAttributeType": "Patient attribute type has unknown format {{personAttributeTypeFormat}}",
|
|
150
|
+
"unknownRelationship": "Unknown",
|
|
141
151
|
"updatePatient": "Update patient",
|
|
142
152
|
"updatePatientErrorSnackbarTitle": "Patient Details Update Failed",
|
|
143
153
|
"updatePatientSuccessSnackbarSubtitle": "The patient's information has been successfully updated",
|
|
144
154
|
"updatePatientSuccessSnackbarTitle": "Patient Details Updated",
|
|
145
155
|
"useValues": "Use values",
|
|
146
156
|
"validate": "Validate",
|
|
157
|
+
"verifyAndUseValues": "Verify & Use values",
|
|
158
|
+
"verifyOTP": "Verify with OTP",
|
|
147
159
|
"viewFullResponse": "View full response",
|
|
148
160
|
"yearsEstimateRequired": "Estimated years required",
|
|
149
161
|
"yes": "Yes"
|