@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/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.136"}
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.136",
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 React from 'react';
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 capitalize from 'lodash-es/capitalize';
8
- import { getPatientName, maskData } from '../hie-resource';
9
-
10
- const PatientInfo: React.FC<{ label: string; value: string | (() => React.ReactNode) }> = ({ label, value }) => {
11
- return (
12
- <div style={{ display: 'grid', gridTemplateColumns: '0.25fr 0.75fr', margin: '0.25rem' }}>
13
- <span style={{ minWidth: '5rem', fontWeight: 'bold' }}>{label}</span>
14
- <span>{typeof value === 'function' ? value() : value}</span>
15
- </div>
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 { familyName, givenName, middleName } = getPatientName(patient);
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 handleUseValues = () => {
30
- onUseValues();
31
- closeModal();
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
- <div>
36
- <ModalHeader closeModal={closeModal}>
37
- <span className={styles.header}>{t('hieModal', 'HIE Patient Record Found')}</span>
38
- </ModalHeader>
39
- <ModalBody>
40
- <div style={{ display: 'flex', margin: '1rem' }}>
41
- <ExtensionSlot
42
- style={{ display: 'flex', alignItems: 'center' }}
43
- name="patient-photo-slot"
44
- state={{ patientName: `${maskData(givenName)} . ${maskData(middleName)} . ${maskData(familyName)}` }}
45
- />
46
- <div style={{ width: '100%', marginLeft: '0.625rem' }}>
47
- <PatientInfo label={t('healthID', 'HealthID')} value={patient?.id} />
48
- <PatientInfo
49
- label={t('patientName', 'Patient name')}
50
- value={() => (
51
- <span className={styles.patientNameValue}>
52
- <p>{maskData(givenName)}</p>
53
- <span>&bull;</span>
54
- <p>{maskData(middleName)}</p>
55
- <span>&bull;</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
- <PatientInfo label={t('age', 'Age')} value={age(patient?.birthDate)} />
61
- <PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.birthDate))} />
62
- <PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
63
- <PatientInfo
64
- label={t('maritalStatus', 'Marital status')}
65
- value={patient?.maritalStatus?.coding?.map((m) => m.code).join('')}
66
- />
67
- <PatientInfo label={t('dependents', 'Dependents')} value="--" />
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
- <Button onClick={handleUseValues} kind="primary">
86
- {t('useValues', 'Use values')}
87
- </Button>
88
- </ModalFooter>
89
- </div>
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
- .body {
10
- @include type.type-style('body-01');
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;
@@ -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>&bull;</span>
37
+ <p>{maskData(middleName)}</p>
38
+ <span>&bull;</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;
@@ -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"