@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
@@ -1,75 +1,203 @@
1
+ @use '@carbon/colors';
1
2
  @use '@carbon/layout';
2
3
  @use '@carbon/type';
3
- @use '@carbon/colors';
4
4
  @use '@openmrs/esm-styleguide/src/vars' as *;
5
5
 
6
- .patientName {
7
- @include type.type-style('label-02');
8
- color: colors.$gray-100;
9
- font-weight: 800;
10
- margin-left: layout.$spacing-04;
11
- margin-right: layout.$spacing-01;
12
- }
13
- .middot {
14
- margin: 0 layout.$spacing-01;
15
- }
16
- .spanField {
17
- margin-left: layout.$spacing-04;
18
- margin-right: layout.$spacing-01;
6
+ .providerDetailsContainer {
7
+ width: 100%;
8
+ background-color: colors.$gray-10;
9
+ padding: layout.$spacing-04;
19
10
  }
20
11
 
21
- .gender {
22
- display: flex;
23
- align-items: center;
24
- color: colors.$gray-70;
25
- gap: layout.$spacing-01;
26
- margin-left: layout.$spacing-02;
27
- }
28
- .statusTag {
12
+ .patientBanner {
29
13
  display: flex;
30
- align-items: center;
31
- color: colors.$gray-70;
32
- gap: layout.$spacing-01;
33
- margin-left: layout.$spacing-02;
14
+ gap: layout.$spacing-05;
15
+ padding: layout.$spacing-05 layout.$spacing-06;
16
+ background-color: colors.$white;
17
+ border-radius: 6px;
18
+ border: 1px solid colors.$gray-20;
34
19
  }
35
- .demographics {
20
+
21
+ .patientAvatar {
22
+ flex-shrink: 0;
23
+ width: 4rem;
24
+ height: 4rem;
36
25
  display: flex;
37
26
  align-items: center;
38
- margin-bottom: layout.$spacing-02;
39
- color: $text-02;
40
- text-transform: uppercase;
27
+ justify-content: center;
28
+ background-color: colors.$gray-10;
29
+ overflow: hidden;
30
+
31
+ :global(img) {
32
+ width: 100%;
33
+ height: 100%;
34
+ object-fit: cover;
35
+ }
41
36
  }
42
37
 
43
- .patientBanner {
38
+ .patientInfo {
44
39
  display: flex;
40
+ flex-direction: column;
41
+ flex: 1;
42
+ min-width: 0;
43
+ gap: layout.$spacing-04;
45
44
  }
46
45
 
47
- .patientInfo {
46
+ .row {
48
47
  display: flex;
49
48
  flex-direction: column;
50
- width: 100%;
49
+ }
50
+
51
+ .patientNameRow {
52
+ align-items: flex-start;
51
53
  }
52
54
 
53
55
  .flexRow {
54
56
  display: flex;
55
57
  flex-flow: row wrap;
56
58
  align-items: center;
57
- margin-top: layout.$spacing-04;
59
+ gap: layout.$spacing-03;
60
+ row-gap: layout.$spacing-02;
58
61
  }
59
- .patientAvatar {
60
- margin-top: layout.$spacing-06;
62
+
63
+ .tagRow {
64
+ display: inline-flex;
65
+ align-items: center;
66
+ flex-wrap: wrap;
67
+ gap: layout.$spacing-02;
68
+ }
69
+
70
+ .patientName {
71
+ @include type.type-style('heading-03');
72
+ color: colors.$gray-100;
73
+ font-weight: 600;
74
+ margin: 0;
75
+ }
76
+
77
+ .gender {
78
+ @include type.type-style('body-01');
79
+ color: colors.$gray-70;
80
+ }
81
+
82
+ .statusTag {
83
+ display: inline-flex;
84
+ align-items: center;
85
+ }
86
+
87
+ .puidBadge {
88
+ @include type.type-style('label-02');
89
+ display: inline-block;
90
+ background-color: colors.$gray-10;
91
+ color: colors.$gray-90;
92
+ padding: layout.$spacing-02 layout.$spacing-03;
93
+ border-radius: 4px;
94
+ border: 1px solid colors.$gray-20;
95
+ align-self: flex-start;
96
+
97
+ strong {
98
+ color: colors.$gray-100;
99
+ font-weight: 600;
100
+ }
101
+ }
102
+
103
+ .spanField {
104
+ @include type.type-style('body-01');
105
+ display: inline-flex;
106
+ align-items: center;
107
+ gap: layout.$spacing-01;
108
+ color: colors.$gray-90;
109
+ }
110
+
111
+ .fieldLabel {
112
+ color: colors.$gray-70;
113
+ font-weight: 400;
114
+ }
115
+
116
+ .fieldValue {
117
+ color: colors.$gray-100;
118
+ font-weight: 500;
119
+ }
120
+
121
+ .middot {
122
+ color: colors.$gray-50;
123
+ user-select: none;
61
124
  }
125
+
126
+ .demographics {
127
+ display: flex;
128
+ align-items: center;
129
+ margin-bottom: layout.$spacing-02;
130
+ color: $text-02;
131
+ text-transform: uppercase;
132
+ }
133
+
62
134
  .viewRoles {
63
- margin-top: layout.$spacing-06;
135
+ margin-top: layout.$spacing-04;
136
+ padding-top: layout.$spacing-04;
137
+ border-top: 1px solid colors.$gray-20;
138
+
139
+ :global(.cds--accordion) {
140
+ border-top: none;
141
+ }
142
+
143
+ :global(.cds--accordion__heading) {
144
+ @include type.type-style('label-02');
145
+ padding: layout.$spacing-03 0;
146
+ }
147
+
148
+ :global(.cds--accordion__title) {
149
+ color: colors.$gray-100;
150
+ font-weight: 600;
151
+ }
64
152
  }
153
+
65
154
  .roleContainer {
66
155
  display: flex;
67
156
  flex-direction: column;
68
- gap: 0.5rem;
157
+ gap: layout.$spacing-02;
158
+ padding: layout.$spacing-03 0;
159
+ }
160
+
161
+ .roleName {
162
+ color: colors.$gray-100;
163
+ font-weight: 600;
69
164
  }
70
165
 
71
166
  .roleDescription {
167
+ @include type.type-style('body-01');
72
168
  margin: 0;
73
169
  color: colors.$gray-70;
74
- font-size: type.type-scale(1);
170
+ font-size: 0.875rem;
171
+ line-height: 1.4;
172
+ }
173
+
174
+ @media (max-width: 768px) {
175
+ .patientBanner {
176
+ flex-direction: column;
177
+ gap: layout.$spacing-04;
178
+ padding: layout.$spacing-04;
179
+ }
180
+
181
+ .patientAvatar {
182
+ width: 3rem;
183
+ height: 3rem;
184
+ }
185
+
186
+ .patientName {
187
+ @include type.type-style('heading-02');
188
+ }
189
+
190
+ .flexRow {
191
+ flex-direction: column;
192
+ align-items: flex-start;
193
+ gap: layout.$spacing-02;
194
+ }
195
+
196
+ .middot {
197
+ display: none;
198
+ }
199
+
200
+ .spanField {
201
+ width: 100%;
202
+ }
75
203
  }
@@ -1,176 +1,184 @@
1
- import React, { useRef } from 'react';
2
- import styles from './user-detail.scss';
1
+ import React, { useMemo, useRef } from 'react';
3
2
  import { useTranslation } from 'react-i18next';
4
- import { Tag, Accordion, AccordionItem, ContainedList, ContainedListItem } from '@carbon/react';
5
- import { PatientPhoto } from '@openmrs/esm-framework';
3
+ import { Tag, Accordion, AccordionItem, ContainedListItem } from '@carbon/react';
4
+ import { PatientPhoto, useConfig } from '@openmrs/esm-framework';
6
5
  import dayjs from 'dayjs';
7
6
  import classNames from 'classnames';
8
7
  import capitalize from 'lodash/capitalize';
8
+
9
+ import styles from './user-detail.scss';
9
10
  import { type ProviderResponse, type UserResponse } from '../../../../types';
11
+ import { type ConfigObject } from '../../../../config-schema';
10
12
 
11
13
  interface UserDetailsProps {
12
14
  provider: ProviderResponse;
13
15
  user: UserResponse;
14
16
  }
15
17
 
16
- interface ProviderAttribute {
17
- value: string;
18
- }
19
-
20
- interface ProviderAttributes {
21
- licenseAttr?: ProviderAttribute;
22
- nationalID?: ProviderAttribute;
23
- dateAttr?: ProviderAttribute;
24
- phoneNumber?: ProviderAttribute;
25
- qualification?: ProviderAttribute;
26
- registrationNumber?: ProviderAttribute;
27
- emailAddress?: ProviderAttribute;
28
- passportNumber: ProviderAttribute;
29
- providerUniqueIdentifier: ProviderAttribute;
30
- }
31
-
32
18
  const UserDetails: React.FC<UserDetailsProps> = ({ provider, user }) => {
33
19
  const { t } = useTranslation();
34
- const patientBannerRef = useRef(null);
35
-
36
- const attributeMap = {
37
- licenseAttr: 'Practising License Number',
38
- nationalID: 'Provider National Id Number',
39
- dateAttr: 'License Expiry Date',
40
- phoneNumber: 'Provider Telephone',
41
- qualification: 'Provider Qualification',
42
- registrationNumber: 'License Body',
43
- emailAddress: 'Provider Address',
44
- passportNumber: 'Provider passport number',
45
- providerUniqueIdentifier: 'Provider unique identifier',
46
- };
47
-
48
- const attributes: ProviderAttributes = Object.entries(attributeMap).reduce((acc, [key, display]) => {
49
- const attr = provider?.attributes?.find((attr) => attr.attributeType.display === display);
50
- if (attr) {
51
- acc[key as keyof ProviderAttributes] = { value: attr.value };
52
- }
53
- return acc;
54
- }, {} as ProviderAttributes);
20
+ const bannerRef = useRef<HTMLElement>(null);
55
21
 
56
22
  const {
57
- licenseAttr,
58
- nationalID,
59
- dateAttr,
60
- phoneNumber,
61
- qualification,
62
- registrationNumber,
63
- emailAddress,
64
- passportNumber,
65
- providerUniqueIdentifier,
66
- } = attributes;
67
-
68
- const formattedExpiryDate = dateAttr?.value ? dayjs(dateAttr.value).format('YYYY-MM-DD') : null;
23
+ licenseNumberUuid,
24
+ licenseExpiryDateUuid,
25
+ licenseBodyUuid,
26
+ qualificationUuid,
27
+ specialtyUuid,
28
+ providerCadreUuid,
29
+ practiceTypeUuid,
30
+ providerNationalIdUuid,
31
+ passportNumberUuid,
32
+ providerUniqueIdentifierAttributeTypeUuid,
33
+ externalProviderIdentifierUuid,
34
+ phoneNumberUuid,
35
+ providerAddressUuid,
36
+ } = useConfig<ConfigObject>();
37
+
38
+ const attrByUuid = useMemo(() => {
39
+ const map: Record<string, string> = {};
40
+ provider?.attributes?.forEach((a) => {
41
+ const uuid = a?.attributeType?.uuid;
42
+ if (uuid && a.value != null) {
43
+ map[uuid] = typeof a.value === 'string' ? a.value : String(a.value);
44
+ }
45
+ });
46
+ return map;
47
+ }, [provider]);
48
+
49
+ const licenseNumber = attrByUuid[licenseNumberUuid];
50
+ const licenseExpiryRaw = attrByUuid[licenseExpiryDateUuid];
51
+ const licenseBody = attrByUuid[licenseBodyUuid];
52
+ const qualification = attrByUuid[qualificationUuid];
53
+ const specialty = attrByUuid[specialtyUuid];
54
+ const providerCadre = attrByUuid[providerCadreUuid];
55
+ const practiceType = attrByUuid[practiceTypeUuid];
56
+ const nationalId = attrByUuid[providerNationalIdUuid];
57
+ const passportNumber = attrByUuid[passportNumberUuid];
58
+ const providerUniqueIdentifier = attrByUuid[providerUniqueIdentifierAttributeTypeUuid];
59
+ const externalProviderIdentifier = attrByUuid[externalProviderIdentifierUuid];
60
+ const phoneNumber = attrByUuid[phoneNumberUuid];
61
+ const emailAddress = attrByUuid[providerAddressUuid];
62
+
69
63
  const today = dayjs();
70
- const expiryDate = dateAttr?.value ? dayjs(dateAttr.value) : null;
64
+ const expiryDate = licenseExpiryRaw ? dayjs(licenseExpiryRaw) : null;
71
65
  const daysUntilExpiry = expiryDate ? expiryDate.diff(today, 'day') : null;
66
+ const formattedExpiryDate = expiryDate?.isValid() ? expiryDate.format('YYYY-MM-DD') : null;
72
67
 
73
68
  const getLicenseStatusTag = () => {
74
- if (!licenseAttr?.value) {
69
+ if (!licenseNumber) {
75
70
  return <Tag type="red">{t('unlicensed', 'Unlicensed')}</Tag>;
76
71
  }
77
-
78
- if (daysUntilExpiry < 0) {
79
- return <Tag type="red">{t('licenseExpired', 'License has expired')}</Tag>;
80
- } else if (daysUntilExpiry <= 3) {
81
- return (
82
- <>
83
- <Tag type="cyan">{t('licenseExpiringSoon', 'License is expiring soon')}</Tag>
84
- </>
85
- );
86
- } else {
87
- return <Tag type="green">{t('active', 'Active')}</Tag>;
72
+ if (daysUntilExpiry !== null && daysUntilExpiry < 0) {
73
+ return <Tag type="red">{t('licenseExpired', 'License expired')}</Tag>;
74
+ }
75
+ if (daysUntilExpiry !== null && daysUntilExpiry <= 3) {
76
+ return <Tag type="warm-gray">{t('licenseExpiringSoon', 'License expiring soon')}</Tag>;
88
77
  }
78
+ return <Tag type="green">{t('active', 'Active')}</Tag>;
89
79
  };
90
80
 
81
+ const genderLabel =
82
+ provider?.person?.gender === 'M'
83
+ ? t('male', 'Male')
84
+ : provider?.person?.gender === 'F'
85
+ ? t('female', 'Female')
86
+ : '';
87
+
88
+ const Field = ({ label, value }: { label: string; value?: string | null }) => (
89
+ <span className={styles.spanField}>
90
+ <span className={styles.fieldLabel}>{label}:</span> <span className={styles.fieldValue}>{value || '--'}</span>
91
+ </span>
92
+ );
93
+
91
94
  return (
92
95
  <div className={styles.providerDetailsContainer}>
93
- <header aria-label="patient banner" role="banner" ref={patientBannerRef}>
96
+ <header aria-label={t('providerBanner', 'Provider banner')} role="banner" ref={bannerRef}>
94
97
  <div className={styles.patientBanner}>
95
98
  <div className={styles.patientAvatar} role="img">
96
99
  <PatientPhoto patientUuid={provider?.uuid} patientName={provider?.person?.display} />
97
100
  </div>
101
+
98
102
  <div className={styles.patientInfo}>
99
103
  <div className={classNames(styles.row, styles.patientNameRow)}>
100
104
  <div className={styles.flexRow}>
101
- <span className={styles.patientName}>{provider?.person?.display} </span> &middot;
102
- <span className={styles.gender}>
103
- {provider?.person?.gender === 'M' ? 'Male' : provider?.person?.gender === 'F' ? 'Female' : ''}{' '}
104
- &middot;{' '}
105
- </span>
106
- <span className={styles.statusTag}>{getLicenseStatusTag()}</span>
107
- <span className={styles.statusTag}>
108
- {qualification?.value && <Tag type="cyan">{capitalize(qualification?.value)}</Tag>}
109
- </span>
110
- <span className={styles.statusTag}>
111
- {providerUniqueIdentifier?.value && (
112
- <Tag type="cyan">{capitalize(providerUniqueIdentifier?.value)}</Tag>
113
- )}
105
+ <span className={styles.patientName}>{provider?.person?.display}</span>
106
+ {genderLabel && <span className={styles.gender}>{genderLabel}</span>}
107
+
108
+ <span className={styles.tagRow}>
109
+ {getLicenseStatusTag()}
110
+ {qualification && <Tag type="cyan">{capitalize(qualification)}</Tag>}
111
+ {specialty && <Tag type="purple">{capitalize(specialty)}</Tag>}
112
+ {providerCadre && <Tag type="teal">{capitalize(providerCadre)}</Tag>}
114
113
  </span>
115
114
  </div>
116
115
  </div>
117
-
118
- <div className={classNames(styles.row, styles.patientNameRow)}>
116
+ <div className={styles.row}>
119
117
  <div className={styles.flexRow}>
120
- <span className={styles.spanField}>
121
- {t('phoneNumber', 'Phone number')}: {phoneNumber?.value ? phoneNumber.value : '--'}
122
- </span>
123
- <span className={styles.middot}>&middot; </span>
124
-
125
- <span className={styles.spanField}>
126
- {t('emailAddress', 'Email address')}: {emailAddress?.value ? emailAddress.value : '--'}
127
- </span>
118
+ <Field label={t('phoneNumber', 'Phone')} value={phoneNumber} />
119
+ <span className={styles.middot}>·</span>
120
+ <Field label={t('emailAddress', 'Email')} value={emailAddress} />
128
121
  </div>
129
122
  </div>
130
- <div className={classNames(styles.row, styles.patientNameRow)}>
131
- <div className={styles.flexRow}>
132
- <span className={styles.spanField}>
133
- {t('nationalId', 'National ID')}: {nationalID?.value ? nationalID.value : '--'}
134
- </span>
135
- <span className={styles.middot}>&middot; </span>
136
123
 
137
- <span className={styles.spanField}>
138
- {t('licenseNumber', 'License number')}: {licenseAttr?.value ? licenseAttr.value : '--'}
139
- </span>
140
- <span className={styles.middot}>&middot; </span>
141
- <span className={styles.spanField}>
142
- {t('registrationNumber', 'Registration number')}:{' '}
143
- {registrationNumber?.value ? registrationNumber.value : '--'}
144
- </span>
145
- <span className={styles.middot}>&middot; </span>
146
-
147
- <span className={styles.spanField}>
148
- {t('passportNumber', 'Passport number')}: {passportNumber?.value ? passportNumber.value : '--'}
149
- </span>
150
- <span className={styles.middot}>&middot; </span>
124
+ <div className={styles.row}>
125
+ <div className={styles.flexRow}>
126
+ <Field label={t('nationalId', 'National ID')} value={nationalId} />
127
+ <span className={styles.middot}>·</span>
128
+ <Field label={t('passportNumber', 'Passport')} value={passportNumber} />
129
+ <span className={styles.middot}>·</span>
130
+ <Field label={t('licenseNumber', 'License #')} value={licenseNumber} />
131
+ <span className={styles.middot}>·</span>
132
+ <Field label={t('licenseBody', 'License body')} value={licenseBody} />
133
+ <span className={styles.middot}>·</span>
134
+ <Field label={t('licenseExpiryDate', 'License expiry')} value={formattedExpiryDate} />
135
+ </div>
136
+ </div>
151
137
 
152
- <span className={styles.spanField}>
153
- {t('licenseExpiryDate', 'License expiry date')}: {formattedExpiryDate ? formattedExpiryDate : '--'}
138
+ {/* Practice row */}
139
+ {practiceType && (
140
+ <div className={styles.row}>
141
+ <div className={styles.flexRow}>
142
+ <Field label={t('practiceType', 'Practice')} value={practiceType} />
143
+ </div>
144
+ </div>
145
+ )}
146
+ {providerUniqueIdentifier && (
147
+ <div className={styles.row}>
148
+ <span className={styles.puidBadge}>
149
+ {t('puid', 'PUID')}: <strong>{providerUniqueIdentifier}</strong>
150
+ {externalProviderIdentifier && (
151
+ <>
152
+ <span className={styles.middot}>·</span>
153
+ <span className={styles.fieldLabel}>{t('externalRef', 'External ref')}:</span>{' '}
154
+ <strong>{externalProviderIdentifier}</strong>
155
+ </>
156
+ )}
154
157
  </span>
155
158
  </div>
156
- <div className={classNames(styles.row, styles.patientNameRow, styles.viewRoles)}>
157
- <Accordion>
158
- <AccordionItem title={t('viewRoles', 'View roles')}>
159
- {user?.roles?.map((role, i) => (
159
+ )}
160
+
161
+ {/* Roles */}
162
+ <div className={classNames(styles.row, styles.viewRoles)}>
163
+ <Accordion>
164
+ <AccordionItem title={t('viewRoles', 'View roles ({{count}})', { count: user?.roles?.length ?? 0 })}>
165
+ {user?.roles?.length ? (
166
+ user.roles.map((role, i) => (
160
167
  <ContainedListItem key={i}>
161
168
  <div className={styles.roleContainer}>
162
- <strong>{role.display}</strong>
169
+ <strong className={styles.roleName}>{role.display}</strong>
163
170
  <p className={styles.roleDescription}>
164
171
  {role.description || t('noDescriptionAvailable', 'No description available')}
165
172
  </p>
166
173
  </div>
167
174
  </ContainedListItem>
168
- ))}
169
- </AccordionItem>
170
- </Accordion>
171
- </div>
175
+ ))
176
+ ) : (
177
+ <p className={styles.roleDescription}>{t('noRolesAssigned', 'No roles assigned')}</p>
178
+ )}
179
+ </AccordionItem>
180
+ </Accordion>
172
181
  </div>
173
- <br />
174
182
  </div>
175
183
  </div>
176
184
  </header>
@@ -29,6 +29,7 @@ import {
29
29
  isDesktop,
30
30
  usePagination,
31
31
  showModal,
32
+ launchWorkspace2,
32
33
  } from '@openmrs/esm-framework';
33
34
  import { CardHeader, usePaginationInfo } from '@openmrs/esm-patient-common-lib';
34
35
  import capitalize from 'lodash-es/capitalize';
@@ -261,10 +262,15 @@ const UserList: React.FC = () => {
261
262
  onClick={() => {
262
263
  const selectedUser = users.find((u) => u.uuid === user.uuid);
263
264
  if (selectedUser) {
264
- launchWorkspace('manage-user-workspace', {
265
- workspaceTitle: t('editUser', 'Edit User'),
266
- initialUserValue: user,
267
- });
265
+ launchWorkspace2(
266
+ 'manage-user-workspace',
267
+ {
268
+ workspaceTitle: t('editUser', 'Edit User'),
269
+ initialUserValue: user,
270
+ },
271
+ {},
272
+ {},
273
+ );
268
274
  } else {
269
275
  console.error('User not found:', user.uuid);
270
276
  }
@@ -280,10 +286,15 @@ const UserList: React.FC = () => {
280
286
  hasDivider
281
287
  disabled={!userHasInventoryRole}
282
288
  onClick={() => {
283
- launchWorkspace('user-role-scope-workspace', {
284
- workspaceTitle: t('manageUserRoleScope', 'Manage user role scope'),
285
- user: user,
286
- });
289
+ launchWorkspace2(
290
+ 'user-role-scope-workspace',
291
+ {
292
+ workspaceTitle: t('manageUserRoleScope', 'Manage user role scope'),
293
+ user: user,
294
+ },
295
+ {},
296
+ {},
297
+ );
287
298
  }}
288
299
  itemText={t('manageUserRoleScope', 'Manage user role scope')}
289
300
  />
@@ -317,7 +328,9 @@ const UserList: React.FC = () => {
317
328
  size={isDesktop(layout) ? 'lg' : 'sm'}
318
329
  />
319
330
  <Button
320
- onClick={() => launchWorkspace('manage-user-workspace', { workspaceTitle: t('addUser', 'Add user') })}
331
+ onClick={() =>
332
+ launchWorkspace2('manage-user-workspace', { workspaceTitle: t('addUser', 'Add user') }, {}, {})
333
+ }
321
334
  className={styles.userManagementModeButton}
322
335
  renderIcon={UserFollow}
323
336
  size={isDesktop(layout) ? 'lg' : 'sm'}