@kenyaemr/esm-admin-app 5.4.4-pre.28 → 5.4.4-pre.281
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 +5 -12
- package/dist/1074.js +1 -0
- package/dist/1074.js.map +1 -0
- package/dist/12.js +17 -0
- package/dist/12.js.map +1 -0
- package/dist/1201.js +1 -0
- package/dist/1201.js.map +1 -0
- package/dist/1242.js +1 -0
- package/dist/1242.js.map +1 -0
- package/dist/1311.js +1 -0
- package/dist/1311.js.map +1 -0
- package/dist/1462.js +1 -0
- package/dist/1462.js.map +1 -0
- package/dist/1469.js +1 -0
- package/dist/1469.js.map +1 -0
- package/dist/1506.js +13 -0
- package/dist/1506.js.map +1 -0
- package/dist/1718.js +1 -0
- package/dist/1718.js.map +1 -0
- package/dist/1722.js +1 -0
- package/dist/1722.js.map +1 -0
- package/dist/1772.js +1 -0
- package/dist/1772.js.map +1 -0
- package/dist/1889.js +1 -0
- package/dist/1889.js.map +1 -0
- package/dist/1972.js +1 -0
- package/dist/1972.js.map +1 -0
- package/dist/1990.js +1 -0
- package/dist/1990.js.map +1 -0
- package/dist/2016.js +1 -0
- package/dist/2016.js.map +1 -0
- package/dist/2080.js +1 -0
- package/dist/2080.js.map +1 -0
- package/dist/2096.js +1 -0
- package/dist/2096.js.map +1 -0
- package/dist/2153.js +1 -0
- package/dist/2153.js.map +1 -0
- package/dist/216.js +1 -0
- package/dist/216.js.map +1 -0
- package/dist/2270.js +1 -0
- package/dist/2270.js.map +1 -0
- package/dist/2294.js +1 -0
- package/dist/2294.js.map +1 -0
- package/dist/2345.js +1 -0
- package/dist/2345.js.map +1 -0
- package/dist/2402.js +1 -0
- package/dist/2402.js.map +1 -0
- package/dist/2500.js +1 -0
- package/dist/2500.js.map +1 -0
- package/dist/251.js +1 -0
- package/dist/251.js.map +1 -0
- package/dist/257.js +1 -0
- package/dist/257.js.map +1 -0
- package/dist/2586.js +1 -0
- package/dist/2586.js.map +1 -0
- package/dist/2625.js +1 -0
- package/dist/2625.js.map +1 -0
- package/dist/2652.js +1 -0
- package/dist/2652.js.map +1 -0
- package/dist/2685.js +1 -0
- package/dist/2685.js.map +1 -0
- package/dist/2948.js +1 -0
- package/dist/2948.js.map +1 -0
- package/dist/3089.js +1 -0
- package/dist/3089.js.map +1 -0
- package/dist/3190.js +1 -0
- package/dist/3190.js.map +1 -0
- package/dist/3224.js +1 -0
- package/dist/3224.js.map +1 -0
- package/dist/3366.js +1 -0
- package/dist/3366.js.map +1 -0
- package/dist/3548.js +1 -0
- package/dist/3548.js.map +1 -0
- package/dist/3571.js +1 -0
- package/dist/3571.js.map +1 -0
- package/dist/3691.js +1 -0
- package/dist/3691.js.map +1 -0
- package/dist/3775.js +1 -0
- package/dist/3775.js.map +1 -0
- package/dist/3816.js +1 -0
- package/dist/3816.js.map +1 -0
- package/dist/3852.js +1 -0
- package/dist/3852.js.map +1 -0
- package/dist/3906.js +1 -0
- package/dist/3906.js.map +1 -0
- package/dist/3963.js +1 -0
- package/dist/3963.js.map +1 -0
- package/dist/4047.js +1 -0
- package/dist/4047.js.map +1 -0
- package/dist/405.js +1 -0
- package/dist/405.js.map +1 -0
- package/dist/4296.js +1 -0
- package/dist/4296.js.map +1 -0
- package/dist/4337.js +1 -0
- package/dist/4337.js.map +1 -0
- package/dist/4584.js +1 -0
- package/dist/4584.js.map +1 -0
- package/dist/4735.js +1 -0
- package/dist/4735.js.map +1 -0
- package/dist/4744.js +1 -0
- package/dist/4744.js.map +1 -0
- package/dist/4813.js +2 -0
- package/dist/4813.js.map +1 -0
- package/dist/4858.js +1 -0
- package/dist/4858.js.map +1 -0
- package/dist/487.js +1 -0
- package/dist/487.js.map +1 -0
- package/dist/4970.js +1 -0
- package/dist/4970.js.map +1 -0
- package/dist/5202.js +1 -0
- package/dist/5202.js.map +1 -0
- package/dist/5294.js +1 -0
- package/dist/5294.js.map +1 -0
- package/dist/545.js +1 -0
- package/dist/545.js.map +1 -0
- package/dist/552.js +1 -0
- package/dist/552.js.map +1 -0
- package/dist/5592.js +1 -0
- package/dist/5592.js.map +1 -0
- package/dist/5669.js +1 -0
- package/dist/5669.js.map +1 -0
- package/dist/5884.js +1 -0
- package/dist/5884.js.map +1 -0
- package/dist/5940.js +1 -0
- package/dist/5940.js.map +1 -0
- package/dist/6092.js +1 -0
- package/dist/6092.js.map +1 -0
- package/dist/6155.js +1 -0
- package/dist/6155.js.map +1 -0
- package/dist/6178.js +1 -0
- package/dist/6178.js.map +1 -0
- package/dist/6399.js +1 -0
- package/dist/6399.js.map +1 -0
- package/dist/6456.js +1 -0
- package/dist/6466.js +3 -0
- package/dist/6466.js.map +1 -0
- package/dist/6492.js +1 -0
- package/dist/6492.js.map +1 -0
- package/dist/6676.js +1 -0
- package/dist/6676.js.map +1 -0
- package/dist/6800.js +1 -0
- package/dist/6800.js.map +1 -0
- package/dist/6976.js +1 -0
- package/dist/6976.js.map +1 -0
- package/dist/7005.js +1 -0
- package/dist/7005.js.map +1 -0
- package/dist/7201.js +1 -0
- package/dist/7201.js.map +1 -0
- package/dist/7210.js +1 -0
- package/dist/7210.js.map +1 -0
- package/dist/7234.js +1 -0
- package/dist/7234.js.map +1 -0
- package/dist/7261.js +1 -0
- package/dist/7261.js.map +1 -0
- package/dist/7326.js +1 -0
- package/dist/7463.js +1 -0
- package/dist/7463.js.map +1 -0
- package/dist/7528.js +1 -0
- package/dist/7528.js.map +1 -0
- package/dist/7607.js +1 -0
- package/dist/7717.js +1 -0
- package/dist/7717.js.map +1 -0
- package/dist/7737.js +1 -0
- package/dist/7737.js.map +1 -0
- package/dist/7739.js +1 -0
- package/dist/7739.js.map +1 -0
- package/dist/7765.js +1 -0
- package/dist/7765.js.map +1 -0
- package/dist/7820.js +1 -0
- package/dist/7820.js.map +1 -0
- package/dist/7844.js +1 -0
- package/dist/7844.js.map +1 -0
- package/dist/7866.js +1 -0
- package/dist/7866.js.map +1 -0
- package/dist/7916.js +1 -0
- package/dist/7916.js.map +1 -0
- package/dist/7971.js +1 -0
- package/dist/7971.js.map +1 -0
- package/dist/8159.js +7 -0
- package/dist/8159.js.map +1 -0
- package/dist/8244.js +1 -0
- package/dist/8244.js.map +1 -0
- package/dist/8262.js +1 -0
- package/dist/8262.js.map +1 -0
- package/dist/8376.js +1 -0
- package/dist/8376.js.map +1 -0
- package/dist/845.js +1 -0
- package/dist/845.js.map +1 -0
- package/dist/8570.js +1 -0
- package/dist/8570.js.map +1 -0
- package/dist/87.js +1 -0
- package/dist/87.js.map +1 -0
- package/dist/8727.js +1 -0
- package/dist/8828.js +1 -0
- package/dist/8828.js.map +1 -0
- package/dist/8860.js +1 -0
- package/dist/8860.js.map +1 -0
- package/dist/9036.js +1 -0
- package/dist/9036.js.map +1 -0
- package/dist/9124.js +1 -0
- package/dist/9124.js.map +1 -0
- package/dist/9182.js +1 -0
- package/dist/921.js +1 -0
- package/dist/921.js.map +1 -0
- package/dist/9404.js +1 -0
- package/dist/9404.js.map +1 -0
- package/dist/9406.js +1 -0
- package/dist/9406.js.map +1 -0
- package/dist/9446.js +1 -0
- package/dist/9446.js.map +1 -0
- package/dist/9449.js +1 -0
- package/dist/9449.js.map +1 -0
- package/dist/9566.js +5 -0
- package/dist/9566.js.map +1 -0
- package/dist/9641.js +1 -0
- package/dist/9641.js.map +1 -0
- package/dist/9711.js +1 -0
- package/dist/9711.js.map +1 -0
- package/dist/9801.js +1 -0
- package/dist/9801.js.map +1 -0
- package/dist/9835.js +11 -0
- package/dist/9835.js.map +1 -0
- package/dist/kenyaemr-esm-admin-app.js +5 -5
- package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +2649 -154
- package/dist/kenyaemr-esm-admin-app.js.map +1 -1
- package/dist/main.js +5 -31
- package/dist/main.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +5 -7
- package/rspack.config.js +1 -1
- package/src/components/facility-setup/constant/index.ts +3 -0
- package/src/components/facility-setup/facility-info.component.tsx +247 -108
- package/src/components/facility-setup/facility-info.scss +136 -55
- package/src/components/facility-setup/facility-setup.component.tsx +2 -2
- package/src/components/facility-setup/header/header.component.tsx +4 -10
- package/src/components/facility-setup/header/header.scss +3 -9
- package/src/components/facility-setup/shared/custom-info.component.tsx +9 -0
- package/src/components/facility-setup/shared/custom-section-card.component.tsx +10 -0
- package/src/components/facility-setup/shared/custom-status-tag.component.tsx +22 -0
- package/src/components/facility-setup/type/index.ts +61 -0
- package/src/components/facility-setup/useFacilityRegistry.ts +29 -0
- package/src/components/hook/healthWorkerRegistry.ts +78 -0
- package/src/components/hook/useProfessionalRegistryEnums.ts +59 -0
- package/src/components/locations/forms/add-location/add-location.workspace.tsx +96 -95
- package/src/components/locations/forms/search-location/search-location.workspace.tsx +90 -85
- package/src/components/locations/tables/locations-table.component.tsx +117 -121
- package/src/components/modal/hwr-confirmation.modal.scss +80 -4
- package/src/components/modal/hwr-confirmation.modal.tsx +118 -128
- package/src/components/modal/hwr-sync.modal.tsx +194 -106
- package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +13 -13
- package/src/components/users/manage-users/user-details/user-detail.scss +168 -39
- package/src/components/users/manage-users/user-details/user-details.component.tsx +130 -122
- package/src/components/users/manage-users/user-list/user-list.component.tsx +22 -9
- package/src/components/users/manage-users/user-management.workspace.scss +233 -95
- package/src/components/users/manage-users/user-management.workspace.tsx +800 -687
- package/src/components/users/userManagementFormSchema.tsx +17 -8
- package/src/config-schema.ts +48 -68
- package/src/index.ts +55 -30
- package/src/left-pannel-link.component.tsx +5 -3
- package/src/root.component.tsx +11 -13
- package/src/routes.json +40 -40
- package/src/types/index.ts +29 -1
- package/translations/am.json +158 -13
- package/translations/en.json +158 -13
- package/translations/fr.json +193 -47
- package/translations/sw.json +158 -13
- package/tsconfig.json +1 -1
- package/dist/127.js +0 -1
- package/dist/267.js +0 -1
- package/dist/267.js.map +0 -1
- package/dist/281.js +0 -15
- package/dist/281.js.map +0 -1
- package/dist/329.js +0 -1
- package/dist/329.js.map +0 -1
- package/dist/40.js +0 -1
- package/dist/466.js +0 -1
- package/dist/466.js.map +0 -1
- package/dist/472.js +0 -1
- package/dist/472.js.map +0 -1
- package/dist/478.js +0 -1
- package/dist/478.js.map +0 -1
- package/dist/585.js +0 -1
- package/dist/585.js.map +0 -1
- package/dist/630.js +0 -1
- package/dist/630.js.map +0 -1
- package/dist/675.js +0 -1
- package/dist/675.js.map +0 -1
- package/dist/689.js +0 -1
- package/dist/689.js.map +0 -1
- package/dist/706.js +0 -27
- package/dist/706.js.map +0 -1
- package/dist/729.js +0 -17
- package/dist/729.js.map +0 -1
- package/dist/774.js +0 -1
- package/dist/774.js.map +0 -1
- package/dist/847.js +0 -1
- package/dist/847.js.map +0 -1
- package/dist/85.js +0 -1
- package/dist/85.js.map +0 -1
- package/dist/882.js +0 -1
- package/dist/91.js +0 -1
- package/dist/91.js.map +0 -1
- package/dist/916.js +0 -1
- package/dist/998.js +0 -1
- package/dist/998.js.map +0 -1
- package/jest.config.js +0 -8
- package/src/components/facility-setup/card.component.tsx +0 -16
- package/src/components/facility-setup/facility-setup.resource.tsx +0 -7
- package/src/components/hook/healthWorkerAdapter.ts +0 -213
- package/src/components/hook/useFacilityInfo.tsx +0 -37
|
@@ -1,147 +1,218 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import { Button, Column, Search, ComboBox, InlineLoading } from '@carbon/react';
|
|
4
|
-
import
|
|
5
|
-
import { useConfig, showSnackbar, formatDate, parseDate, showToast, restBaseUrl } from '@openmrs/esm-framework';
|
|
4
|
+
import { useConfig, showSnackbar, showToast, restBaseUrl } from '@openmrs/esm-framework';
|
|
6
5
|
import { mutate } from 'swr';
|
|
7
|
-
|
|
6
|
+
|
|
7
|
+
import styles from './hwr-sync.modal.scss';
|
|
8
|
+
import { type ProviderResponse } from '../../types';
|
|
8
9
|
import { ConfigObject } from '../../config-schema';
|
|
9
|
-
import { searchHealthCareWork, HealthWorkerAdapter } from '../hook/healthWorkerAdapter';
|
|
10
10
|
import { createProviderAttribute, updateProviderAttributes } from './hwr-sync.resource';
|
|
11
|
+
import { searchHealthCareWork, ProfessionalRegistryResponse } from '../hook/healthWorkerRegistry';
|
|
12
|
+
import {
|
|
13
|
+
useProfessionalRegistryIdentificationTypes,
|
|
14
|
+
useProfessionalRegistryRegulators,
|
|
15
|
+
} from '../hook/useProfessionalRegistryEnums';
|
|
11
16
|
|
|
12
17
|
interface HWRSyncModalProps {
|
|
13
18
|
close: () => void;
|
|
14
19
|
provider: ProviderResponse;
|
|
15
20
|
}
|
|
16
21
|
|
|
22
|
+
interface EnumEntry {
|
|
23
|
+
code: string;
|
|
24
|
+
label: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const pickCurrentLicense = (licenses: ProfessionalRegistryResponse['professional']['licenses']) => {
|
|
28
|
+
if (!licenses || licenses.length === 0) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
const sorted = [...licenses]
|
|
32
|
+
.filter((l) => l.license_end)
|
|
33
|
+
.sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime());
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
return sorted.find((l) => new Date(l.license_end).getTime() >= now) ?? sorted[0];
|
|
36
|
+
};
|
|
37
|
+
|
|
17
38
|
const HWRSyncModal: React.FC<HWRSyncModalProps> = ({ close, provider }) => {
|
|
18
39
|
const { t } = useTranslation();
|
|
19
40
|
const [syncLoading, setSyncLoading] = useState(false);
|
|
20
41
|
|
|
21
|
-
const config = useConfig<ConfigObject>();
|
|
22
42
|
const {
|
|
23
|
-
|
|
24
|
-
licenseBodyUuid,
|
|
43
|
+
licenseNumberUuid,
|
|
25
44
|
licenseExpiryDateUuid,
|
|
45
|
+
licenseBodyUuid,
|
|
46
|
+
qualificationUuid,
|
|
47
|
+
specialtyUuid,
|
|
48
|
+
providerCadreUuid,
|
|
49
|
+
practiceTypeUuid,
|
|
50
|
+
providerNationalIdUuid,
|
|
26
51
|
passportNumberUuid,
|
|
27
|
-
|
|
28
|
-
|
|
52
|
+
providerUniqueIdentifierAttributeTypeUuid,
|
|
53
|
+
externalProviderIdentifierUuid,
|
|
54
|
+
providerHieFhirReference,
|
|
29
55
|
phoneNumberUuid,
|
|
30
|
-
qualificationUuid,
|
|
31
56
|
providerAddressUuid,
|
|
32
|
-
|
|
57
|
+
} = useConfig<ConfigObject>();
|
|
58
|
+
const { regulators } = useProfessionalRegistryRegulators();
|
|
59
|
+
const { identificationTypes } = useProfessionalRegistryIdentificationTypes();
|
|
60
|
+
|
|
61
|
+
const storedIdentifiers = useMemo(() => {
|
|
62
|
+
const attrValue = (uuid: string) => provider?.attributes?.find((a) => a.attributeType?.uuid === uuid)?.value || '';
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
nationalId: attrValue(providerNationalIdUuid),
|
|
66
|
+
puid: attrValue(providerUniqueIdentifierAttributeTypeUuid),
|
|
67
|
+
externalRef: attrValue(externalProviderIdentifierUuid),
|
|
68
|
+
passport: attrValue(passportNumberUuid),
|
|
69
|
+
licenseBody: attrValue(licenseBodyUuid),
|
|
70
|
+
};
|
|
71
|
+
}, [
|
|
72
|
+
provider,
|
|
73
|
+
providerNationalIdUuid,
|
|
33
74
|
providerUniqueIdentifierAttributeTypeUuid,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
75
|
+
externalProviderIdentifierUuid,
|
|
76
|
+
passportNumberUuid,
|
|
77
|
+
licenseBodyUuid,
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const initialIdentifierType = useMemo(() => {
|
|
81
|
+
if (storedIdentifiers.nationalId) {
|
|
82
|
+
return 'National ID';
|
|
83
|
+
}
|
|
84
|
+
if (storedIdentifiers.externalRef) {
|
|
85
|
+
return 'registration_number';
|
|
86
|
+
}
|
|
87
|
+
if (storedIdentifiers.passport) {
|
|
88
|
+
return 'Passport';
|
|
89
|
+
}
|
|
90
|
+
return '';
|
|
91
|
+
}, [storedIdentifiers]);
|
|
92
|
+
|
|
93
|
+
const initialIdentifier = useMemo(() => {
|
|
94
|
+
if (storedIdentifiers.nationalId) {
|
|
95
|
+
return storedIdentifiers.nationalId;
|
|
96
|
+
}
|
|
97
|
+
if (storedIdentifiers.externalRef) {
|
|
98
|
+
return storedIdentifiers.externalRef;
|
|
99
|
+
}
|
|
100
|
+
if (storedIdentifiers.passport) {
|
|
101
|
+
return storedIdentifiers.passport;
|
|
102
|
+
}
|
|
103
|
+
return '';
|
|
104
|
+
}, [storedIdentifiers]);
|
|
105
|
+
|
|
106
|
+
const initialRegulator = storedIdentifiers.licenseBody;
|
|
45
107
|
|
|
46
108
|
const [searchHWR, setSearchHWR] = useState({
|
|
47
|
-
identifierType:
|
|
48
|
-
identifier:
|
|
49
|
-
regulator:
|
|
109
|
+
identifierType: initialIdentifierType,
|
|
110
|
+
identifier: initialIdentifier,
|
|
111
|
+
regulator: initialRegulator,
|
|
50
112
|
});
|
|
51
113
|
|
|
52
|
-
|
|
53
|
-
const selectedKey = selectedItem?.key ?? '';
|
|
114
|
+
useEffect(() => {
|
|
54
115
|
setSearchHWR((prev) => ({
|
|
55
116
|
...prev,
|
|
56
|
-
identifierType:
|
|
57
|
-
identifier:
|
|
117
|
+
identifierType: prev.identifierType || initialIdentifierType,
|
|
118
|
+
identifier: prev.identifier || initialIdentifier,
|
|
119
|
+
regulator: prev.regulator || initialRegulator,
|
|
58
120
|
}));
|
|
59
|
-
};
|
|
121
|
+
}, [initialIdentifierType, initialIdentifier, initialRegulator]);
|
|
122
|
+
|
|
123
|
+
const handleIdentifierTypeChange = (newType: string) => {
|
|
124
|
+
const valueForType: Record<string, string> = {
|
|
125
|
+
'National ID': storedIdentifiers.nationalId,
|
|
126
|
+
registration_number: storedIdentifiers.externalRef,
|
|
127
|
+
Passport: storedIdentifiers.passport,
|
|
128
|
+
};
|
|
60
129
|
|
|
61
|
-
const handleRegulatorChange = (selectedItem: { key: string; name: string } | null) => {
|
|
62
|
-
const selectedKey = selectedItem?.key ?? '';
|
|
63
130
|
setSearchHWR((prev) => ({
|
|
64
131
|
...prev,
|
|
65
|
-
|
|
132
|
+
identifierType: newType,
|
|
133
|
+
identifier: valueForType[newType] ?? '',
|
|
66
134
|
}));
|
|
67
135
|
};
|
|
68
136
|
|
|
69
|
-
const
|
|
137
|
+
const selectedIdentificationType = useMemo<EnumEntry | null>(
|
|
138
|
+
() => identificationTypes?.find((i) => i.code === searchHWR.identifierType) ?? null,
|
|
139
|
+
[identificationTypes, searchHWR.identifierType],
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
const selectedRegulator = useMemo<EnumEntry | null>(
|
|
143
|
+
() => regulators?.find((r) => r.code === searchHWR.regulator) ?? null,
|
|
144
|
+
[regulators, searchHWR.regulator],
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const isSearchDisabled = () =>
|
|
148
|
+
!searchHWR.identifier || !searchHWR.identifierType || !searchHWR.regulator || syncLoading;
|
|
70
149
|
|
|
71
150
|
const handleSync = async () => {
|
|
72
|
-
|
|
73
|
-
setSyncLoading(true);
|
|
74
|
-
const unifiedResponse = await searchHealthCareWork(
|
|
75
|
-
searchHWR.identifierType,
|
|
76
|
-
searchHWR.identifier,
|
|
77
|
-
searchHWR.regulator,
|
|
78
|
-
);
|
|
151
|
+
setSyncLoading(true);
|
|
79
152
|
|
|
80
|
-
|
|
153
|
+
try {
|
|
154
|
+
const response = await searchHealthCareWork(searchHWR.identifierType, searchHWR.identifier, searchHWR.regulator);
|
|
81
155
|
|
|
82
|
-
if (!
|
|
156
|
+
if (!response?.professional) {
|
|
83
157
|
throw new Error(t('noResults', 'No results found'));
|
|
84
158
|
}
|
|
85
159
|
|
|
160
|
+
const { membership, contacts, identifiers, professional_details, licenses } = response.professional;
|
|
161
|
+
const currentLicense = pickCurrentLicense(licenses);
|
|
162
|
+
|
|
86
163
|
const updatableAttributes = [
|
|
87
|
-
{ attributeType: licenseNumberUuid, value:
|
|
88
|
-
{ attributeType: licenseBodyUuid, value: normalizedData.registrationId },
|
|
164
|
+
{ attributeType: licenseNumberUuid, value: currentLicense?.external_reference_id },
|
|
89
165
|
{
|
|
90
166
|
attributeType: licenseExpiryDateUuid,
|
|
91
|
-
value:
|
|
92
|
-
},
|
|
93
|
-
{ attributeType: phoneNumberUuid, value: normalizedData.phoneNumber },
|
|
94
|
-
{ attributeType: qualificationUuid, value: normalizedData.qualification },
|
|
95
|
-
{
|
|
96
|
-
attributeType: providerHieFhirReference,
|
|
97
|
-
value: JSON.stringify({
|
|
98
|
-
...unifiedResponse.data,
|
|
99
|
-
fhirFormat: unifiedResponse.fhirFormat,
|
|
100
|
-
searchParameters: {
|
|
101
|
-
regulator: searchHWR.regulator,
|
|
102
|
-
identifierType: searchHWR.identifierType,
|
|
103
|
-
},
|
|
104
|
-
}),
|
|
105
|
-
},
|
|
106
|
-
{ attributeType: providerAddressUuid, value: normalizedData.email },
|
|
107
|
-
{ attributeType: providerNationalIdUuid, value: normalizedData.nationalId },
|
|
108
|
-
{
|
|
109
|
-
attributeType: providerUniqueIdentifierAttributeTypeUuid,
|
|
110
|
-
value: normalizedData.providerUniqueIdentifier,
|
|
167
|
+
value: currentLicense?.license_end ? new Date(currentLicense.license_end).toISOString() : null,
|
|
111
168
|
},
|
|
112
|
-
|
|
169
|
+
{ attributeType: licenseBodyUuid, value: membership?.licensing_body },
|
|
170
|
+
{ attributeType: qualificationUuid, value: professional_details?.educational_qualifications },
|
|
171
|
+
{ attributeType: specialtyUuid, value: membership?.specialty },
|
|
172
|
+
{ attributeType: providerCadreUuid, value: professional_details?.professional_cadre },
|
|
173
|
+
{ attributeType: practiceTypeUuid, value: professional_details?.practice_type },
|
|
174
|
+
|
|
175
|
+
{ attributeType: providerNationalIdUuid, value: identifiers?.identification_number },
|
|
176
|
+
{ attributeType: providerUniqueIdentifierAttributeTypeUuid, value: membership?.id },
|
|
177
|
+
{ attributeType: externalProviderIdentifierUuid, value: membership?.external_reference_id },
|
|
178
|
+
{ attributeType: providerHieFhirReference, value: membership?.id },
|
|
179
|
+
|
|
180
|
+
{ attributeType: phoneNumberUuid, value: contacts?.phone },
|
|
181
|
+
{ attributeType: providerAddressUuid, value: contacts?.email },
|
|
182
|
+
].filter(
|
|
183
|
+
(attr): attr is { attributeType: string; value: string } =>
|
|
184
|
+
attr.value !== undefined && attr.value !== null && attr.value !== '',
|
|
185
|
+
);
|
|
113
186
|
|
|
114
187
|
await Promise.all(
|
|
115
188
|
updatableAttributes.map((attr) => {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
attributeType: attr.attributeType,
|
|
122
|
-
value: attr.value,
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
if (!existingAttribute) {
|
|
126
|
-
return createProviderAttribute(payload, provider.uuid);
|
|
127
|
-
}
|
|
128
|
-
return updateProviderAttributes(payload, provider.uuid, existingAttribute);
|
|
189
|
+
const existing = provider.attributes?.find((at) => at.attributeType?.uuid === attr.attributeType)?.uuid;
|
|
190
|
+
const payload = { attributeType: attr.attributeType, value: attr.value };
|
|
191
|
+
return existing
|
|
192
|
+
? updateProviderAttributes(payload, provider.uuid, existing)
|
|
193
|
+
: createProviderAttribute(payload, provider.uuid);
|
|
129
194
|
}),
|
|
130
195
|
);
|
|
131
196
|
|
|
132
197
|
mutate((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/provider`));
|
|
198
|
+
|
|
133
199
|
showSnackbar({
|
|
134
|
-
title: '
|
|
200
|
+
title: t('syncSuccess', 'Sync successful'),
|
|
135
201
|
kind: 'success',
|
|
136
|
-
subtitle: t('syncMessage', '
|
|
202
|
+
subtitle: t('syncMessage', 'Provider details synced from the registry'),
|
|
203
|
+
isLowContrast: true,
|
|
137
204
|
});
|
|
205
|
+
|
|
138
206
|
close();
|
|
139
|
-
} catch (err) {
|
|
207
|
+
} catch (err: any) {
|
|
140
208
|
showToast({
|
|
141
209
|
critical: false,
|
|
142
210
|
kind: 'error',
|
|
143
|
-
description: t('errorSyncMsg',
|
|
144
|
-
|
|
211
|
+
description: t('errorSyncMsg', 'Failed to sync {{identifier}}: {{error}}', {
|
|
212
|
+
identifier: searchHWR.identifier,
|
|
213
|
+
error: err?.message ?? 'unknown error',
|
|
214
|
+
}),
|
|
215
|
+
title: t('hwrError', 'Sync failed'),
|
|
145
216
|
});
|
|
146
217
|
} finally {
|
|
147
218
|
setSyncLoading(false);
|
|
@@ -153,53 +224,70 @@ const HWRSyncModal: React.FC<HWRSyncModalProps> = ({ close, provider }) => {
|
|
|
153
224
|
<div className="cds--modal-header">
|
|
154
225
|
<h3 className="cds--modal-header__heading">{t('healthWorkerRegistry', 'Health worker registry')}</h3>
|
|
155
226
|
</div>
|
|
227
|
+
|
|
156
228
|
<div className="cds--modal-content">
|
|
157
|
-
<p
|
|
229
|
+
<p className={styles.intro}>
|
|
230
|
+
{t(
|
|
231
|
+
'healthWorkerSync',
|
|
232
|
+
'Look up this provider in the Kenya health worker registry and overwrite local attributes with registry values.',
|
|
233
|
+
)}
|
|
234
|
+
</p>
|
|
235
|
+
|
|
158
236
|
<div className={styles.modalContainer}>
|
|
159
237
|
<Column className={styles.identifierTypeColumn}>
|
|
160
238
|
<ComboBox
|
|
161
|
-
|
|
162
|
-
id="formIdentifierType"
|
|
239
|
+
id="syncIdentifierType"
|
|
163
240
|
titleText={t('identificationType', 'Identification Type')}
|
|
164
241
|
placeholder={t('chooseIdentifierType', 'Choose identifier type')}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
242
|
+
items={identificationTypes ?? []}
|
|
243
|
+
itemToString={(item) => item?.label ?? ''}
|
|
244
|
+
selectedItem={selectedIdentificationType}
|
|
245
|
+
onChange={({ selectedItem }) => handleIdentifierTypeChange(selectedItem?.code ?? '')}
|
|
246
|
+
className={styles.comboBox}
|
|
169
247
|
/>
|
|
170
248
|
</Column>
|
|
249
|
+
|
|
171
250
|
<Column className={styles.identifierTypeColumn}>
|
|
172
251
|
<ComboBox
|
|
173
|
-
|
|
174
|
-
id="regulatorOptions"
|
|
252
|
+
id="syncRegulator"
|
|
175
253
|
titleText={t('regulator', 'Regulator')}
|
|
176
254
|
placeholder={t('chooseRegulator', 'Choose regulator')}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
255
|
+
items={regulators ?? []}
|
|
256
|
+
itemToString={(item) => item?.label ?? ''}
|
|
257
|
+
selectedItem={selectedRegulator}
|
|
258
|
+
onChange={({ selectedItem }) =>
|
|
259
|
+
setSearchHWR((prev) => ({ ...prev, regulator: selectedItem?.code ?? '' }))
|
|
260
|
+
}
|
|
261
|
+
className={styles.comboBox}
|
|
181
262
|
/>
|
|
182
263
|
</Column>
|
|
264
|
+
|
|
183
265
|
<Column className={styles.identifierTypeColumn}>
|
|
184
266
|
<span className={styles.identifierTypeHeader}>{t('identifierNumber', 'Identifier number*')}</span>
|
|
185
267
|
<Search
|
|
268
|
+
id="syncSearch"
|
|
186
269
|
labelText={t('enterIdentifierNumber', 'Enter identifier number')}
|
|
187
|
-
className={styles.formSearch}
|
|
188
|
-
value={searchHWR.identifier}
|
|
189
270
|
placeholder={t('enterIdentifierNumber', 'Enter identifier number')}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
271
|
+
value={searchHWR.identifier}
|
|
272
|
+
onChange={(e) => setSearchHWR((prev) => ({ ...prev, identifier: e.target.value }))}
|
|
273
|
+
className={styles.formSearch}
|
|
193
274
|
/>
|
|
194
275
|
</Column>
|
|
195
276
|
</div>
|
|
196
277
|
</div>
|
|
278
|
+
|
|
197
279
|
<div className="cds--modal-footer">
|
|
198
|
-
<Button kind="secondary" onClick={close}>
|
|
280
|
+
<Button kind="secondary" onClick={close} disabled={syncLoading}>
|
|
199
281
|
{t('cancel', 'Cancel')}
|
|
200
282
|
</Button>
|
|
201
|
-
<Button disabled={isSearchDisabled()
|
|
202
|
-
{syncLoading ?
|
|
283
|
+
<Button onClick={handleSync} disabled={isSearchDisabled()}>
|
|
284
|
+
{syncLoading ? (
|
|
285
|
+
<span className={styles.syncingLabel}>
|
|
286
|
+
<InlineLoading status="active" description={t('syncing', 'Syncing...')} />
|
|
287
|
+
</span>
|
|
288
|
+
) : (
|
|
289
|
+
t('sync', 'Sync')
|
|
290
|
+
)}
|
|
203
291
|
</Button>
|
|
204
292
|
</div>
|
|
205
293
|
</>
|
|
@@ -6,6 +6,8 @@ import {
|
|
|
6
6
|
restBaseUrl,
|
|
7
7
|
showSnackbar,
|
|
8
8
|
useLayoutType,
|
|
9
|
+
Workspace2,
|
|
10
|
+
Workspace2DefinitionProps,
|
|
9
11
|
} from '@openmrs/esm-framework';
|
|
10
12
|
import { FormProvider, useFieldArray, useForm } from 'react-hook-form';
|
|
11
13
|
import styles from '../../../manage-users/user-management.workspace.scss';
|
|
@@ -29,15 +31,13 @@ import { useSystemUserRoleConfigSetting } from '../../../../hook/useSystemRoleSe
|
|
|
29
31
|
import UserRoleScopeFormFields from './user-role-scope-fields.component';
|
|
30
32
|
import StockUserRoleScopesList from '../user-role-scope-list/user-role-scope-list.component';
|
|
31
33
|
|
|
32
|
-
type UserRoleScopeWorkspaceProps =
|
|
34
|
+
type UserRoleScopeWorkspaceProps = {
|
|
33
35
|
user?: User;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
|
-
const UserRoleScopeWorkspace: React.FC<UserRoleScopeWorkspaceProps
|
|
38
|
+
const UserRoleScopeWorkspace: React.FC<Workspace2DefinitionProps<UserRoleScopeWorkspaceProps, {}, {}>> = ({
|
|
37
39
|
closeWorkspace,
|
|
38
|
-
|
|
39
|
-
closeWorkspaceWithSavedChanges,
|
|
40
|
-
user = {} as User,
|
|
40
|
+
workspaceProps: { user = {} as User },
|
|
41
41
|
}) => {
|
|
42
42
|
const { t } = useTranslation();
|
|
43
43
|
const isTablet = useLayoutType() === 'tablet';
|
|
@@ -46,7 +46,7 @@ const UserRoleScopeWorkspace: React.FC<UserRoleScopeWorkspaceProps> = ({
|
|
|
46
46
|
const { stockLocations, isLoading: loadinglocation } = useStockTagLocations();
|
|
47
47
|
const { rolesConfig, error } = useSystemUserRoleConfigSetting();
|
|
48
48
|
const { items, loadingRoleScope } = useUserRoleScopes();
|
|
49
|
-
|
|
49
|
+
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
50
50
|
const [userRoleScopeInitialValues, setUserRoleScopeInitialValues] = useState<UserRoleScope | null>(null);
|
|
51
51
|
const handleEditUserRoleScope = useCallback((userRoleScope: UserRoleScope) => {
|
|
52
52
|
setUserRoleScopeInitialValues(userRoleScope);
|
|
@@ -93,9 +93,9 @@ const UserRoleScopeWorkspace: React.FC<UserRoleScopeWorkspaceProps> = ({
|
|
|
93
93
|
const { errors, isSubmitting, isDirty } = roleScopeformMethods.formState;
|
|
94
94
|
useEffect(() => {
|
|
95
95
|
if (isDirty) {
|
|
96
|
-
|
|
96
|
+
setHasUnsavedChanges(true);
|
|
97
97
|
}
|
|
98
|
-
}, [isDirty,
|
|
98
|
+
}, [isDirty, setHasUnsavedChanges]);
|
|
99
99
|
|
|
100
100
|
useEffect(() => {
|
|
101
101
|
if (userRoleScopeInitialValues && !loadingStock) {
|
|
@@ -154,7 +154,7 @@ const UserRoleScopeWorkspace: React.FC<UserRoleScopeWorkspaceProps> = ({
|
|
|
154
154
|
const response = await createOrUpdateUserRoleScope(userRoleScopeUrl, roleScope, user?.uuid ?? '');
|
|
155
155
|
if (response.ok) {
|
|
156
156
|
showNotification('userRoleScopeSaved', 'User role scope saved successfully', 'success');
|
|
157
|
-
|
|
157
|
+
closeWorkspace({ discardUnsavedChanges: true });
|
|
158
158
|
}
|
|
159
159
|
}),
|
|
160
160
|
);
|
|
@@ -184,9 +184,9 @@ const UserRoleScopeWorkspace: React.FC<UserRoleScopeWorkspaceProps> = ({
|
|
|
184
184
|
|
|
185
185
|
useEffect(() => {
|
|
186
186
|
if (isDirty) {
|
|
187
|
-
|
|
187
|
+
setHasUnsavedChanges(true);
|
|
188
188
|
}
|
|
189
|
-
}, [isDirty,
|
|
189
|
+
}, [isDirty, setHasUnsavedChanges]);
|
|
190
190
|
|
|
191
191
|
function extractInventoryRoleNames(rolesConfig) {
|
|
192
192
|
return rolesConfig.find((category) => category.category === ROLE_CATEGORIES.CORE_INVENTORY)?.roles || [];
|
|
@@ -225,7 +225,7 @@ const UserRoleScopeWorkspace: React.FC<UserRoleScopeWorkspaceProps> = ({
|
|
|
225
225
|
);
|
|
226
226
|
|
|
227
227
|
return (
|
|
228
|
-
|
|
228
|
+
<Workspace2 title={t('userRoleScopeWorkspace', 'User Role Scope Workspace')} hasUnsavedChanges={hasUnsavedChanges}>
|
|
229
229
|
<div>
|
|
230
230
|
<StockUserRoleScopesList onEditUserRoleScope={handleEditUserRoleScope} user={user} />
|
|
231
231
|
</div>
|
|
@@ -309,7 +309,7 @@ const UserRoleScopeWorkspace: React.FC<UserRoleScopeWorkspaceProps> = ({
|
|
|
309
309
|
</form>
|
|
310
310
|
</FormProvider>
|
|
311
311
|
)}
|
|
312
|
-
|
|
312
|
+
</Workspace2>
|
|
313
313
|
);
|
|
314
314
|
};
|
|
315
315
|
|