@kenyaemr/esm-admin-app 5.4.4-pre.34 → 5.4.4-pre.340

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 (332) hide show
  1. package/.turbo/turbo-build.log +5 -12
  2. package/dist/100.js +1 -0
  3. package/dist/100.js.map +1 -0
  4. package/dist/1074.js +1 -0
  5. package/dist/1074.js.map +1 -0
  6. package/dist/1242.js +1 -0
  7. package/dist/1242.js.map +1 -0
  8. package/dist/1311.js +1 -0
  9. package/dist/1311.js.map +1 -0
  10. package/dist/1442.js +1 -0
  11. package/dist/1442.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/2467.js +1 -0
  47. package/dist/2467.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/3380.js +1 -0
  71. package/dist/3380.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/3906.js +1 -0
  83. package/dist/3906.js.map +1 -0
  84. package/dist/3963.js +1 -0
  85. package/dist/3963.js.map +1 -0
  86. package/dist/405.js +1 -0
  87. package/dist/405.js.map +1 -0
  88. package/dist/4296.js +1 -0
  89. package/dist/4296.js.map +1 -0
  90. package/dist/4337.js +1 -0
  91. package/dist/4337.js.map +1 -0
  92. package/dist/4584.js +1 -0
  93. package/dist/4584.js.map +1 -0
  94. package/dist/4687.js +1 -0
  95. package/dist/4687.js.map +1 -0
  96. package/dist/4735.js +1 -0
  97. package/dist/4735.js.map +1 -0
  98. package/dist/4744.js +1 -0
  99. package/dist/4744.js.map +1 -0
  100. package/dist/4813.js +2 -0
  101. package/dist/4813.js.map +1 -0
  102. package/dist/4858.js +1 -0
  103. package/dist/4858.js.map +1 -0
  104. package/dist/487.js +1 -0
  105. package/dist/487.js.map +1 -0
  106. package/dist/4970.js +1 -0
  107. package/dist/4970.js.map +1 -0
  108. package/dist/5202.js +1 -0
  109. package/dist/5202.js.map +1 -0
  110. package/dist/5294.js +1 -0
  111. package/dist/5294.js.map +1 -0
  112. package/dist/5297.js +1 -0
  113. package/dist/5297.js.map +1 -0
  114. package/dist/545.js +1 -0
  115. package/dist/545.js.map +1 -0
  116. package/dist/5592.js +1 -0
  117. package/dist/5592.js.map +1 -0
  118. package/dist/5669.js +1 -0
  119. package/dist/5669.js.map +1 -0
  120. package/dist/5884.js +1 -0
  121. package/dist/5884.js.map +1 -0
  122. package/dist/5910.js +1 -0
  123. package/dist/5910.js.map +1 -0
  124. package/dist/5940.js +1 -0
  125. package/dist/5940.js.map +1 -0
  126. package/dist/6155.js +1 -0
  127. package/dist/6155.js.map +1 -0
  128. package/dist/6178.js +1 -0
  129. package/dist/6178.js.map +1 -0
  130. package/dist/6253.js +1 -0
  131. package/dist/6253.js.map +1 -0
  132. package/dist/6455.js +1 -0
  133. package/dist/6455.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/6800.js +1 -0
  140. package/dist/6800.js.map +1 -0
  141. package/dist/6925.js +1 -0
  142. package/dist/6925.js.map +1 -0
  143. package/dist/7005.js +1 -0
  144. package/dist/7005.js.map +1 -0
  145. package/dist/7201.js +1 -0
  146. package/dist/7201.js.map +1 -0
  147. package/dist/7210.js +1 -0
  148. package/dist/7210.js.map +1 -0
  149. package/dist/7234.js +1 -0
  150. package/dist/7234.js.map +1 -0
  151. package/dist/7261.js +1 -0
  152. package/dist/7261.js.map +1 -0
  153. package/dist/7326.js +1 -0
  154. package/dist/7463.js +1 -0
  155. package/dist/7463.js.map +1 -0
  156. package/dist/7528.js +1 -0
  157. package/dist/7528.js.map +1 -0
  158. package/dist/7607.js +1 -0
  159. package/dist/7717.js +1 -0
  160. package/dist/7717.js.map +1 -0
  161. package/dist/7737.js +1 -0
  162. package/dist/7737.js.map +1 -0
  163. package/dist/7739.js +1 -0
  164. package/dist/7739.js.map +1 -0
  165. package/dist/7765.js +1 -0
  166. package/dist/7765.js.map +1 -0
  167. package/dist/7820.js +1 -0
  168. package/dist/7820.js.map +1 -0
  169. package/dist/7844.js +1 -0
  170. package/dist/7844.js.map +1 -0
  171. package/dist/7866.js +1 -0
  172. package/dist/7866.js.map +1 -0
  173. package/dist/7971.js +1 -0
  174. package/dist/7971.js.map +1 -0
  175. package/dist/8159.js +7 -0
  176. package/dist/8159.js.map +1 -0
  177. package/dist/8206.js +1 -0
  178. package/dist/8206.js.map +1 -0
  179. package/dist/8244.js +1 -0
  180. package/dist/8244.js.map +1 -0
  181. package/dist/8262.js +1 -0
  182. package/dist/8262.js.map +1 -0
  183. package/dist/8376.js +1 -0
  184. package/dist/8376.js.map +1 -0
  185. package/dist/845.js +1 -0
  186. package/dist/845.js.map +1 -0
  187. package/dist/846.js +17 -0
  188. package/dist/846.js.map +1 -0
  189. package/dist/8487.js +1 -0
  190. package/dist/8487.js.map +1 -0
  191. package/dist/8528.js +1 -0
  192. package/dist/8528.js.map +1 -0
  193. package/dist/8570.js +1 -0
  194. package/dist/8570.js.map +1 -0
  195. package/dist/87.js +1 -0
  196. package/dist/87.js.map +1 -0
  197. package/dist/8727.js +1 -0
  198. package/dist/8828.js +1 -0
  199. package/dist/8828.js.map +1 -0
  200. package/dist/8860.js +1 -0
  201. package/dist/8860.js.map +1 -0
  202. package/dist/9036.js +1 -0
  203. package/dist/9036.js.map +1 -0
  204. package/dist/9124.js +1 -0
  205. package/dist/9124.js.map +1 -0
  206. package/dist/9182.js +1 -0
  207. package/dist/921.js +1 -0
  208. package/dist/921.js.map +1 -0
  209. package/dist/9404.js +1 -0
  210. package/dist/9404.js.map +1 -0
  211. package/dist/9446.js +1 -0
  212. package/dist/9446.js.map +1 -0
  213. package/dist/9449.js +1 -0
  214. package/dist/9449.js.map +1 -0
  215. package/dist/9566.js +5 -0
  216. package/dist/9566.js.map +1 -0
  217. package/dist/9585.js +1 -0
  218. package/dist/9585.js.map +1 -0
  219. package/dist/9641.js +1 -0
  220. package/dist/9641.js.map +1 -0
  221. package/dist/9647.js +1 -0
  222. package/dist/9647.js.map +1 -0
  223. package/dist/9801.js +1 -0
  224. package/dist/9801.js.map +1 -0
  225. package/dist/9835.js +11 -0
  226. package/dist/9835.js.map +1 -0
  227. package/dist/kenyaemr-esm-admin-app.js +5 -5
  228. package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +2672 -154
  229. package/dist/kenyaemr-esm-admin-app.js.map +1 -1
  230. package/dist/main.js +5 -31
  231. package/dist/main.js.map +1 -1
  232. package/dist/routes.json +1 -1
  233. package/package.json +5 -7
  234. package/rspack.config.js +1 -1
  235. package/src/components/confirm-modal/confirmation-operation.test.tsx +8 -19
  236. package/src/components/dashboard/dashboard.component.tsx +4 -1
  237. package/src/components/empty-state/empty-state-log.test.tsx +3 -4
  238. package/src/components/facility-setup/constant/index.ts +3 -0
  239. package/src/components/facility-setup/facility-info.component.tsx +247 -108
  240. package/src/components/facility-setup/facility-info.scss +136 -55
  241. package/src/components/facility-setup/facility-setup.component.tsx +2 -2
  242. package/src/components/facility-setup/header/header.component.tsx +4 -10
  243. package/src/components/facility-setup/header/header.scss +3 -9
  244. package/src/components/facility-setup/shared/custom-info.component.tsx +9 -0
  245. package/src/components/facility-setup/shared/custom-section-card.component.tsx +10 -0
  246. package/src/components/facility-setup/shared/custom-status-tag.component.tsx +22 -0
  247. package/src/components/facility-setup/type/index.ts +61 -0
  248. package/src/components/facility-setup/useFacilityRegistry.ts +29 -0
  249. package/src/components/global-property/dashboard/global-property-dashboard.component.tsx +23 -0
  250. package/src/components/global-property/dashboard/global-property-dashboard.scss +6 -0
  251. package/src/components/global-property/hooks/useGlobalProperty.ts +64 -0
  252. package/src/components/global-property/index.ts +14 -0
  253. package/src/components/global-property/modal/delete-global-property-modal.component.tsx +71 -0
  254. package/src/components/global-property/modal/delete-global-property-modal.test.tsx +131 -0
  255. package/src/components/global-property/table/global-property-table.component.tsx +249 -0
  256. package/src/components/global-property/table/global-property-table.scss +34 -0
  257. package/src/components/global-property/table/global-property-table.test.tsx +198 -0
  258. package/src/components/global-property/workspace/global-property-form-schema.ts +32 -0
  259. package/src/components/global-property/workspace/global-property.workspace.scss +40 -0
  260. package/src/components/global-property/workspace/global-property.workspace.test.tsx +172 -0
  261. package/src/components/global-property/workspace/global-property.workspace.tsx +260 -0
  262. package/src/components/hook/healthWorkerRegistry.ts +78 -0
  263. package/src/components/hook/useProfessionalRegistryEnums.ts +59 -0
  264. package/src/components/locations/forms/add-location/add-location.workspace.tsx +96 -95
  265. package/src/components/locations/forms/search-location/search-location.workspace.tsx +90 -85
  266. package/src/components/locations/tables/locations-table.component.tsx +117 -121
  267. package/src/components/logs-table/operation-log-table.component.tsx +87 -75
  268. package/src/components/logs-table/operation-log.test.tsx +134 -28
  269. package/src/components/modal/hwr-confirmation.modal.scss +80 -4
  270. package/src/components/modal/hwr-confirmation.modal.tsx +118 -128
  271. package/src/components/modal/hwr-sync.modal.tsx +194 -106
  272. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +13 -13
  273. package/src/components/users/manage-users/user-details/user-detail.scss +167 -39
  274. package/src/components/users/manage-users/user-details/user-details.component.tsx +130 -122
  275. package/src/components/users/manage-users/user-list/user-list.component.tsx +22 -9
  276. package/src/components/users/manage-users/user-management.workspace.scss +233 -95
  277. package/src/components/users/manage-users/user-management.workspace.tsx +800 -687
  278. package/src/components/users/userManagementFormSchema.tsx +17 -8
  279. package/src/config-schema.ts +48 -68
  280. package/src/index.ts +64 -31
  281. package/src/left-pannel-link.component.tsx +5 -3
  282. package/src/root.component.tsx +13 -13
  283. package/src/routes.json +57 -38
  284. package/src/types/index.ts +40 -3
  285. package/translations/am.json +196 -13
  286. package/translations/en.json +207 -24
  287. package/translations/fr.json +243 -58
  288. package/translations/sw.json +312 -129
  289. package/tsconfig.json +1 -1
  290. package/dist/127.js +0 -1
  291. package/dist/267.js +0 -1
  292. package/dist/267.js.map +0 -1
  293. package/dist/281.js +0 -15
  294. package/dist/281.js.map +0 -1
  295. package/dist/329.js +0 -1
  296. package/dist/329.js.map +0 -1
  297. package/dist/40.js +0 -1
  298. package/dist/466.js +0 -1
  299. package/dist/466.js.map +0 -1
  300. package/dist/472.js +0 -1
  301. package/dist/472.js.map +0 -1
  302. package/dist/478.js +0 -1
  303. package/dist/478.js.map +0 -1
  304. package/dist/585.js +0 -1
  305. package/dist/585.js.map +0 -1
  306. package/dist/630.js +0 -1
  307. package/dist/630.js.map +0 -1
  308. package/dist/675.js +0 -1
  309. package/dist/675.js.map +0 -1
  310. package/dist/689.js +0 -1
  311. package/dist/689.js.map +0 -1
  312. package/dist/706.js +0 -27
  313. package/dist/706.js.map +0 -1
  314. package/dist/729.js +0 -17
  315. package/dist/729.js.map +0 -1
  316. package/dist/774.js +0 -1
  317. package/dist/774.js.map +0 -1
  318. package/dist/847.js +0 -1
  319. package/dist/847.js.map +0 -1
  320. package/dist/85.js +0 -1
  321. package/dist/85.js.map +0 -1
  322. package/dist/882.js +0 -1
  323. package/dist/91.js +0 -1
  324. package/dist/91.js.map +0 -1
  325. package/dist/916.js +0 -1
  326. package/dist/998.js +0 -1
  327. package/dist/998.js.map +0 -1
  328. package/jest.config.js +0 -8
  329. package/src/components/facility-setup/card.component.tsx +0 -16
  330. package/src/components/facility-setup/facility-setup.resource.tsx +0 -7
  331. package/src/components/hook/healthWorkerAdapter.ts +0 -213
  332. package/src/components/hook/useFacilityInfo.tsx +0 -37
@@ -9,6 +9,8 @@ import {
9
9
  useConfig,
10
10
  showModal,
11
11
  parseDate,
12
+ Workspace2,
13
+ Workspace2DefinitionProps,
12
14
  } from '@openmrs/esm-framework';
13
15
  import { Controller, FormProvider, useForm } from 'react-hook-form';
14
16
  import styles from './user-management.workspace.scss';
@@ -46,95 +48,144 @@ import {
46
48
  import UserManagementFormSchema from '../userManagementFormSchema';
47
49
  import { ChevronLeft, Query, ChevronRight } from '@carbon/react/icons';
48
50
  import { useSystemUserRoleConfigSetting } from '../../hook/useSystemRoleSetting';
49
- import { type CustomHIEPractitionerResponse, type PractitionerResponse, Provider, User } from '../../../types';
50
- import { searchHealthCareWork, HealthWorkerAdapter, NormalizedPractitioner } from '../../hook/healthWorkerAdapter';
51
- import { ROLE_CATEGORIES, SECTIONS, today } from '../../../constants';
51
+ import { Provider, User } from '../../../types';
52
+ import { ROLE_CATEGORIES, SECTIONS } from '../../../constants';
52
53
  import { ConfigObject } from '../../../config-schema';
53
54
  import { mutate } from 'swr';
54
55
  import { createProviderAttribute, updateProviderAttributes } from '../../modal/hwr-sync.resource';
55
56
  import { useUsers } from './user-list/user-list.resource';
57
+ import { searchHealthCareWork, ProfessionalRegistryResponse } from '../../hook/healthWorkerRegistry';
58
+ import {
59
+ useProfessionalRegistryIdentificationTypes,
60
+ useProfessionalRegistryRegulators,
61
+ } from '../../hook/useProfessionalRegistryEnums';
56
62
 
57
63
  type ManageUserWorkspaceProps = DefaultWorkspaceProps & {
58
64
  initialUserValue?: User;
59
65
  };
60
66
 
61
- const MinDate: Date = today();
67
+ /**
68
+ * Pick the active license: latest end-date that hasn't expired yet.
69
+ * Fall back to the most recently expired license if none are current,
70
+ * so the UI surfaces something concrete rather than nothing.
71
+ */
72
+ const pickCurrentLicense = (licenses: ProfessionalRegistryResponse['professional']['licenses']) => {
73
+ if (!licenses || licenses.length === 0) {
74
+ return undefined;
75
+ }
76
+ const sorted = [...licenses]
77
+ .filter((l) => l.license_end)
78
+ .sort((a, b) => new Date(b.license_end).getTime() - new Date(a.license_end).getTime());
79
+ const now = Date.now();
80
+ return sorted.find((l) => new Date(l.license_end).getTime() >= now) ?? sorted[0];
81
+ };
82
+
83
+ /**
84
+ * Savannah returns "Male"/"Female"; OpenMRS uses "M"/"F".
85
+ * Normalize the upstream gender to the OpenMRS shape, or undefined if unknown.
86
+ */
87
+ const normalizeGender = (upstream?: string): 'M' | 'F' | undefined => {
88
+ if (!upstream) {
89
+ return undefined;
90
+ }
91
+ const v = upstream.trim().toUpperCase();
92
+ if (v === 'MALE' || v === 'M') {
93
+ return 'M';
94
+ }
95
+ if (v === 'FEMALE' || v === 'F') {
96
+ return 'F';
97
+ }
98
+ return undefined;
99
+ };
62
100
 
63
- const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
101
+ const ManageUserWorkspace: React.FC<Workspace2DefinitionProps<ManageUserWorkspaceProps, {}, {}>> = ({
64
102
  closeWorkspace,
65
- promptBeforeClosing,
66
- closeWorkspaceWithSavedChanges,
67
- initialUserValue = {} as User,
103
+ workspaceProps: { initialUserValue = {} as User },
68
104
  }) => {
69
105
  const { t } = useTranslation();
70
106
  const isTablet = useLayoutType() === 'tablet';
71
107
  const [activeSection, setActiveSection] = useState('demographic');
72
108
  const [currentIndex, setCurrentIndex] = useState(0);
73
- const [healthWorker, setHealthWorker] = useState(null);
109
+ const [healthWorker, setHealthWorker] = useState<ProfessionalRegistryResponse | null>(null);
74
110
  const { provider = [], loadingProvider, providerError } = useProvider(initialUserValue.systemId);
75
111
  const { users, isLoading: isLoadingUsers, error: usersError } = useUsers();
76
112
  const usernames =
77
113
  users?.map((user) => user.username).filter((username) => username !== initialUserValue?.username) || [];
78
114
  const isInitialValuesEmpty = Object.keys(initialUserValue).length === 0;
115
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
79
116
 
80
117
  const { userManagementFormSchema } = UserManagementFormSchema(usernames);
81
-
82
118
  const { providerAttributeType = [] } = useProviderAttributeType();
83
-
84
119
  const { roles = [], isLoading } = useRoles();
85
120
  const { rolesConfig, error } = useSystemUserRoleConfigSetting();
121
+
86
122
  const {
87
- identifierTypes,
88
- regulatorOptions,
89
- licenseBodyUuid,
123
+ providerNationalIdUuid,
90
124
  passportNumberUuid,
91
- personEmailAttributeUuid,
92
- personPhonenumberAttributeUuid,
125
+ providerUniqueIdentifierAttributeTypeUuid,
126
+ externalProviderIdentifierUuid,
127
+ licenseNumberUuid,
128
+ licenseExpiryDateUuid,
129
+ licenseBodyUuid,
130
+ qualificationUuid,
131
+ specialtyUuid,
132
+ providerCadreUuid,
133
+ practiceTypeUuid,
93
134
  phoneNumberUuid,
94
135
  providerAddressUuid,
95
136
  providerHieFhirReference,
96
- qualificationUuid,
97
- providerNationalIdUuid,
98
- licenseNumberUuid,
99
- licenseExpiryDateUuid,
100
- providerUniqueIdentifierAttributeTypeUuid,
137
+ personEmailAttributeUuid,
138
+ personPhonenumberAttributeUuid,
101
139
  } = useConfig<ConfigObject>();
140
+
141
+ const { regulators } = useProfessionalRegistryRegulators();
142
+ const { identificationTypes } = useProfessionalRegistryIdentificationTypes();
143
+
102
144
  const [searchHWR, setSearchHWR] = useState({
103
- identifierType: identifierTypes[0]?.key ?? '',
104
- regulator: regulatorOptions[0]?.key ?? '',
145
+ identifierType: '',
146
+ regulator: '',
105
147
  identifier: '',
106
148
  isHWRLoading: false,
107
149
  });
108
- const defaultIdentifierType = identifierTypes.find((item) => item.key === searchHWR.identifierType);
109
- const defaultRegulator = regulatorOptions.find((item) => item.key === searchHWR.regulator);
110
150
 
111
151
  const attributeTypeMapping = useMemo(() => {
112
152
  return {
113
- licenseNumber: providerAttributeType.find((type) => type.uuid === licenseNumberUuid)?.uuid || '',
114
- licenseExpiry: providerAttributeType.find((type) => type.uuid === licenseExpiryDateUuid)?.uuid || '',
115
153
  providerNationalId: providerAttributeType.find((type) => type.uuid === providerNationalIdUuid)?.uuid || '',
154
+ passportNumber: providerAttributeType.find((type) => type.uuid === passportNumberUuid)?.uuid || '',
155
+ providerUniqueIdentifier:
156
+ providerAttributeType.find((type) => type.uuid === providerUniqueIdentifierAttributeTypeUuid)?.uuid || '',
157
+ externalProviderIdentifier:
158
+ providerAttributeType.find((type) => type.uuid === externalProviderIdentifierUuid)?.uuid || '',
116
159
  providerHieFhirReference:
117
160
  providerAttributeType.find((type) => type.uuid === providerHieFhirReference)?.uuid || '',
118
- qualification: providerAttributeType.find((type) => type.uuid === qualificationUuid)?.uuid || '',
161
+
162
+ licenseNumber: providerAttributeType.find((type) => type.uuid === licenseNumberUuid)?.uuid || '',
163
+ licenseExpiry: providerAttributeType.find((type) => type.uuid === licenseExpiryDateUuid)?.uuid || '',
119
164
  licenseBody: providerAttributeType.find((type) => type.uuid === licenseBodyUuid)?.uuid || '',
165
+ qualification: providerAttributeType.find((type) => type.uuid === qualificationUuid)?.uuid || '',
166
+ specialty: providerAttributeType.find((type) => type.uuid === specialtyUuid)?.uuid || '',
167
+ providerCadre: providerAttributeType.find((type) => type.uuid === providerCadreUuid)?.uuid || '',
168
+ practiceType: providerAttributeType.find((type) => type.uuid === practiceTypeUuid)?.uuid || '',
169
+
120
170
  phoneNumber: providerAttributeType.find((type) => type.uuid === phoneNumberUuid)?.uuid || '',
121
171
  providerAddress: providerAttributeType.find((type) => type.uuid === providerAddressUuid)?.uuid || '',
122
- passportNumber: providerAttributeType.find((type) => type.uuid === passportNumberUuid)?.uuid || '',
123
- providerUniqueIdentifier:
124
- providerAttributeType.find((type) => type.uuid === providerUniqueIdentifierAttributeTypeUuid)?.uuid || '',
125
172
  };
126
173
  }, [
127
- licenseBodyUuid,
128
- licenseExpiryDateUuid,
129
- licenseNumberUuid,
130
- passportNumberUuid,
131
- phoneNumberUuid,
132
- providerAddressUuid,
133
174
  providerAttributeType,
134
- providerHieFhirReference,
135
175
  providerNationalIdUuid,
136
- qualificationUuid,
176
+ passportNumberUuid,
137
177
  providerUniqueIdentifierAttributeTypeUuid,
178
+ externalProviderIdentifierUuid,
179
+ providerHieFhirReference,
180
+ licenseNumberUuid,
181
+ licenseExpiryDateUuid,
182
+ licenseBodyUuid,
183
+ qualificationUuid,
184
+ specialtyUuid,
185
+ providerCadreUuid,
186
+ practiceTypeUuid,
187
+ phoneNumberUuid,
188
+ providerAddressUuid,
138
189
  ]);
139
190
 
140
191
  const providerAttributes = useMemo(() => provider.flatMap((item) => item.attributes || []), [provider]);
@@ -166,7 +217,7 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
166
217
  () => getProviderAttributeValue(attributeTypeMapping.passportNumber),
167
218
  [attributeTypeMapping, getProviderAttributeValue],
168
219
  );
169
- const registrationNumber = useMemo(
220
+ const licenseBody = useMemo(
170
221
  () => getProviderAttributeValue(attributeTypeMapping.licenseBody),
171
222
  [attributeTypeMapping, getProviderAttributeValue],
172
223
  );
@@ -182,25 +233,37 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
182
233
  () => getProviderAttributeValue(attributeTypeMapping.providerUniqueIdentifier),
183
234
  [attributeTypeMapping, getProviderAttributeValue],
184
235
  );
236
+ const specialty = useMemo(
237
+ () => getProviderAttributeValue(attributeTypeMapping.specialty),
238
+ [attributeTypeMapping, getProviderAttributeValue],
239
+ );
240
+ const providerCadre = useMemo(
241
+ () => getProviderAttributeValue(attributeTypeMapping.providerCadre),
242
+ [attributeTypeMapping, getProviderAttributeValue],
243
+ );
244
+ const practiceType = useMemo(
245
+ () => getProviderAttributeValue(attributeTypeMapping.practiceType),
246
+ [attributeTypeMapping, getProviderAttributeValue],
247
+ );
248
+
185
249
  type UserFormSchema = z.infer<typeof userManagementFormSchema>;
250
+
186
251
  const formDefaultValues = useMemo(() => {
187
252
  if (isInitialValuesEmpty) {
188
253
  return {};
189
254
  }
190
255
  const extractNameParts = (display = '') => {
191
256
  const nameParts = display.split(' ');
192
-
193
257
  const [givenName = '', middleName = '', familyName = ''] =
194
258
  nameParts.length === 3 ? nameParts : [nameParts[0], '', nameParts[1] || ''];
195
-
196
259
  return { givenName, middleName, familyName };
197
260
  };
198
261
 
199
262
  return {
200
263
  ...initialUserValue,
201
264
  ...extractNameParts(initialUserValue.person?.display || ''),
202
- phoneNumber: phoneNumber,
203
- email: email,
265
+ phoneNumber,
266
+ email,
204
267
  roles:
205
268
  initialUserValue.roles?.map((role) => ({
206
269
  uuid: role.uuid,
@@ -210,11 +273,14 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
210
273
  gender: initialUserValue.person?.gender,
211
274
  providerLicense: providerLicenseNumber,
212
275
  licenseExpiryDate: licenseExpiryDate ? new Date(licenseExpiryDate) : undefined,
213
- qualification: qualification,
214
- nationalId: nationalId,
215
- passportNumber: passportNumber,
216
- registrationNumber: registrationNumber,
217
- providerUniqueIdentifier: providerUniqueIdentifier,
276
+ qualification,
277
+ nationalId,
278
+ passportNumber,
279
+ licenseBody,
280
+ providerUniqueIdentifier,
281
+ specialty,
282
+ providerCadre,
283
+ practiceType,
218
284
  };
219
285
  }, [
220
286
  isInitialValuesEmpty,
@@ -226,8 +292,11 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
226
292
  qualification,
227
293
  nationalId,
228
294
  passportNumber,
229
- registrationNumber,
295
+ licenseBody,
230
296
  providerUniqueIdentifier,
297
+ specialty,
298
+ providerCadre,
299
+ practiceType,
231
300
  ]);
232
301
 
233
302
  const userFormMethods = useForm<UserFormSchema>({
@@ -237,7 +306,6 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
237
306
  });
238
307
 
239
308
  const { reset, setValue } = userFormMethods;
240
-
241
309
  const { errors, isSubmitting, isDirty } = userFormMethods.formState;
242
310
 
243
311
  useEffect(() => {
@@ -248,58 +316,63 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
248
316
 
249
317
  useEffect(() => {
250
318
  if (isDirty) {
251
- promptBeforeClosing(() => isDirty);
319
+ setHasUnsavedChanges(true);
320
+ }
321
+ }, [isDirty, setHasUnsavedChanges]);
322
+
323
+ const setHealthWorkerValues = (response: ProfessionalRegistryResponse) => {
324
+ const { membership, contacts, identifiers, professional_details, licenses } = response.professional;
325
+ const currentLicense = pickCurrentLicense(licenses);
326
+ const gender = normalizeGender(membership.gender);
327
+
328
+ setValue('givenName', membership.first_name || '');
329
+ setValue('middleName', membership.middle_name || '');
330
+ setValue('familyName', membership.last_name || '');
331
+
332
+ if (gender) {
333
+ setValue('gender', gender, {
334
+ shouldDirty: true,
335
+ shouldTouch: true,
336
+ shouldValidate: true,
337
+ });
252
338
  }
253
- }, [isDirty, promptBeforeClosing]);
254
-
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 || '');
267
- setValue(
268
- 'licenseExpiryDate',
269
- normalized.licenseEndDate ? parseDate(normalized.licenseEndDate) : parseDate(t('unknown', 'Unknown')),
270
- );
339
+
340
+ setValue('nationalId', identifiers?.identification_number || '');
341
+ setValue('passportNumber', '');
342
+ setValue('providerLicense', currentLicense?.external_reference_id || '');
343
+ setValue('licenseBody', membership.licensing_body || '');
344
+ setValue('providerUniqueIdentifier', membership.id || '');
345
+ setValue('phoneNumber', contacts?.phone || '');
346
+ setValue('email', contacts?.email || '');
347
+ setValue('qualification', professional_details?.educational_qualifications || '');
348
+ setValue('specialty', membership.specialty || '');
349
+ setValue('providerCadre', professional_details?.professional_cadre || '');
350
+ setValue('practiceType', professional_details?.practice_type || '');
351
+ setValue('licenseExpiryDate', currentLicense?.license_end ? parseDate(currentLicense.license_end) : undefined);
271
352
  };
272
353
 
273
354
  const handleSearch = async () => {
274
355
  try {
275
- setSearchHWR({ ...searchHWR, isHWRLoading: true });
276
- const unifiedResponse = await searchHealthCareWork(
277
- searchHWR.identifierType,
278
- searchHWR.identifier,
279
- searchHWR.regulator,
280
- );
281
-
282
- const normalizedData = HealthWorkerAdapter.normalize(unifiedResponse);
356
+ setSearchHWR((prev) => ({ ...prev, isHWRLoading: true }));
357
+ const response = await searchHealthCareWork(searchHWR.identifierType, searchHWR.identifier, searchHWR.regulator);
283
358
 
284
- if (!normalizedData) {
359
+ if (!response?.professional) {
285
360
  showModal('hwr-empty-modal', { errorCode: t('noResults', 'No results found') });
286
361
  return;
287
362
  }
288
363
 
289
364
  const dispose = showModal('hwr-confirmation-modal', {
290
- healthWorker: unifiedResponse.data,
291
- normalizedData: normalizedData,
292
- fhirFormat: unifiedResponse.fhirFormat,
365
+ healthWorker: response,
293
366
  onConfirm: () => {
294
367
  dispose();
295
- setPractitionerValuesFromNormalized(normalizedData);
296
- setHealthWorker(unifiedResponse.data);
368
+ setHealthWorkerValues(response);
369
+ setHealthWorker(response);
297
370
  },
298
371
  });
299
- } catch (error) {
300
- showModal('hwr-empty-modal', { errorCode: error.message });
372
+ } catch (err: any) {
373
+ showModal('hwr-empty-modal', { errorCode: err?.message ?? t('searchFailed', 'Search failed') });
301
374
  } finally {
302
- setSearchHWR({ ...searchHWR, isHWRLoading: false });
375
+ setSearchHWR((prev) => ({ ...prev, isHWRLoading: false }));
303
376
  }
304
377
  };
305
378
 
@@ -315,35 +388,27 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
315
388
  attributeType: attributeTypeMapping.licenseExpiry,
316
389
  value: data.licenseExpiryDate ? data.licenseExpiryDate.toISOString() : '',
317
390
  },
318
- { attributeType: attributeTypeMapping.licenseBody, value: data.registrationNumber },
319
- {
320
- attributeType: attributeTypeMapping?.providerHieFhirReference,
321
- value: JSON.stringify(healthWorker),
322
- },
323
- {
324
- attributeType: attributeTypeMapping.providerUniqueIdentifier,
325
- value: data.providerUniqueIdentifier,
326
- },
327
- {
328
- attributeType: attributeTypeMapping.providerNationalId,
329
- value: data.nationalId,
330
- },
331
- {
332
- attributeType: attributeTypeMapping.qualification,
333
- value: data.qualification,
334
- },
335
- {
336
- attributeType: attributeTypeMapping.passportNumber,
337
- value: data.passportNumber,
338
- },
391
+ { attributeType: attributeTypeMapping.licenseBody, value: data.licenseBody },
392
+
393
+ { attributeType: attributeTypeMapping.providerUniqueIdentifier, value: data.providerUniqueIdentifier },
339
394
  {
340
- attributeType: attributeTypeMapping.phoneNumber,
341
- value: data.phoneNumber,
395
+ attributeType: attributeTypeMapping.externalProviderIdentifier,
396
+ value: healthWorker?.professional?.membership?.external_reference_id ?? '',
342
397
  },
343
398
  {
344
- attributeType: attributeTypeMapping.providerAddress,
345
- value: data.email,
399
+ attributeType: attributeTypeMapping.providerHieFhirReference,
400
+ value: healthWorker?.professional?.membership?.id ?? '',
346
401
  },
402
+ { attributeType: attributeTypeMapping.providerNationalId, value: data.nationalId },
403
+ { attributeType: attributeTypeMapping.passportNumber, value: data.passportNumber },
404
+
405
+ { attributeType: attributeTypeMapping.qualification, value: data.qualification },
406
+ { attributeType: attributeTypeMapping.specialty, value: data.specialty },
407
+ { attributeType: attributeTypeMapping.providerCadre, value: data.providerCadre },
408
+ { attributeType: attributeTypeMapping.practiceType, value: data.practiceType },
409
+
410
+ { attributeType: attributeTypeMapping.phoneNumber, value: data.phoneNumber },
411
+ { attributeType: attributeTypeMapping.providerAddress, value: data.email },
347
412
  ].filter((attr) => attr.value),
348
413
  };
349
414
 
@@ -388,7 +453,7 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
388
453
  if (providerResponse.ok) {
389
454
  showSnackbarMessage(t('providerSaved', 'Provider saved successfully'), '', 'success');
390
455
  }
391
- } catch (error) {
456
+ } catch {
392
457
  showSnackbarMessage(
393
458
  t('providerFail', 'Failed to save provider'),
394
459
  t('providerFailedSubtitle', 'An error occurred while creating provider'),
@@ -396,27 +461,20 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
396
461
  );
397
462
  }
398
463
  }
464
+
399
465
  if (editProvider) {
400
466
  const updatableAttributes = [
467
+ { attributeType: licenseBodyUuid, value: data?.licenseBody },
468
+ { attributeType: providerNationalIdUuid, value: data?.nationalId },
469
+ { attributeType: licenseNumberUuid, value: data?.providerLicense },
470
+ { attributeType: passportNumberUuid, value: data?.passportNumber },
471
+ { attributeType: providerUniqueIdentifierAttributeTypeUuid, value: data?.providerUniqueIdentifier },
472
+ { attributeType: specialtyUuid, value: data?.specialty },
473
+ { attributeType: providerCadreUuid, value: data?.providerCadre },
474
+ { attributeType: practiceTypeUuid, value: data?.practiceType },
401
475
  {
402
- attributeType: licenseBodyUuid,
403
- value: data?.registrationNumber,
404
- },
405
- {
406
- attributeType: providerNationalIdUuid,
407
- value: data?.nationalId,
408
- },
409
- {
410
- attributeType: licenseNumberUuid,
411
- value: data?.providerLicense,
412
- },
413
- {
414
- attributeType: passportNumberUuid,
415
- value: data?.passportNumber,
416
- },
417
- {
418
- attributeType: providerUniqueIdentifierAttributeTypeUuid,
419
- value: data?.providerUniqueIdentifier,
476
+ attributeType: externalProviderIdentifierUuid,
477
+ value: healthWorker?.professional?.membership?.external_reference_id,
420
478
  },
421
479
  ].filter((attr) => attr?.value !== undefined && attr?.value !== null && attr?.value !== '');
422
480
 
@@ -426,15 +484,15 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
426
484
  (at) => at?.attributeType?.uuid === attr?.attributeType,
427
485
  )?.uuid;
428
486
 
429
- const payload = {
487
+ const attrPayload = {
430
488
  attributeType: attr?.attributeType,
431
489
  value: attr?.value,
432
490
  };
433
491
 
434
492
  if (!existingAttributes) {
435
- return createProviderAttribute(payload, providerUUID);
493
+ return createProviderAttribute(attrPayload, providerUUID);
436
494
  }
437
- return updateProviderAttributes(payload, providerUUID, existingAttributes);
495
+ return updateProviderAttributes(attrPayload, providerUUID, existingAttributes);
438
496
  }),
439
497
  );
440
498
  showSnackbar({
@@ -446,10 +504,11 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
446
504
  } else {
447
505
  throw new Error('User creation failed');
448
506
  }
507
+
449
508
  handleMutation(`${restBaseUrl}/user`);
450
509
  mutate((key) => typeof key === 'string' && key.startsWith(`${restBaseUrl}/provider`));
451
- closeWorkspaceWithSavedChanges();
452
- } catch (error) {
510
+ closeWorkspace({ discardUnsavedChanges: true });
511
+ } catch (err) {
453
512
  showSnackbarMessage(
454
513
  t('userSaveFailed', 'Failed to save user'),
455
514
  t('userCreationFailedSubtitle', 'An error occurred while saving user form '),
@@ -458,12 +517,14 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
458
517
  }
459
518
  };
460
519
 
461
- const handleError = (error, response) => {
520
+ const handleError = (err, response) => {
462
521
  showSnackbar({
463
- title: t('userSaveFailed', 'Fail to save {{error}}', response),
464
- subtitle: t('userCreationFailedSubtitle', 'An error occurred while creating user {{errorMessage}}', {
465
- errorMessage: JSON.stringify(error, null, 2),
466
- }),
522
+ title: String(t('userSaveFailed', 'Fail to save {{error}}', response)),
523
+ subtitle: String(
524
+ t('userCreationFailedSubtitle', 'An error occurred while creating user {{errorMessage}}', {
525
+ errorMessage: JSON.stringify(err, null, 2),
526
+ }),
527
+ ),
467
528
  kind: 'error',
468
529
  isLowContrast: true,
469
530
  });
@@ -493,12 +554,9 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
493
554
  const hasProviderAccount = activeSection === SECTIONS.PROVIDER;
494
555
 
495
556
  const isSaveAndClose = () => !(hasDemographicInfo || hasLoginInfo || hasProviderAccount);
496
-
497
557
  const getSubmitButtonText = () =>
498
558
  t(isSaveAndClose() ? 'saveAndClose' : 'next', isSaveAndClose() ? 'Save & close' : 'Next');
499
-
500
559
  const getSubmitButtonType = () => (isSaveAndClose() ? 'submit' : 'button');
501
-
502
560
  const getSubmitButtonIcon = () => (isSaveAndClose() ? ChevronLeft : ChevronRight);
503
561
 
504
562
  const handleBackClick = () => {
@@ -519,364 +577,425 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
519
577
  };
520
578
 
521
579
  return (
522
- <div className={styles.leftContainer}>
523
- <div>
524
- <div className={styles.leftLayout}>
525
- <ProgressIndicator
526
- currentIndex={currentIndex}
527
- spaceEqually={true}
528
- vertical={true}
529
- className={styles.progressIndicator}
530
- onChange={(newIndex) => {
531
- if (!searchHWR.isHWRLoading) {
532
- toggleSection(steps[newIndex].id);
533
- setCurrentIndex(newIndex);
534
- }
535
- }}>
536
- {steps.map((step, index) => (
537
- <ProgressStep key={step.id} label={step.label} className={styles.ProgresStep} />
538
- ))}
539
- </ProgressIndicator>
540
- <div className={styles.sections}>
541
- <FormProvider {...userFormMethods}>
542
- <form onSubmit={userFormMethods.handleSubmit(onSubmit, handleError)} className={styles.form}>
543
- <div className={styles.formContainer}>
544
- <Stack className={styles.formStackControl} gap={7}>
545
- {hasDemographicInfo && (
546
- <ResponsiveWrapper>
547
- <span className={styles.formHeaderSection}>
548
- {t('healthWorkVerify', 'Health worker registry verification')}
549
- </span>
550
- {searchHWR.isHWRLoading ? (
551
- <InlineLoading
552
- className={styles.formLoading}
553
- description={t('pullDetailsfromHWR', 'Pulling data from Health worker registry...')}
554
- />
555
- ) : (
556
- <>
580
+ <Workspace2 title={t('manageUserWorkspace', 'Manage User Workspace')} hasUnsavedChanges={hasUnsavedChanges}>
581
+ <div className={styles.leftContainer}>
582
+ <div>
583
+ <div className={styles.leftLayout}>
584
+ <ProgressIndicator
585
+ currentIndex={currentIndex}
586
+ spaceEqually={true}
587
+ vertical={true}
588
+ className={styles.progressIndicator}
589
+ onChange={(newIndex) => {
590
+ if (!searchHWR.isHWRLoading) {
591
+ toggleSection(steps[newIndex].id);
592
+ setCurrentIndex(newIndex);
593
+ }
594
+ }}>
595
+ {steps.map((step) => (
596
+ <ProgressStep key={step.id} label={step.label} className={styles.ProgresStep} />
597
+ ))}
598
+ </ProgressIndicator>
599
+ <div className={styles.sections}>
600
+ <FormProvider {...userFormMethods}>
601
+ <form onSubmit={userFormMethods.handleSubmit(onSubmit, handleError)} className={styles.form}>
602
+ <div className={styles.formContainer}>
603
+ <Stack className={styles.formStackControl} gap={7}>
604
+ {hasDemographicInfo && (
605
+ <ResponsiveWrapper>
606
+ {isInitialValuesEmpty && (
557
607
  <>
558
- <Column>
559
- <ComboBox
560
- onChange={({ selectedItem }) => {
561
- setSearchHWR({ ...searchHWR, identifierType: selectedItem?.key ?? '' });
562
- }}
563
- id="formIdentifierType"
564
- titleText={t('identificationType', 'Identification Type')}
565
- placeholder={t('chooseIdentifierType', 'Choose identifier type')}
566
- initialSelectedItem={defaultIdentifierType}
567
- items={identifierTypes}
568
- itemToString={(item) => (item ? item.name : '')}
569
- />
570
- </Column>
571
- <Column>
572
- <ComboBox
573
- onChange={({ selectedItem }) => {
574
- setSearchHWR({ ...searchHWR, regulator: selectedItem?.key ?? '' });
575
- }}
576
- id="formRegulatorOptions"
577
- titleText={t('regulator', 'Regulator')}
578
- placeholder={t('chooseRegulatorType', 'Choose regulator option')}
579
- initialSelectedItem={defaultRegulator}
580
- items={regulatorOptions}
581
- itemToString={(item) => (item ? item.name : '')}
608
+ <span className={styles.formHeaderSection}>
609
+ {t('healthWorkVerify', 'Health worker registry verification')}
610
+ </span>
611
+
612
+ {searchHWR.isHWRLoading ? (
613
+ <InlineLoading
614
+ className={styles.formLoading}
615
+ description={t('pullDetailsfromHWR', 'Pulling data from Health worker registry...')}
582
616
  />
583
- </Column>
584
- <Column>
585
- <span className={styles.formIdentifierType}>
586
- {t('identifierNumber', 'Identifier number*')}
587
- </span>
588
- <Row className={styles.formRow}>
589
- <Search
590
- labelText={t('enterIdentifierNumber', 'Enter identifier number')}
591
- className={styles.formSearch}
592
- defaultValue={searchHWR.identifier}
593
- placeholder={t('enterIdentifierNumber', 'Enter identifier number')}
594
- id="formSearchHealthWorkers"
595
- onChange={(value) => {
596
- setSearchHWR({ ...searchHWR, identifier: value.target.value });
597
- }}
598
- />
599
- <Button
600
- kind="secondary"
601
- size="md"
602
- renderIcon={Query}
603
- disabled={
604
- !searchHWR.identifier ||
605
- searchHWR.isHWRLoading ||
606
- !searchHWR.identifierType ||
607
- !searchHWR.regulator
608
- }
609
- iconDescription={t('search', 'Search')}
610
- hasIconOnly
611
- className={styles.formSearchButton}
612
- onClick={handleSearch}
613
- />
614
- </Row>
615
- </Column>
616
- </>
617
- <span className={styles.formHeaderSection}>{t('demographicInfo', 'Demographic info')}</span>
618
- <ResponsiveWrapper>
619
- <Controller
620
- name="givenName"
621
- control={userFormMethods.control}
622
- render={({ field }) => (
623
- <TextInput
624
- {...field}
625
- id="givenName"
626
- type="text"
627
- labelText={t('givenName', 'Given Name')}
628
- placeholder={t('userGivenName', 'Enter Given Name')}
629
- invalid={!!errors.givenName}
630
- invalidText={errors.givenName?.message}
631
- />
632
- )}
633
- />
634
- </ResponsiveWrapper>
635
- <ResponsiveWrapper>
636
- <Controller
637
- name="middleName"
638
- control={userFormMethods.control}
639
- render={({ field }) => (
640
- <TextInput
641
- {...field}
642
- id="middleName"
643
- labelText={t('middleName', 'Middle Name')}
644
- placeholder={t('middleName', 'Middle Name')}
645
- />
646
- )}
647
- />
648
- </ResponsiveWrapper>
649
- <ResponsiveWrapper>
650
- <Controller
651
- name="familyName"
652
- control={userFormMethods.control}
653
- render={({ field }) => (
654
- <TextInput
655
- {...field}
656
- id="familyName"
657
- labelText={t('familyName', 'Family Name')}
658
- placeholder={t('familyName', 'Family Name')}
659
- invalid={!!errors.familyName}
660
- invalidText={errors.familyName?.message}
661
- />
662
- )}
663
- />
664
- </ResponsiveWrapper>
665
- <ResponsiveWrapper>
666
- <Controller
667
- name="phoneNumber"
668
- control={userFormMethods.control}
669
- render={({ field }) => (
670
- <TextInput
671
- {...field}
672
- id="phoneNumber"
673
- type="text"
674
- disabled={isInitialValuesEmpty}
675
- labelText={t('phoneNumber', 'Phone Number')}
676
- placeholder={t('phoneNumber', 'Enter Phone Number')}
677
- invalid={!!errors.phoneNumber}
678
- invalidText={errors.phoneNumber?.message}
679
- />
680
- )}
681
- />
682
- </ResponsiveWrapper>
683
- <ResponsiveWrapper>
684
- <Controller
685
- name="email"
686
- control={userFormMethods.control}
687
- render={({ field }) => (
688
- <TextInput
689
- {...field}
690
- id="email"
691
- type="email"
692
- disabled={isInitialValuesEmpty}
693
- labelText={t('email', 'Email')}
694
- placeholder={t('email', 'Enter Email')}
695
- invalid={!!errors.email}
696
- invalidText={errors.email?.message}
697
- className={styles.checkboxLabelSingleLine}
698
- />
699
- )}
700
- />
701
- </ResponsiveWrapper>
702
- <ResponsiveWrapper>
703
- <Controller
704
- name="gender"
705
- control={userFormMethods.control}
706
- render={({ field }) => (
707
- <RadioButtonGroup
708
- {...field}
709
- legendText={t('sex', 'Sex')}
710
- orientation="vertical"
711
- invalid={!!errors.gender}
712
- invalidText={errors.gender?.message}>
713
- <RadioButton
714
- value="M"
715
- id="M"
716
- labelText={t('male', 'Male')}
717
- checked={field.value === 'M'}
617
+ ) : (
618
+ <>
619
+ <Column>
620
+ <ComboBox
621
+ onChange={({ selectedItem }) => {
622
+ setSearchHWR({
623
+ ...searchHWR,
624
+ identifierType: selectedItem?.code ?? '',
625
+ });
626
+ }}
627
+ id="formIdentifierType"
628
+ titleText={t('identificationType', 'Identification Type')}
629
+ placeholder={t('chooseIdentifierType', 'Choose identifier type')}
630
+ items={identificationTypes}
631
+ itemToString={(item) => item?.label ?? ''}
718
632
  />
719
- <RadioButton
720
- value="F"
721
- id="F"
722
- labelText={t('female', 'Female')}
723
- checked={field.value === 'F'}
633
+ </Column>
634
+ <Column>
635
+ <ComboBox
636
+ onChange={({ selectedItem }) => {
637
+ setSearchHWR({
638
+ ...searchHWR,
639
+ regulator: selectedItem?.code ?? '',
640
+ });
641
+ }}
642
+ id="formRegulatorOptions"
643
+ titleText={t('regulator', 'Regulator')}
644
+ placeholder={t('chooseRegulatorType', 'Choose regulator option')}
645
+ items={regulators}
646
+ itemToString={(item) => item?.label ?? ''}
724
647
  />
725
- </RadioButtonGroup>
726
- )}
727
- />
728
- </ResponsiveWrapper>
729
- </>
730
- )}
731
- </ResponsiveWrapper>
732
- )}
733
- {hasProviderAccount && (
734
- <ResponsiveWrapper>
735
- <span className={styles.formHeaderSection}>{t('providerDetails', 'Provider details')}</span>
736
- <ResponsiveWrapper>
737
- <Controller
738
- name="providerUniqueIdentifier"
739
- control={userFormMethods.control}
740
- render={({ field }) => (
741
- <TextInput
742
- {...field}
743
- id="providerUniqueIdentifier"
744
- type="text"
745
- labelText={t('providerUniqueIdentifier', 'Provider Unique Identifier')}
746
- placeholder={t(
747
- 'providerUniqueIdentifierPlaceholder',
748
- 'Enter Provider Unqiue Identifier',
749
- )}
750
- invalid={!!errors.providerUniqueIdentifier}
751
- invalidText={errors.providerUniqueIdentifier?.message}
752
- />
753
- )}
754
- />
755
- </ResponsiveWrapper>
756
- <ResponsiveWrapper>
757
- <Controller
758
- name="nationalId"
759
- control={userFormMethods.control}
760
- render={({ field }) => (
761
- <TextInput
762
- {...field}
763
- id="nationalId"
764
- disabled={isInitialValuesEmpty}
765
- type="text"
766
- labelText={t('nationalID', 'National id')}
767
- placeholder={t('nationalID', 'National id')}
768
- invalid={!!errors.nationalId}
769
- invalidText={errors.nationalId?.message}
770
- className={styles.checkboxLabelSingleLine}
771
- />
772
- )}
773
- />
774
- </ResponsiveWrapper>
775
- <ResponsiveWrapper>
776
- <Controller
777
- name="passportNumber"
778
- control={userFormMethods.control}
779
- render={({ field }) => (
780
- <TextInput
781
- {...field}
782
- id="passportNumber"
783
- disabled={isInitialValuesEmpty}
784
- type="text"
785
- labelText={t('passportNumber', 'Passport number')}
786
- placeholder={t('passportNumber', 'Passport number')}
787
- invalid={!!errors.nationalId}
788
- invalidText={errors.nationalId?.message}
789
- className={styles.checkboxLabelSingleLine}
790
- />
791
- )}
792
- />
793
- </ResponsiveWrapper>
794
- <ResponsiveWrapper>
795
- <Controller
796
- name="providerLicense"
797
- control={userFormMethods.control}
798
- render={({ field }) => (
799
- <TextInput
800
- {...field}
801
- id="providerLicense"
802
- type="text"
803
- disabled={isInitialValuesEmpty}
804
- labelText={t('providerLicense', 'License Number')}
805
- placeholder={t('providerLicense', 'License Number')}
806
- className={styles.checkboxLabelSingleLine}
807
- />
808
- )}
809
- />
810
- </ResponsiveWrapper>
811
- <ResponsiveWrapper>
812
- <Controller
813
- name="registrationNumber"
814
- control={userFormMethods.control}
815
- render={({ field }) => (
816
- <TextInput
817
- {...field}
818
- id="registrationNumber"
819
- type="text"
820
- disabled={isInitialValuesEmpty}
821
- labelText={t('registrationNumber', 'Registration Number')}
822
- placeholder={t('registrationNumber', 'Registration Number')}
823
- className={styles.checkboxLabelSingleLine}
824
- />
825
- )}
826
- />
827
- </ResponsiveWrapper>
828
- <ResponsiveWrapper>
829
- <Controller
830
- name="qualification"
831
- control={userFormMethods.control}
832
- render={({ field }) => (
833
- <TextInput
834
- {...field}
835
- id="qualification"
836
- type="qualification"
837
- disabled
838
- labelText={t('qualification', 'Qualification')}
839
- placeholder={t('qualification', 'Qualification')}
840
- invalid={!!errors.qualification}
841
- invalidText={errors.qualification?.message}
842
- className={styles.checkboxLabelSingleLine}
843
- />
844
- )}
845
- />
648
+ </Column>
649
+ <Column>
650
+ <span className={styles.formIdentifierType}>
651
+ {t('identifierNumber', 'Identifier number*')}
652
+ </span>
653
+ <Row className={styles.formRow}>
654
+ <Search
655
+ labelText={t('enterIdentifierNumber', 'Enter identifier number')}
656
+ className={styles.formSearch}
657
+ defaultValue={searchHWR.identifier}
658
+ placeholder={t('enterIdentifierNumber', 'Enter identifier number')}
659
+ id="formSearchHealthWorkers"
660
+ onChange={(value) => {
661
+ setSearchHWR({ ...searchHWR, identifier: value.target.value });
662
+ }}
663
+ />
664
+ <Button
665
+ kind="secondary"
666
+ size="md"
667
+ renderIcon={Query}
668
+ disabled={
669
+ !searchHWR.identifier ||
670
+ searchHWR.isHWRLoading ||
671
+ !searchHWR.identifierType ||
672
+ !searchHWR.regulator
673
+ }
674
+ iconDescription={t('search', 'Search')}
675
+ hasIconOnly
676
+ className={styles.formSearchButton}
677
+ onClick={handleSearch}
678
+ />
679
+ </Row>
680
+ </Column>
681
+ </>
682
+ )}
683
+ </>
684
+ )}
685
+
686
+ <span className={styles.formHeaderSection}>{t('demographicInfo', 'Demographic info')}</span>
687
+ <ResponsiveWrapper>
688
+ <Controller
689
+ name="givenName"
690
+ control={userFormMethods.control}
691
+ render={({ field }) => (
692
+ <TextInput
693
+ {...field}
694
+ id="givenName"
695
+ type="text"
696
+ labelText={t('givenName', 'Given Name')}
697
+ placeholder={t('userGivenName', 'Enter Given Name')}
698
+ invalid={!!errors.givenName}
699
+ invalidText={errors.givenName?.message}
700
+ />
701
+ )}
702
+ />
703
+ </ResponsiveWrapper>
704
+ <ResponsiveWrapper>
705
+ <Controller
706
+ name="middleName"
707
+ control={userFormMethods.control}
708
+ render={({ field }) => (
709
+ <TextInput
710
+ {...field}
711
+ id="middleName"
712
+ labelText={t('middleName', 'Middle Name')}
713
+ placeholder={t('middleName', 'Middle Name')}
714
+ />
715
+ )}
716
+ />
717
+ </ResponsiveWrapper>
718
+ <ResponsiveWrapper>
719
+ <Controller
720
+ name="familyName"
721
+ control={userFormMethods.control}
722
+ render={({ field }) => (
723
+ <TextInput
724
+ {...field}
725
+ id="familyName"
726
+ labelText={t('familyName', 'Family Name')}
727
+ placeholder={t('familyName', 'Family Name')}
728
+ invalid={!!errors.familyName}
729
+ invalidText={errors.familyName?.message}
730
+ />
731
+ )}
732
+ />
733
+ </ResponsiveWrapper>
734
+ <ResponsiveWrapper>
735
+ <Controller
736
+ name="phoneNumber"
737
+ control={userFormMethods.control}
738
+ render={({ field }) => (
739
+ <TextInput
740
+ {...field}
741
+ id="phoneNumber"
742
+ type="text"
743
+ disabled={isInitialValuesEmpty}
744
+ labelText={t('phoneNumber', 'Phone Number')}
745
+ placeholder={t('phoneNumber', 'Enter Phone Number')}
746
+ invalid={!!errors.phoneNumber}
747
+ invalidText={errors.phoneNumber?.message}
748
+ />
749
+ )}
750
+ />
751
+ </ResponsiveWrapper>
752
+ <ResponsiveWrapper>
753
+ <Controller
754
+ name="email"
755
+ control={userFormMethods.control}
756
+ render={({ field }) => (
757
+ <TextInput
758
+ {...field}
759
+ id="email"
760
+ type="email"
761
+ disabled={isInitialValuesEmpty}
762
+ labelText={t('email', 'Email')}
763
+ placeholder={t('email', 'Enter Email')}
764
+ invalid={!!errors.email}
765
+ invalidText={errors.email?.message}
766
+ className={styles.checkboxLabelSingleLine}
767
+ />
768
+ )}
769
+ />
770
+ </ResponsiveWrapper>
771
+ <ResponsiveWrapper>
772
+ <Controller
773
+ name="gender"
774
+ control={userFormMethods.control}
775
+ render={({ field: { onChange, value } }) => (
776
+ <RadioButtonGroup
777
+ legendText={t('sex', 'Sex')}
778
+ orientation="vertical"
779
+ name="gender"
780
+ valueSelected={value ?? ''}
781
+ key={value ?? 'empty'}
782
+ onChange={(selected) => onChange(selected)}
783
+ invalid={!!errors.gender}
784
+ invalidText={errors.gender?.message}>
785
+ <RadioButton value="M" id="genderMale" labelText={t('male', 'Male')} />
786
+ <RadioButton value="F" id="genderFemale" labelText={t('female', 'Female')} />
787
+ </RadioButtonGroup>
788
+ )}
789
+ />
790
+ </ResponsiveWrapper>
846
791
  </ResponsiveWrapper>
792
+ )}
847
793
 
794
+ {hasProviderAccount && (
848
795
  <ResponsiveWrapper>
849
- <Controller
850
- name="licenseExpiryDate"
851
- control={userFormMethods.control}
852
- render={({ field }) => (
853
- <DatePicker
854
- datePickerType="single"
855
- className={styles.formDatePicker}
856
- onChange={(event) => {
857
- if (event.length) {
858
- field.onChange(event[0]);
859
- }
860
- }}
861
- value={field.value ? new Date(field.value) : ''}>
862
- <DatePickerInput
863
- className={styles.formDatePicker}
864
- placeholder="mm/dd/yyyy"
865
- labelText={t('licenseExpiryDate', 'License Expiry Date')}
866
- id="formLicenseDatePicker"
867
- size="md"
796
+ <span className={styles.formHeaderSection}>{t('providerDetails', 'Provider details')}</span>
797
+
798
+ <ResponsiveWrapper>
799
+ <Controller
800
+ name="providerUniqueIdentifier"
801
+ control={userFormMethods.control}
802
+ render={({ field }) => (
803
+ <TextInput
804
+ {...field}
805
+ id="providerUniqueIdentifier"
806
+ type="text"
868
807
  disabled
869
- invalid={!!errors.licenseExpiryDate}
870
- invalidText={errors.licenseExpiryDate?.message}
808
+ labelText={t('providerUniqueIdentifier', 'Provider Unique Identifier')}
809
+ placeholder={t('providerUniqueIdentifierPlaceholder', 'e.g. PUID-0002011-6')}
810
+ invalid={!!errors.providerUniqueIdentifier}
811
+ invalidText={errors.providerUniqueIdentifier?.message}
871
812
  />
872
- </DatePicker>
873
- )}
874
- />
875
- </ResponsiveWrapper>
876
- {loadingProvider || providerError ? (
877
- <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />
878
- ) : provider.length > 0 ? (
879
- <>
813
+ )}
814
+ />
815
+ </ResponsiveWrapper>
816
+
817
+ <ResponsiveWrapper>
818
+ <Controller
819
+ name="nationalId"
820
+ control={userFormMethods.control}
821
+ render={({ field }) => (
822
+ <TextInput
823
+ {...field}
824
+ id="nationalId"
825
+ disabled={isInitialValuesEmpty}
826
+ type="text"
827
+ labelText={t('nationalID', 'National ID')}
828
+ placeholder={t('nationalID', 'National ID')}
829
+ invalid={!!errors.nationalId}
830
+ invalidText={errors.nationalId?.message}
831
+ className={styles.checkboxLabelSingleLine}
832
+ />
833
+ )}
834
+ />
835
+ </ResponsiveWrapper>
836
+
837
+ <ResponsiveWrapper>
838
+ <Controller
839
+ name="passportNumber"
840
+ control={userFormMethods.control}
841
+ render={({ field }) => (
842
+ <TextInput
843
+ {...field}
844
+ id="passportNumber"
845
+ disabled={isInitialValuesEmpty}
846
+ type="text"
847
+ labelText={t('passportNumber', 'Passport number')}
848
+ placeholder={t('passportNumber', 'Passport number')}
849
+ invalid={!!errors.passportNumber}
850
+ invalidText={errors.passportNumber?.message}
851
+ className={styles.checkboxLabelSingleLine}
852
+ />
853
+ )}
854
+ />
855
+ </ResponsiveWrapper>
856
+
857
+ <ResponsiveWrapper>
858
+ <Controller
859
+ name="providerLicense"
860
+ control={userFormMethods.control}
861
+ render={({ field }) => (
862
+ <TextInput
863
+ {...field}
864
+ id="providerLicense"
865
+ type="text"
866
+ disabled
867
+ labelText={t('providerLicense', 'License Number')}
868
+ placeholder={t('providerLicensePlaceholder', 'e.g. GP/2026/638333')}
869
+ className={styles.checkboxLabelSingleLine}
870
+ />
871
+ )}
872
+ />
873
+ </ResponsiveWrapper>
874
+
875
+ <ResponsiveWrapper>
876
+ <Controller
877
+ name="licenseBody"
878
+ control={userFormMethods.control}
879
+ render={({ field }) => (
880
+ <TextInput
881
+ {...field}
882
+ id="licenseBody"
883
+ type="text"
884
+ disabled
885
+ labelText={t('licenseBody', 'License body')}
886
+ placeholder={t('licenseBodyPlaceholder', 'e.g. KMPDC')}
887
+ className={styles.checkboxLabelSingleLine}
888
+ />
889
+ )}
890
+ />
891
+ </ResponsiveWrapper>
892
+
893
+ <ResponsiveWrapper>
894
+ <Controller
895
+ name="qualification"
896
+ control={userFormMethods.control}
897
+ render={({ field }) => (
898
+ <TextInput
899
+ {...field}
900
+ id="qualification"
901
+ type="text"
902
+ disabled
903
+ labelText={t('qualification', 'Qualification')}
904
+ placeholder={t('qualificationPlaceholder', 'e.g. MBChB(UON)2009')}
905
+ invalid={!!errors.qualification}
906
+ invalidText={errors.qualification?.message}
907
+ className={styles.checkboxLabelSingleLine}
908
+ />
909
+ )}
910
+ />
911
+ </ResponsiveWrapper>
912
+
913
+ <ResponsiveWrapper>
914
+ <Controller
915
+ name="specialty"
916
+ control={userFormMethods.control}
917
+ render={({ field }) => (
918
+ <TextInput
919
+ {...field}
920
+ id="specialty"
921
+ type="text"
922
+ disabled
923
+ labelText={t('specialty', 'Specialty')}
924
+ placeholder={t('specialtyPlaceholder', 'e.g. MEDICAL DOCTOR')}
925
+ className={styles.checkboxLabelSingleLine}
926
+ />
927
+ )}
928
+ />
929
+ </ResponsiveWrapper>
930
+
931
+ <ResponsiveWrapper>
932
+ <Controller
933
+ name="providerCadre"
934
+ control={userFormMethods.control}
935
+ render={({ field }) => (
936
+ <TextInput
937
+ {...field}
938
+ id="providerCadre"
939
+ type="text"
940
+ disabled
941
+ labelText={t('providerCadre', 'Provider cadre')}
942
+ placeholder={t('providerCadrePlaceholder', 'e.g. MEDICINE')}
943
+ className={styles.checkboxLabelSingleLine}
944
+ />
945
+ )}
946
+ />
947
+ </ResponsiveWrapper>
948
+
949
+ <ResponsiveWrapper>
950
+ <Controller
951
+ name="practiceType"
952
+ control={userFormMethods.control}
953
+ render={({ field }) => (
954
+ <TextInput
955
+ {...field}
956
+ id="practiceType"
957
+ type="text"
958
+ disabled
959
+ labelText={t('practiceType', 'Practice type')}
960
+ placeholder={t('practiceTypePlaceholder', 'e.g. Clinical Practice')}
961
+ className={styles.checkboxLabelSingleLine}
962
+ />
963
+ )}
964
+ />
965
+ </ResponsiveWrapper>
966
+
967
+ <ResponsiveWrapper>
968
+ <Controller
969
+ name="licenseExpiryDate"
970
+ control={userFormMethods.control}
971
+ render={({ field }) => (
972
+ <DatePicker
973
+ datePickerType="single"
974
+ className={styles.formDatePicker}
975
+ onChange={(event) => {
976
+ if (event.length) {
977
+ field.onChange(event[0]);
978
+ }
979
+ }}
980
+ value={field.value ? new Date(field.value) : ''}>
981
+ <DatePickerInput
982
+ className={styles.formDatePicker}
983
+ placeholder="mm/dd/yyyy"
984
+ labelText={t('licenseExpiryDate', 'License Expiry Date')}
985
+ id="formLicenseDatePicker"
986
+ size="md"
987
+ disabled
988
+ invalid={!!errors.licenseExpiryDate}
989
+ invalidText={errors.licenseExpiryDate?.message}
990
+ />
991
+ </DatePicker>
992
+ )}
993
+ />
994
+ </ResponsiveWrapper>
995
+
996
+ {loadingProvider || providerError ? (
997
+ <InlineLoading status="active" iconDescription="Loading" description="Loading data..." />
998
+ ) : provider.length > 0 ? (
880
999
  <ResponsiveWrapper>
881
1000
  <Controller
882
1001
  name="systemId"
@@ -912,9 +1031,7 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
912
1031
  )}
913
1032
  />
914
1033
  </ResponsiveWrapper>
915
- </>
916
- ) : (
917
- <>
1034
+ ) : (
918
1035
  <Controller
919
1036
  name="providerIdentifiers"
920
1037
  control={userFormMethods.control}
@@ -932,208 +1049,204 @@ const ManageUserWorkspace: React.FC<ManageUserWorkspaceProps> = ({
932
1049
  </CheckboxGroup>
933
1050
  )}
934
1051
  />
935
- </>
936
- )}
937
- </ResponsiveWrapper>
938
- )}
939
- {hasLoginInfo && (
940
- <ResponsiveWrapper>
941
- <span className={styles.formHeaderSection}>{t('loginInfo', 'Login Info')}</span>
942
- <ResponsiveWrapper>
943
- <Controller
944
- name="username"
945
- control={userFormMethods.control}
946
- render={({ field }) => (
947
- <TextInput
948
- {...field}
949
- id="username"
950
- labelText={t('username', 'Username')}
951
- invalid={!!errors.username}
952
- invalidText={errors.username?.message}
953
- />
954
- )}
955
- />
1052
+ )}
956
1053
  </ResponsiveWrapper>
1054
+ )}
957
1055
 
1056
+ {hasLoginInfo && (
958
1057
  <ResponsiveWrapper>
959
- <Controller
960
- name="password"
961
- control={userFormMethods.control}
962
- rules={
963
- isInitialValuesEmpty
964
- ? {
965
- required: 'Password is required',
966
- minLength: { value: 8, message: 'Password must be at least 8 characters long' },
967
- pattern: {
968
- value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
969
- message: 'Password must include uppercase, lowercase, and a number',
970
- },
971
- }
972
- : {}
973
- }
974
- render={({ field }) => (
975
- <PasswordInput
976
- {...field}
977
- id="password"
978
- labelText="Password"
979
- invalid={!!errors.password}
980
- invalidText={errors.password?.message}
981
- />
982
- )}
983
- />
984
- </ResponsiveWrapper>
985
- <ResponsiveWrapper>
986
- <Controller
987
- name="confirmPassword"
988
- control={userFormMethods.control}
989
- rules={
990
- isInitialValuesEmpty
991
- ? {
992
- required: 'Please confirm your password',
993
- validate: (value) =>
994
- value === userFormMethods.watch('password') || 'Passwords do not match',
995
- }
996
- : {}
997
- }
998
- render={({ field }) => (
999
- <PasswordInput
1000
- {...field}
1001
- id="confirmPassword"
1002
- labelText="Confirm Password"
1003
- invalid={!!errors.confirmPassword}
1004
- invalidText={errors.confirmPassword?.message}
1005
- />
1006
- )}
1007
- />
1008
- </ResponsiveWrapper>
1009
- <ResponsiveWrapper>
1010
- <Controller
1011
- name="forcePasswordChange"
1012
- control={userFormMethods.control}
1013
- render={({ field }) => (
1014
- <CheckboxGroup
1015
- legendText={t('forcePasswordChange', 'Force Password Change')}
1016
- className={styles.checkboxGroupGrid}>
1017
- <Checkbox
1018
- className={styles.multilineCheckboxLabel}
1019
- id="forcePasswordChange"
1020
- labelText={t(
1021
- 'forcePasswordChangeHelper',
1022
- 'Optionally require this user to change their password on next login',
1023
- )}
1024
- checked={!!field.value || false}
1025
- onChange={(e) => field.onChange(e.target.checked)}
1058
+ <span className={styles.formHeaderSection}>{t('loginInfo', 'Login Info')}</span>
1059
+ <ResponsiveWrapper>
1060
+ <Controller
1061
+ name="username"
1062
+ control={userFormMethods.control}
1063
+ render={({ field }) => (
1064
+ <TextInput
1065
+ {...field}
1066
+ id="username"
1067
+ labelText={t('username', 'Username')}
1068
+ invalid={!!errors.username}
1069
+ invalidText={errors.username?.message}
1026
1070
  />
1027
- </CheckboxGroup>
1028
- )}
1029
- />
1071
+ )}
1072
+ />
1073
+ </ResponsiveWrapper>
1074
+ <ResponsiveWrapper>
1075
+ <Controller
1076
+ name="password"
1077
+ control={userFormMethods.control}
1078
+ rules={
1079
+ isInitialValuesEmpty
1080
+ ? {
1081
+ required: 'Password is required',
1082
+ minLength: { value: 8, message: 'Password must be at least 8 characters long' },
1083
+ pattern: {
1084
+ value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
1085
+ message: 'Password must include uppercase, lowercase, and a number',
1086
+ },
1087
+ }
1088
+ : {}
1089
+ }
1090
+ render={({ field }) => (
1091
+ <PasswordInput
1092
+ {...field}
1093
+ id="password"
1094
+ labelText="Password"
1095
+ invalid={!!errors.password}
1096
+ invalidText={errors.password?.message}
1097
+ />
1098
+ )}
1099
+ />
1100
+ </ResponsiveWrapper>
1101
+ <ResponsiveWrapper>
1102
+ <Controller
1103
+ name="confirmPassword"
1104
+ control={userFormMethods.control}
1105
+ rules={
1106
+ isInitialValuesEmpty
1107
+ ? {
1108
+ required: 'Please confirm your password',
1109
+ validate: (value) =>
1110
+ value === userFormMethods.watch('password') || 'Passwords do not match',
1111
+ }
1112
+ : {}
1113
+ }
1114
+ render={({ field }) => (
1115
+ <PasswordInput
1116
+ {...field}
1117
+ id="confirmPassword"
1118
+ labelText="Confirm Password"
1119
+ invalid={!!errors.confirmPassword}
1120
+ invalidText={errors.confirmPassword?.message}
1121
+ />
1122
+ )}
1123
+ />
1124
+ </ResponsiveWrapper>
1125
+ <ResponsiveWrapper>
1126
+ <Controller
1127
+ name="forcePasswordChange"
1128
+ control={userFormMethods.control}
1129
+ render={({ field }) => (
1130
+ <CheckboxGroup
1131
+ legendText={t('forcePasswordChange', 'Force Password Change')}
1132
+ className={styles.checkboxGroupGrid}>
1133
+ <Checkbox
1134
+ className={styles.multilineCheckboxLabel}
1135
+ id="forcePasswordChange"
1136
+ labelText={t(
1137
+ 'forcePasswordChangeHelper',
1138
+ 'Optionally require this user to change their password on next login',
1139
+ )}
1140
+ checked={!!field.value || false}
1141
+ onChange={(e) => field.onChange(e.target.checked)}
1142
+ />
1143
+ </CheckboxGroup>
1144
+ )}
1145
+ />
1146
+ </ResponsiveWrapper>
1030
1147
  </ResponsiveWrapper>
1031
- </ResponsiveWrapper>
1032
- )}
1148
+ )}
1033
1149
 
1034
- {hasRoles && (
1035
- <ResponsiveWrapper>
1036
- <span className={styles.formHeaderSection}>{t('rolesInfo', 'Roles Info')}</span>
1150
+ {hasRoles && (
1037
1151
  <ResponsiveWrapper>
1038
- {filterRolesConfig(rolesConfig).map((category) => (
1039
- <Column key={category.category} xsm={8} md={12} lg={12} className={styles.checkBoxColumn}>
1040
- <CheckboxGroup legendText={category.category} className={styles.checkboxGroupGrid}>
1041
- {isLoading ? (
1042
- <InlineLoading
1043
- status="active"
1044
- iconDescription="Loading"
1045
- description="Loading data..."
1046
- />
1047
- ) : (
1048
- <Controller
1049
- name="roles"
1050
- control={userFormMethods.control}
1051
- render={({ field }) => {
1052
- const selectedRoles = field.value || [];
1053
-
1054
- return (
1055
- <>
1056
- {roles
1057
- .filter((role) => category.roles.includes(role.name))
1058
- .map((role) => {
1059
- const isSelected = selectedRoles.some(
1060
- (r) =>
1061
- r.display === role.display &&
1062
- r.description === role.description &&
1063
- r.uuid === role.uuid,
1064
- );
1065
-
1066
- return (
1067
- <label
1068
- key={role.display}
1069
- className={
1070
- isSelected ? styles.checkboxLabelSelected : styles.checkboxLabel
1071
- }>
1072
- <input
1073
- type="checkbox"
1074
- id={role.display}
1075
- checked={isSelected}
1076
- onChange={(e) => {
1077
- const updatedValue = e.target.checked
1078
- ? [
1079
- ...selectedRoles,
1080
- {
1081
- uuid: role.uuid,
1082
- display: role.display,
1083
- description: role.description ?? null,
1084
- },
1085
- ]
1086
- : selectedRoles.filter(
1087
- (selectedRole) => selectedRole.display !== role.display,
1088
- );
1089
-
1090
- field.onChange(updatedValue);
1091
- }}
1092
- />
1093
- {role.display}
1094
- </label>
1095
- );
1096
- })}
1097
- </>
1098
- );
1099
- }}
1100
- />
1101
- )}
1102
- </CheckboxGroup>
1103
- </Column>
1104
- ))}
1152
+ <span className={styles.formHeaderSection}>{t('rolesInfo', 'Roles Info')}</span>
1153
+ <ResponsiveWrapper>
1154
+ {filterRolesConfig(rolesConfig).map((category) => (
1155
+ <Column key={category.category} xsm={8} md={12} lg={12} className={styles.checkBoxColumn}>
1156
+ <CheckboxGroup legendText={category.category} className={styles.checkboxGroupGrid}>
1157
+ {isLoading ? (
1158
+ <InlineLoading
1159
+ status="active"
1160
+ iconDescription="Loading"
1161
+ description="Loading data..."
1162
+ />
1163
+ ) : (
1164
+ <Controller
1165
+ name="roles"
1166
+ control={userFormMethods.control}
1167
+ render={({ field }) => {
1168
+ const selectedRoles = field.value || [];
1169
+ return (
1170
+ <>
1171
+ {roles
1172
+ .filter((role) => category.roles.includes(role.name))
1173
+ .map((role) => {
1174
+ const isSelected = selectedRoles.some(
1175
+ (r) =>
1176
+ r.display === role.display &&
1177
+ r.description === role.description &&
1178
+ r.uuid === role.uuid,
1179
+ );
1180
+ return (
1181
+ <label
1182
+ key={role.display}
1183
+ className={
1184
+ isSelected ? styles.checkboxLabelSelected : styles.checkboxLabel
1185
+ }>
1186
+ <input
1187
+ type="checkbox"
1188
+ id={role.display}
1189
+ checked={isSelected}
1190
+ onChange={(e) => {
1191
+ const updatedValue = e.target.checked
1192
+ ? [
1193
+ ...selectedRoles,
1194
+ {
1195
+ uuid: role.uuid,
1196
+ display: role.display,
1197
+ description: role.description ?? null,
1198
+ },
1199
+ ]
1200
+ : selectedRoles.filter(
1201
+ (selectedRole) => selectedRole.display !== role.display,
1202
+ );
1203
+ field.onChange(updatedValue);
1204
+ }}
1205
+ />
1206
+ {role.display}
1207
+ </label>
1208
+ );
1209
+ })}
1210
+ </>
1211
+ );
1212
+ }}
1213
+ />
1214
+ )}
1215
+ </CheckboxGroup>
1216
+ </Column>
1217
+ ))}
1218
+ </ResponsiveWrapper>
1105
1219
  </ResponsiveWrapper>
1106
- </ResponsiveWrapper>
1107
- )}
1108
- </Stack>
1109
- </div>
1110
- <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
1111
- <Button kind="secondary" onClick={handleBackClick} className={styles.btn}>
1112
- {t(hasDemographicInfo ? 'cancel' : 'back', hasDemographicInfo ? 'Cancel' : 'Back')}
1113
- </Button>
1114
-
1115
- <Button
1116
- kind="primary"
1117
- type={getSubmitButtonType()}
1118
- disabled={isSubmitting || Object.keys(errors).length > 0 || searchHWR.isHWRLoading}
1119
- renderIcon={getSubmitButtonIcon()}
1120
- className={styles.btn}
1121
- onClick={handleNextClick}>
1122
- {isSubmitting ? (
1123
- <span style={{ display: 'flex', alignItems: 'center' }}>
1124
- {t('submitting', 'Submitting...')} <InlineLoading status="active" />
1125
- </span>
1126
- ) : (
1127
- getSubmitButtonText()
1128
- )}
1129
- </Button>
1130
- </ButtonSet>
1131
- </form>
1132
- </FormProvider>
1220
+ )}
1221
+ </Stack>
1222
+ </div>
1223
+ <ButtonSet className={classNames({ [styles.tablet]: isTablet, [styles.desktop]: !isTablet })}>
1224
+ <Button kind="secondary" onClick={handleBackClick} className={styles.btn}>
1225
+ {t(hasDemographicInfo ? 'cancel' : 'back', hasDemographicInfo ? 'Cancel' : 'Back')}
1226
+ </Button>
1227
+ <Button
1228
+ kind="primary"
1229
+ type={getSubmitButtonType()}
1230
+ disabled={isSubmitting || Object.keys(errors).length > 0 || searchHWR.isHWRLoading}
1231
+ renderIcon={getSubmitButtonIcon()}
1232
+ className={styles.btn}
1233
+ onClick={handleNextClick}>
1234
+ {isSubmitting ? (
1235
+ <span style={{ display: 'flex', alignItems: 'center' }}>
1236
+ {t('submitting', 'Submitting...')} <InlineLoading status="active" />
1237
+ </span>
1238
+ ) : (
1239
+ getSubmitButtonText()
1240
+ )}
1241
+ </Button>
1242
+ </ButtonSet>
1243
+ </form>
1244
+ </FormProvider>
1245
+ </div>
1133
1246
  </div>
1134
1247
  </div>
1135
1248
  </div>
1136
- </div>
1249
+ </Workspace2>
1137
1250
  );
1138
1251
  };
1139
1252