@kenyaemr/esm-admin-app 5.4.4-pre.26 → 5.4.4-pre.265

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 (310) hide show
  1. package/.turbo/turbo-build.log +5 -12
  2. package/dist/1074.js +1 -0
  3. package/dist/1074.js.map +1 -0
  4. package/dist/12.js +17 -0
  5. package/dist/12.js.map +1 -0
  6. package/dist/1201.js +1 -0
  7. package/dist/1201.js.map +1 -0
  8. package/dist/1242.js +1 -0
  9. package/dist/1242.js.map +1 -0
  10. package/dist/1311.js +1 -0
  11. package/dist/1311.js.map +1 -0
  12. package/dist/1462.js +1 -0
  13. package/dist/1462.js.map +1 -0
  14. package/dist/1469.js +1 -0
  15. package/dist/1469.js.map +1 -0
  16. package/dist/1506.js +13 -0
  17. package/dist/1506.js.map +1 -0
  18. package/dist/1718.js +1 -0
  19. package/dist/1718.js.map +1 -0
  20. package/dist/1722.js +1 -0
  21. package/dist/1722.js.map +1 -0
  22. package/dist/1772.js +1 -0
  23. package/dist/1772.js.map +1 -0
  24. package/dist/1889.js +1 -0
  25. package/dist/1889.js.map +1 -0
  26. package/dist/1972.js +1 -0
  27. package/dist/1972.js.map +1 -0
  28. package/dist/1990.js +1 -0
  29. package/dist/1990.js.map +1 -0
  30. package/dist/2016.js +1 -0
  31. package/dist/2016.js.map +1 -0
  32. package/dist/2080.js +1 -0
  33. package/dist/2080.js.map +1 -0
  34. package/dist/2096.js +1 -0
  35. package/dist/2096.js.map +1 -0
  36. package/dist/2153.js +1 -0
  37. package/dist/2153.js.map +1 -0
  38. package/dist/216.js +1 -0
  39. package/dist/216.js.map +1 -0
  40. package/dist/2270.js +1 -0
  41. package/dist/2270.js.map +1 -0
  42. package/dist/2294.js +1 -0
  43. package/dist/2294.js.map +1 -0
  44. package/dist/2345.js +1 -0
  45. package/dist/2345.js.map +1 -0
  46. package/dist/2402.js +1 -0
  47. package/dist/2402.js.map +1 -0
  48. package/dist/2500.js +1 -0
  49. package/dist/2500.js.map +1 -0
  50. package/dist/251.js +1 -0
  51. package/dist/251.js.map +1 -0
  52. package/dist/257.js +1 -0
  53. package/dist/257.js.map +1 -0
  54. package/dist/2586.js +1 -0
  55. package/dist/2586.js.map +1 -0
  56. package/dist/2625.js +1 -0
  57. package/dist/2625.js.map +1 -0
  58. package/dist/2652.js +1 -0
  59. package/dist/2652.js.map +1 -0
  60. package/dist/2685.js +1 -0
  61. package/dist/2685.js.map +1 -0
  62. package/dist/2948.js +1 -0
  63. package/dist/2948.js.map +1 -0
  64. package/dist/3089.js +1 -0
  65. package/dist/3089.js.map +1 -0
  66. package/dist/3190.js +1 -0
  67. package/dist/3190.js.map +1 -0
  68. package/dist/3224.js +1 -0
  69. package/dist/3224.js.map +1 -0
  70. package/dist/3366.js +1 -0
  71. package/dist/3366.js.map +1 -0
  72. package/dist/3548.js +1 -0
  73. package/dist/3548.js.map +1 -0
  74. package/dist/3571.js +1 -0
  75. package/dist/3571.js.map +1 -0
  76. package/dist/3691.js +1 -0
  77. package/dist/3691.js.map +1 -0
  78. package/dist/3775.js +1 -0
  79. package/dist/3775.js.map +1 -0
  80. package/dist/3816.js +1 -0
  81. package/dist/3816.js.map +1 -0
  82. package/dist/3852.js +1 -0
  83. package/dist/3852.js.map +1 -0
  84. package/dist/3906.js +1 -0
  85. package/dist/3906.js.map +1 -0
  86. package/dist/3963.js +1 -0
  87. package/dist/3963.js.map +1 -0
  88. package/dist/4047.js +1 -0
  89. package/dist/4047.js.map +1 -0
  90. package/dist/405.js +1 -0
  91. package/dist/405.js.map +1 -0
  92. package/dist/4296.js +1 -0
  93. package/dist/4296.js.map +1 -0
  94. package/dist/4337.js +1 -0
  95. package/dist/4337.js.map +1 -0
  96. package/dist/4584.js +1 -0
  97. package/dist/4584.js.map +1 -0
  98. package/dist/4735.js +1 -0
  99. package/dist/4735.js.map +1 -0
  100. package/dist/4744.js +1 -0
  101. package/dist/4744.js.map +1 -0
  102. package/dist/4813.js +2 -0
  103. package/dist/4813.js.map +1 -0
  104. package/dist/4858.js +1 -0
  105. package/dist/4858.js.map +1 -0
  106. package/dist/487.js +1 -0
  107. package/dist/487.js.map +1 -0
  108. package/dist/4970.js +1 -0
  109. package/dist/4970.js.map +1 -0
  110. package/dist/5202.js +1 -0
  111. package/dist/5202.js.map +1 -0
  112. package/dist/5294.js +1 -0
  113. package/dist/5294.js.map +1 -0
  114. package/dist/545.js +1 -0
  115. package/dist/545.js.map +1 -0
  116. package/dist/552.js +1 -0
  117. package/dist/552.js.map +1 -0
  118. package/dist/5592.js +1 -0
  119. package/dist/5592.js.map +1 -0
  120. package/dist/5669.js +1 -0
  121. package/dist/5669.js.map +1 -0
  122. package/dist/5884.js +1 -0
  123. package/dist/5884.js.map +1 -0
  124. package/dist/5940.js +1 -0
  125. package/dist/5940.js.map +1 -0
  126. package/dist/6092.js +1 -0
  127. package/dist/6092.js.map +1 -0
  128. package/dist/6155.js +1 -0
  129. package/dist/6155.js.map +1 -0
  130. package/dist/6178.js +1 -0
  131. package/dist/6178.js.map +1 -0
  132. package/dist/6399.js +1 -0
  133. package/dist/6399.js.map +1 -0
  134. package/dist/6456.js +1 -0
  135. package/dist/6466.js +3 -0
  136. package/dist/6466.js.map +1 -0
  137. package/dist/6492.js +1 -0
  138. package/dist/6492.js.map +1 -0
  139. package/dist/6676.js +1 -0
  140. package/dist/6676.js.map +1 -0
  141. package/dist/6800.js +1 -0
  142. package/dist/6800.js.map +1 -0
  143. package/dist/6976.js +1 -0
  144. package/dist/6976.js.map +1 -0
  145. package/dist/7005.js +1 -0
  146. package/dist/7005.js.map +1 -0
  147. package/dist/7201.js +1 -0
  148. package/dist/7201.js.map +1 -0
  149. package/dist/7210.js +1 -0
  150. package/dist/7210.js.map +1 -0
  151. package/dist/7234.js +1 -0
  152. package/dist/7234.js.map +1 -0
  153. package/dist/7261.js +1 -0
  154. package/dist/7261.js.map +1 -0
  155. package/dist/7326.js +1 -0
  156. package/dist/7463.js +1 -0
  157. package/dist/7463.js.map +1 -0
  158. package/dist/7528.js +1 -0
  159. package/dist/7528.js.map +1 -0
  160. package/dist/7607.js +1 -0
  161. package/dist/7717.js +1 -0
  162. package/dist/7717.js.map +1 -0
  163. package/dist/7737.js +1 -0
  164. package/dist/7737.js.map +1 -0
  165. package/dist/7739.js +1 -0
  166. package/dist/7739.js.map +1 -0
  167. package/dist/7765.js +1 -0
  168. package/dist/7765.js.map +1 -0
  169. package/dist/7820.js +1 -0
  170. package/dist/7820.js.map +1 -0
  171. package/dist/7844.js +1 -0
  172. package/dist/7844.js.map +1 -0
  173. package/dist/7866.js +1 -0
  174. package/dist/7866.js.map +1 -0
  175. package/dist/7916.js +1 -0
  176. package/dist/7916.js.map +1 -0
  177. package/dist/7971.js +1 -0
  178. package/dist/7971.js.map +1 -0
  179. package/dist/8159.js +7 -0
  180. package/dist/8159.js.map +1 -0
  181. package/dist/8244.js +1 -0
  182. package/dist/8244.js.map +1 -0
  183. package/dist/8262.js +1 -0
  184. package/dist/8262.js.map +1 -0
  185. package/dist/8376.js +1 -0
  186. package/dist/8376.js.map +1 -0
  187. package/dist/845.js +1 -0
  188. package/dist/845.js.map +1 -0
  189. package/dist/8570.js +1 -0
  190. package/dist/8570.js.map +1 -0
  191. package/dist/87.js +1 -0
  192. package/dist/87.js.map +1 -0
  193. package/dist/8727.js +1 -0
  194. package/dist/8828.js +1 -0
  195. package/dist/8828.js.map +1 -0
  196. package/dist/8860.js +1 -0
  197. package/dist/8860.js.map +1 -0
  198. package/dist/9036.js +1 -0
  199. package/dist/9036.js.map +1 -0
  200. package/dist/9124.js +1 -0
  201. package/dist/9124.js.map +1 -0
  202. package/dist/9182.js +1 -0
  203. package/dist/921.js +1 -0
  204. package/dist/921.js.map +1 -0
  205. package/dist/9404.js +1 -0
  206. package/dist/9404.js.map +1 -0
  207. package/dist/9406.js +1 -0
  208. package/dist/9406.js.map +1 -0
  209. package/dist/9446.js +1 -0
  210. package/dist/9446.js.map +1 -0
  211. package/dist/9449.js +1 -0
  212. package/dist/9449.js.map +1 -0
  213. package/dist/9566.js +5 -0
  214. package/dist/9566.js.map +1 -0
  215. package/dist/9641.js +1 -0
  216. package/dist/9641.js.map +1 -0
  217. package/dist/9711.js +1 -0
  218. package/dist/9711.js.map +1 -0
  219. package/dist/9801.js +1 -0
  220. package/dist/9801.js.map +1 -0
  221. package/dist/9835.js +11 -0
  222. package/dist/9835.js.map +1 -0
  223. package/dist/kenyaemr-esm-admin-app.js +5 -5
  224. package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +2649 -154
  225. package/dist/kenyaemr-esm-admin-app.js.map +1 -1
  226. package/dist/main.js +5 -31
  227. package/dist/main.js.map +1 -1
  228. package/dist/routes.json +1 -1
  229. package/package.json +5 -7
  230. package/rspack.config.js +1 -1
  231. package/src/components/facility-setup/constant/index.ts +3 -0
  232. package/src/components/facility-setup/facility-info.component.tsx +247 -108
  233. package/src/components/facility-setup/facility-info.scss +136 -55
  234. package/src/components/facility-setup/facility-setup.component.tsx +2 -2
  235. package/src/components/facility-setup/header/header.component.tsx +4 -10
  236. package/src/components/facility-setup/header/header.scss +3 -9
  237. package/src/components/facility-setup/shared/custom-info.component.tsx +9 -0
  238. package/src/components/facility-setup/shared/custom-section-card.component.tsx +10 -0
  239. package/src/components/facility-setup/shared/custom-status-tag.component.tsx +22 -0
  240. package/src/components/facility-setup/type/index.ts +61 -0
  241. package/src/components/facility-setup/useFacilityRegistry.ts +29 -0
  242. package/src/components/hook/healthWorkerRegistry.ts +78 -0
  243. package/src/components/hook/useProfessionalRegistryEnums.ts +59 -0
  244. package/src/components/locations/forms/add-location/add-location.workspace.tsx +96 -95
  245. package/src/components/locations/forms/search-location/search-location.workspace.tsx +90 -85
  246. package/src/components/locations/tables/locations-table.component.tsx +117 -121
  247. package/src/components/modal/hwr-confirmation.modal.scss +80 -4
  248. package/src/components/modal/hwr-confirmation.modal.tsx +118 -128
  249. package/src/components/modal/hwr-sync.modal.tsx +194 -106
  250. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +13 -13
  251. package/src/components/users/manage-users/user-details/user-detail.scss +168 -39
  252. package/src/components/users/manage-users/user-details/user-details.component.tsx +130 -122
  253. package/src/components/users/manage-users/user-list/user-list.component.tsx +22 -9
  254. package/src/components/users/manage-users/user-management.workspace.scss +233 -95
  255. package/src/components/users/manage-users/user-management.workspace.tsx +800 -687
  256. package/src/components/users/userManagementFormSchema.tsx +17 -8
  257. package/src/config-schema.ts +48 -68
  258. package/src/index.ts +55 -30
  259. package/src/left-pannel-link.component.tsx +5 -3
  260. package/src/root.component.tsx +11 -13
  261. package/src/routes.json +40 -40
  262. package/src/types/index.ts +29 -1
  263. package/translations/am.json +158 -13
  264. package/translations/en.json +158 -13
  265. package/translations/fr.json +193 -47
  266. package/translations/sw.json +158 -13
  267. package/tsconfig.json +1 -1
  268. package/dist/127.js +0 -1
  269. package/dist/267.js +0 -1
  270. package/dist/267.js.map +0 -1
  271. package/dist/281.js +0 -15
  272. package/dist/281.js.map +0 -1
  273. package/dist/329.js +0 -1
  274. package/dist/329.js.map +0 -1
  275. package/dist/40.js +0 -1
  276. package/dist/466.js +0 -1
  277. package/dist/466.js.map +0 -1
  278. package/dist/472.js +0 -1
  279. package/dist/472.js.map +0 -1
  280. package/dist/478.js +0 -1
  281. package/dist/478.js.map +0 -1
  282. package/dist/585.js +0 -1
  283. package/dist/585.js.map +0 -1
  284. package/dist/630.js +0 -1
  285. package/dist/630.js.map +0 -1
  286. package/dist/675.js +0 -1
  287. package/dist/675.js.map +0 -1
  288. package/dist/689.js +0 -1
  289. package/dist/689.js.map +0 -1
  290. package/dist/706.js +0 -27
  291. package/dist/706.js.map +0 -1
  292. package/dist/729.js +0 -17
  293. package/dist/729.js.map +0 -1
  294. package/dist/774.js +0 -1
  295. package/dist/774.js.map +0 -1
  296. package/dist/847.js +0 -1
  297. package/dist/847.js.map +0 -1
  298. package/dist/85.js +0 -1
  299. package/dist/85.js.map +0 -1
  300. package/dist/882.js +0 -1
  301. package/dist/91.js +0 -1
  302. package/dist/91.js.map +0 -1
  303. package/dist/916.js +0 -1
  304. package/dist/998.js +0 -1
  305. package/dist/998.js.map +0 -1
  306. package/jest.config.js +0 -8
  307. package/src/components/facility-setup/card.component.tsx +0 -16
  308. package/src/components/facility-setup/facility-setup.resource.tsx +0 -7
  309. package/src/components/hook/healthWorkerAdapter.ts +0 -213
  310. 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 styles from './hwr-sync.modal.scss';
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
- import { CustomHIEPractitionerResponse, type PractitionerResponse, type ProviderResponse } from '../../types';
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
- providerNationalIdUuid,
24
- licenseBodyUuid,
43
+ licenseNumberUuid,
25
44
  licenseExpiryDateUuid,
45
+ licenseBodyUuid,
46
+ qualificationUuid,
47
+ specialtyUuid,
48
+ providerCadreUuid,
49
+ practiceTypeUuid,
50
+ providerNationalIdUuid,
26
51
  passportNumberUuid,
27
- licenseNumberUuid,
28
- identifierTypes,
52
+ providerUniqueIdentifierAttributeTypeUuid,
53
+ externalProviderIdentifierUuid,
54
+ providerHieFhirReference,
29
55
  phoneNumberUuid,
30
- qualificationUuid,
31
56
  providerAddressUuid,
32
- providerHieFhirReference,
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
- regulatorOptions,
35
- } = config;
36
-
37
- const attributeMapping = {
38
- [identifierTypes[0]?.key]:
39
- provider.attributes.find((attr) => attr.attributeType.uuid === providerNationalIdUuid)?.value || '--',
40
- [identifierTypes[1]?.key]:
41
- provider.attributes.find((attr) => attr.attributeType.uuid === licenseBodyUuid)?.value || '--',
42
- [identifierTypes[2]?.key]:
43
- provider.attributes.find((attr) => attr.attributeType.uuid === passportNumberUuid)?.value || '--',
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: identifierTypes[0]?.key,
48
- identifier: attributeMapping[identifierTypes[0]?.key],
49
- regulator: regulatorOptions[0]?.key,
109
+ identifierType: initialIdentifierType,
110
+ identifier: initialIdentifier,
111
+ regulator: initialRegulator,
50
112
  });
51
113
 
52
- const handleIdentifierTypeChange = (selectedItem: { key: string; name: string } | null) => {
53
- const selectedKey = selectedItem?.key ?? '';
114
+ useEffect(() => {
54
115
  setSearchHWR((prev) => ({
55
116
  ...prev,
56
- identifierType: selectedKey,
57
- identifier: attributeMapping[selectedKey] || '',
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
- regulator: selectedKey,
132
+ identifierType: newType,
133
+ identifier: valueForType[newType] ?? '',
66
134
  }));
67
135
  };
68
136
 
69
- const isSearchDisabled = () => !searchHWR.identifier;
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
- try {
73
- setSyncLoading(true);
74
- const unifiedResponse = await searchHealthCareWork(
75
- searchHWR.identifierType,
76
- searchHWR.identifier,
77
- searchHWR.regulator,
78
- );
151
+ setSyncLoading(true);
79
152
 
80
- const normalizedData = HealthWorkerAdapter.normalize(unifiedResponse);
153
+ try {
154
+ const response = await searchHealthCareWork(searchHWR.identifierType, searchHWR.identifier, searchHWR.regulator);
81
155
 
82
- if (!normalizedData) {
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: normalizedData.licenseNumber },
88
- { attributeType: licenseBodyUuid, value: normalizedData.registrationId },
164
+ { attributeType: licenseNumberUuid, value: currentLicense?.external_reference_id },
89
165
  {
90
166
  attributeType: licenseExpiryDateUuid,
91
- value: normalizedData.licenseEndDate ? parseDate(normalizedData.licenseEndDate) : null,
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
- ].filter((attr) => attr.value !== undefined && attr.value !== null && attr.value !== '');
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 existingAttribute = provider.attributes.find(
117
- (at) => at.attributeType.uuid === attr.attributeType,
118
- )?.uuid;
119
-
120
- const payload = {
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: 'Success',
200
+ title: t('syncSuccess', 'Sync successful'),
135
201
  kind: 'success',
136
- subtitle: t('syncMessage', 'user details synced successfully'),
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', `Failed to sync the account with ${searchHWR.identifier}. ${err}`),
144
- title: t('hwrERROR', 'Sync Failed'),
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>{t('healthWorkerSync', 'Health worker information to be synced with the registry.')}</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
- onChange={({ selectedItem }) => handleIdentifierTypeChange(selectedItem)}
162
- id="formIdentifierType"
239
+ id="syncIdentifierType"
163
240
  titleText={t('identificationType', 'Identification Type')}
164
241
  placeholder={t('chooseIdentifierType', 'Choose identifier type')}
165
- initialSelectedItem={identifierTypes.find((item) => item.key === searchHWR.identifierType)}
166
- items={identifierTypes}
167
- itemToString={(item) => (item ? item.name : '')}
168
- className={styles.ComboBox}
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
- onChange={({ selectedItem }) => handleRegulatorChange(selectedItem)}
174
- id="regulatorOptions"
252
+ id="syncRegulator"
175
253
  titleText={t('regulator', 'Regulator')}
176
254
  placeholder={t('chooseRegulator', 'Choose regulator')}
177
- initialSelectedItem={regulatorOptions.find((item) => item.key === searchHWR.regulator)}
178
- items={regulatorOptions}
179
- itemToString={(item) => (item ? item.name : '')}
180
- className={styles.ComboBox}
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
- id="formSearchHealthWorkers"
191
- disabled={isSearchDisabled()}
192
- onChange={(value) => setSearchHWR({ ...searchHWR, identifier: value.target.value })}
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() || syncLoading} onClick={handleSync}>
202
- {syncLoading ? <InlineLoading status="active" description={t('syncing', 'Syncing...')} /> : t('sync', 'Sync')}
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 = DefaultWorkspaceProps & {
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
- promptBeforeClosing,
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
- promptBeforeClosing(() => isDirty);
96
+ setHasUnsavedChanges(true);
97
97
  }
98
- }, [isDirty, promptBeforeClosing]);
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
- closeWorkspaceWithSavedChanges();
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
- promptBeforeClosing(() => isDirty);
187
+ setHasUnsavedChanges(true);
188
188
  }
189
- }, [isDirty, promptBeforeClosing]);
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