@kenyaemr/esm-admin-app 5.4.4-pre.98 → 5.4.5-pre.544

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 (582) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/dist/1102.js +1 -0
  3. package/dist/1102.js.map +1 -0
  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 -0
  29. package/dist/2007.js.map +1 -0
  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 -0
  47. package/dist/2534.js.map +1 -0
  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 -0
  71. package/dist/3248.js.map +1 -0
  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 -0
  111. package/dist/4178.js.map +1 -0
  112. package/dist/4308.js +1 -0
  113. package/dist/4308.js.map +1 -0
  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 -0
  119. package/dist/4637.js.map +1 -0
  120. package/dist/466.js +1 -0
  121. package/dist/466.js.map +1 -0
  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 -0
  137. package/dist/5086.js.map +1 -0
  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 -0
  143. package/dist/5361.js.map +1 -0
  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 -0
  155. package/dist/5676.js.map +1 -0
  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 -0
  163. package/dist/5809.js.map +1 -0
  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 -0
  171. package/dist/6104.js.map +1 -0
  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/{6155.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 -0
  181. package/dist/6427.js.map +1 -0
  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 -0
  193. package/dist/682.js.map +1 -0
  194. package/dist/6848.js +1 -0
  195. package/dist/6848.js.map +1 -0
  196. package/dist/689.js +1 -0
  197. package/dist/689.js.map +1 -0
  198. package/dist/6978.js +1 -0
  199. package/dist/6978.js.map +1 -0
  200. package/dist/7085.js +1 -0
  201. package/dist/7085.js.map +1 -0
  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 -0
  209. package/dist/7419.js.map +1 -0
  210. package/dist/7478.js +1 -0
  211. package/dist/7478.js.map +1 -0
  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 -0
  223. package/dist/781.js.map +1 -0
  224. package/dist/8183.js +1 -0
  225. package/dist/8183.js.map +1 -0
  226. package/dist/8196.js +1 -0
  227. package/dist/8196.js.map +1 -0
  228. package/dist/820.js +1 -0
  229. package/dist/820.js.map +1 -0
  230. package/dist/8240.js +1 -0
  231. package/dist/{1074.js.map → 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 -0
  257. package/dist/9011.js.map +1 -0
  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 -0
  263. package/dist/9061.js.map +1 -0
  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 -0
  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 -0
  278. package/dist/9520.js.map +1 -0
  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 +1636 -742
  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 +3 -3
  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 +177 -107
  313. package/src/components/facility-setup/facility-info.scss +39 -1
  314. package/src/components/facility-setup/facility-setup.component.tsx +2 -2
  315. package/src/components/facility-setup/type/index.ts +61 -0
  316. package/src/components/facility-setup/useFacilityRegistry.ts +29 -0
  317. package/src/components/global-property/dashboard/global-property-dashboard.component.tsx +23 -0
  318. package/src/components/global-property/dashboard/global-property-dashboard.scss +6 -0
  319. package/src/components/global-property/hooks/useGlobalProperty.ts +64 -0
  320. package/src/components/global-property/hooks/useLogoUpload.ts +28 -0
  321. package/src/components/global-property/index.ts +14 -0
  322. package/src/components/global-property/modal/delete-global-property-modal.component.tsx +71 -0
  323. package/src/components/global-property/modal/delete-global-property-modal.test.tsx +131 -0
  324. package/src/components/global-property/table/global-property-table.component.tsx +241 -0
  325. package/src/components/global-property/table/global-property-table.scss +44 -0
  326. package/src/components/global-property/table/global-property-table.test.tsx +202 -0
  327. package/src/components/global-property/workspace/global-property-form-schema.ts +32 -0
  328. package/src/components/global-property/workspace/global-property.workspace.scss +40 -0
  329. package/src/components/global-property/workspace/global-property.workspace.test.tsx +172 -0
  330. package/src/components/global-property/workspace/global-property.workspace.tsx +260 -0
  331. package/src/components/global-property/workspace/upload-logo.workspace.scss +61 -0
  332. package/src/components/global-property/workspace/upload-logo.workspace.test.tsx +180 -0
  333. package/src/components/global-property/workspace/upload-logo.workspace.tsx +191 -0
  334. package/src/components/hook/healthWorkerRegistry.ts +78 -0
  335. package/src/components/hook/useProfessionalRegistryEnums.ts +59 -0
  336. package/src/components/logs-table/operation-log-table.component.tsx +87 -75
  337. package/src/components/logs-table/operation-log.test.tsx +134 -28
  338. package/src/components/modal/hwr-confirmation.modal.scss +80 -4
  339. package/src/components/modal/hwr-confirmation.modal.tsx +118 -128
  340. package/src/components/modal/hwr-sync.modal.tsx +194 -106
  341. package/src/components/users/manage-users/manage-user-role-scope/user-role-scope-workspace/user-role-scope.workspace.tsx +1 -1
  342. package/src/components/users/manage-users/user-details/user-detail.scss +167 -39
  343. package/src/components/users/manage-users/user-details/user-details.component.tsx +130 -122
  344. package/src/components/users/manage-users/user-management.workspace.scss +233 -95
  345. package/src/components/users/manage-users/user-management.workspace.tsx +481 -375
  346. package/src/components/users/userManagementFormSchema.tsx +17 -8
  347. package/src/config-schema.ts +48 -68
  348. package/src/index.ts +21 -7
  349. package/src/left-pannel-link.component.tsx +5 -3
  350. package/src/root.component.tsx +2 -0
  351. package/src/routes.json +25 -1
  352. package/src/types/index.ts +11 -2
  353. package/translations/am.json +192 -10
  354. package/translations/en.json +205 -23
  355. package/translations/fr.json +260 -58
  356. package/translations/sw.json +329 -147
  357. package/dist/1074.js +0 -1
  358. package/dist/12.js +0 -17
  359. package/dist/12.js.map +0 -1
  360. package/dist/1201.js +0 -1
  361. package/dist/1201.js.map +0 -1
  362. package/dist/1242.js +0 -1
  363. package/dist/1242.js.map +0 -1
  364. package/dist/1311.js +0 -1
  365. package/dist/1311.js.map +0 -1
  366. package/dist/1462.js +0 -1
  367. package/dist/1462.js.map +0 -1
  368. package/dist/1469.js +0 -1
  369. package/dist/1469.js.map +0 -1
  370. package/dist/1506.js +0 -13
  371. package/dist/1506.js.map +0 -1
  372. package/dist/1718.js +0 -1
  373. package/dist/1718.js.map +0 -1
  374. package/dist/1722.js +0 -1
  375. package/dist/1722.js.map +0 -1
  376. package/dist/1772.js +0 -1
  377. package/dist/1772.js.map +0 -1
  378. package/dist/1889.js +0 -1
  379. package/dist/1889.js.map +0 -1
  380. package/dist/1972.js +0 -1
  381. package/dist/1972.js.map +0 -1
  382. package/dist/1990.js +0 -1
  383. package/dist/1990.js.map +0 -1
  384. package/dist/2016.js +0 -1
  385. package/dist/2016.js.map +0 -1
  386. package/dist/2080.js +0 -1
  387. package/dist/2080.js.map +0 -1
  388. package/dist/2096.js +0 -1
  389. package/dist/2096.js.map +0 -1
  390. package/dist/2153.js +0 -1
  391. package/dist/2153.js.map +0 -1
  392. package/dist/216.js +0 -1
  393. package/dist/216.js.map +0 -1
  394. package/dist/2270.js +0 -1
  395. package/dist/2270.js.map +0 -1
  396. package/dist/2294.js +0 -1
  397. package/dist/2294.js.map +0 -1
  398. package/dist/2345.js +0 -1
  399. package/dist/2345.js.map +0 -1
  400. package/dist/2402.js +0 -1
  401. package/dist/2402.js.map +0 -1
  402. package/dist/2500.js +0 -1
  403. package/dist/2500.js.map +0 -1
  404. package/dist/251.js +0 -1
  405. package/dist/251.js.map +0 -1
  406. package/dist/257.js +0 -1
  407. package/dist/257.js.map +0 -1
  408. package/dist/2586.js +0 -1
  409. package/dist/2586.js.map +0 -1
  410. package/dist/2625.js +0 -1
  411. package/dist/2625.js.map +0 -1
  412. package/dist/2652.js +0 -1
  413. package/dist/2652.js.map +0 -1
  414. package/dist/2685.js +0 -1
  415. package/dist/2685.js.map +0 -1
  416. package/dist/2948.js +0 -1
  417. package/dist/2948.js.map +0 -1
  418. package/dist/3089.js +0 -1
  419. package/dist/3089.js.map +0 -1
  420. package/dist/3190.js +0 -1
  421. package/dist/3190.js.map +0 -1
  422. package/dist/3224.js +0 -1
  423. package/dist/3224.js.map +0 -1
  424. package/dist/3366.js +0 -1
  425. package/dist/3366.js.map +0 -1
  426. package/dist/349.js +0 -1
  427. package/dist/349.js.map +0 -1
  428. package/dist/3548.js +0 -1
  429. package/dist/3548.js.map +0 -1
  430. package/dist/3571.js +0 -1
  431. package/dist/3571.js.map +0 -1
  432. package/dist/3691.js +0 -1
  433. package/dist/3691.js.map +0 -1
  434. package/dist/3775.js +0 -1
  435. package/dist/3775.js.map +0 -1
  436. package/dist/3816.js +0 -1
  437. package/dist/3816.js.map +0 -1
  438. package/dist/3852.js +0 -1
  439. package/dist/3852.js.map +0 -1
  440. package/dist/3906.js +0 -1
  441. package/dist/3906.js.map +0 -1
  442. package/dist/3963.js +0 -1
  443. package/dist/3963.js.map +0 -1
  444. package/dist/405.js +0 -1
  445. package/dist/405.js.map +0 -1
  446. package/dist/4296.js +0 -1
  447. package/dist/4296.js.map +0 -1
  448. package/dist/4337.js +0 -1
  449. package/dist/4337.js.map +0 -1
  450. package/dist/4735.js +0 -1
  451. package/dist/4735.js.map +0 -1
  452. package/dist/4744.js +0 -1
  453. package/dist/4744.js.map +0 -1
  454. package/dist/4813.js +0 -2
  455. package/dist/4813.js.map +0 -1
  456. package/dist/4858.js +0 -1
  457. package/dist/4858.js.map +0 -1
  458. package/dist/487.js +0 -1
  459. package/dist/487.js.map +0 -1
  460. package/dist/4970.js +0 -1
  461. package/dist/4970.js.map +0 -1
  462. package/dist/5202.js +0 -1
  463. package/dist/5202.js.map +0 -1
  464. package/dist/5294.js +0 -1
  465. package/dist/5294.js.map +0 -1
  466. package/dist/545.js +0 -1
  467. package/dist/545.js.map +0 -1
  468. package/dist/552.js +0 -1
  469. package/dist/552.js.map +0 -1
  470. package/dist/5592.js +0 -1
  471. package/dist/5592.js.map +0 -1
  472. package/dist/5669.js +0 -1
  473. package/dist/5669.js.map +0 -1
  474. package/dist/5884.js +0 -1
  475. package/dist/5884.js.map +0 -1
  476. package/dist/5940.js +0 -1
  477. package/dist/5940.js.map +0 -1
  478. package/dist/6092.js +0 -1
  479. package/dist/6092.js.map +0 -1
  480. package/dist/6155.js +0 -1
  481. package/dist/6178.js +0 -1
  482. package/dist/6178.js.map +0 -1
  483. package/dist/6399.js +0 -1
  484. package/dist/6399.js.map +0 -1
  485. package/dist/6456.js +0 -1
  486. package/dist/6466.js +0 -3
  487. package/dist/6466.js.map +0 -1
  488. package/dist/6492.js +0 -1
  489. package/dist/6492.js.map +0 -1
  490. package/dist/6676.js +0 -1
  491. package/dist/6676.js.map +0 -1
  492. package/dist/6800.js +0 -1
  493. package/dist/6800.js.map +0 -1
  494. package/dist/6976.js +0 -1
  495. package/dist/6976.js.map +0 -1
  496. package/dist/7005.js +0 -1
  497. package/dist/7005.js.map +0 -1
  498. package/dist/7201.js +0 -1
  499. package/dist/7201.js.map +0 -1
  500. package/dist/7210.js +0 -1
  501. package/dist/7210.js.map +0 -1
  502. package/dist/7234.js +0 -1
  503. package/dist/7234.js.map +0 -1
  504. package/dist/7261.js +0 -1
  505. package/dist/7261.js.map +0 -1
  506. package/dist/7326.js +0 -1
  507. package/dist/7463.js +0 -1
  508. package/dist/7463.js.map +0 -1
  509. package/dist/7528.js +0 -1
  510. package/dist/7528.js.map +0 -1
  511. package/dist/7584.js +0 -1
  512. package/dist/7584.js.map +0 -1
  513. package/dist/7607.js +0 -1
  514. package/dist/7717.js +0 -1
  515. package/dist/7717.js.map +0 -1
  516. package/dist/7737.js +0 -1
  517. package/dist/7737.js.map +0 -1
  518. package/dist/7739.js +0 -1
  519. package/dist/7739.js.map +0 -1
  520. package/dist/7765.js +0 -1
  521. package/dist/7765.js.map +0 -1
  522. package/dist/7820.js +0 -1
  523. package/dist/7820.js.map +0 -1
  524. package/dist/7844.js +0 -1
  525. package/dist/7844.js.map +0 -1
  526. package/dist/7866.js +0 -1
  527. package/dist/7866.js.map +0 -1
  528. package/dist/7916.js +0 -1
  529. package/dist/7916.js.map +0 -1
  530. package/dist/7971.js +0 -1
  531. package/dist/7971.js.map +0 -1
  532. package/dist/8159.js +0 -7
  533. package/dist/8159.js.map +0 -1
  534. package/dist/8244.js +0 -1
  535. package/dist/8244.js.map +0 -1
  536. package/dist/8262.js +0 -1
  537. package/dist/8262.js.map +0 -1
  538. package/dist/834.js +0 -1
  539. package/dist/834.js.map +0 -1
  540. package/dist/8376.js +0 -1
  541. package/dist/8376.js.map +0 -1
  542. package/dist/845.js +0 -1
  543. package/dist/845.js.map +0 -1
  544. package/dist/8546.js +0 -1
  545. package/dist/8546.js.map +0 -1
  546. package/dist/8570.js +0 -1
  547. package/dist/8570.js.map +0 -1
  548. package/dist/87.js +0 -1
  549. package/dist/87.js.map +0 -1
  550. package/dist/8727.js +0 -1
  551. package/dist/8828.js +0 -1
  552. package/dist/8828.js.map +0 -1
  553. package/dist/8860.js +0 -1
  554. package/dist/8860.js.map +0 -1
  555. package/dist/9036.js +0 -1
  556. package/dist/9036.js.map +0 -1
  557. package/dist/9124.js +0 -1
  558. package/dist/9124.js.map +0 -1
  559. package/dist/9182.js +0 -1
  560. package/dist/921.js +0 -1
  561. package/dist/921.js.map +0 -1
  562. package/dist/9404.js +0 -1
  563. package/dist/9404.js.map +0 -1
  564. package/dist/9406.js +0 -1
  565. package/dist/9406.js.map +0 -1
  566. package/dist/9446.js +0 -1
  567. package/dist/9446.js.map +0 -1
  568. package/dist/9449.js +0 -1
  569. package/dist/9449.js.map +0 -1
  570. package/dist/9566.js +0 -5
  571. package/dist/9566.js.map +0 -1
  572. package/dist/9641.js +0 -1
  573. package/dist/9641.js.map +0 -1
  574. package/dist/9711.js +0 -1
  575. package/dist/9711.js.map +0 -1
  576. package/dist/9801.js +0 -1
  577. package/dist/9801.js.map +0 -1
  578. package/dist/9835.js +0 -11
  579. package/dist/9835.js.map +0 -1
  580. package/src/components/facility-setup/facility-setup.resource.tsx +0 -7
  581. package/src/components/hook/healthWorkerAdapter.ts +0 -213
  582. package/src/components/hook/useFacilityInfo.tsx +0 -37
@@ -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
+ });
@@ -0,0 +1,191 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { showSnackbar, useLayoutType, Workspace2, type Workspace2DefinitionProps } from '@openmrs/esm-framework';
3
+ import {
4
+ Button,
5
+ ButtonSet,
6
+ FileUploader,
7
+ Form,
8
+ FormGroup,
9
+ InlineLoading,
10
+ InlineNotification,
11
+ RadioButton,
12
+ RadioButtonGroup,
13
+ Stack,
14
+ } from '@carbon/react';
15
+ import { useTranslation } from 'react-i18next';
16
+ import classNames from 'classnames';
17
+
18
+ import styles from './upload-logo.workspace.scss';
19
+ import { ALLOWED_LOGO_TYPES, MAX_LOGO_SIZE_BYTES, type LogoTarget, uploadLogo } from '../hooks/useLogoUpload';
20
+
21
+ type UploadLogoWorkspaceProps = {
22
+ mutateGlobalProperty?: () => void;
23
+ };
24
+
25
+ const UploadLogoWorkspace: React.FC<Workspace2DefinitionProps<UploadLogoWorkspaceProps, {}, {}>> = ({
26
+ closeWorkspace,
27
+ workspaceProps,
28
+ }) => {
29
+ const { t } = useTranslation();
30
+ const isTablet = useLayoutType() === 'tablet';
31
+ const mutateGlobalProperty = workspaceProps?.mutateGlobalProperty;
32
+
33
+ const [target, setTarget] = useState<LogoTarget>('prescription');
34
+ const [file, setFile] = useState<File | null>(null);
35
+ const [previewUrl, setPreviewUrl] = useState<string | null>(null);
36
+ const [validationError, setValidationError] = useState<string | null>(null);
37
+ const [isSubmitting, setIsSubmitting] = useState(false);
38
+
39
+ useEffect(() => {
40
+ if (!file) {
41
+ setPreviewUrl(null);
42
+ return;
43
+ }
44
+ const url = URL.createObjectURL(file);
45
+ setPreviewUrl(url);
46
+ return () => URL.revokeObjectURL(url);
47
+ }, [file]);
48
+
49
+ const handleFileChange = (selected: File | null) => {
50
+ setValidationError(null);
51
+ if (!selected) {
52
+ setFile(null);
53
+ return;
54
+ }
55
+ if (!ALLOWED_LOGO_TYPES.includes(selected.type)) {
56
+ setValidationError(t('unsupportedImageType', 'Unsupported file type. Accepted: JPEG, PNG, GIF, BMP or WebP.'));
57
+ setFile(null);
58
+ return;
59
+ }
60
+ if (selected.size > MAX_LOGO_SIZE_BYTES) {
61
+ setValidationError(t('imageTooLarge', 'File exceeds the 2 MB maximum size.'));
62
+ setFile(null);
63
+ return;
64
+ }
65
+ setFile(selected);
66
+ };
67
+
68
+ const onSubmit = async (event: React.FormEvent) => {
69
+ event.preventDefault();
70
+ if (!file) {
71
+ setValidationError(t('selectImageFirst', 'Please select an image to upload.'));
72
+ return;
73
+ }
74
+ setIsSubmitting(true);
75
+ try {
76
+ const { savedPath } = await uploadLogo(file, target);
77
+ const fileName = savedPath?.split(/[\\/]/).pop() ?? file.name;
78
+ showSnackbar({
79
+ title: t('success', 'Success'),
80
+ kind: 'success',
81
+ subtitle: t('logoUploaded', 'Image "{{fileName}}" uploaded successfully', { fileName }),
82
+ timeoutInMs: 5000,
83
+ });
84
+ mutateGlobalProperty?.();
85
+ closeWorkspace({ discardUnsavedChanges: true });
86
+ } catch (error: any) {
87
+ showSnackbar({
88
+ title: t('error', 'Error'),
89
+ kind: 'error',
90
+ subtitle: error?.message ?? t('logoUploadError', 'Error uploading image'),
91
+ });
92
+ } finally {
93
+ setIsSubmitting(false);
94
+ }
95
+ };
96
+
97
+ return (
98
+ <Workspace2 title={t('uploadImage', 'Upload image')} hasUnsavedChanges={Boolean(file)}>
99
+ <Form onSubmit={onSubmit} className={styles.form}>
100
+ <div className={styles.formContainer}>
101
+ <Stack gap={5}>
102
+ <InlineNotification
103
+ kind="info"
104
+ lowContrast
105
+ hideCloseButton
106
+ title={t('logoUploadTitle', 'Printed documents logo')}
107
+ subtitle={t(
108
+ 'logoUploadDesc',
109
+ 'Upload a logo used in printed documents. JPEG, PNG, GIF, BMP or WebP, up to 2 MB.',
110
+ )}
111
+ />
112
+
113
+ <FormGroup legendText={t('logoTarget', 'Logo destination')}>
114
+ <RadioButtonGroup
115
+ name="logo-target"
116
+ legendText=""
117
+ valueSelected={target}
118
+ onChange={(value: string) => setTarget(value as LogoTarget)}
119
+ disabled={isSubmitting}>
120
+ <RadioButton
121
+ labelText={t('prescriptionLogo', 'Prescription logo')}
122
+ value="prescription"
123
+ id="logo-target-prescription"
124
+ />
125
+ <RadioButton labelText={t('receiptLogo', 'Receipt logo')} value="receipt" id="logo-target-receipt" />
126
+ </RadioButtonGroup>
127
+ </FormGroup>
128
+
129
+ <FormGroup legendText={t('image', 'Image')}>
130
+ <FileUploader
131
+ labelTitle=""
132
+ labelDescription={t('logoUploaderHint', 'Max file size is 2 MB. Supported: JPEG, PNG, GIF, BMP, WebP.')}
133
+ buttonLabel={t('addFile', 'Add file')}
134
+ buttonKind="tertiary"
135
+ size="md"
136
+ filenameStatus="edit"
137
+ accept={ALLOWED_LOGO_TYPES}
138
+ multiple={false}
139
+ disabled={isSubmitting}
140
+ onChange={(e: { target: HTMLInputElement; addedFiles?: File[] }) => {
141
+ const selected = e.addedFiles?.[0] ?? e.target?.files?.[0] ?? null;
142
+ handleFileChange(selected);
143
+ }}
144
+ onDelete={() => handleFileChange(null)}
145
+ />
146
+ </FormGroup>
147
+
148
+ {validationError && (
149
+ <InlineNotification
150
+ kind="error"
151
+ lowContrast
152
+ hideCloseButton
153
+ title={t('invalidImage', 'Invalid image')}
154
+ subtitle={validationError}
155
+ />
156
+ )}
157
+
158
+ {previewUrl && (
159
+ <div className={styles.previewContainer}>
160
+ <span className={styles.previewLabel}>{t('preview', 'Preview')}</span>
161
+ <img src={previewUrl} alt={t('imagePreview', 'Image preview')} className={styles.preview} />
162
+ </div>
163
+ )}
164
+ </Stack>
165
+ </div>
166
+
167
+ <ButtonSet
168
+ className={classNames({
169
+ [styles.tablet]: isTablet,
170
+ [styles.desktop]: !isTablet,
171
+ })}>
172
+ <Button className={styles.buttonContainer} kind="secondary" onClick={() => closeWorkspace()}>
173
+ {t('cancel', 'Cancel')}
174
+ </Button>
175
+ <Button className={styles.buttonContainer} disabled={!file || isSubmitting} kind="primary" type="submit">
176
+ {isSubmitting ? (
177
+ <span className={styles.inlineLoading}>
178
+ {t('uploading', 'Uploading...')}
179
+ <InlineLoading status="active" iconDescription="Loading" />
180
+ </span>
181
+ ) : (
182
+ t('upload', 'Upload')
183
+ )}
184
+ </Button>
185
+ </ButtonSet>
186
+ </Form>
187
+ </Workspace2>
188
+ );
189
+ };
190
+
191
+ export default UploadLogoWorkspace;