@kenyaemr/esm-admin-app 5.4.4-pre.45 → 5.4.4-pre.459

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 (539) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/dist/1102.js +1 -0
  3. package/dist/{7988.js.map → 1102.js.map} +1 -1
  4. package/dist/1278.js +1 -0
  5. package/dist/1278.js.map +1 -0
  6. package/dist/1442.js +1 -0
  7. package/dist/1442.js.map +1 -0
  8. package/dist/15.js +1 -0
  9. package/dist/15.js.map +1 -0
  10. package/dist/162.js +1 -0
  11. package/dist/162.js.map +1 -0
  12. package/dist/1620.js +1 -0
  13. package/dist/1620.js.map +1 -0
  14. package/dist/1789.js +1 -0
  15. package/dist/1789.js.map +1 -0
  16. package/dist/1831.js +1 -0
  17. package/dist/1831.js.map +1 -0
  18. package/dist/1844.js +1 -0
  19. package/dist/1844.js.map +1 -0
  20. package/dist/1854.js +1 -0
  21. package/dist/1854.js.map +1 -0
  22. package/dist/1919.js +1 -0
  23. package/dist/1919.js.map +1 -0
  24. package/dist/193.js +1 -0
  25. package/dist/193.js.map +1 -0
  26. package/dist/1946.js +1 -0
  27. package/dist/1946.js.map +1 -0
  28. package/dist/2007.js +1 -1
  29. package/dist/2007.js.map +1 -1
  30. package/dist/2043.js +1 -0
  31. package/dist/2043.js.map +1 -0
  32. package/dist/2127.js +1 -0
  33. package/dist/2127.js.map +1 -0
  34. package/dist/2146.js +1 -0
  35. package/dist/2146.js.map +1 -0
  36. package/dist/2152.js +1 -0
  37. package/dist/2152.js.map +1 -0
  38. package/dist/2202.js +1 -0
  39. package/dist/2202.js.map +1 -0
  40. package/dist/2258.js +1 -0
  41. package/dist/2258.js.map +1 -0
  42. package/dist/2394.js +1 -0
  43. package/dist/2394.js.map +1 -0
  44. package/dist/2468.js +1 -0
  45. package/dist/2468.js.map +1 -0
  46. package/dist/2534.js +1 -1
  47. package/dist/2534.js.map +1 -1
  48. package/dist/2556.js +1 -0
  49. package/dist/2556.js.map +1 -0
  50. package/dist/26.js +1 -0
  51. package/dist/26.js.map +1 -0
  52. package/dist/2638.js +1 -0
  53. package/dist/2638.js.map +1 -0
  54. package/dist/2648.js +1 -0
  55. package/dist/2648.js.map +1 -0
  56. package/dist/2709.js +1 -0
  57. package/dist/2709.js.map +1 -0
  58. package/dist/2823.js +1 -0
  59. package/dist/2823.js.map +1 -0
  60. package/dist/2840.js +1 -0
  61. package/dist/2840.js.map +1 -0
  62. package/dist/2842.js +1 -0
  63. package/dist/2842.js.map +1 -0
  64. package/dist/2916.js +1 -0
  65. package/dist/2916.js.map +1 -0
  66. package/dist/2924.js +1 -0
  67. package/dist/2924.js.map +1 -0
  68. package/dist/3137.js +1 -0
  69. package/dist/3137.js.map +1 -0
  70. package/dist/3248.js +1 -1
  71. package/dist/3248.js.map +1 -1
  72. package/dist/3282.js +1 -0
  73. package/dist/3282.js.map +1 -0
  74. package/dist/3285.js +1 -0
  75. package/dist/3285.js.map +1 -0
  76. package/dist/3302.js +1 -0
  77. package/dist/3302.js.map +1 -0
  78. package/dist/3335.js +1 -0
  79. package/dist/3335.js.map +1 -0
  80. package/dist/3378.js +1 -0
  81. package/dist/3378.js.map +1 -0
  82. package/dist/3504.js +1 -0
  83. package/dist/3504.js.map +1 -0
  84. package/dist/356.js +1 -0
  85. package/dist/356.js.map +1 -0
  86. package/dist/3569.js +1 -0
  87. package/dist/3569.js.map +1 -0
  88. package/dist/3580.js +1 -0
  89. package/dist/3580.js.map +1 -0
  90. package/dist/3690.js +1 -0
  91. package/dist/3690.js.map +1 -0
  92. package/dist/3723.js +1 -0
  93. package/dist/3723.js.map +1 -0
  94. package/dist/3745.js +1 -0
  95. package/dist/3745.js.map +1 -0
  96. package/dist/3796.js +1 -0
  97. package/dist/3796.js.map +1 -0
  98. package/dist/3813.js +1 -0
  99. package/dist/3813.js.map +1 -0
  100. package/dist/393.js +6 -0
  101. package/dist/393.js.map +1 -0
  102. package/dist/3953.js +1 -0
  103. package/dist/3953.js.map +1 -0
  104. package/dist/3989.js +1 -0
  105. package/dist/3989.js.map +1 -0
  106. package/dist/4124.js +1 -0
  107. package/dist/4124.js.map +1 -0
  108. package/dist/4163.js +1 -0
  109. package/dist/4163.js.map +1 -0
  110. package/dist/4178.js +1 -1
  111. package/dist/4178.js.map +1 -1
  112. package/dist/4308.js +1 -1
  113. package/dist/4308.js.map +1 -1
  114. package/dist/4487.js +11 -0
  115. package/dist/4487.js.map +1 -0
  116. package/dist/4588.js +1 -0
  117. package/dist/4588.js.map +1 -0
  118. package/dist/4637.js +1 -1
  119. package/dist/4637.js.map +1 -1
  120. package/dist/466.js +1 -1
  121. package/dist/466.js.map +1 -1
  122. package/dist/4711.js +1 -0
  123. package/dist/4711.js.map +1 -0
  124. package/dist/4759.js +1 -0
  125. package/dist/4759.js.map +1 -0
  126. package/dist/4817.js +1 -0
  127. package/dist/4817.js.map +1 -0
  128. package/dist/4818.js +1 -0
  129. package/dist/4818.js.map +1 -0
  130. package/dist/4902.js +1 -0
  131. package/dist/4902.js.map +1 -0
  132. package/dist/4973.js +1 -0
  133. package/dist/4973.js.map +1 -0
  134. package/dist/500.js +1 -0
  135. package/dist/500.js.map +1 -0
  136. package/dist/5086.js +1 -1
  137. package/dist/5086.js.map +1 -1
  138. package/dist/5117.js +1 -0
  139. package/dist/5117.js.map +1 -0
  140. package/dist/5203.js +1 -0
  141. package/dist/5203.js.map +1 -0
  142. package/dist/5361.js +1 -1
  143. package/dist/5361.js.map +1 -1
  144. package/dist/5362.js +1 -0
  145. package/dist/5362.js.map +1 -0
  146. package/dist/5366.js +1 -0
  147. package/dist/5366.js.map +1 -0
  148. package/dist/5536.js +1 -0
  149. package/dist/5536.js.map +1 -0
  150. package/dist/5567.js +1 -0
  151. package/dist/5567.js.map +1 -0
  152. package/dist/5598.js +1 -0
  153. package/dist/5598.js.map +1 -0
  154. package/dist/5676.js +1 -1
  155. package/dist/5676.js.map +1 -1
  156. package/dist/5693.js +1 -0
  157. package/dist/5693.js.map +1 -0
  158. package/dist/5731.js +5 -0
  159. package/dist/5731.js.map +1 -0
  160. package/dist/5767.js +1 -0
  161. package/dist/5767.js.map +1 -0
  162. package/dist/5809.js +1 -1
  163. package/dist/5809.js.map +1 -1
  164. package/dist/5825.js +1 -0
  165. package/dist/5825.js.map +1 -0
  166. package/dist/5932.js +1 -0
  167. package/dist/5932.js.map +1 -0
  168. package/dist/5941.js +1 -0
  169. package/dist/5941.js.map +1 -0
  170. package/dist/6104.js +1 -1
  171. package/dist/6104.js.map +1 -1
  172. package/dist/6273.js +1 -0
  173. package/dist/6273.js.map +1 -0
  174. package/dist/6337.js +1 -0
  175. package/dist/6337.js.map +1 -0
  176. package/dist/6355.js +1 -0
  177. package/dist/{2797.js.map → 6355.js.map} +1 -1
  178. package/dist/6357.js +1 -0
  179. package/dist/6357.js.map +1 -0
  180. package/dist/6427.js +1 -1
  181. package/dist/6427.js.map +1 -1
  182. package/dist/6478.js +7 -0
  183. package/dist/6478.js.map +1 -0
  184. package/dist/6508.js +1 -0
  185. package/dist/6508.js.map +1 -0
  186. package/dist/6567.js +1 -0
  187. package/dist/6567.js.map +1 -0
  188. package/dist/6634.js +1 -0
  189. package/dist/6634.js.map +1 -0
  190. package/dist/6698.js +1 -0
  191. package/dist/6698.js.map +1 -0
  192. package/dist/682.js +1 -1
  193. package/dist/682.js.map +1 -1
  194. package/dist/6848.js +1 -0
  195. package/dist/6848.js.map +1 -0
  196. package/dist/689.js +1 -1
  197. package/dist/689.js.map +1 -1
  198. package/dist/6978.js +1 -0
  199. package/dist/6978.js.map +1 -0
  200. package/dist/7085.js +1 -1
  201. package/dist/7085.js.map +1 -1
  202. package/dist/7187.js +1 -0
  203. package/dist/7187.js.map +1 -0
  204. package/dist/7209.js +1 -0
  205. package/dist/7209.js.map +1 -0
  206. package/dist/740.js +1 -0
  207. package/dist/740.js.map +1 -0
  208. package/dist/7419.js +1 -1
  209. package/dist/7419.js.map +1 -1
  210. package/dist/7478.js +1 -1
  211. package/dist/7478.js.map +1 -1
  212. package/dist/7686.js +1 -0
  213. package/dist/7686.js.map +1 -0
  214. package/dist/770.js +1 -0
  215. package/dist/770.js.map +1 -0
  216. package/dist/7782.js +1 -0
  217. package/dist/7782.js.map +1 -0
  218. package/dist/7783.js +1 -0
  219. package/dist/7783.js.map +1 -0
  220. package/dist/7808.js +1 -0
  221. package/dist/7808.js.map +1 -0
  222. package/dist/781.js +1 -1
  223. package/dist/781.js.map +1 -1
  224. package/dist/8183.js +1 -0
  225. package/dist/8183.js.map +1 -0
  226. package/dist/8196.js +1 -1
  227. package/dist/8196.js.map +1 -1
  228. package/dist/820.js +1 -0
  229. package/dist/820.js.map +1 -0
  230. package/dist/8240.js +1 -1
  231. package/dist/8240.js.map +1 -1
  232. package/dist/8267.js +1 -0
  233. package/dist/8267.js.map +1 -0
  234. package/dist/8472.js +1 -0
  235. package/dist/8472.js.map +1 -0
  236. package/dist/8521.js +1 -0
  237. package/dist/8521.js.map +1 -0
  238. package/dist/8602.js +1 -0
  239. package/dist/8602.js.map +1 -0
  240. package/dist/8607.js +1 -0
  241. package/dist/8607.js.map +1 -0
  242. package/dist/8743.js +1 -0
  243. package/dist/8743.js.map +1 -0
  244. package/dist/8762.js +1 -0
  245. package/dist/8762.js.map +1 -0
  246. package/dist/8781.js +1 -0
  247. package/dist/8781.js.map +1 -0
  248. package/dist/8812.js +1 -0
  249. package/dist/8812.js.map +1 -0
  250. package/dist/8952.js +1 -0
  251. package/dist/8952.js.map +1 -0
  252. package/dist/8985.js +1 -0
  253. package/dist/8985.js.map +1 -0
  254. package/dist/8996.js +1 -0
  255. package/dist/8996.js.map +1 -0
  256. package/dist/9011.js +1 -1
  257. package/dist/9011.js.map +1 -1
  258. package/dist/9049.js +1 -0
  259. package/dist/9049.js.map +1 -0
  260. package/dist/9050.js +1 -0
  261. package/dist/9050.js.map +1 -0
  262. package/dist/9061.js +1 -1
  263. package/dist/9061.js.map +1 -1
  264. package/dist/9087.js +1 -0
  265. package/dist/9087.js.map +1 -0
  266. package/dist/9148.js +1 -0
  267. package/dist/9148.js.map +1 -0
  268. package/dist/9168.js +1 -0
  269. package/dist/9168.js.map +1 -0
  270. package/dist/935.js +1 -0
  271. package/dist/935.js.map +1 -0
  272. package/dist/9425.js +1 -1
  273. package/dist/944.js +1 -0
  274. package/dist/944.js.map +1 -0
  275. package/dist/9443.js +1 -0
  276. package/dist/9443.js.map +1 -0
  277. package/dist/9520.js +1 -1
  278. package/dist/9520.js.map +1 -1
  279. package/dist/9547.js +1 -0
  280. package/dist/9547.js.map +1 -0
  281. package/dist/9549.js +1 -0
  282. package/dist/9549.js.map +1 -0
  283. package/dist/9603.js +1 -0
  284. package/dist/9603.js.map +1 -0
  285. package/dist/9612.js +1 -0
  286. package/dist/9612.js.map +1 -0
  287. package/dist/9621.js +1 -0
  288. package/dist/9621.js.map +1 -0
  289. package/dist/9662.js +1 -0
  290. package/dist/9662.js.map +1 -0
  291. package/dist/9771.js +1 -0
  292. package/dist/9771.js.map +1 -0
  293. package/dist/9774.js +1 -0
  294. package/dist/9774.js.map +1 -0
  295. package/dist/9800.js +1 -0
  296. package/dist/9800.js.map +1 -0
  297. package/dist/9806.js +1 -0
  298. package/dist/9806.js.map +1 -0
  299. package/dist/992.js +1 -0
  300. package/dist/992.js.map +1 -0
  301. package/dist/kenyaemr-esm-admin-app.js +6 -5
  302. package/dist/kenyaemr-esm-admin-app.js.buildmanifest.json +1694 -719
  303. package/dist/kenyaemr-esm-admin-app.js.map +1 -1
  304. package/dist/main.js +6 -5
  305. package/dist/main.js.map +1 -1
  306. package/dist/routes.json +1 -1
  307. package/package.json +4 -6
  308. package/src/components/confirm-modal/confirmation-operation.test.tsx +8 -19
  309. package/src/components/dashboard/dashboard.component.tsx +4 -1
  310. package/src/components/empty-state/empty-state-log.test.tsx +3 -4
  311. package/src/components/facility-setup/constant/index.ts +3 -0
  312. package/src/components/facility-setup/facility-info.component.tsx +247 -108
  313. package/src/components/facility-setup/facility-info.scss +136 -55
  314. package/src/components/facility-setup/facility-setup.component.tsx +2 -2
  315. package/src/components/facility-setup/header/header.component.tsx +4 -10
  316. package/src/components/facility-setup/header/header.scss +3 -9
  317. package/src/components/facility-setup/shared/custom-info.component.tsx +9 -0
  318. package/src/components/facility-setup/shared/custom-section-card.component.tsx +10 -0
  319. package/src/components/facility-setup/shared/custom-status-tag.component.tsx +22 -0
  320. package/src/components/facility-setup/type/index.ts +61 -0
  321. package/src/components/facility-setup/useFacilityRegistry.ts +29 -0
  322. package/src/components/global-property/dashboard/global-property-dashboard.component.tsx +23 -0
  323. package/src/components/global-property/dashboard/global-property-dashboard.scss +6 -0
  324. package/src/components/global-property/hooks/useGlobalProperty.ts +64 -0
  325. package/src/components/global-property/hooks/useLogoUpload.ts +28 -0
  326. package/src/components/global-property/index.ts +14 -0
  327. package/src/components/global-property/modal/delete-global-property-modal.component.tsx +71 -0
  328. package/src/components/global-property/modal/delete-global-property-modal.test.tsx +131 -0
  329. package/src/components/global-property/table/global-property-table.component.tsx +241 -0
  330. package/src/components/global-property/table/global-property-table.scss +44 -0
  331. package/src/components/global-property/table/global-property-table.test.tsx +202 -0
  332. package/src/components/global-property/workspace/global-property-form-schema.ts +32 -0
  333. package/src/components/global-property/workspace/global-property.workspace.scss +40 -0
  334. package/src/components/global-property/workspace/global-property.workspace.test.tsx +172 -0
  335. package/src/components/global-property/workspace/global-property.workspace.tsx +260 -0
  336. package/src/components/global-property/workspace/upload-logo.workspace.scss +61 -0
  337. package/src/components/global-property/workspace/upload-logo.workspace.test.tsx +180 -0
  338. package/src/components/global-property/workspace/upload-logo.workspace.tsx +191 -0
  339. package/src/components/hook/healthWorkerRegistry.ts +78 -0
  340. package/src/components/hook/useProfessionalRegistryEnums.ts +59 -0
  341. package/src/components/locations/forms/add-location/add-location.workspace.tsx +96 -95
  342. package/src/components/locations/forms/search-location/search-location.workspace.tsx +90 -85
  343. package/src/components/locations/tables/locations-table.component.tsx +117 -121
  344. package/src/components/logs-table/operation-log-table.component.tsx +87 -75
  345. package/src/components/logs-table/operation-log.test.tsx +134 -28
  346. package/src/components/modal/hwr-confirmation.modal.scss +80 -4
  347. package/src/components/modal/hwr-confirmation.modal.tsx +118 -128
  348. package/src/components/modal/hwr-sync.modal.tsx +194 -106
  349. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +14 -14
  350. package/src/components/users/manage-users/user-details/user-detail.scss +167 -39
  351. package/src/components/users/manage-users/user-details/user-details.component.tsx +130 -122
  352. package/src/components/users/manage-users/user-list/user-list.component.tsx +22 -9
  353. package/src/components/users/manage-users/user-management.workspace.scss +233 -95
  354. package/src/components/users/manage-users/user-management.workspace.tsx +800 -687
  355. package/src/components/users/userManagementFormSchema.tsx +17 -8
  356. package/src/config-schema.ts +48 -68
  357. package/src/index.ts +21 -7
  358. package/src/left-pannel-link.component.tsx +5 -3
  359. package/src/root.component.tsx +13 -13
  360. package/src/routes.json +60 -36
  361. package/src/types/index.ts +40 -3
  362. package/translations/am.json +213 -13
  363. package/translations/en.json +225 -25
  364. package/translations/fr.json +260 -58
  365. package/translations/sw.json +329 -129
  366. package/tsconfig.json +1 -1
  367. package/dist/1072.js +0 -1
  368. package/dist/1072.js.map +0 -1
  369. package/dist/1091.js +0 -1
  370. package/dist/1091.js.map +0 -1
  371. package/dist/1172.js +0 -1
  372. package/dist/1172.js.map +0 -1
  373. package/dist/132.js +0 -1
  374. package/dist/132.js.map +0 -1
  375. package/dist/1332.js +0 -1
  376. package/dist/1332.js.map +0 -1
  377. package/dist/1577.js +0 -1
  378. package/dist/1577.js.map +0 -1
  379. package/dist/1660.js +0 -1
  380. package/dist/1660.js.map +0 -1
  381. package/dist/1729.js +0 -17
  382. package/dist/1729.js.map +0 -1
  383. package/dist/1761.js +0 -1
  384. package/dist/1761.js.map +0 -1
  385. package/dist/1809.js +0 -7
  386. package/dist/1809.js.map +0 -1
  387. package/dist/1866.js +0 -1
  388. package/dist/1866.js.map +0 -1
  389. package/dist/1962.js +0 -1
  390. package/dist/1962.js.map +0 -1
  391. package/dist/2045.js +0 -1
  392. package/dist/2045.js.map +0 -1
  393. package/dist/2163.js +0 -1
  394. package/dist/2163.js.map +0 -1
  395. package/dist/2267.js +0 -1
  396. package/dist/2267.js.map +0 -1
  397. package/dist/2357.js +0 -1
  398. package/dist/2357.js.map +0 -1
  399. package/dist/2419.js +0 -1
  400. package/dist/2419.js.map +0 -1
  401. package/dist/2561.js +0 -1
  402. package/dist/2561.js.map +0 -1
  403. package/dist/2564.js +0 -1
  404. package/dist/2564.js.map +0 -1
  405. package/dist/2738.js +0 -1
  406. package/dist/2738.js.map +0 -1
  407. package/dist/2797.js +0 -1
  408. package/dist/2810.js +0 -2
  409. package/dist/2810.js.map +0 -1
  410. package/dist/2867.js +0 -1
  411. package/dist/2867.js.map +0 -1
  412. package/dist/3040.js +0 -1
  413. package/dist/3134.js +0 -1
  414. package/dist/3134.js.map +0 -1
  415. package/dist/3189.js +0 -1
  416. package/dist/3189.js.map +0 -1
  417. package/dist/3228.js +0 -1
  418. package/dist/3228.js.map +0 -1
  419. package/dist/3381.js +0 -1
  420. package/dist/3381.js.map +0 -1
  421. package/dist/3472.js +0 -1
  422. package/dist/3472.js.map +0 -1
  423. package/dist/3491.js +0 -1
  424. package/dist/3491.js.map +0 -1
  425. package/dist/3509.js +0 -1
  426. package/dist/3509.js.map +0 -1
  427. package/dist/3576.js +0 -1
  428. package/dist/3576.js.map +0 -1
  429. package/dist/3968.js +0 -1
  430. package/dist/3968.js.map +0 -1
  431. package/dist/4146.js +0 -1
  432. package/dist/4146.js.map +0 -1
  433. package/dist/4220.js +0 -1
  434. package/dist/4220.js.map +0 -1
  435. package/dist/4224.js +0 -1
  436. package/dist/4224.js.map +0 -1
  437. package/dist/4402.js +0 -1
  438. package/dist/4402.js.map +0 -1
  439. package/dist/4724.js +0 -1
  440. package/dist/4724.js.map +0 -1
  441. package/dist/4972.js +0 -1
  442. package/dist/4972.js.map +0 -1
  443. package/dist/509.js +0 -1
  444. package/dist/509.js.map +0 -1
  445. package/dist/5096.js +0 -1
  446. package/dist/5096.js.map +0 -1
  447. package/dist/5119.js +0 -1
  448. package/dist/5119.js.map +0 -1
  449. package/dist/5136.js +0 -1
  450. package/dist/5136.js.map +0 -1
  451. package/dist/5159.js +0 -13
  452. package/dist/5159.js.map +0 -1
  453. package/dist/5533.js +0 -1
  454. package/dist/5533.js.map +0 -1
  455. package/dist/5870.js +0 -1
  456. package/dist/5870.js.map +0 -1
  457. package/dist/5882.js +0 -1
  458. package/dist/5939.js +0 -5
  459. package/dist/5939.js.map +0 -1
  460. package/dist/6004.js +0 -1
  461. package/dist/6004.js.map +0 -1
  462. package/dist/6121.js +0 -3
  463. package/dist/6121.js.map +0 -1
  464. package/dist/6137.js +0 -1
  465. package/dist/6137.js.map +0 -1
  466. package/dist/6192.js +0 -1
  467. package/dist/6192.js.map +0 -1
  468. package/dist/6232.js +0 -1
  469. package/dist/6232.js.map +0 -1
  470. package/dist/6356.js +0 -1
  471. package/dist/6356.js.map +0 -1
  472. package/dist/6363.js +0 -1
  473. package/dist/6363.js.map +0 -1
  474. package/dist/6548.js +0 -1
  475. package/dist/6548.js.map +0 -1
  476. package/dist/6571.js +0 -1
  477. package/dist/6571.js.map +0 -1
  478. package/dist/6615.js +0 -1
  479. package/dist/6615.js.map +0 -1
  480. package/dist/6721.js +0 -1
  481. package/dist/6721.js.map +0 -1
  482. package/dist/6868.js +0 -1
  483. package/dist/6868.js.map +0 -1
  484. package/dist/6892.js +0 -1
  485. package/dist/6892.js.map +0 -1
  486. package/dist/7127.js +0 -1
  487. package/dist/7329.js +0 -1
  488. package/dist/7329.js.map +0 -1
  489. package/dist/754.js +0 -1
  490. package/dist/754.js.map +0 -1
  491. package/dist/7544.js +0 -1
  492. package/dist/7544.js.map +0 -1
  493. package/dist/7560.js +0 -1
  494. package/dist/7560.js.map +0 -1
  495. package/dist/7988.js +0 -1
  496. package/dist/8179.js +0 -6
  497. package/dist/8179.js.map +0 -1
  498. package/dist/8189.js +0 -1
  499. package/dist/8189.js.map +0 -1
  500. package/dist/8249.js +0 -1
  501. package/dist/8249.js.map +0 -1
  502. package/dist/8502.js +0 -1
  503. package/dist/8502.js.map +0 -1
  504. package/dist/8522.js +0 -1
  505. package/dist/8522.js.map +0 -1
  506. package/dist/8622.js +0 -1
  507. package/dist/8622.js.map +0 -1
  508. package/dist/8775.js +0 -11
  509. package/dist/8775.js.map +0 -1
  510. package/dist/8820.js +0 -1
  511. package/dist/8820.js.map +0 -1
  512. package/dist/887.js +0 -1
  513. package/dist/887.js.map +0 -1
  514. package/dist/8927.js +0 -1
  515. package/dist/8927.js.map +0 -1
  516. package/dist/8977.js +0 -1
  517. package/dist/8977.js.map +0 -1
  518. package/dist/916.js +0 -1
  519. package/dist/9303.js +0 -1
  520. package/dist/9303.js.map +0 -1
  521. package/dist/9466.js +0 -1
  522. package/dist/9466.js.map +0 -1
  523. package/dist/9675.js +0 -1
  524. package/dist/9675.js.map +0 -1
  525. package/dist/9787.js +0 -1
  526. package/dist/9787.js.map +0 -1
  527. package/dist/981.js +0 -1
  528. package/dist/981.js.map +0 -1
  529. package/dist/984.js +0 -1
  530. package/dist/984.js.map +0 -1
  531. package/dist/9931.js +0 -1
  532. package/dist/9931.js.map +0 -1
  533. package/dist/9947.js +0 -1
  534. package/dist/9947.js.map +0 -1
  535. package/jest.config.js +0 -8
  536. package/src/components/facility-setup/card.component.tsx +0 -16
  537. package/src/components/facility-setup/facility-setup.resource.tsx +0 -7
  538. package/src/components/hook/healthWorkerAdapter.ts +0 -213
  539. package/src/components/hook/useFacilityInfo.tsx +0 -37
@@ -0,0 +1,172 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom';
5
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
6
+ import GlobalPropertyWorkspace from './global-property.workspace';
7
+
8
+ const mockShowSnackbar = vi.fn();
9
+ const mockSaveOrUpdateGlobalProperty = vi.fn();
10
+ const mockCloseWorkspace = vi.fn();
11
+ const mockMutateGlobalProperty = vi.fn();
12
+
13
+ vi.mock('react-i18next', () => ({
14
+ useTranslation: () => ({ t: (_key: string, fallback: string) => fallback }),
15
+ }));
16
+
17
+ vi.mock('@openmrs/esm-framework', () => ({
18
+ useLayoutType: vi.fn(() => 'desktop'),
19
+ showSnackbar: (...args: unknown[]) => mockShowSnackbar(...args),
20
+ ResponsiveWrapper: ({ children }: { children: React.ReactNode }) => <>{children}</>,
21
+ Workspace2: ({ children, title }: { children: React.ReactNode; title: string }) => (
22
+ <div>
23
+ <h2>{title}</h2>
24
+ {children}
25
+ </div>
26
+ ),
27
+ }));
28
+
29
+ vi.mock('../hooks/useGlobalProperty', () => ({
30
+ saveOrUpdateGlobalProperty: (...args: unknown[]) => mockSaveOrUpdateGlobalProperty(...args),
31
+ }));
32
+
33
+ vi.mock('@hookform/resolvers/zod', () => ({
34
+ zodResolver: () => async (values: unknown) => ({ values, errors: {} }),
35
+ }));
36
+
37
+ const baseProps = {
38
+ closeWorkspace: mockCloseWorkspace,
39
+ workspaceProps: {
40
+ systemSetting: null,
41
+ mutateGlobalProperty: mockMutateGlobalProperty,
42
+ },
43
+ };
44
+
45
+ describe('GlobalPropertyWorkspace', () => {
46
+ beforeEach(() => {
47
+ vi.clearAllMocks();
48
+ });
49
+
50
+ it('shows "Add global property" title in add mode', () => {
51
+ render(<GlobalPropertyWorkspace {...baseProps} />);
52
+ expect(screen.getByText('Add global property')).toBeInTheDocument();
53
+ });
54
+
55
+ it('shows "Edit global property" title when editing an existing property', () => {
56
+ const props = {
57
+ ...baseProps,
58
+ workspaceProps: {
59
+ systemSetting: { uuid: 'uuid-1', property: 'setting.one', value: 'value1' },
60
+ mutateGlobalProperty: mockMutateGlobalProperty,
61
+ },
62
+ };
63
+ render(<GlobalPropertyWorkspace {...props} />);
64
+ expect(screen.getByText('Edit global property')).toBeInTheDocument();
65
+ });
66
+
67
+ it('pre-fills form fields when editing an existing property', () => {
68
+ const props = {
69
+ ...baseProps,
70
+ workspaceProps: {
71
+ systemSetting: { uuid: 'uuid-1', property: 'my.setting', value: 'my-value' },
72
+ mutateGlobalProperty: mockMutateGlobalProperty,
73
+ },
74
+ };
75
+ render(<GlobalPropertyWorkspace {...props} />);
76
+ expect(screen.getByDisplayValue('my.setting')).toBeInTheDocument();
77
+ expect(screen.getByDisplayValue('my-value')).toBeInTheDocument();
78
+ });
79
+
80
+ it('disables the property field when editing an existing property', () => {
81
+ const props = {
82
+ ...baseProps,
83
+ workspaceProps: {
84
+ systemSetting: { uuid: 'uuid-1', property: 'setting.one', value: 'value1' },
85
+ mutateGlobalProperty: mockMutateGlobalProperty,
86
+ },
87
+ };
88
+ render(<GlobalPropertyWorkspace {...props} />);
89
+ expect(screen.getByLabelText(/Property/i)).toBeDisabled();
90
+ });
91
+
92
+ it('enables the property field in add mode', () => {
93
+ render(<GlobalPropertyWorkspace {...baseProps} />);
94
+ expect(screen.getByLabelText(/Property/i)).not.toBeDisabled();
95
+ });
96
+
97
+ it('renders the Cancel and Save & close buttons', () => {
98
+ render(<GlobalPropertyWorkspace {...baseProps} />);
99
+ expect(screen.getByText('Cancel')).toBeInTheDocument();
100
+ expect(screen.getByText('Save & close')).toBeInTheDocument();
101
+ });
102
+
103
+ it('calls closeWorkspace when Cancel is clicked', () => {
104
+ render(<GlobalPropertyWorkspace {...baseProps} />);
105
+ fireEvent.click(screen.getByText('Cancel'));
106
+ expect(mockCloseWorkspace).toHaveBeenCalledTimes(1);
107
+ });
108
+
109
+ it('shows validation error when form is submitted with empty required fields', async () => {
110
+ render(<GlobalPropertyWorkspace {...baseProps} />);
111
+ const saveButton = screen.getByText('Save & close');
112
+ fireEvent.click(saveButton);
113
+ await waitFor(() => {
114
+ expect(mockSaveOrUpdateGlobalProperty).not.toHaveBeenCalled();
115
+ });
116
+ });
117
+
118
+ it('saves a new property with the entered values', async () => {
119
+ mockSaveOrUpdateGlobalProperty.mockResolvedValue(undefined);
120
+ render(<GlobalPropertyWorkspace {...baseProps} />);
121
+
122
+ await userEvent.type(screen.getByLabelText(/Property/i), 'new.setting');
123
+ await userEvent.type(screen.getByLabelText(/Value/i), 'new-value');
124
+
125
+ fireEvent.submit(screen.getByLabelText(/Property/i).closest('form')!);
126
+
127
+ await waitFor(() => {
128
+ expect(mockSaveOrUpdateGlobalProperty).toHaveBeenCalledWith(
129
+ expect.objectContaining({ property: 'new.setting', value: 'new-value' }),
130
+ undefined,
131
+ );
132
+ });
133
+ });
134
+
135
+ it('shows a success snackbar and closes workspace after saving', async () => {
136
+ mockSaveOrUpdateGlobalProperty.mockResolvedValue(undefined);
137
+ render(<GlobalPropertyWorkspace {...baseProps} />);
138
+
139
+ await userEvent.type(screen.getByLabelText(/Property/i), 'new.setting');
140
+ await userEvent.type(screen.getByLabelText(/Value/i), 'new-value');
141
+
142
+ fireEvent.submit(screen.getByLabelText(/Property/i).closest('form')!);
143
+
144
+ await waitFor(() => {
145
+ expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' }));
146
+ expect(mockMutateGlobalProperty).toHaveBeenCalled();
147
+ expect(mockCloseWorkspace).toHaveBeenCalled();
148
+ });
149
+ });
150
+
151
+ it('shows an error snackbar when saving fails', async () => {
152
+ mockSaveOrUpdateGlobalProperty.mockRejectedValue(new Error('Failed to save'));
153
+ render(<GlobalPropertyWorkspace {...baseProps} />);
154
+
155
+ await userEvent.type(screen.getByLabelText(/Property/i), 'new.setting');
156
+ await userEvent.type(screen.getByLabelText(/Value/i), 'new-value');
157
+
158
+ fireEvent.submit(screen.getByLabelText(/Property/i).closest('form')!);
159
+
160
+ await waitFor(() => {
161
+ expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
162
+ expect(mockCloseWorkspace).not.toHaveBeenCalled();
163
+ });
164
+ });
165
+
166
+ it('renders optional fields: description, datatype config, handler config', () => {
167
+ render(<GlobalPropertyWorkspace {...baseProps} />);
168
+ expect(screen.getByLabelText(/Description/i)).toBeInTheDocument();
169
+ expect(screen.getByLabelText(/Datatype config/i)).toBeInTheDocument();
170
+ expect(screen.getByLabelText(/Handler config/i)).toBeInTheDocument();
171
+ });
172
+ });
@@ -0,0 +1,260 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ ResponsiveWrapper,
4
+ Workspace2,
5
+ showSnackbar,
6
+ useLayoutType,
7
+ type Workspace2DefinitionProps,
8
+ } from '@openmrs/esm-framework';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { Controller, useForm } from 'react-hook-form';
11
+ import { Button, ButtonSet, ComboBox, Form, FormGroup, InlineLoading, Stack, TextArea, TextInput } from '@carbon/react';
12
+ import { zodResolver } from '@hookform/resolvers/zod';
13
+ import classNames from 'classnames';
14
+
15
+ import styles from './global-property.workspace.scss';
16
+ import { type SystemSettings } from '../../../types';
17
+ import { saveOrUpdateGlobalProperty } from '../hooks/useGlobalProperty';
18
+ import {
19
+ createGlobalPropertyFormSchema,
20
+ openmrsCustomDatatypes,
21
+ type GlobalPropertyFormType,
22
+ } from './global-property-form-schema';
23
+
24
+ type GlobalPropertyWorkspaceProps = {
25
+ systemSetting?: SystemSettings | null;
26
+ mutateGlobalProperty: () => void;
27
+ };
28
+
29
+ const GlobalPropertyWorkspace: React.FC<Workspace2DefinitionProps<GlobalPropertyWorkspaceProps, {}, {}>> = ({
30
+ closeWorkspace,
31
+ workspaceProps: { systemSetting, mutateGlobalProperty },
32
+ }) => {
33
+ const { t } = useTranslation();
34
+ const isTablet = useLayoutType() === 'tablet';
35
+ const isEditMode = Boolean(systemSetting?.uuid);
36
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
37
+
38
+ const {
39
+ handleSubmit,
40
+ control,
41
+ formState: { isSubmitting, isDirty, errors },
42
+ } = useForm<GlobalPropertyFormType>({
43
+ resolver: zodResolver(createGlobalPropertyFormSchema(t)),
44
+ defaultValues: {
45
+ property: systemSetting?.property ?? '',
46
+ description: systemSetting?.description ?? '',
47
+ datatypeClassname: systemSetting?.datatypeClassname ?? '',
48
+ datatypeConfig: systemSetting?.datatypeConfig ?? '',
49
+ preferredHandlerClassname: systemSetting?.preferredHandlerClassname ?? '',
50
+ handlerConfig: systemSetting?.handlerConfig ?? '',
51
+ value: systemSetting?.value ?? '',
52
+ },
53
+ });
54
+
55
+ useEffect(() => {
56
+ setHasUnsavedChanges(isDirty);
57
+ }, [isDirty]);
58
+
59
+ const onSubmit = async (data: GlobalPropertyFormType) => {
60
+ const payload = {
61
+ property: data.property,
62
+ value: data.value,
63
+ description: data.description,
64
+ datatypeClassname: data.datatypeClassname,
65
+ datatypeConfig: data.datatypeConfig,
66
+ preferredHandlerClassname: data.preferredHandlerClassname,
67
+ handlerConfig: data.handlerConfig,
68
+ };
69
+ try {
70
+ await saveOrUpdateGlobalProperty(payload, systemSetting?.uuid);
71
+
72
+ showSnackbar({
73
+ title: t('success', 'Success'),
74
+ kind: 'success',
75
+ subtitle: isEditMode
76
+ ? t('gpUpdated', 'Global property {{property}} was updated successfully.', { property: data.property })
77
+ : t('gpCreated', 'Global property {{property}} was created successfully.', { property: data.property }),
78
+ });
79
+ mutateGlobalProperty();
80
+ closeWorkspace({ discardUnsavedChanges: true });
81
+ } catch (error: any) {
82
+ showSnackbar({
83
+ title: t('error', 'Error'),
84
+ kind: 'error',
85
+ subtitle: error?.message ?? t('gpSaveError', 'Error saving global property'),
86
+ });
87
+ }
88
+ };
89
+
90
+ const title = isEditMode
91
+ ? t('editGlobalProperty', 'Edit global property')
92
+ : t('addGlobalProperty', 'Add global property');
93
+
94
+ return (
95
+ <Workspace2 title={title} hasUnsavedChanges={hasUnsavedChanges}>
96
+ <Form onSubmit={handleSubmit(onSubmit)} className={styles.form}>
97
+ <div className={styles.formContainer}>
98
+ <Stack gap={4}>
99
+ <ResponsiveWrapper>
100
+ <FormGroup legendText="">
101
+ <Controller
102
+ control={control}
103
+ name="property"
104
+ render={({ field }) => (
105
+ <TextInput
106
+ id="gpProperty"
107
+ labelText={t('property', 'Property (required)')}
108
+ placeholder={t('gpPropertyPlaceholder', 'e.g. setting.name')}
109
+ value={field.value}
110
+ onChange={field.onChange}
111
+ invalid={!!errors.property}
112
+ invalidText={errors.property?.message}
113
+ disabled={isEditMode}
114
+ />
115
+ )}
116
+ />
117
+ </FormGroup>
118
+ </ResponsiveWrapper>
119
+
120
+ <ResponsiveWrapper>
121
+ <FormGroup legendText="">
122
+ <Controller
123
+ control={control}
124
+ name="value"
125
+ render={({ field }) => (
126
+ <TextArea
127
+ id="gpValue"
128
+ labelText={t('value', 'Value (required)')}
129
+ placeholder={t('gpValuePlaceholder', 'Enter value')}
130
+ value={field.value}
131
+ onChange={field.onChange}
132
+ invalid={!!errors.value}
133
+ invalidText={errors.value?.message}
134
+ rows={3}
135
+ />
136
+ )}
137
+ />
138
+ </FormGroup>
139
+ </ResponsiveWrapper>
140
+
141
+ <ResponsiveWrapper>
142
+ <FormGroup legendText="">
143
+ <Controller
144
+ control={control}
145
+ name="description"
146
+ render={({ field }) => (
147
+ <TextArea
148
+ id="gpDescription"
149
+ labelText={t('description', 'Description')}
150
+ placeholder={t('gpDescriptionPlaceholder', 'Optional description')}
151
+ value={field.value ?? ''}
152
+ onChange={field.onChange}
153
+ rows={2}
154
+ />
155
+ )}
156
+ />
157
+ </FormGroup>
158
+ </ResponsiveWrapper>
159
+
160
+ <ResponsiveWrapper>
161
+ <FormGroup legendText="">
162
+ <Controller
163
+ control={control}
164
+ name="datatypeClassname"
165
+ render={({ field }) => (
166
+ <ComboBox
167
+ id="datatypeClassname"
168
+ invalidText={errors.datatypeClassname?.message}
169
+ items={openmrsCustomDatatypes}
170
+ onChange={function Zye() {}}
171
+ titleText={t('datatypeClassname', 'Datatype classname')}
172
+ typeahead
173
+ placeholder={t('selectDatatypeClassname', 'Select datatype classname')}
174
+ />
175
+ )}
176
+ />
177
+ </FormGroup>
178
+ </ResponsiveWrapper>
179
+
180
+ <ResponsiveWrapper>
181
+ <FormGroup legendText="">
182
+ <Controller
183
+ control={control}
184
+ name="datatypeConfig"
185
+ render={({ field }) => (
186
+ <TextInput
187
+ id="gpDatatypeConfig"
188
+ labelText={t('datatypeConfig', 'Datatype config')}
189
+ placeholder={t('gpDatatypeConfigPlaceholder', 'Optional datatype configuration')}
190
+ value={field.value ?? ''}
191
+ onChange={field.onChange}
192
+ />
193
+ )}
194
+ />
195
+ </FormGroup>
196
+ </ResponsiveWrapper>
197
+
198
+ <ResponsiveWrapper>
199
+ <FormGroup legendText="">
200
+ <Controller
201
+ control={control}
202
+ name="preferredHandlerClassname"
203
+ render={({ field }) => (
204
+ <TextInput
205
+ id="gpPreferredHandlerClassname"
206
+ labelText={t('preferredHandlerClassname', 'Preferred handler classname')}
207
+ placeholder={t('gpPreferredHandlerPlaceholder', 'Optional preferred handler classname')}
208
+ value={field.value ?? ''}
209
+ onChange={field.onChange}
210
+ />
211
+ )}
212
+ />
213
+ </FormGroup>
214
+ </ResponsiveWrapper>
215
+
216
+ <ResponsiveWrapper>
217
+ <FormGroup legendText="">
218
+ <Controller
219
+ control={control}
220
+ name="handlerConfig"
221
+ render={({ field }) => (
222
+ <TextInput
223
+ id="gpHandlerConfig"
224
+ labelText={t('handlerConfig', 'Handler config')}
225
+ placeholder={t('gpHandlerConfigPlaceholder', 'Optional handler configuration')}
226
+ value={field.value ?? ''}
227
+ onChange={field.onChange}
228
+ />
229
+ )}
230
+ />
231
+ </FormGroup>
232
+ </ResponsiveWrapper>
233
+ </Stack>
234
+ </div>
235
+
236
+ <ButtonSet
237
+ className={classNames({
238
+ [styles.tablet]: isTablet,
239
+ [styles.desktop]: !isTablet,
240
+ })}>
241
+ <Button className={styles.buttonContainer} kind="secondary" onClick={() => closeWorkspace()}>
242
+ {t('cancel', 'Cancel')}
243
+ </Button>
244
+ <Button className={styles.buttonContainer} disabled={isSubmitting || !isDirty} kind="primary" type="submit">
245
+ {isSubmitting ? (
246
+ <span className={styles.inlineLoading}>
247
+ {t('submitting', 'Submitting...')}
248
+ <InlineLoading status="active" iconDescription="Loading" />
249
+ </span>
250
+ ) : (
251
+ t('saveAndClose', 'Save & close')
252
+ )}
253
+ </Button>
254
+ </ButtonSet>
255
+ </Form>
256
+ </Workspace2>
257
+ );
258
+ };
259
+
260
+ export default GlobalPropertyWorkspace;
@@ -0,0 +1,61 @@
1
+ @use '@carbon/colors';
2
+ @use '@carbon/layout';
3
+ @use '@carbon/type';
4
+
5
+ .form {
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: space-between;
9
+ height: 100%;
10
+ }
11
+
12
+ .formContainer {
13
+ margin: layout.$spacing-05;
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: layout.$spacing-05;
17
+ overflow-y: auto;
18
+ }
19
+
20
+ .previewContainer {
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: layout.$spacing-03;
24
+ }
25
+
26
+ .previewLabel {
27
+ @include type.type-style('label-01');
28
+ color: colors.$gray-70;
29
+ }
30
+
31
+ .preview {
32
+ max-width: 100%;
33
+ max-height: 240px;
34
+ object-fit: contain;
35
+ border: 1px solid colors.$gray-20;
36
+ padding: layout.$spacing-03;
37
+ background-color: colors.$white;
38
+ }
39
+
40
+ .tablet {
41
+ padding: layout.$spacing-06 layout.$spacing-05;
42
+ background-color: colors.$white;
43
+ }
44
+
45
+ .desktop {
46
+ padding: 0;
47
+
48
+ & button {
49
+ max-width: 50% !important;
50
+ }
51
+ }
52
+
53
+ .buttonContainer {
54
+ max-width: 50%;
55
+ }
56
+
57
+ .inlineLoading {
58
+ display: flex;
59
+ align-items: center;
60
+ gap: layout.$spacing-03;
61
+ }
@@ -0,0 +1,180 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import '@testing-library/jest-dom';
5
+ import { vi, describe, it, expect, beforeEach } from 'vitest';
6
+ import UploadLogoWorkspace from './upload-logo.workspace';
7
+
8
+ const mockShowSnackbar = vi.fn();
9
+ const mockUploadLogo = vi.fn();
10
+ const mockCloseWorkspace = vi.fn();
11
+ const mockMutateGlobalProperty = vi.fn();
12
+
13
+ vi.mock('react-i18next', () => ({
14
+ useTranslation: () => ({ t: (_key: string, fallback: string) => fallback }),
15
+ }));
16
+
17
+ vi.mock('@openmrs/esm-framework', () => ({
18
+ useLayoutType: vi.fn(() => 'desktop'),
19
+ showSnackbar: (...args: unknown[]) => mockShowSnackbar(...args),
20
+ Workspace2: ({ children, title }: { children: React.ReactNode; title: string }) => (
21
+ <div>
22
+ <h2>{title}</h2>
23
+ {children}
24
+ </div>
25
+ ),
26
+ }));
27
+
28
+ vi.mock('../hooks/useLogoUpload', async (importOriginal) => {
29
+ const original = await importOriginal<typeof import('../hooks/useLogoUpload')>();
30
+ return {
31
+ ...original,
32
+ uploadLogo: (...args: unknown[]) => mockUploadLogo(...args),
33
+ };
34
+ });
35
+
36
+ vi.mock('@carbon/react', async (importOriginal) => {
37
+ const original = await importOriginal<typeof import('@carbon/react')>();
38
+ return {
39
+ ...original,
40
+ Button: ({ children, onClick, disabled, type }: any) => (
41
+ <button onClick={onClick} disabled={disabled} type={type || 'button'}>
42
+ {children}
43
+ </button>
44
+ ),
45
+ FileUploader: ({ onChange, onDelete, disabled }: any) => (
46
+ <div>
47
+ <input
48
+ data-testid="file-input"
49
+ type="file"
50
+ disabled={disabled}
51
+ onChange={(e) => onChange({ target: e.target, addedFiles: Array.from(e.target.files ?? []) })}
52
+ />
53
+ <button type="button" onClick={() => onDelete?.()}>
54
+ remove-file
55
+ </button>
56
+ </div>
57
+ ),
58
+ };
59
+ });
60
+
61
+ const baseProps = {
62
+ closeWorkspace: mockCloseWorkspace,
63
+ workspaceProps: {
64
+ mutateGlobalProperty: mockMutateGlobalProperty,
65
+ },
66
+ } as any;
67
+
68
+ function makeFile(name: string, type: string, size?: number) {
69
+ const file = new File(['logo-bytes'], name, { type });
70
+ if (size != null) {
71
+ Object.defineProperty(file, 'size', { value: size });
72
+ }
73
+ return file;
74
+ }
75
+
76
+ describe('UploadLogoWorkspace', () => {
77
+ beforeEach(() => {
78
+ vi.clearAllMocks();
79
+ global.URL.createObjectURL = vi.fn(() => 'blob:preview');
80
+ global.URL.revokeObjectURL = vi.fn();
81
+ });
82
+
83
+ it('renders the workspace with the Upload button disabled until a file is chosen', () => {
84
+ render(<UploadLogoWorkspace {...baseProps} />);
85
+ expect(screen.getByText('Upload image')).toBeInTheDocument();
86
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeDisabled();
87
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
88
+ });
89
+
90
+ it('shows a preview and enables Upload after selecting a valid image', async () => {
91
+ render(<UploadLogoWorkspace {...baseProps} />);
92
+
93
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('logo.png', 'image/png'));
94
+
95
+ expect(screen.getByRole('img', { name: 'Image preview' })).toBeInTheDocument();
96
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeEnabled();
97
+ });
98
+
99
+ it('rejects an unsupported file type and keeps Upload disabled', async () => {
100
+ render(<UploadLogoWorkspace {...baseProps} />);
101
+
102
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('notes.pdf', 'application/pdf'));
103
+
104
+ expect(screen.getByText(/Unsupported file type/i)).toBeInTheDocument();
105
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeDisabled();
106
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
107
+ });
108
+
109
+ it('rejects a file larger than the 2 MB limit', async () => {
110
+ render(<UploadLogoWorkspace {...baseProps} />);
111
+
112
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('huge.png', 'image/png', 2 * 1024 * 1024 + 1));
113
+
114
+ expect(screen.getByText(/exceeds the 2 MB maximum/i)).toBeInTheDocument();
115
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeDisabled();
116
+ });
117
+
118
+ it('uploads the selected image, shows a success snackbar, refreshes and closes', async () => {
119
+ mockUploadLogo.mockResolvedValue({ savedPath: '/srv/openmrs/prescription-logo.png', message: 'ok' });
120
+ render(<UploadLogoWorkspace {...baseProps} />);
121
+
122
+ const file = makeFile('logo.png', 'image/png');
123
+ await userEvent.upload(screen.getByTestId('file-input'), file);
124
+ await userEvent.click(screen.getByRole('button', { name: 'Upload' }));
125
+
126
+ await waitFor(() => {
127
+ expect(mockUploadLogo).toHaveBeenCalledWith(file, 'prescription');
128
+ expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'success' }));
129
+ expect(mockMutateGlobalProperty).toHaveBeenCalled();
130
+ expect(mockCloseWorkspace).toHaveBeenCalled();
131
+ });
132
+ });
133
+
134
+ it('uploads to the receipt destination when the receipt option is selected', async () => {
135
+ mockUploadLogo.mockResolvedValue({ savedPath: '/srv/openmrs/receipt-logo.png', message: 'ok' });
136
+ render(<UploadLogoWorkspace {...baseProps} />);
137
+
138
+ await userEvent.click(screen.getByRole('radio', { name: 'Receipt logo' }));
139
+ const file = makeFile('mtrh-logo.png', 'image/png');
140
+ await userEvent.upload(screen.getByTestId('file-input'), file);
141
+ await userEvent.click(screen.getByRole('button', { name: 'Upload' }));
142
+
143
+ await waitFor(() => {
144
+ expect(mockUploadLogo).toHaveBeenCalledWith(file, 'receipt');
145
+ });
146
+ });
147
+
148
+ it('shows an error snackbar and keeps the workspace open when upload fails', async () => {
149
+ mockUploadLogo.mockRejectedValue(new Error('Upload failed'));
150
+ render(<UploadLogoWorkspace {...baseProps} />);
151
+
152
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('logo.png', 'image/png'));
153
+ await userEvent.click(screen.getByRole('button', { name: 'Upload' }));
154
+
155
+ await waitFor(() => {
156
+ expect(mockShowSnackbar).toHaveBeenCalledWith(expect.objectContaining({ kind: 'error' }));
157
+ });
158
+ expect(mockMutateGlobalProperty).not.toHaveBeenCalled();
159
+ expect(mockCloseWorkspace).not.toHaveBeenCalled();
160
+ });
161
+
162
+ it('closes the workspace when Cancel is clicked', async () => {
163
+ render(<UploadLogoWorkspace {...baseProps} />);
164
+ await userEvent.click(screen.getByRole('button', { name: 'Cancel' }));
165
+ expect(mockCloseWorkspace).toHaveBeenCalledTimes(1);
166
+ expect(mockUploadLogo).not.toHaveBeenCalled();
167
+ });
168
+
169
+ it('clears the preview and disables Upload when the chosen file is removed', async () => {
170
+ render(<UploadLogoWorkspace {...baseProps} />);
171
+
172
+ await userEvent.upload(screen.getByTestId('file-input'), makeFile('logo.png', 'image/png'));
173
+ expect(screen.getByRole('img', { name: 'Image preview' })).toBeInTheDocument();
174
+
175
+ await userEvent.click(screen.getByRole('button', { name: 'remove-file' }));
176
+
177
+ expect(screen.queryByRole('img')).not.toBeInTheDocument();
178
+ expect(screen.getByRole('button', { name: 'Upload' })).toBeDisabled();
179
+ });
180
+ });