@kenyaemr/esm-patient-registration-app 8.0.3-pre.138 → 8.0.3-pre.143

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.
Files changed (32) hide show
  1. package/.turbo/turbo-build.log +17 -17
  2. package/dist/108.js +1 -1
  3. package/dist/250.js +1 -1
  4. package/dist/574.js +1 -1
  5. package/dist/610.js +1 -0
  6. package/dist/610.js.map +1 -0
  7. package/dist/662.js +1 -1
  8. package/dist/76.js +1 -1
  9. package/dist/94.js +2 -0
  10. package/dist/94.js.map +1 -0
  11. package/dist/kenyaemr-esm-patient-registration-app.js +1 -1
  12. package/dist/kenyaemr-esm-patient-registration-app.js.buildmanifest.json +62 -62
  13. package/dist/kenyaemr-esm-patient-registration-app.js.map +1 -1
  14. package/dist/main.js +1 -1
  15. package/dist/main.js.map +1 -1
  16. package/dist/routes.json +1 -1
  17. package/package.json +1 -1
  18. package/src/client-registry/hie-client-registry/dependants/dependants.component.tsx +5 -7
  19. package/src/client-registry/hie-client-registry/hie-client-registry.component.tsx +6 -4
  20. package/src/client-registry/hie-client-registry/hie-resource.ts +101 -18
  21. package/src/client-registry/hie-client-registry/hie-types.ts +100 -0
  22. package/src/client-registry/hie-client-registry/modal/confirm-hie.modal.tsx +79 -73
  23. package/src/client-registry/hie-client-registry/modal/confirm-hie.scss +12 -0
  24. package/src/client-registry/hie-client-registry/modal/hie-otp-verification-form.component.tsx +88 -0
  25. package/src/client-registry/hie-client-registry/modal/hie-patient-detail-preview.component.tsx +77 -0
  26. package/src/config-schema.ts +1 -1
  27. package/translations/en.json +7 -0
  28. package/dist/652.js +0 -1
  29. package/dist/652.js.map +0 -1
  30. package/dist/895.js +0 -2
  31. package/dist/895.js.map +0 -1
  32. /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.138"}
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.143"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-patient-registration-app",
3
- "version": "8.0.3-pre.138",
3
+ "version": "8.0.3-pre.143",
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",
@@ -14,15 +14,13 @@ const DependentInfo: React.FC<{ dependents: any[] }> = ({ dependents }) => {
14
14
  const name = dependent?.name?.text;
15
15
  const relationship =
16
16
  dependent?.relationship?.[0]?.coding?.[0]?.display || t('unknownRelationship', 'Unknown');
17
+
17
18
  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',
19
+ (ext) => ext?.valueIdentifier?.type?.coding?.some((coding) => coding.code === 'national-id'),
21
20
  )?.valueIdentifier?.value;
21
+
22
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',
23
+ (ext) => ext?.valueIdentifier?.type?.coding?.some((coding) => coding.code === 'birth-certificate'),
26
24
  )?.valueIdentifier?.value;
27
25
 
28
26
  const primaryIdentifier = nationalID || birthCertificate;
@@ -34,7 +32,7 @@ const DependentInfo: React.FC<{ dependents: any[] }> = ({ dependents }) => {
34
32
  <div key={index} className={styles.dependentInfo}>
35
33
  <PatientInfo label={t('name', 'Name')} value={name} />
36
34
  <PatientInfo label={t('relationship', 'Relationship')} value={relationship} />
37
- <PatientInfo label={identifierLabel} value={primaryIdentifier} />
35
+ {primaryIdentifier && <PatientInfo label={identifierLabel} value={primaryIdentifier} />}
38
36
  </div>
39
37
  );
40
38
  })}
@@ -11,7 +11,7 @@ import { type RegistrationConfig } from '../../config-schema';
11
11
  import { useForm, Controller, type SubmitHandler } from 'react-hook-form';
12
12
  import { zodResolver } from '@hookform/resolvers/zod';
13
13
  import { fetchPatientFromHIE, mapHIEPatientToFormValues } from './hie-resource';
14
- import { type HIEPatient } from './hie-types';
14
+ import { type HIEPatientResponse, type HIEPatient } from './hie-types';
15
15
 
16
16
  type HIEClientRegistryProps = {
17
17
  props: FormikProps<FormValues>;
@@ -43,16 +43,18 @@ const HIEClientRegistry: React.FC<HIEClientRegistryProps> = ({ setInitialFormVal
43
43
  try {
44
44
  const hieClientRegistry = await fetchPatientFromHIE(data.identifierType, data.identifierValue);
45
45
 
46
- if (hieClientRegistry && hieClientRegistry.resourceType === 'Patient') {
46
+ if (hieClientRegistry && hieClientRegistry.resourceType === 'Bundle') {
47
47
  const dispose = showModal('hie-confirmation-modal', {
48
48
  patient: hieClientRegistry,
49
49
  closeModal: () => dispose(),
50
50
  onUseValues: () =>
51
- setInitialFormValues(mapHIEPatientToFormValues(hieClientRegistry as HIEPatient, props.values)),
51
+ setInitialFormValues(
52
+ mapHIEPatientToFormValues(hieClientRegistry as unknown as HIEPatientResponse, props.values),
53
+ ),
52
54
  });
53
55
  }
54
56
 
55
- if (hieClientRegistry && hieClientRegistry.resourceType === 'OperationOutcome') {
57
+ if (hieClientRegistry && hieClientRegistry?.resourceType === 'OperationOutcome') {
56
58
  const issueMessage = hieClientRegistry?.['issue']?.map((issue) => issue.diagnostics).join(', ');
57
59
  const dispose = showModal('empty-client-registry-modal', {
58
60
  onConfirm: () => dispose(),
@@ -1,8 +1,10 @@
1
1
  import capitalize from 'lodash-es/capitalize';
2
2
  import { type PatientIdentifierValue, type FormValues } from '../../patient-registration/patient-registration.types';
3
- import { type MapperConfig, type HIEPatient, type ErrorResponse } from './hie-types';
3
+ import { type MapperConfig, type HIEPatient, type ErrorResponse, type HIEPatientResponse } 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.
@@ -11,7 +13,6 @@ class HealthInformationExchangeClient<T> {
11
13
  async fetchResource(resourceType: string, params: Record<string, string>): Promise<T> {
12
14
  const [identifierType, identifierValue] = Object.entries(params)[0];
13
15
  const url = `${restBaseUrl}/kenyaemr/getSHAPatient/${identifierValue}/${identifierType}`;
14
-
15
16
  const response = await openmrsFetch(url);
16
17
  return response.json();
17
18
  }
@@ -33,19 +34,19 @@ class Mapper<T, U> {
33
34
  /**
34
35
  * Maps HIEPatient objects to FormValues objects.
35
36
  */
36
- class PatientMapper extends Mapper<HIEPatient, FormValues> {
37
- mapHIEPatientToFormValues(hiePatient: HIEPatient, currentFormValues: FormValues): FormValues {
37
+ class PatientMapper extends Mapper<HIEPatientResponse, FormValues> {
38
+ mapHIEPatientToFormValues(hiePatient: HIEPatientResponse, currentFormValues: FormValues): FormValues {
38
39
  const { familyName, givenName, middleName } = getPatientName(hiePatient);
39
- const telecom = hiePatient.telecom || [];
40
+ const telecom = hiePatient?.entry[0]?.resource.telecom || [];
40
41
 
41
42
  const telecomAttributes = this.mapTelecomToAttributes(telecom);
42
43
  const updatedIdentifiers = this.mapIdentifiers(hiePatient, currentFormValues);
43
- const extensionAddressEntries = this.mapExtensionsToAddress(hiePatient.extension);
44
+ const extensionAddressEntries = this.mapExtensionsToAddress(hiePatient?.entry[0]?.resource.extension);
44
45
 
45
46
  return {
46
- isDead: hiePatient.deceasedBoolean || false,
47
- gender: hiePatient.gender || '',
48
- birthdate: hiePatient.birthDate || '',
47
+ isDead: hiePatient?.entry[0]?.resource?.active || false,
48
+ gender: hiePatient?.entry[0]?.resource.gender || '',
49
+ birthdate: hiePatient?.entry[0]?.resource?.birthDate || '',
49
50
  givenName,
50
51
  familyName,
51
52
  telephoneNumber: telecom.find((t) => t.system === 'phone')?.value || '',
@@ -71,7 +72,7 @@ class PatientMapper extends Mapper<HIEPatient, FormValues> {
71
72
  }
72
73
 
73
74
  private mapIdentifiers(
74
- hiePatient: HIEPatient,
75
+ hiePatient: HIEPatientResponse,
75
76
  currentFormValues: FormValues,
76
77
  ): Record<string, PatientIdentifierValue> {
77
78
  const updatedIdentifiers: Record<string, PatientIdentifierValue> = { ...currentFormValues.identifiers };
@@ -79,11 +80,11 @@ class PatientMapper extends Mapper<HIEPatient, FormValues> {
79
80
  // See https://github.com/palladiumkenya/openmrs-module-kenyaemr/blob/1e1d281eaba8041c45318e60ca0730449b8e4197/api/src/main/distro/metadata/identifierTypes.xml#L33
80
81
  updatedIdentifiers.socialHealthAuthorityIdentificationNumber = {
81
82
  ...currentFormValues.identifiers['socialHealthAuthorityIdentificationNumber'],
82
- identifierValue: hiePatient.id,
83
+ identifierValue: hiePatient?.entry[0]?.resource?.id,
83
84
  };
84
85
 
85
86
  // Map fhir.Patient.Identifier to identifiers
86
- hiePatient.identifier?.forEach((identifier: fhir.Identifier) => {
87
+ hiePatient.entry[0]?.resource.identifier?.forEach((identifier: fhir.Identifier) => {
87
88
  const identifierType = identifier.type?.coding?.[0]?.code;
88
89
  const mappedIdentifierType = this.convertToCamelCase(identifierType);
89
90
  const identifierValue = identifier.value;
@@ -157,7 +158,10 @@ export const fetchPatientFromHIE = async (
157
158
  return hieApiClient.fetchResource('Patient', { [identifierType]: identifierValue });
158
159
  };
159
160
 
160
- export const mapHIEPatientToFormValues = (hiePatient: HIEPatient, currentFormValues: FormValues): FormValues => {
161
+ export const mapHIEPatientToFormValues = (
162
+ hiePatient: HIEPatientResponse,
163
+ currentFormValues: FormValues,
164
+ ): FormValues => {
161
165
  return patientMapper.mapHIEPatientToFormValues(hiePatient, currentFormValues);
162
166
  };
163
167
 
@@ -167,7 +171,7 @@ export const mapHIEPatientToFormValues = (hiePatient: HIEPatient, currentFormVal
167
171
  * @returns {string} - The masked data
168
172
  */
169
173
  export const maskData = (data: string): string => {
170
- const maskedData = data.slice(0, 2) + '*'.repeat(data.length - 2);
174
+ const maskedData = data.slice(0, 2) + '*'.repeat(Math.max(0, data.length - 2));
171
175
  return maskedData;
172
176
  };
173
177
 
@@ -176,9 +180,88 @@ export const maskData = (data: string): string => {
176
180
  * @param patient {fhir.Patient} - The FHIR Patient resource
177
181
  * @returns {object} - The patient name
178
182
  */
179
- export const getPatientName = (patient: fhir.Patient) => {
180
- const familyName = patient?.name[0]?.['family'] ?? '';
181
- const givenName = patient.name[0]?.['given']?.[0]?.split(' ')?.[0] ?? '';
182
- const middleName = patient.name[0]?.['given']?.[0]?.replace(givenName, '')?.trim() ?? '';
183
+ export const getPatientName = (patient: HIEPatientResponse) => {
184
+ const familyName = patient?.entry?.[0]?.resource?.name?.[0]?.family ?? ''; // Safely access the family name
185
+ const givenNames = patient?.entry?.[0]?.resource?.name?.[0]?.given ?? []; // Safely access the given names array
186
+
187
+ const givenName = givenNames?.[0] ?? ''; // The first item is the given name (first name)
188
+ const middleName = givenNames.slice(1).join(' ').trim(); // Combine all other given names as middle name(s)
189
+
183
190
  return { familyName, givenName, middleName };
184
191
  };
192
+
193
+ export const authorizationFormSchema = z.object({
194
+ otp: z.string().min(1, 'Required'),
195
+ receiver: z
196
+ .string()
197
+ .regex(/^(\+?254|0)((7|1)\d{8})$/)
198
+ .optional(),
199
+ });
200
+
201
+ export function generateOTP(length = 5) {
202
+ let otpNumbers = '0123456789';
203
+ let OTP = '';
204
+ const len = otpNumbers.length;
205
+ for (let i = 0; i < length; i++) {
206
+ OTP += otpNumbers[Math.floor(Math.random() * len)];
207
+ }
208
+ return OTP;
209
+ }
210
+
211
+ export function persistOTP(otp: string, patientUuid: string) {
212
+ sessionStorage.setItem(
213
+ patientUuid,
214
+ JSON.stringify({
215
+ otp,
216
+ timestamp: new Date().toISOString(),
217
+ }),
218
+ );
219
+ }
220
+
221
+ export async function sendOtp({ otp, receiver }: z.infer<typeof authorizationFormSchema>, patientName: string) {
222
+ const payload = parseMessage(
223
+ { otp, patient_name: patientName, expiry_time: 5 },
224
+ '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.',
225
+ );
226
+
227
+ const url = `${restBaseUrl}/kenyaemr/send-kenyaemr-sms?message=${payload}&phone=${receiver}`;
228
+
229
+ const res = await openmrsFetch(url, {
230
+ method: 'POST',
231
+ redirect: 'follow',
232
+ });
233
+ if (res.ok) {
234
+ return await res.json();
235
+ }
236
+ throw new Error('Error sending otp');
237
+ }
238
+
239
+ function parseMessage(object, template) {
240
+ const placeholderRegex = /{{(.*?)}}/g;
241
+
242
+ const parsedMessage = template.replace(placeholderRegex, (match, fieldName) => {
243
+ if (object.hasOwnProperty(fieldName)) {
244
+ return object[fieldName];
245
+ } else {
246
+ return match;
247
+ }
248
+ });
249
+
250
+ return parsedMessage;
251
+ }
252
+ export function verifyOtp(otp: string, patientUuid: string) {
253
+ const data = sessionStorage.getItem(patientUuid);
254
+ if (!data) {
255
+ throw new Error('Invalid OTP');
256
+ }
257
+ const { otp: storedOtp, timestamp } = JSON.parse(data);
258
+ const isExpired = dayjs(timestamp).add(5, 'minutes').isBefore(dayjs());
259
+ if (storedOtp !== otp) {
260
+ throw new Error('Invalid OTP');
261
+ }
262
+ if (isExpired) {
263
+ throw new Error('OTP Expired');
264
+ }
265
+ sessionStorage.removeItem(patientUuid);
266
+ return 'Verification success';
267
+ }
@@ -5,6 +5,106 @@ export type HIEPatient = fhir.Patient & {
5
5
  }>;
6
6
  };
7
7
 
8
+ export interface HIEPatientResponse {
9
+ id: string;
10
+ meta: Metadata;
11
+ link: Link[];
12
+ entry: Entry[];
13
+ }
14
+
15
+ interface Metadata {
16
+ lastUpdated: string;
17
+ }
18
+
19
+ interface Link {
20
+ relation: string;
21
+ url: string;
22
+ }
23
+
24
+ interface Entry {
25
+ resource: Resource;
26
+ }
27
+
28
+ interface Resource {
29
+ id: string;
30
+ extension: Extension[];
31
+ identifier: Identifier[];
32
+ active: boolean;
33
+ name: Name[];
34
+ telecom: Telecom[];
35
+ birthDate: string;
36
+ address: Address[];
37
+ gender: string;
38
+ maritalStatus: MaritalStatus;
39
+ contact: Contact[];
40
+ }
41
+
42
+ interface Extension {
43
+ url: string;
44
+ valueString: string;
45
+ }
46
+
47
+ interface Identifier {
48
+ type: CodingType;
49
+ value: string;
50
+ }
51
+
52
+ interface CodingType {
53
+ coding: Coding[];
54
+ }
55
+
56
+ interface Coding {
57
+ system?: string;
58
+ code: string;
59
+ display: string;
60
+ }
61
+
62
+ interface Name {
63
+ text: string;
64
+ family: string;
65
+ given: string[];
66
+ }
67
+
68
+ interface Telecom {
69
+ system: string;
70
+ value?: string;
71
+ }
72
+
73
+ interface Address {
74
+ extension: AddressExtension[];
75
+ city: string;
76
+ country: string;
77
+ }
78
+
79
+ interface AddressExtension {
80
+ url: string;
81
+ valueString: string;
82
+ }
83
+
84
+ interface MaritalStatus {
85
+ coding: Coding[];
86
+ }
87
+
88
+ interface Contact {
89
+ id: string;
90
+ extension: ContactExtension[];
91
+ relationship: Relationship[];
92
+ name: Name;
93
+ telecom: Telecom[];
94
+ address: Address;
95
+ gender: string;
96
+ }
97
+
98
+ interface ContactExtension {
99
+ url: string;
100
+ valueIdentifier?: Identifier;
101
+ valueString?: string;
102
+ }
103
+
104
+ interface Relationship {
105
+ coding: Coding[];
106
+ }
107
+
8
108
  export type APIClientConfig = {
9
109
  baseUrl: string;
10
110
  credentials: string;
@@ -1,93 +1,99 @@
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 { age, ExtensionSlot, formatDate } from '@openmrs/esm-framework';
5
- import { type HIEPatient } from '../hie-types';
6
- import capitalize from 'lodash-es/capitalize';
4
+ import { type HIEPatientResponse, type HIEPatient } from '../hie-types';
7
5
  import styles from './confirm-hie.scss';
8
- import PatientInfo from '../patient-info/patient-info.component';
9
- import DependentInfo from '../dependants/dependants.component';
10
- import { getPatientName, maskData } from '../hie-resource';
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';
11
14
 
12
15
  interface HIEConfirmationModalProps {
13
16
  closeModal: () => void;
14
- patient: HIEPatient;
17
+ patient: HIEPatientResponse;
15
18
  onUseValues: () => void;
16
19
  }
17
20
 
18
21
  const HIEConfirmationModal: React.FC<HIEConfirmationModalProps> = ({ closeModal, patient, onUseValues }) => {
19
22
  const { t } = useTranslation();
20
- 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?.entry[0]?.resource.telecom?.find((num) => num.system === 'phone')?.value;
26
+ const getidentifier = (code: string) =>
27
+ patient?.entry[0]?.resource.identifier?.find(
28
+ (identifier) => identifier?.type?.coding?.some((coding) => coding?.code === code),
29
+ );
30
+ const patientId = patient?.id ?? getidentifier('SHA-number')?.value;
31
+ const form = useForm<z.infer<typeof authorizationFormSchema>>({
32
+ defaultValues: {
33
+ receiver: phoneNumber,
34
+ },
35
+ resolver: zodResolver(authorizationFormSchema),
36
+ });
37
+ const patientName = getPatientName(patient);
21
38
 
22
- const handleUseValues = () => {
23
- onUseValues();
24
- closeModal();
39
+ const onSubmit = async (values: z.infer<typeof authorizationFormSchema>) => {
40
+ try {
41
+ verifyOtp(values.otp, patientId);
42
+ showSnackbar({ title: 'Success', kind: 'success', subtitle: 'Access granted successfully' });
43
+ onUseValues();
44
+ closeModal();
45
+ } catch (error) {
46
+ showSnackbar({ title: 'Faulure', kind: 'error', subtitle: `${error}` });
47
+ }
25
48
  };
26
49
 
27
50
  return (
28
- <div>
29
- <ModalHeader closeModal={closeModal}>
30
- <span className={styles.header}>{t('hieModal', 'HIE Patient Record Found')}</span>
31
- </ModalHeader>
32
- <ModalBody>
33
- <div className={styles.patientDetails}>
34
- <ExtensionSlot
35
- className={styles.patientPhotoContainer}
36
- name="patient-photo-slot"
37
- state={{ patientName: `${maskData(givenName)} . ${maskData(middleName)} . ${maskData(familyName)}` }}
38
- />
39
- <div className={styles.patientInfoContainer}>
40
- <PatientInfo label={t('healthID', 'HealthID')} value={patient?.id} />
41
- <PatientInfo
42
- label={t('patientName', 'Patient name')}
43
- customValue={
44
- <span className={styles.patientNameValue}>
45
- <p>{maskData(givenName)}</p>
46
- <span>&bull;</span>
47
- <p>{maskData(middleName)}</p>
48
- <span>&bull;</span>
49
- <p>{maskData(familyName)}</p>
50
- </span>
51
- }
51
+ <FormProvider {...form}>
52
+ <Form onSubmit={form.handleSubmit(onSubmit)}>
53
+ <ModalHeader closeModal={closeModal}>
54
+ <span className={styles.header}>
55
+ {mode === 'authorization'
56
+ ? t('hiePatientVerification', 'HIE Patient Verification')
57
+ : t('hieModal', 'HIE Patient Record Found')}
58
+ </span>
59
+ </ModalHeader>
60
+ <ModalBody>
61
+ {mode === 'authorization' ? (
62
+ <HIEOTPVerficationForm
63
+ name={`${patientName.givenName} ${patientName.middleName}`}
64
+ patientId={patientId}
65
+ status={status}
66
+ setStatus={setStatus}
52
67
  />
68
+ ) : (
69
+ <HIEPatientDetailPreview patient={patient} />
70
+ )}
71
+ </ModalBody>
72
+ <ModalFooter>
73
+ <Button kind="secondary" onClick={closeModal}>
74
+ {t('cancel', 'Cancel')}
75
+ </Button>
53
76
 
54
- <PatientInfo label={t('age', 'Age')} value={age(patient?.birthDate)} />
55
- <PatientInfo label={t('dateOfBirth', 'Date of birth')} value={formatDate(new Date(patient?.birthDate))} />
56
- <PatientInfo label={t('gender', 'Gender')} value={capitalize(patient?.gender)} />
57
- <PatientInfo
58
- label={t('maritalStatus', 'Marital status')}
59
- value={patient?.maritalStatus?.coding?.map((m) => m.code).join('')}
60
- />
61
-
62
- {(!patient?.contact || patient?.contact.length === 0) && (
63
- <PatientInfo label={t('dependents', 'Dependents')} value="--" />
64
- )}
65
- </div>
66
- </div>
67
-
68
- <DependentInfo dependents={patient?.contact} />
69
-
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>
84
-
85
- <Button onClick={handleUseValues} kind="primary">
86
- {t('useValues', 'Use values')}
87
- </Button>
88
- </ModalFooter>
89
- </div>
77
+ {mode === 'preview' && (
78
+ <Button onClick={() => setMode('authorization')} kind="primary">
79
+ {t('useValues', 'Use values')}
80
+ </Button>
81
+ )}
82
+ {mode === 'authorization' && (
83
+ <Button
84
+ kind="primary"
85
+ type="submit"
86
+ disabled={form.formState.isSubmitting || status !== 'otpSendSuccessfull'}>
87
+ {t('verifyAndUseValues', 'Verify & Use values')}
88
+ </Button>
89
+ )}
90
+ </ModalFooter>
91
+ </Form>
92
+ </FormProvider>
90
93
  );
91
94
  };
92
95
 
93
96
  export default HIEConfirmationModal;
97
+ function onVerificationSuccesfull() {
98
+ throw new Error('Function not implemented.');
99
+ }
@@ -48,3 +48,15 @@
48
48
  color: colors.$gray-40;
49
49
  }
50
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;