@kenyaemr/esm-admin-app 5.4.2-pre.2694 → 5.4.2-pre.2707

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":{"kenyaemrCharts":"^1.6.7"},"extensions":[{"component":"adminLeftPanelLink","name":"admin-left-panel-link","slot":"admin-left-panel-slot"},{"component":"userManagementLeftPannelLink","name":"user-management-left-panel-link","slot":"admin-left-panel-slot"},{"component":"etlAdministrationLeftPannelLink","name":"etl-administration-left-panel-link","slot":"admin-left-panel-slot"},{"component":"locationsLeftPanelLink","name":"locations-left-panel-link","slot":"admin-left-panel-slot"},{"component":"facilitySetupLeftPanelLink","name":"facility-setup-left-panel-link","slot":"admin-left-panel-slot"},{"component":"providerBanner","name":"provider-banner","slot":"provider-banner-info-slot","order":1}],"workspaces":[{"name":"manage-user-workspace","component":"manageUserWorkspace","title":"Manage User Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"user-role-scope-workspace","component":"userRoleScopeWorkspace","title":"User Rple Scope Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"add-location-workspace","title":"Add Location","component":"addLocation","type":"workspace"},{"name":"search-location-workspace","title":"Search Location","component":"searchLocationWorkspace","type":"workspace"},{"name":"hwr-sync-workspace","title":"HWR Sync Workspace","component":"hwrSyncWorkspace","type":"other-form"},{"name":"hwr-sync-modal","title":"HWR Sync Modal","component":"hwrSyncModal","type":"modal"}],"modals":[{"component":"operationConfirmationModal","name":"operation-confirmation-modal"},{"component":"hwrConfirmationModal","name":"hwr-confirmation-modal"},{"component":"hwrEmptyModal","name":"hwr-empty-modal"},{"component":"hwrSyncModal","name":"hwr-syncing-modal"}],"pages":[{"component":"root","route":"admin"}],"version":"5.4.2-pre.2694"}
1
+ {"$schema":"https://json.openmrs.org/routes.schema.json","backendDependencies":{"kenyaemrCharts":"^1.6.7"},"extensions":[{"component":"adminLeftPanelLink","name":"admin-left-panel-link","slot":"admin-left-panel-slot"},{"component":"userManagementLeftPannelLink","name":"user-management-left-panel-link","slot":"admin-left-panel-slot"},{"component":"etlAdministrationLeftPannelLink","name":"etl-administration-left-panel-link","slot":"admin-left-panel-slot"},{"component":"locationsLeftPanelLink","name":"locations-left-panel-link","slot":"admin-left-panel-slot"},{"component":"facilitySetupLeftPanelLink","name":"facility-setup-left-panel-link","slot":"admin-left-panel-slot"},{"component":"providerBanner","name":"provider-banner","slot":"provider-banner-info-slot","order":1}],"workspaces":[{"name":"manage-user-workspace","component":"manageUserWorkspace","title":"Manage User Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"user-role-scope-workspace","component":"userRoleScopeWorkspace","title":"User Rple Scope Workspace","type":"other-form","canMaximize":true,"width":"extra-wide"},{"name":"add-location-workspace","title":"Add Location","component":"addLocation","type":"workspace"},{"name":"search-location-workspace","title":"Search Location","component":"searchLocationWorkspace","type":"workspace"},{"name":"hwr-sync-workspace","title":"HWR Sync Workspace","component":"hwrSyncWorkspace","type":"other-form"},{"name":"hwr-sync-modal","title":"HWR Sync Modal","component":"hwrSyncModal","type":"modal"}],"modals":[{"component":"operationConfirmationModal","name":"operation-confirmation-modal"},{"component":"hwrConfirmationModal","name":"hwr-confirmation-modal"},{"component":"hwrEmptyModal","name":"hwr-empty-modal"},{"component":"hwrSyncModal","name":"hwr-syncing-modal"}],"pages":[{"component":"root","route":"admin"}],"version":"5.4.2-pre.2707"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kenyaemr/esm-admin-app",
3
- "version": "5.4.2-pre.2694",
3
+ "version": "5.4.2-pre.2707",
4
4
  "description": "Facilitates the management of ETL tables",
5
5
  "keywords": [
6
6
  "openmrs"
@@ -0,0 +1,213 @@
1
+ import { FetchResponse, makeUrl, openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
2
+ import { HWR_API_NO_CREDENTIALS, PROVIDER_NOT_FOUND, RESOURCE_NOT_FOUND, UNKNOWN } from '../../constants';
3
+ import { CustomHIEPractitionerResponse, PractitionerResponse } from '../../types';
4
+ import { useState } from 'react';
5
+
6
+ /**
7
+ * Unified response type that can handle both FHIR and custom HIE formats
8
+ */
9
+ export interface UnifiedHealthWorkerResponse {
10
+ fhirFormat: boolean;
11
+ data: PractitionerResponse | CustomHIEPractitionerResponse;
12
+ }
13
+
14
+ /**
15
+ * Adapter to normalize health worker data based on response format
16
+ */
17
+ export class HealthWorkerAdapter {
18
+ /**
19
+ * Extracts common practitioner information from either response format
20
+ */
21
+ static normalize(response: UnifiedHealthWorkerResponse): NormalizedPractitioner {
22
+ if (response.fhirFormat) {
23
+ return this.normalizeFHIRResponse(response.data as PractitionerResponse);
24
+ } else {
25
+ return this.normalizeCustomResponse(response.data as CustomHIEPractitionerResponse);
26
+ }
27
+ }
28
+
29
+ private static normalizeFHIRResponse(data: PractitionerResponse): NormalizedPractitioner {
30
+ const resource = data.entry?.[0]?.resource;
31
+ if (!resource) {
32
+ throw new Error('Invalid FHIR response: No resource found');
33
+ }
34
+
35
+ const name = resource.name?.[0];
36
+ const identifierByType = (code: string) =>
37
+ resource.identifier?.find((id) => id.type?.coding?.[0]?.code === code)?.value;
38
+
39
+ const primaryQualification = resource.qualification?.[0];
40
+ const licenseExtension = primaryQualification?.extension?.find(
41
+ (ext) => ext.url.includes('license') || ext.url.includes('registration'),
42
+ );
43
+
44
+ return {
45
+ id: resource.id,
46
+ fullName: name?.text || '',
47
+ firstName: name?.text?.split(' ')[0] || '',
48
+ middleName: name?.text?.split(' ')[1] || '',
49
+ lastName: name?.text?.split(' ')[2] || name?.text?.split(' ')[1] || '',
50
+ gender: resource.gender || '',
51
+ registrationId: identifierByType('registration') || '',
52
+ externalReferenceId: identifierByType('license') || '',
53
+ nationalId: identifierByType('national_id') || '',
54
+ passportNumber: identifierByType('passport') || '',
55
+ licenseNumber: identifierByType('license') || '',
56
+ licensingBody: licenseExtension?.valueCoding?.display || '',
57
+ specialty: primaryQualification?.code?.coding?.[0]?.display || '',
58
+ qualification: primaryQualification?.code?.coding?.[0]?.display || '',
59
+ phoneNumber: resource.telecom?.find((t) => t.system === 'phone')?.value || '',
60
+ email: resource.telecom?.find((t) => t.system === 'email')?.value || '',
61
+ licenseStartDate: primaryQualification?.period?.start || '',
62
+ licenseEndDate: primaryQualification?.period?.end || '',
63
+ isActive: resource.active,
64
+ providerUniqueIdentifier: resource.id,
65
+ status: resource.active ? 'active' : 'inactive',
66
+ };
67
+ }
68
+
69
+ private static normalizeCustomResponse(data: CustomHIEPractitionerResponse): NormalizedPractitioner {
70
+ const { membership, licenses, contacts, identifiers, professional_details } = data.message;
71
+
72
+ const mostRecentLicense = licenses
73
+ ?.filter((l) => l.license_end)
74
+ .sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime())[0];
75
+
76
+ return {
77
+ id: membership.id,
78
+ fullName: membership.full_name,
79
+ firstName: membership.first_name,
80
+ middleName: membership.middle_name,
81
+ lastName: membership.last_name,
82
+ gender: membership.gender,
83
+ registrationId: membership.registration_id,
84
+ externalReferenceId: membership.external_reference_id,
85
+ nationalId: identifiers.identification_number,
86
+ passportNumber: '',
87
+ licenseNumber: membership.external_reference_id,
88
+ licensingBody: membership.licensing_body,
89
+ specialty: membership.specialty || professional_details?.specialty,
90
+ qualification: professional_details?.educational_qualifications || membership.specialty,
91
+ phoneNumber: contacts.phone,
92
+ email: contacts.email,
93
+ licenseStartDate: mostRecentLicense?.license_start || '',
94
+ licenseEndDate: mostRecentLicense?.license_end || '',
95
+ isActive: membership.is_active === 1,
96
+ providerUniqueIdentifier: membership.id,
97
+ status: membership.status,
98
+ professionalCadre: professional_details?.professional_cadre,
99
+ practiceType: professional_details?.practice_type,
100
+ licenses: licenses,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Type guard to check if response is FHIR format
106
+ */
107
+ static isFHIRFormat(response: any): response is PractitionerResponse {
108
+ return response.resourceType === 'Bundle' || (response.entry && Array.isArray(response.entry));
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Normalized practitioner data structure
114
+ */
115
+ export interface NormalizedPractitioner {
116
+ id: string;
117
+ fullName: string;
118
+ firstName: string;
119
+ middleName: string;
120
+ lastName: string;
121
+ gender: string;
122
+ registrationId: string;
123
+ externalReferenceId: string;
124
+ nationalId: string;
125
+ passportNumber: string;
126
+ licenseNumber: string;
127
+ licensingBody: string;
128
+ specialty: string;
129
+ qualification: string;
130
+ phoneNumber: string;
131
+ email: string;
132
+ licenseStartDate: string;
133
+ licenseEndDate: string;
134
+ isActive: boolean;
135
+ providerUniqueIdentifier: string;
136
+ status: string;
137
+ professionalCadre?: string;
138
+ practiceType?: string;
139
+ licenses?: Array<{
140
+ id: string;
141
+ external_reference_id: string;
142
+ license_type: string;
143
+ license_start: string;
144
+ license_end: string;
145
+ }>;
146
+ }
147
+
148
+ /**
149
+ * Search for health care worker with automatic format detection
150
+ */
151
+ export const searchHealthCareWork = async (
152
+ identifierType: string,
153
+ identifierNumber: string,
154
+ regulator: string,
155
+ ): Promise<UnifiedHealthWorkerResponse> => {
156
+ const url = `${restBaseUrl}/kenyaemr/practitionersearch?identifierType=${identifierType}&identifierNumber=${identifierNumber}&regulator=${regulator}`;
157
+
158
+ const response = await fetch(makeUrl(url));
159
+
160
+ if (!response.ok) {
161
+ if (response.status === 401) {
162
+ throw new Error(HWR_API_NO_CREDENTIALS);
163
+ } else if (response.status === 404) {
164
+ throw new Error(RESOURCE_NOT_FOUND);
165
+ }
166
+ throw new Error(UNKNOWN);
167
+ }
168
+
169
+ const responseData = await response.json();
170
+
171
+ // Check for error in response
172
+ if (responseData?.issue) {
173
+ throw new Error(PROVIDER_NOT_FOUND);
174
+ }
175
+
176
+ // Determine format based on fhirFormat flag or structure
177
+ const isFhir = responseData.fhirFormat === true || HealthWorkerAdapter.isFHIRFormat(responseData);
178
+
179
+ return {
180
+ fhirFormat: isFhir,
181
+ data: responseData,
182
+ };
183
+ };
184
+
185
+ /**
186
+ * Hook for searching health care worker with normalized data
187
+ */
188
+ export const useHealthWorkerSearch = () => {
189
+ const [isSearching, setIsSearching] = useState(false);
190
+ const [error, setError] = useState<string | null>(null);
191
+
192
+ const search = async (
193
+ identifierType: string,
194
+ identifierNumber: string,
195
+ regulator: string,
196
+ ): Promise<NormalizedPractitioner | null> => {
197
+ try {
198
+ setIsSearching(true);
199
+ setError(null);
200
+
201
+ const response = await searchHealthCareWork(identifierType, identifierNumber, regulator);
202
+ return HealthWorkerAdapter.normalize(response);
203
+ } catch (err) {
204
+ const errorMessage = err instanceof Error ? err.message : UNKNOWN;
205
+ setError(errorMessage);
206
+ return null;
207
+ } finally {
208
+ setIsSearching(false);
209
+ }
210
+ };
211
+
212
+ return { search, isSearching, error };
213
+ };
@@ -4,6 +4,7 @@ import capitalize from 'lodash-es/capitalize';
4
4
  import React from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { CustomHIEPractitionerResponse, type PractitionerResponse } from '../../types';
7
+ import { NormalizedPractitioner } from '../hook/healthWorkerAdapter';
7
8
  import styles from './hwr-confirmation.modal.scss';
8
9
  import { formatDateTime } from '../../utils/utils';
9
10
 
@@ -24,19 +25,21 @@ const HealthWorkerInfo: React.FC<HealthWorkerInfoProps> = ({ label, value }) =>
24
25
  interface HWRConfirmModalProps {
25
26
  onConfirm: () => void;
26
27
  close: () => void;
27
- healthWorker: CustomHIEPractitionerResponse;
28
+ healthWorker: CustomHIEPractitionerResponse | PractitionerResponse;
29
+ normalizedData: NormalizedPractitioner;
30
+ fhirFormat: boolean;
28
31
  }
29
32
 
30
- const HWRConfirmModal: React.FC<HWRConfirmModalProps> = ({ close, onConfirm, healthWorker }) => {
33
+ const HWRConfirmModal: React.FC<HWRConfirmModalProps> = ({
34
+ close,
35
+ onConfirm,
36
+ healthWorker,
37
+ normalizedData,
38
+ fhirFormat,
39
+ }) => {
31
40
  const { t } = useTranslation();
32
41
 
33
- const { membership, licenses, contacts, identifiers, professional_details } = healthWorker?.message || {};
34
-
35
- const mostRecentLicense = licenses
36
- ?.filter((l) => l.license_end)
37
- .sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime())[0];
38
-
39
- const isLicenseValid = mostRecentLicense?.license_end ? new Date(mostRecentLicense.license_end) > new Date() : false;
42
+ const isLicenseValid = normalizedData.licenseEndDate ? new Date(normalizedData.licenseEndDate) > new Date() : false;
40
43
 
41
44
  return (
42
45
  <>
@@ -55,69 +58,71 @@ const HWRConfirmModal: React.FC<HWRConfirmModalProps> = ({ close, onConfirm, hea
55
58
  className={styles.healthWorkerPhoto}
56
59
  name="patient-photo-slot"
57
60
  state={{
58
- patientName: membership?.full_name || '',
61
+ patientName: normalizedData.fullName || '',
59
62
  }}
60
63
  />
61
64
  <div style={{ width: '100%', marginLeft: '0.625rem' }}>
62
65
  <HealthWorkerInfo
63
66
  label={t('healthWorkerName', 'Health worker name')}
64
- value={membership?.full_name || '--'}
67
+ value={normalizedData.fullName || '--'}
65
68
  />
66
69
 
67
70
  <HealthWorkerInfo
68
71
  label={t('providerUniqueIdentifier', 'Provider unique identifier')}
69
- value={membership?.id || '--'}
72
+ value={normalizedData.providerUniqueIdentifier || '--'}
70
73
  />
71
74
 
72
75
  <HealthWorkerInfo
73
76
  label={t('registrationId', 'Registration ID')}
74
- value={membership?.registration_id || '--'}
77
+ value={normalizedData.registrationId || '--'}
75
78
  />
76
79
 
77
80
  <HealthWorkerInfo
78
81
  label={t('externalReferenceId', 'External Reference ID')}
79
- value={membership?.external_reference_id || '--'}
82
+ value={normalizedData.externalReferenceId || '--'}
80
83
  />
81
84
 
82
- <HealthWorkerInfo label={t('gender', 'Gender')} value={membership?.gender || '--'} />
85
+ <HealthWorkerInfo label={t('gender', 'Gender')} value={normalizedData.gender || '--'} />
83
86
 
84
- <HealthWorkerInfo label={t('status', 'Status')} value={membership?.status || '--'} />
87
+ <HealthWorkerInfo label={t('status', 'Status')} value={normalizedData.status || '--'} />
85
88
 
86
- {contacts?.phone && <HealthWorkerInfo label={t('phone', 'Phone')} value={contacts.phone} />}
89
+ {normalizedData.phoneNumber && (
90
+ <HealthWorkerInfo label={t('phone', 'Phone')} value={normalizedData.phoneNumber} />
91
+ )}
87
92
 
88
- {contacts?.email && <HealthWorkerInfo label={t('email', 'Email')} value={contacts.email} />}
93
+ {normalizedData.email && <HealthWorkerInfo label={t('email', 'Email')} value={normalizedData.email} />}
89
94
 
90
- {identifiers?.identification_number && (
95
+ {normalizedData.nationalId && (
91
96
  <HealthWorkerInfo
92
- label={identifiers.identification_type || t('identificationNumber', 'Identification Number')}
93
- value={identifiers.identification_number}
97
+ label={t('identificationNumber', 'Identification Number')}
98
+ value={normalizedData.nationalId}
94
99
  />
95
100
  )}
96
101
 
97
- <HealthWorkerInfo label={t('licensingBody', 'Licensing Body')} value={membership?.licensing_body || '--'} />
98
-
99
102
  <HealthWorkerInfo
100
- label={t('specialty', 'Specialty')}
101
- value={membership?.specialty || professional_details?.specialty || '--'}
103
+ label={t('licensingBody', 'Licensing Body')}
104
+ value={normalizedData.licensingBody || '--'}
102
105
  />
103
106
 
104
- {professional_details?.professional_cadre && (
107
+ <HealthWorkerInfo label={t('specialty', 'Specialty')} value={normalizedData.specialty || '--'} />
108
+
109
+ {normalizedData.professionalCadre && (
105
110
  <HealthWorkerInfo
106
111
  label={t('professionalCadre', 'Professional Cadre')}
107
- value={professional_details.professional_cadre}
112
+ value={normalizedData.professionalCadre}
108
113
  />
109
114
  )}
110
115
 
111
- {professional_details?.practice_type && (
112
- <HealthWorkerInfo label={t('practiceType', 'Practice Type')} value={professional_details.practice_type} />
116
+ {normalizedData.practiceType && (
117
+ <HealthWorkerInfo label={t('practiceType', 'Practice Type')} value={normalizedData.practiceType} />
113
118
  )}
114
119
 
115
- {licenses && licenses.length > 0 && (
120
+ {normalizedData.licenses && normalizedData.licenses.length > 0 && (
116
121
  <>
117
122
  <div style={{ marginTop: '1rem', marginBottom: '0.5rem' }}>
118
123
  <strong>{t('licenses', 'Licenses')}</strong>
119
124
  </div>
120
- {licenses.map((license, index) => (
125
+ {normalizedData.licenses.map((license, index) => (
121
126
  <div key={index} style={{ marginBottom: '0.5rem', paddingLeft: '1rem' }}>
122
127
  <HealthWorkerInfo
123
128
  label={`${license.license_type} ${t('license', 'License')}`}
@@ -132,6 +137,15 @@ const HWRConfirmModal: React.FC<HWRConfirmModalProps> = ({ close, onConfirm, hea
132
137
  </>
133
138
  )}
134
139
 
140
+ {normalizedData.licenseStartDate && normalizedData.licenseEndDate && (
141
+ <HealthWorkerInfo
142
+ label={t('licenseValidity', 'License Validity Period')}
143
+ value={`${formatDateTime(normalizedData.licenseStartDate)} - ${formatDateTime(
144
+ normalizedData.licenseEndDate,
145
+ )}`}
146
+ />
147
+ )}
148
+
135
149
  <HealthWorkerInfo
136
150
  label={t('licenseValid', 'License Validity')}
137
151
  value={
@@ -6,7 +6,7 @@ import { useConfig, showSnackbar, formatDate, parseDate, showToast, restBaseUrl
6
6
  import { mutate } from 'swr';
7
7
  import { CustomHIEPractitionerResponse, type PractitionerResponse, type ProviderResponse } from '../../types';
8
8
  import { ConfigObject } from '../../config-schema';
9
- import { searchHealthCareWork } from '../hook/searchHealthCareWork';
9
+ import { searchHealthCareWork, HealthWorkerAdapter } from '../hook/healthWorkerAdapter';
10
10
  import { createProviderAttribute, updateProviderAttributes } from './hwr-sync.resource';
11
11
 
12
12
  interface HWRSyncModalProps {
@@ -71,57 +71,43 @@ const HWRSyncModal: React.FC<HWRSyncModalProps> = ({ close, provider }) => {
71
71
  const handleSync = async () => {
72
72
  try {
73
73
  setSyncLoading(true);
74
- const healthWorker: CustomHIEPractitionerResponse = await searchHealthCareWork(
74
+ const unifiedResponse = await searchHealthCareWork(
75
75
  searchHWR.identifierType,
76
76
  searchHWR.identifier,
77
77
  searchHWR.regulator,
78
78
  );
79
79
 
80
- if (!healthWorker?.message) {
80
+ const normalizedData = HealthWorkerAdapter.normalize(unifiedResponse);
81
+
82
+ if (!normalizedData) {
81
83
  throw new Error(t('noResults', 'No results found'));
82
84
  }
83
85
 
84
- const { membership, licenses, contacts, identifiers } = healthWorker.message;
85
-
86
- const mostRecentLicense = licenses
87
- ?.filter((l) => l.license_end)
88
- .sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime())[0];
89
-
90
- const extractedAttributes = {
91
- licenseNumber: membership.external_reference_id,
92
- regNumber: membership.registration_id,
93
- licenseDate: mostRecentLicense?.license_end ? formatDate(new Date(mostRecentLicense.license_end)) : null,
94
- phoneNumber: contacts.phone,
95
- email: contacts.email,
96
- qualification: membership.specialty,
97
- nationalId: identifiers.identification_number,
98
- providerUniqueIdentifier: membership.id,
99
- };
100
-
101
86
  const updatableAttributes = [
102
- { attributeType: licenseNumberUuid, value: extractedAttributes.licenseNumber },
103
- { attributeType: licenseBodyUuid, value: extractedAttributes.regNumber },
87
+ { attributeType: licenseNumberUuid, value: normalizedData.licenseNumber },
88
+ { attributeType: licenseBodyUuid, value: normalizedData.registrationId },
104
89
  {
105
90
  attributeType: licenseExpiryDateUuid,
106
- value: extractedAttributes.licenseDate ? parseDate(extractedAttributes.licenseDate) : null,
91
+ value: normalizedData.licenseEndDate ? parseDate(normalizedData.licenseEndDate) : null,
107
92
  },
108
- { attributeType: phoneNumberUuid, value: extractedAttributes.phoneNumber },
109
- { attributeType: qualificationUuid, value: extractedAttributes.qualification },
93
+ { attributeType: phoneNumberUuid, value: normalizedData.phoneNumber },
94
+ { attributeType: qualificationUuid, value: normalizedData.qualification },
110
95
  {
111
96
  attributeType: providerHieFhirReference,
112
97
  value: JSON.stringify({
113
- ...healthWorker,
98
+ ...unifiedResponse.data,
99
+ fhirFormat: unifiedResponse.fhirFormat,
114
100
  searchParameters: {
115
101
  regulator: searchHWR.regulator,
116
102
  identifierType: searchHWR.identifierType,
117
103
  },
118
104
  }),
119
105
  },
120
- { attributeType: providerAddressUuid, value: extractedAttributes.email },
121
- { attributeType: providerNationalIdUuid, value: extractedAttributes.nationalId },
106
+ { attributeType: providerAddressUuid, value: normalizedData.email },
107
+ { attributeType: providerNationalIdUuid, value: normalizedData.nationalId },
122
108
  {
123
109
  attributeType: providerUniqueIdentifierAttributeTypeUuid,
124
- value: extractedAttributes.providerUniqueIdentifier,
110
+ value: normalizedData.providerUniqueIdentifier,
125
111
  },
126
112
  ].filter((attr) => attr.value !== undefined && attr.value !== null && attr.value !== '');
127
113
 
@@ -47,7 +47,7 @@ import UserManagementFormSchema from '../userManagementFormSchema';
47
47
  import { ChevronLeft, Query, ChevronRight } from '@carbon/react/icons';
48
48
  import { useSystemUserRoleConfigSetting } from '../../hook/useSystemRoleSetting';
49
49
  import { type CustomHIEPractitionerResponse, type PractitionerResponse, Provider, User } from '../../../types';
50
- import { searchHealthCareWork } from '../../hook/searchHealthCareWork';
50
+ import { searchHealthCareWork, HealthWorkerAdapter, NormalizedPractitioner } from '../../hook/healthWorkerAdapter';
51
51
  import { ROLE_CATEGORIES, SECTIONS, today } from '../../../constants';
52
52
  import { ConfigObject } from '../../../config-schema';
53
53
  import { mutate } from 'swr';
@@ -230,10 +230,6 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
230
230
  providerUniqueIdentifier,
231
231
  ]);
232
232
 
233
- function extractAttributeValue(attributes, prefix: string) {
234
- return attributes?.find((attr) => attr.display.startsWith(prefix))?.display?.split(' ')[3] || '';
235
- }
236
-
237
233
  const userFormMethods = useForm<UserFormSchema>({
238
234
  resolver: zodResolver(userManagementFormSchema),
239
235
  mode: 'all',
@@ -256,55 +252,48 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
256
252
  }
257
253
  }, [isDirty, promptBeforeClosing]);
258
254
 
259
- const setPractitionerValues = (healthWorker: CustomHIEPractitionerResponse) => {
260
- const { membership, licenses, contacts, identifiers } = healthWorker.message;
261
-
262
- setValue('givenName', membership.first_name || '');
263
- setValue('middleName', membership.middle_name || '');
264
- setValue('familyName', membership.last_name || '');
265
-
266
- setValue('nationalId', identifiers.identification_number || '');
267
- setValue('providerLicense', membership.external_reference_id || '');
268
- setValue('registrationNumber', membership.registration_id || '');
269
- setValue('providerUniqueIdentifier', membership.id || '');
270
-
271
- setValue('phoneNumber', contacts.phone || '');
272
- setValue('email', contacts.email || '');
273
-
274
- setValue('qualification', membership.specialty || '');
275
-
276
- setValue('passportNumber', '');
277
-
278
- const mostRecentLicense = licenses
279
- .filter((l) => l.license_end)
280
- .sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime())[0];
281
-
255
+ const setPractitionerValuesFromNormalized = (normalized: NormalizedPractitioner) => {
256
+ setValue('givenName', normalized.firstName || '');
257
+ setValue('middleName', normalized.middleName || '');
258
+ setValue('familyName', normalized.lastName || '');
259
+ setValue('nationalId', normalized.nationalId || '');
260
+ setValue('providerLicense', normalized.licenseNumber || '');
261
+ setValue('registrationNumber', normalized.registrationId || '');
262
+ setValue('providerUniqueIdentifier', normalized.providerUniqueIdentifier || '');
263
+ setValue('phoneNumber', normalized.phoneNumber || '');
264
+ setValue('email', normalized.email || '');
265
+ setValue('qualification', normalized.qualification || '');
266
+ setValue('passportNumber', normalized.passportNumber || '');
282
267
  setValue(
283
268
  'licenseExpiryDate',
284
- mostRecentLicense?.license_end ? parseDate(mostRecentLicense.license_end) : parseDate(t('unknown', 'Unknown')),
269
+ normalized.licenseEndDate ? parseDate(normalized.licenseEndDate) : parseDate(t('unknown', 'Unknown')),
285
270
  );
286
271
  };
287
272
 
288
273
  const handleSearch = async () => {
289
274
  try {
290
275
  setSearchHWR({ ...searchHWR, isHWRLoading: true });
291
- const fetchedHealthWorker: CustomHIEPractitionerResponse = await searchHealthCareWork(
276
+ const unifiedResponse = await searchHealthCareWork(
292
277
  searchHWR.identifierType,
293
278
  searchHWR.identifier,
294
279
  searchHWR.regulator,
295
280
  );
296
281
 
297
- if (!fetchedHealthWorker?.message) {
282
+ const normalizedData = HealthWorkerAdapter.normalize(unifiedResponse);
283
+
284
+ if (!normalizedData) {
298
285
  showModal('hwr-empty-modal', { errorCode: t('noResults', 'No results found') });
299
286
  return;
300
287
  }
301
288
 
302
289
  const dispose = showModal('hwr-confirmation-modal', {
303
- healthWorker: fetchedHealthWorker,
290
+ healthWorker: unifiedResponse.data,
291
+ normalizedData: normalizedData,
292
+ fhirFormat: unifiedResponse.fhirFormat,
304
293
  onConfirm: () => {
305
294
  dispose();
306
- setPractitionerValues(fetchedHealthWorker);
307
- setHealthWorker(fetchedHealthWorker);
295
+ setPractitionerValuesFromNormalized(normalizedData);
296
+ setHealthWorker(unifiedResponse.data);
308
297
  },
309
298
  });
310
299
  } catch (error) {
@@ -313,6 +302,7 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
313
302
  setSearchHWR({ ...searchHWR, isHWRLoading: false });
314
303
  }
315
304
  };
305
+
316
306
  const onSubmit = async (data: UserFormSchema) => {
317
307
  const setProvider = data.providerIdentifiers;
318
308
  const editProvider = data.isEditProvider;
@@ -479,12 +469,6 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
479
469
  });
480
470
  };
481
471
 
482
- useEffect(() => {
483
- if (isDirty) {
484
- promptBeforeClosing(() => isDirty);
485
- }
486
- }, [isDirty, promptBeforeClosing]);
487
-
488
472
  const toggleSection = (section: string) => {
489
473
  setActiveSection((prev) => (prev !== section ? section : prev));
490
474
  };