@kenyaemr/esm-admin-app 5.4.2-pre.2694 → 5.4.2-pre.2699
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/dist/{484.js → 585.js} +1 -1
- package/dist/585.js.map +1 -0
- package/dist/kenyaemr-esm-admin-app.js +2 -2
- package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +9 -9
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +1 -1
- package/src/components/hook/healthWorkerAdapter.ts +213 -0
- package/src/components/modal/hwr-confirmation.modal.tsx +45 -31
- package/src/components/modal/hwr-sync.modal.tsx +15 -29
- package/src/components/users/manage-users/user-management.workspace.tsx +24 -40
- package/dist/484.js.map +0 -1
- package/src/components/hook/searchHealthCareWork.ts +0 -22
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.
|
|
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.2699"}
|
package/package.json
CHANGED
|
@@ -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}®ulator=${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> = ({
|
|
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
|
|
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:
|
|
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={
|
|
67
|
+
value={normalizedData.fullName || '--'}
|
|
65
68
|
/>
|
|
66
69
|
|
|
67
70
|
<HealthWorkerInfo
|
|
68
71
|
label={t('providerUniqueIdentifier', 'Provider unique identifier')}
|
|
69
|
-
value={
|
|
72
|
+
value={normalizedData.providerUniqueIdentifier || '--'}
|
|
70
73
|
/>
|
|
71
74
|
|
|
72
75
|
<HealthWorkerInfo
|
|
73
76
|
label={t('registrationId', 'Registration ID')}
|
|
74
|
-
value={
|
|
77
|
+
value={normalizedData.registrationId || '--'}
|
|
75
78
|
/>
|
|
76
79
|
|
|
77
80
|
<HealthWorkerInfo
|
|
78
81
|
label={t('externalReferenceId', 'External Reference ID')}
|
|
79
|
-
value={
|
|
82
|
+
value={normalizedData.externalReferenceId || '--'}
|
|
80
83
|
/>
|
|
81
84
|
|
|
82
|
-
<HealthWorkerInfo label={t('gender', 'Gender')} value={
|
|
85
|
+
<HealthWorkerInfo label={t('gender', 'Gender')} value={normalizedData.gender || '--'} />
|
|
83
86
|
|
|
84
|
-
<HealthWorkerInfo label={t('status', 'Status')} value={
|
|
87
|
+
<HealthWorkerInfo label={t('status', 'Status')} value={normalizedData.status || '--'} />
|
|
85
88
|
|
|
86
|
-
{
|
|
89
|
+
{normalizedData.phoneNumber && (
|
|
90
|
+
<HealthWorkerInfo label={t('phone', 'Phone')} value={normalizedData.phoneNumber} />
|
|
91
|
+
)}
|
|
87
92
|
|
|
88
|
-
{
|
|
93
|
+
{normalizedData.email && <HealthWorkerInfo label={t('email', 'Email')} value={normalizedData.email} />}
|
|
89
94
|
|
|
90
|
-
{
|
|
95
|
+
{normalizedData.nationalId && (
|
|
91
96
|
<HealthWorkerInfo
|
|
92
|
-
label={
|
|
93
|
-
value={
|
|
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('
|
|
101
|
-
value={
|
|
103
|
+
label={t('licensingBody', 'Licensing Body')}
|
|
104
|
+
value={normalizedData.licensingBody || '--'}
|
|
102
105
|
/>
|
|
103
106
|
|
|
104
|
-
{
|
|
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={
|
|
112
|
+
value={normalizedData.professionalCadre}
|
|
108
113
|
/>
|
|
109
114
|
)}
|
|
110
115
|
|
|
111
|
-
{
|
|
112
|
-
<HealthWorkerInfo label={t('practiceType', 'Practice Type')} value={
|
|
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/
|
|
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
|
|
74
|
+
const unifiedResponse = await searchHealthCareWork(
|
|
75
75
|
searchHWR.identifierType,
|
|
76
76
|
searchHWR.identifier,
|
|
77
77
|
searchHWR.regulator,
|
|
78
78
|
);
|
|
79
79
|
|
|
80
|
-
|
|
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:
|
|
103
|
-
{ attributeType: licenseBodyUuid, value:
|
|
87
|
+
{ attributeType: licenseNumberUuid, value: normalizedData.licenseNumber },
|
|
88
|
+
{ attributeType: licenseBodyUuid, value: normalizedData.registrationId },
|
|
104
89
|
{
|
|
105
90
|
attributeType: licenseExpiryDateUuid,
|
|
106
|
-
value:
|
|
91
|
+
value: normalizedData.licenseEndDate ? parseDate(normalizedData.licenseEndDate) : null,
|
|
107
92
|
},
|
|
108
|
-
{ attributeType: phoneNumberUuid, value:
|
|
109
|
-
{ attributeType: qualificationUuid, value:
|
|
93
|
+
{ attributeType: phoneNumberUuid, value: normalizedData.phoneNumber },
|
|
94
|
+
{ attributeType: qualificationUuid, value: normalizedData.qualification },
|
|
110
95
|
{
|
|
111
96
|
attributeType: providerHieFhirReference,
|
|
112
97
|
value: JSON.stringify({
|
|
113
|
-
...
|
|
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:
|
|
121
|
-
{ attributeType: providerNationalIdUuid, value:
|
|
106
|
+
{ attributeType: providerAddressUuid, value: normalizedData.email },
|
|
107
|
+
{ attributeType: providerNationalIdUuid, value: normalizedData.nationalId },
|
|
122
108
|
{
|
|
123
109
|
attributeType: providerUniqueIdentifierAttributeTypeUuid,
|
|
124
|
-
value:
|
|
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/
|
|
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
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
setValue('
|
|
263
|
-
setValue('
|
|
264
|
-
setValue('
|
|
265
|
-
|
|
266
|
-
setValue('
|
|
267
|
-
setValue('
|
|
268
|
-
setValue('
|
|
269
|
-
setValue('
|
|
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
|
-
|
|
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
|
|
276
|
+
const unifiedResponse = await searchHealthCareWork(
|
|
292
277
|
searchHWR.identifierType,
|
|
293
278
|
searchHWR.identifier,
|
|
294
279
|
searchHWR.regulator,
|
|
295
280
|
);
|
|
296
281
|
|
|
297
|
-
|
|
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:
|
|
290
|
+
healthWorker: unifiedResponse.data,
|
|
291
|
+
normalizedData: normalizedData,
|
|
292
|
+
fhirFormat: unifiedResponse.fhirFormat,
|
|
304
293
|
onConfirm: () => {
|
|
305
294
|
dispose();
|
|
306
|
-
|
|
307
|
-
setHealthWorker(
|
|
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
|
};
|