@ampath/esm-patient-registration-app 9.2.0-next.15 → 9.2.0-next.17

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 (67) hide show
  1. package/dist/4300.js +1 -1
  2. package/dist/4395.js +1 -0
  3. package/dist/4395.js.map +1 -0
  4. package/dist/5239.js +1 -1
  5. package/dist/5239.js.LICENSE.txt +10 -0
  6. package/dist/5239.js.map +1 -1
  7. package/dist/6276.js +1 -1
  8. package/dist/6687.js +1 -0
  9. package/dist/6687.js.map +1 -0
  10. package/dist/6996.js +1 -0
  11. package/dist/6996.js.map +1 -0
  12. package/dist/7125.js +2 -0
  13. package/dist/7125.js.map +1 -0
  14. package/dist/7821.js +1 -0
  15. package/dist/7821.js.map +1 -0
  16. package/dist/8414.js +1 -0
  17. package/dist/8414.js.map +1 -0
  18. package/dist/8434.js +1 -1
  19. package/dist/8882.js +1 -0
  20. package/dist/8882.js.map +1 -0
  21. package/dist/9898.js +1 -0
  22. package/dist/9898.js.map +1 -0
  23. package/dist/9933.js +2 -0
  24. package/dist/{2615.js.LICENSE.txt → 9933.js.LICENSE.txt} +9 -0
  25. package/dist/9933.js.map +1 -0
  26. package/dist/main.js +1 -1
  27. package/dist/main.js.map +1 -1
  28. package/dist/openmrs-esm-patient-registration-app.js +1 -1
  29. package/dist/openmrs-esm-patient-registration-app.js.buildmanifest.json +226 -150
  30. package/dist/openmrs-esm-patient-registration-app.js.map +1 -1
  31. package/dist/routes.json +1 -1
  32. package/package.json +1 -1
  33. package/src/index.ts +9 -0
  34. package/src/patient-registration/client-registry/client-registry.resource.ts +1 -1
  35. package/src/patient-registration/client-registry-search/client-registry-dependant-details.component.tsx +237 -0
  36. package/src/patient-registration/client-registry-search/client-registry-details.component.tsx +111 -0
  37. package/src/patient-registration/client-registry-search/client-registry-patient-details.component.tsx +234 -0
  38. package/src/patient-registration/client-registry-search/client-registry-search.component.tsx +234 -0
  39. package/src/patient-registration/client-registry-search/client-registry-verification-tag.component.tsx +78 -0
  40. package/src/patient-registration/client-registry-search/client-registry.resource.ts +135 -0
  41. package/src/patient-registration/client-registry-search/client-registry.types.ts +243 -0
  42. package/src/patient-registration/client-registry-search/map-client-registry-to-form-utils.ts +590 -0
  43. package/src/patient-registration/field/field.component.tsx +3 -0
  44. package/src/patient-registration/field/id/id-field.component.tsx +1 -1
  45. package/src/patient-registration/field/id/id-field.test.tsx +1 -1
  46. package/src/patient-registration/form-manager.test.ts +1 -0
  47. package/src/patient-registration/patient-registration.component.tsx +0 -1
  48. package/src/patient-registration/patient-registration.resource.ts +1 -1
  49. package/src/patient-registration/patient-registration.scss +4 -0
  50. package/src/routes.json +12 -1
  51. package/src/widgets/client-registry-verification.modal.tsx +27 -0
  52. package/translations/en.json +4 -0
  53. package/dist/2450.js +0 -1
  54. package/dist/2450.js.map +0 -1
  55. package/dist/2615.js +0 -2
  56. package/dist/2615.js.map +0 -1
  57. package/dist/320.js +0 -2
  58. package/dist/320.js.LICENSE.txt +0 -8
  59. package/dist/320.js.map +0 -1
  60. package/dist/3474.js +0 -2
  61. package/dist/3474.js.LICENSE.txt +0 -8
  62. package/dist/3474.js.map +0 -1
  63. package/dist/7071.js +0 -1
  64. package/dist/7071.js.map +0 -1
  65. package/dist/729.js +0 -2
  66. package/dist/729.js.map +0 -1
  67. /package/dist/{729.js.LICENSE.txt → 7125.js.LICENSE.txt} +0 -0
@@ -0,0 +1,234 @@
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ TableRow,
4
+ TableCell,
5
+ Checkbox,
6
+ Table,
7
+ TableHead,
8
+ TableHeader,
9
+ TableBody,
10
+ Button,
11
+ Row,
12
+ InlineLoading,
13
+ } from '@carbon/react';
14
+ import { HieIdentificationType, type AmrsPerson, type ClientRegistryBody } from './client-registry.types';
15
+ import {
16
+ addressFields,
17
+ getIdentifierUuid,
18
+ identifiersSyncFields,
19
+ mapFieldValue,
20
+ nameFields,
21
+ patientObjFields,
22
+ personSyncFields,
23
+ } from './map-client-registry-to-form-utils';
24
+ import { updatePerson, updateAmrsPersonIdentifiers } from './client-registry.resource';
25
+ import { showSnackbar } from '@openmrs/esm-framework';
26
+
27
+ interface ClientRegistryPatientDetailsProps {
28
+ hieData: ClientRegistryBody;
29
+ amrsPerson: AmrsPerson;
30
+ fromDependant?: boolean;
31
+ }
32
+
33
+ interface GetTableDataRowProps {
34
+ field: string;
35
+ label: string;
36
+ amrsPerson: string;
37
+ hiePatient: string;
38
+ onChange?(e: boolean, field: string, value: string, multiple: boolean): void;
39
+ allChecked: boolean;
40
+ }
41
+
42
+ const TableDataRow: React.FC<GetTableDataRowProps> = ({
43
+ field,
44
+ label,
45
+ amrsPerson,
46
+ hiePatient,
47
+ onChange,
48
+ allChecked,
49
+ }) => {
50
+ const [checked, setChecked] = useState(false);
51
+ const randomString = Math.random().toString(36).substring(2, 6).toUpperCase();
52
+ useEffect(() => {
53
+ onChange?.(allChecked, field, hiePatient, true);
54
+ setChecked(allChecked);
55
+ // eslint-disable-next-line react-hooks/exhaustive-deps
56
+ }, [allChecked]);
57
+
58
+ return (
59
+ <TableRow>
60
+ <TableCell>{label}</TableCell>
61
+ <TableCell>{amrsPerson}</TableCell>
62
+ <TableCell>{hiePatient}</TableCell>
63
+ <TableCell>
64
+ <Checkbox
65
+ id={`cbox-${randomString}`}
66
+ onChange={(e) => {
67
+ onChange(e.target.checked, field, hiePatient, false);
68
+ setChecked(e.target.checked);
69
+ }}
70
+ checked={checked}
71
+ />
72
+ </TableCell>
73
+ </TableRow>
74
+ );
75
+ };
76
+
77
+ const ClientRegistryPatientDetails: React.FC<ClientRegistryPatientDetailsProps> = ({
78
+ hieData,
79
+ amrsPerson,
80
+ fromDependant,
81
+ }) => {
82
+ const [syncFields, setSyncFields] = useState<Array<Record<string, string>>>([]);
83
+ const [allChecked, setAllChecked] = useState(false);
84
+ const [loading, setLoading] = useState(false);
85
+ const locationUuid = '18c343eb-b353-462a-9139-b16606e6b6c2';
86
+ const randomString = Math.random().toString(36).substring(2, 6).toUpperCase();
87
+
88
+ const handleFieldChange = (checked: boolean, field: string, value: string, multiple: boolean) => {
89
+ if (multiple) {
90
+ if (checked) {
91
+ setSyncFields((prev) => [...prev, { [field]: value }]);
92
+ } else {
93
+ setSyncFields([]);
94
+ }
95
+ } else {
96
+ if (checked) {
97
+ setSyncFields([...syncFields, { [field]: value }]);
98
+ } else {
99
+ setSyncFields((prev) => prev.filter((p) => !Object.keys(p).includes(field)));
100
+ }
101
+ }
102
+ };
103
+
104
+ const handleCheckAll = (e) => {
105
+ setAllChecked(e.target.checked);
106
+ };
107
+
108
+ const handleSync = async () => {
109
+ try {
110
+ const payload = {};
111
+ syncFields.forEach((field) => {
112
+ let key = Object.keys(field)[0];
113
+ payload[key] = field[key];
114
+ });
115
+
116
+ // Person
117
+ const patientPayload = {};
118
+ const names = {};
119
+ const addresses = {};
120
+ const otherFields = {};
121
+ Object.entries(payload).forEach(([k, v]) => {
122
+ if (personSyncFields.includes(k)) {
123
+ // names
124
+ if (nameFields.includes(k)) {
125
+ names[k] = v;
126
+ }
127
+ // addresses
128
+ else if (addressFields.includes(k)) {
129
+ if (v) {
130
+ addresses[k] = v;
131
+ }
132
+ } else {
133
+ otherFields[k] = v;
134
+ }
135
+ }
136
+ });
137
+ Object.assign(patientPayload, otherFields, { addresses: [addresses] }, { names: [names] });
138
+ await updatePerson(amrsPerson.person.uuid, patientPayload);
139
+ showSnackbar({
140
+ kind: 'success',
141
+ title: 'Patient successfully synced.',
142
+ });
143
+
144
+ // Identifiers
145
+ Object.entries(payload).forEach(async ([k, v]) => {
146
+ if (identifiersSyncFields().includes(k)) {
147
+ if (v) {
148
+ const identifierUuid = getIdentifierUuid(HieIdentificationType[k]);
149
+ const identifierPayload = {
150
+ identifier: v,
151
+ location: locationUuid,
152
+ identifierType: identifierUuid,
153
+ };
154
+ try {
155
+ // Check if the identifier exists
156
+ if (amrsPerson?.person?.identifiers?.find((i) => i.identifierType.uuid === identifierUuid)) {
157
+ // update to have the selected identifier
158
+ await updateAmrsPersonIdentifiers(
159
+ amrsPerson.person.uuid,
160
+ identifierUuid + '',
161
+ identifierPayload,
162
+ fromDependant,
163
+ );
164
+ } else {
165
+ // create to have the blank identifier
166
+ await updateAmrsPersonIdentifiers(amrsPerson.person.uuid, '', identifierPayload, fromDependant);
167
+ }
168
+ } catch (err) {
169
+ showSnackbar({
170
+ kind: 'error',
171
+ title: 'Error syncing patient identifiers.',
172
+ });
173
+ }
174
+ }
175
+ }
176
+ });
177
+ showSnackbar({
178
+ kind: 'success',
179
+ title: 'Patient identifiers successfully synced.',
180
+ });
181
+
182
+ setSyncFields([]);
183
+ } catch (err) {
184
+ showSnackbar({
185
+ kind: 'error',
186
+ title: 'Error syncing patient data.',
187
+ subtitle: JSON.stringify(err?.error?.message),
188
+ });
189
+ } finally {
190
+ setLoading(false);
191
+ }
192
+ };
193
+
194
+ return (
195
+ <>
196
+ <Row>
197
+ {syncFields.length ? <Button onClick={handleSync}>Sync data</Button> : null}
198
+ {loading ? <InlineLoading description="Syncing patient details..." /> : null}
199
+ </Row>
200
+ <Table>
201
+ <TableHead>
202
+ <TableRow>
203
+ <TableHeader>Field</TableHeader>
204
+ <TableHeader>AMRS Person</TableHeader>
205
+ <TableHeader>HIE Patient</TableHeader>
206
+ <TableHeader>
207
+ <Checkbox id={`cbox-multiple-${randomString}`} onChange={(e) => handleCheckAll(e)} />
208
+ </TableHeader>
209
+ </TableRow>
210
+ </TableHead>
211
+ <TableBody>
212
+ {patientObjFields.map((field) => {
213
+ const fieldValue = mapFieldValue(field, hieData, amrsPerson);
214
+ const amrsField = fieldValue[0];
215
+ const hieField = fieldValue[1];
216
+
217
+ return (
218
+ <TableDataRow
219
+ label={field}
220
+ field={field}
221
+ amrsPerson={amrsField}
222
+ hiePatient={hieField}
223
+ onChange={handleFieldChange}
224
+ allChecked={allChecked}
225
+ />
226
+ );
227
+ })}
228
+ </TableBody>
229
+ </Table>
230
+ </>
231
+ );
232
+ };
233
+
234
+ export default ClientRegistryPatientDetails;
@@ -0,0 +1,234 @@
1
+ import React, { useState } from 'react';
2
+ import { Button, TextInput, InlineLoading, InlineNotification, Select, SelectItem } from '@carbon/react';
3
+ import { showSnackbar } from '@openmrs/esm-framework';
4
+ import { useFormikContext } from 'formik';
5
+ import styles from '../patient-registration.scss';
6
+ import { requestCustomOtp, validateCustomOtp, fetchClientRegistryData } from './client-registry.resource';
7
+ import { applyClientRegistryMapping } from './map-client-registry-to-form-utils';
8
+ import { type RequestCustomOtpDto } from './client-registry.types';
9
+
10
+ export interface ClientRegistryLookupSectionProps {
11
+ onClientVerified?: (payload: RequestCustomOtpDto) => void;
12
+ }
13
+
14
+ const ClientRegistryLookupSection: React.FC<ClientRegistryLookupSectionProps> = ({ onClientVerified }) => {
15
+ const { setFieldValue, values } = useFormikContext<any>();
16
+ const [identifier, setIdentifier] = useState('');
17
+ const [otp, setOtp] = useState('');
18
+ const [otpSent, setOtpSent] = useState(false);
19
+ const [otpVerified, setOtpVerified] = useState(false);
20
+ const [loading, setLoading] = useState(false);
21
+ const [sessionId, setSessionId] = useState('');
22
+ const [error, setError] = useState<string>('');
23
+ const [identificationType, setIdentificationType] = useState('National ID');
24
+
25
+ const locationUuid = '18c343eb-b353-462a-9139-b16606e6b6c2';
26
+ const identificationTypes = [
27
+ { text: 'National ID', value: 'National ID' },
28
+ { text: 'Refugee ID', value: 'Refugee ID' },
29
+ { text: 'Alien ID', value: 'Alien ID' },
30
+ { text: 'Mandate Number', value: 'Mandate Number' },
31
+ ];
32
+
33
+ async function withTimeout<T>(promise: Promise<T>, ms = 10000): Promise<T> {
34
+ const controller = new AbortController();
35
+ const timeout = setTimeout(() => controller.abort(), ms);
36
+ try {
37
+ const response = await promise;
38
+ return response;
39
+ } catch (err) {
40
+ if (err.name === 'AbortError') {
41
+ throw new Error('Request timeout');
42
+ }
43
+ throw err;
44
+ } finally {
45
+ clearTimeout(timeout);
46
+ }
47
+ }
48
+
49
+ const handleFetchCR = async () => {
50
+ setLoading(true);
51
+ setError('');
52
+
53
+ try {
54
+ const payload = {
55
+ identificationNumber: identifier,
56
+ identificationType: identificationType,
57
+ locationUuid,
58
+ };
59
+ const result = await withTimeout(fetchClientRegistryData(payload));
60
+ const patients = Array.isArray(result) ? result : [];
61
+
62
+ if (patients.length === 0) {
63
+ throw new Error('No matching patient found in Client Registry.');
64
+ }
65
+
66
+ const patient = patients[0];
67
+ applyClientRegistryMapping(patient, setFieldValue);
68
+
69
+ showSnackbar({
70
+ kind: 'success',
71
+ title: 'Client Data Loaded',
72
+ subtitle: `Patient ${patient.first_name} ${patient.last_name} fetched successfully. Loaded education, next of kin, and relationships.`,
73
+ });
74
+ } catch (err) {
75
+ const errorMessage = err.message || 'Failed to fetch client data';
76
+ setError(errorMessage);
77
+ showSnackbar({
78
+ kind: 'error',
79
+ title: 'Fetch Failed',
80
+ subtitle: errorMessage,
81
+ });
82
+ } finally {
83
+ setLoading(false);
84
+ }
85
+ };
86
+
87
+ const handleSendOtp = async () => {
88
+ if (!identifier.trim()) {
89
+ setError('Please enter a valid National/Alien ID');
90
+ return;
91
+ }
92
+
93
+ setLoading(true);
94
+ setError('');
95
+ try {
96
+ const payload = {
97
+ identificationNumber: identifier,
98
+ identificationType: identificationType,
99
+ locationUuid,
100
+ };
101
+ const response = await withTimeout(requestCustomOtp(payload));
102
+ setSessionId(response.sessionId);
103
+ setOtpSent(true);
104
+
105
+ showSnackbar({
106
+ kind: 'success',
107
+ title: 'OTP sent successfully',
108
+ subtitle: `A code was sent to ${response.maskedPhone}`,
109
+ });
110
+ } catch (err) {
111
+ const errorMessage = err.message || 'Failed to send OTP';
112
+ setError(errorMessage);
113
+ showSnackbar({
114
+ kind: 'error',
115
+ title: 'Error sending OTP',
116
+ subtitle: errorMessage,
117
+ });
118
+ } finally {
119
+ setLoading(false);
120
+ }
121
+ };
122
+
123
+ const handleVerifyOtp = async () => {
124
+ if (!otp.trim()) {
125
+ setError('Please enter the OTP code');
126
+ return;
127
+ }
128
+
129
+ setLoading(true);
130
+ setError('');
131
+ try {
132
+ const payload = {
133
+ sessionId,
134
+ otp,
135
+ locationUuid,
136
+ };
137
+ await withTimeout(validateCustomOtp(payload));
138
+
139
+ const customOtpPayload = {
140
+ identificationNumber: identifier,
141
+ identificationType: identificationType,
142
+ locationUuid,
143
+ };
144
+ setOtpVerified(true);
145
+ onClientVerified?.(customOtpPayload);
146
+ showSnackbar({
147
+ kind: 'success',
148
+ title: 'OTP Verified',
149
+ subtitle: 'You can now fetch data from Client Registry.',
150
+ });
151
+ } catch (err) {
152
+ const errorMessage = err.message || 'OTP verification failed';
153
+ setError(errorMessage);
154
+ showSnackbar({
155
+ kind: 'error',
156
+ title: 'OTP Verification Failed',
157
+ subtitle: errorMessage,
158
+ });
159
+ } finally {
160
+ setLoading(false);
161
+ }
162
+ };
163
+
164
+ return (
165
+ <div className={styles.section}>
166
+ <h4 className={styles.sectionTitle}>Client Registry Verification</h4>
167
+
168
+ {error && (
169
+ <div className={styles.notificationSpacing}>
170
+ <InlineNotification title="Error" subtitle={error} kind="error" lowContrast />
171
+ </div>
172
+ )}
173
+
174
+ <div className={styles.fieldGroup}>
175
+ <Select labelText="Select Identification Type" onChange={(e) => setIdentificationType(e.target.value)}>
176
+ {identificationTypes.map((item) => (
177
+ <SelectItem text={item.text} value={item.value} />
178
+ ))}
179
+ </Select>
180
+ </div>
181
+
182
+ <div className={styles.fieldGroup}>
183
+ <TextInput
184
+ id="client-registry-id"
185
+ labelText="National ID or Alien ID"
186
+ value={identifier}
187
+ onChange={(e) => setIdentifier(e.target.value)}
188
+ disabled={otpSent}
189
+ placeholder="Enter identification number"
190
+ />
191
+ </div>
192
+
193
+ <div style={{ marginTop: '0.75rem' }}>
194
+ {!otpSent ? (
195
+ <Button kind="secondary" onClick={handleSendOtp} disabled={loading}>
196
+ {loading ? <InlineLoading description="Sending..." /> : 'Send OTP'}
197
+ </Button>
198
+ ) : (
199
+ <>
200
+ <div style={{ marginTop: '0.75rem' }}>
201
+ <TextInput
202
+ id="otp-input"
203
+ labelText="Enter OTP"
204
+ value={otp}
205
+ onChange={(e) => setOtp(e.target.value)}
206
+ disabled={otpVerified}
207
+ placeholder="Enter the code sent to your phone"
208
+ />
209
+ </div>
210
+
211
+ <div style={{ marginTop: '0.5rem', display: 'flex', gap: '0.5rem' }}>
212
+ {!otpVerified ? (
213
+ <Button size="sm" kind="secondary" onClick={handleVerifyOtp} disabled={loading}>
214
+ {loading ? <InlineLoading description="Verifying..." /> : 'Verify OTP'}
215
+ </Button>
216
+ ) : (
217
+ <Button kind="primary" onClick={handleFetchCR} disabled={loading}>
218
+ {loading ? <InlineLoading description="Fetching..." /> : 'Fetch Client Registry Data'}
219
+ </Button>
220
+ )}
221
+ {!otpVerified && (
222
+ <Button size="sm" kind="tertiary" onClick={() => setOtpSent(false)}>
223
+ Change ID
224
+ </Button>
225
+ )}
226
+ </div>
227
+ </>
228
+ )}
229
+ </div>
230
+ </div>
231
+ );
232
+ };
233
+
234
+ export default ClientRegistryLookupSection;
@@ -0,0 +1,78 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Button } from '@carbon/react';
3
+ import { showModal, age, usePatient } from '@openmrs/esm-framework';
4
+ import ClientRegistryLookupSection from './client-registry-search.component';
5
+ import { Formik } from 'formik';
6
+ import { type RequestCustomOtpDto } from './client-registry.types';
7
+ import ClientRegistryDetails from './client-registry-details.component';
8
+
9
+ const ClientRegistryVerificationTag = () => {
10
+ const { patient } = usePatient();
11
+ const [showCrBtn, setShowCrBtn] = useState(false);
12
+
13
+ useEffect(() => {
14
+ if (patient && patient.birthDate) {
15
+ const ageArr = age(patient.birthDate).split(' ');
16
+ if (ageArr.includes('yrs')) {
17
+ const yrs = Number(ageArr[0]);
18
+ setShowCrBtn(yrs > 17);
19
+ }
20
+ }
21
+ }, [patient]);
22
+
23
+ const handleClientRegistryVerification = () => {
24
+ const dispose = showModal(
25
+ 'client-registry-verification-modal',
26
+ {
27
+ onConfirm: () => {
28
+ dispose();
29
+ },
30
+ Component: CrFormikComponent,
31
+ props: {
32
+ forceClose: () => {
33
+ dispose();
34
+ },
35
+ },
36
+ },
37
+ () => {},
38
+ );
39
+ };
40
+
41
+ return showCrBtn ? (
42
+ <Button kind="ghost" onClick={handleClientRegistryVerification}>
43
+ Verify CR
44
+ </Button>
45
+ ) : (
46
+ <></>
47
+ );
48
+ };
49
+
50
+ interface CrFormicComponentProps {
51
+ forceClose?(): void;
52
+ }
53
+ const CrFormikComponent: React.FC<CrFormicComponentProps> = ({ forceClose }) => {
54
+ const initialFormValues = {};
55
+
56
+ const handleOnClientVerified = (payload: RequestCustomOtpDto) => {
57
+ forceClose?.();
58
+ const dispose = showModal(
59
+ 'client-registry-verification-modal',
60
+ {
61
+ onConfirm: () => {
62
+ dispose();
63
+ },
64
+ Component: ClientRegistryDetails,
65
+ props: { payload: payload },
66
+ },
67
+ () => {},
68
+ );
69
+ };
70
+
71
+ return (
72
+ <Formik initialValues={initialFormValues} onSubmit={null}>
73
+ <ClientRegistryLookupSection onClientVerified={handleOnClientVerified} />
74
+ </Formik>
75
+ );
76
+ };
77
+
78
+ export default ClientRegistryVerificationTag;
@@ -0,0 +1,135 @@
1
+ import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import {
3
+ type AmrsPerson,
4
+ type ClientRegistrySearchRequest,
5
+ type ClientRegistrySearchResponse,
6
+ type RequestCustomOtpDto,
7
+ type RequestCustomOtpResponse,
8
+ type ValidateCustomOtpResponse,
9
+ type ValidateHieCustomOtpDto,
10
+ } from './client-registry.types';
11
+ import { mapAmrsPatientRelationship } from './map-client-registry-to-form-utils';
12
+
13
+ const HIE_BASE_URL = 'https://staging.ampath.or.ke/hie';
14
+
15
+ async function postJson<T>(url: string, payload: unknown): Promise<T> {
16
+ const response = await fetch(url, {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json' },
19
+ body: JSON.stringify(payload),
20
+ });
21
+
22
+ if (!response.ok) {
23
+ const errorText = await response.text();
24
+ throw new Error(`Request failed with ${response.status}: ${errorText}`);
25
+ }
26
+
27
+ return response.json() as Promise<T>;
28
+ }
29
+
30
+ export async function requestCustomOtp(payload: RequestCustomOtpDto): Promise<RequestCustomOtpResponse> {
31
+ const url = `${HIE_BASE_URL}/client/send-custom-otp`;
32
+ const formattedPayload = {
33
+ identificationNumber: payload.identificationNumber,
34
+ identificationType: payload.identificationType,
35
+ locationUuid: payload.locationUuid,
36
+ };
37
+ return postJson<RequestCustomOtpResponse>(url, formattedPayload);
38
+ }
39
+
40
+ export async function validateCustomOtp(payload: ValidateHieCustomOtpDto): Promise<ValidateCustomOtpResponse> {
41
+ const url = `${HIE_BASE_URL}/client/validate-custom-otp`;
42
+ const formattedPayload = {
43
+ sessionId: payload.sessionId,
44
+ otp: payload.otp,
45
+ locationUuid: payload.locationUuid,
46
+ };
47
+ return postJson<ValidateCustomOtpResponse>(url, formattedPayload);
48
+ }
49
+
50
+ export async function fetchClientRegistryData(
51
+ payload: ClientRegistrySearchRequest,
52
+ ): Promise<ClientRegistrySearchResponse> {
53
+ const url = `${HIE_BASE_URL}/client/search`;
54
+ const formattedPayload = {
55
+ identificationNumber: payload.identificationNumber,
56
+ identificationType: payload.identificationType,
57
+ locationUuid: payload.locationUuid,
58
+ };
59
+ return postJson<ClientRegistrySearchResponse>(url, formattedPayload);
60
+ }
61
+
62
+ export async function fetchAmrsPatientData(patientUuid: string) {
63
+ return await openmrsFetch<AmrsPerson>(`${restBaseUrl}/patient/${patientUuid}?v=full`, {
64
+ method: 'GET',
65
+ }).catch((err) => {
66
+ console.error(err);
67
+ });
68
+ }
69
+
70
+ export async function updateAmrsPersonIdentifiers(
71
+ patientUuid: string,
72
+ identifierUuid: string,
73
+ payload: unknown,
74
+ fromDependant = false,
75
+ ) {
76
+ const resource = fromDependant ? 'person' : 'patient';
77
+ return await openmrsFetch(`${restBaseUrl}/${resource}/${patientUuid}/identifier/${identifierUuid}`, {
78
+ method: 'POST',
79
+ headers: {
80
+ 'Content-Type': 'application/json',
81
+ },
82
+ body: payload,
83
+ });
84
+ }
85
+
86
+ export async function fetchAmrsPersonData(personUuid: string) {
87
+ return await openmrsFetch(`${restBaseUrl}/person/${personUuid}?v=full`, {
88
+ method: 'GET',
89
+ }).catch((err) => {
90
+ console.error(err);
91
+ });
92
+ }
93
+
94
+ export async function updatePerson(patientUuid: string, payload: unknown) {
95
+ return await openmrsFetch<AmrsPerson>(`${restBaseUrl}/person/${patientUuid}`, {
96
+ method: 'POST',
97
+ headers: {
98
+ 'Content-Type': 'application/json',
99
+ },
100
+ body: payload,
101
+ });
102
+ }
103
+
104
+ export async function createPerson(payload: unknown) {
105
+ return await openmrsFetch<AmrsPerson>(`${restBaseUrl}/person`, {
106
+ method: 'POST',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ },
110
+ body: payload,
111
+ });
112
+ }
113
+
114
+ export async function createRelationship(payload: unknown) {
115
+ return await openmrsFetch(`${restBaseUrl}/relationship`, {
116
+ method: 'POST',
117
+ headers: {
118
+ 'Content-Type': 'application/json',
119
+ },
120
+ body: payload,
121
+ });
122
+ }
123
+
124
+ export async function getRelationships(patientUuid: string) {
125
+ const response = await openmrsFetch(`${restBaseUrl}/relationship?person=${patientUuid}&v=full`, {
126
+ method: 'GET',
127
+ headers: {
128
+ 'Content-Type': 'application/json',
129
+ },
130
+ });
131
+ if (response && response.data) {
132
+ return mapAmrsPatientRelationship(patientUuid, response.data.results);
133
+ }
134
+ return [];
135
+ }