@ampath/esm-dha-workflow-app 4.0.0-claim-workflows.159

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 (405) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintignore +2 -0
  3. package/.eslintrc +57 -0
  4. package/.husky/pre-commit +7 -0
  5. package/.husky/pre-push +6 -0
  6. package/.prettierignore +14 -0
  7. package/.turbo.json +18 -0
  8. package/.yarn/plugins/@yarnpkg/plugin-outdated.cjs +35 -0
  9. package/LICENSE +401 -0
  10. package/README.md +37 -0
  11. package/__mocks__/react-i18next.js +50 -0
  12. package/dist/1197.js +1 -0
  13. package/dist/1277.js +1 -0
  14. package/dist/1277.js.map +1 -0
  15. package/dist/1558.js +2 -0
  16. package/dist/1558.js.LICENSE.txt +14 -0
  17. package/dist/1558.js.map +1 -0
  18. package/dist/1729.js +1 -0
  19. package/dist/1729.js.map +1 -0
  20. package/dist/182.js +1 -0
  21. package/dist/182.js.map +1 -0
  22. package/dist/1826.js +1 -0
  23. package/dist/1826.js.map +1 -0
  24. package/dist/1833.js +1 -0
  25. package/dist/1833.js.map +1 -0
  26. package/dist/1856.js +1 -0
  27. package/dist/1856.js.map +1 -0
  28. package/dist/1925.js +1 -0
  29. package/dist/1925.js.map +1 -0
  30. package/dist/2021.js +1 -0
  31. package/dist/2021.js.map +1 -0
  32. package/dist/2054.js +1 -0
  33. package/dist/2054.js.map +1 -0
  34. package/dist/2177.js +2 -0
  35. package/dist/2177.js.LICENSE.txt +9 -0
  36. package/dist/2177.js.map +1 -0
  37. package/dist/2333.js +1 -0
  38. package/dist/2333.js.map +1 -0
  39. package/dist/2801.js +1 -0
  40. package/dist/2801.js.map +1 -0
  41. package/dist/282.js +2 -0
  42. package/dist/282.js.LICENSE.txt +32 -0
  43. package/dist/282.js.map +1 -0
  44. package/dist/3041.js +1 -0
  45. package/dist/3041.js.map +1 -0
  46. package/dist/3099.js +1 -0
  47. package/dist/3184.js +2 -0
  48. package/dist/3184.js.LICENSE.txt +14 -0
  49. package/dist/3184.js.map +1 -0
  50. package/dist/3654.js +1 -0
  51. package/dist/3654.js.map +1 -0
  52. package/dist/4055.js +1 -0
  53. package/dist/4205.js +1 -0
  54. package/dist/4205.js.map +1 -0
  55. package/dist/4225.js +1 -0
  56. package/dist/4225.js.map +1 -0
  57. package/dist/4300.js +1 -0
  58. package/dist/4335.js +1 -0
  59. package/dist/4353.js +1 -0
  60. package/dist/4353.js.map +1 -0
  61. package/dist/4517.js +1 -0
  62. package/dist/4517.js.map +1 -0
  63. package/dist/4652.js +1 -0
  64. package/dist/4674.js +1 -0
  65. package/dist/4674.js.map +1 -0
  66. package/dist/4947.js +1 -0
  67. package/dist/4947.js.map +1 -0
  68. package/dist/5015.js +1 -0
  69. package/dist/5015.js.map +1 -0
  70. package/dist/5422.js +1 -0
  71. package/dist/5422.js.map +1 -0
  72. package/dist/5428.js +1 -0
  73. package/dist/5428.js.map +1 -0
  74. package/dist/5752.js +1 -0
  75. package/dist/5752.js.map +1 -0
  76. package/dist/5851.js +1 -0
  77. package/dist/5851.js.map +1 -0
  78. package/dist/6260.js +1 -0
  79. package/dist/6260.js.map +1 -0
  80. package/dist/6264.js +1 -0
  81. package/dist/6264.js.map +1 -0
  82. package/dist/6540.js +2 -0
  83. package/dist/6540.js.LICENSE.txt +9 -0
  84. package/dist/6540.js.map +1 -0
  85. package/dist/6606.js +1 -0
  86. package/dist/6606.js.map +1 -0
  87. package/dist/693.js +1 -0
  88. package/dist/693.js.map +1 -0
  89. package/dist/6991.js +1 -0
  90. package/dist/6991.js.map +1 -0
  91. package/dist/7199.js +1 -0
  92. package/dist/7199.js.map +1 -0
  93. package/dist/7255.js +1 -0
  94. package/dist/7255.js.map +1 -0
  95. package/dist/7258.js +1 -0
  96. package/dist/7258.js.map +1 -0
  97. package/dist/7420.js +1 -0
  98. package/dist/7420.js.map +1 -0
  99. package/dist/8114.js +1 -0
  100. package/dist/8114.js.map +1 -0
  101. package/dist/8321.js +1 -0
  102. package/dist/8321.js.map +1 -0
  103. package/dist/8339.js +1 -0
  104. package/dist/8339.js.map +1 -0
  105. package/dist/8558.js +1 -0
  106. package/dist/8558.js.map +1 -0
  107. package/dist/8778.js +1 -0
  108. package/dist/8778.js.map +1 -0
  109. package/dist/8803.js +2 -0
  110. package/dist/8803.js.LICENSE.txt +54 -0
  111. package/dist/8803.js.map +1 -0
  112. package/dist/8865.js +1 -0
  113. package/dist/8865.js.map +1 -0
  114. package/dist/8895.js +1 -0
  115. package/dist/8895.js.map +1 -0
  116. package/dist/9037.js +1 -0
  117. package/dist/9037.js.map +1 -0
  118. package/dist/9091.js +1 -0
  119. package/dist/9091.js.map +1 -0
  120. package/dist/9093.js +1 -0
  121. package/dist/9093.js.map +1 -0
  122. package/dist/9117.js +1 -0
  123. package/dist/9117.js.map +1 -0
  124. package/dist/961.js +2 -0
  125. package/dist/961.js.LICENSE.txt +19 -0
  126. package/dist/961.js.map +1 -0
  127. package/dist/9721.js +1 -0
  128. package/dist/9721.js.map +1 -0
  129. package/dist/esm-dha-workflow-app.js +1 -0
  130. package/dist/esm-dha-workflow-app.js.buildmanifest.json +1594 -0
  131. package/dist/esm-dha-workflow-app.js.map +1 -0
  132. package/dist/main.js +1 -0
  133. package/dist/main.js.map +1 -0
  134. package/dist/routes.json +1 -0
  135. package/e2e/README.md +115 -0
  136. package/e2e/core/global-setup.ts +32 -0
  137. package/e2e/core/index.ts +1 -0
  138. package/e2e/core/test.ts +20 -0
  139. package/e2e/fixtures/api.ts +26 -0
  140. package/e2e/fixtures/index.ts +1 -0
  141. package/e2e/pages/index.ts +1 -0
  142. package/e2e/pages/root-page.ts +32 -0
  143. package/e2e/specs/template-app.spec.ts +23 -0
  144. package/e2e/support/github/Dockerfile +34 -0
  145. package/e2e/support/github/docker-compose.yml +24 -0
  146. package/e2e/support/github/run-e2e-docker-env.sh +37 -0
  147. package/example.env +6 -0
  148. package/jest.config.js +33 -0
  149. package/package.json +117 -0
  150. package/playwright.config.ts +32 -0
  151. package/prettier.config.js +8 -0
  152. package/src/accounting/accounting.component.tsx +13 -0
  153. package/src/admissions/admission-request-list/admission-request-list.scss +0 -0
  154. package/src/admissions/admission-request-list/admission-request-list.tsx +134 -0
  155. package/src/admissions/admissions-dashboard.component.scss +15 -0
  156. package/src/admissions/admissions-dashboard.component.tsx +160 -0
  157. package/src/admissions/admissions.resource.ts +101 -0
  158. package/src/admissions/admitted-list/admitted-patients-list.tsx +141 -0
  159. package/src/admissions/constants/index.ts +33 -0
  160. package/src/admissions/discharged-list/discharged-list.scss +0 -0
  161. package/src/admissions/discharged-list/discharged-list.tsx +58 -0
  162. package/src/admissions/inpatient/admission-history/patient-admission-history.tsx +76 -0
  163. package/src/admissions/inpatient/admission-requests/patient-admission-requests.scss +19 -0
  164. package/src/admissions/inpatient/admission-requests/patient-admission-requests.tsx +72 -0
  165. package/src/admissions/inpatient/inpatient-admissions.component.scss +7 -0
  166. package/src/admissions/inpatient/inpatient-admissions.component.tsx +46 -0
  167. package/src/admissions/modal/admit-elsewhere/admit-elsewhere.modal.scss +23 -0
  168. package/src/admissions/modal/admit-elsewhere/admit-elsewhere.modal.tsx +156 -0
  169. package/src/admissions/modal/admit-patient/admit-patient.modal.scss +0 -0
  170. package/src/admissions/modal/admit-patient/admit-patient.modal.tsx +109 -0
  171. package/src/admissions/modal/bed-swap/bed-swap.modal.scss +0 -0
  172. package/src/admissions/modal/bed-swap/bed-swap.modal.tsx +102 -0
  173. package/src/admissions/modal/cancel-admission-request/cancel-admission-request.scss +11 -0
  174. package/src/admissions/modal/cancel-admission-request/cancel-admission-request.tsx +105 -0
  175. package/src/admissions/modal/discharge/discharge-patient.modal.scss +13 -0
  176. package/src/admissions/modal/discharge/discharge-patient.modal.tsx +84 -0
  177. package/src/admissions/types/index.ts +192 -0
  178. package/src/appointments/appointments.component.tsx +13 -0
  179. package/src/billing/api/billing.api.ts +107 -0
  180. package/src/billing/billing-root.tsx +20 -0
  181. package/src/billing/dashboard/billingDashboard.component.scss +0 -0
  182. package/src/billing/dashboard/billingDashboard.component.tsx +25 -0
  183. package/src/billing/extensions/visit-billing/visit-billing.extension.scss +0 -0
  184. package/src/billing/extensions/visit-billing/visit-billing.extension.tsx +508 -0
  185. package/src/billing/invoice/bill.resource.ts +47 -0
  186. package/src/billing/invoice/invoice-header/header-card/header-card.scss +17 -0
  187. package/src/billing/invoice/invoice-header/header-card/header-card.tsx +21 -0
  188. package/src/billing/invoice/invoice.scss +113 -0
  189. package/src/billing/invoice/invoice.tsx +1441 -0
  190. package/src/billing/invoice/line-items/line-items.scss +0 -0
  191. package/src/billing/invoice/line-items/line-items.tsx +120 -0
  192. package/src/billing/invoice/modal/delete-bill-item/delete-bill-item.modal.scss +23 -0
  193. package/src/billing/invoice/modal/delete-bill-item/delete-bill-item.modal.tsx +114 -0
  194. package/src/billing/invoice/modal/edit-bill-item/edit-bill-item.modal.scss +0 -0
  195. package/src/billing/invoice/modal/edit-bill-item/edit-bill-item.modal.tsx +122 -0
  196. package/src/billing/invoice/payment-details/payment-details.scss +0 -0
  197. package/src/billing/invoice/payment-details/payment-details.tsx +43 -0
  198. package/src/billing/invoice/print-invoice/print-receipt.component.tsx +44 -0
  199. package/src/billing/invoice/print-invoice/print-receipt.scss +14 -0
  200. package/src/billing/types/index.ts +84 -0
  201. package/src/billing/utils/index.ts +17 -0
  202. package/src/billing/widgets/billingTotalsRow.component.scss +0 -0
  203. package/src/billing/widgets/billingTotalsRow.component.tsx +712 -0
  204. package/src/billing/workspaces/create-order-bill-form-workspace/create-order-bill-form.resource.ts +103 -0
  205. package/src/billing/workspaces/create-order-bill-form-workspace/create-order-bill-form.scss +58 -0
  206. package/src/billing/workspaces/create-order-bill-form-workspace/create-order-bill-form.workspace.tsx +357 -0
  207. package/src/billing/workspaces/create-order-bill-form-workspace/schema.ts +11 -0
  208. package/src/bookings/bookings.component.scss +7 -0
  209. package/src/bookings/bookings.component.tsx +31 -0
  210. package/src/bookings/daily/daily-bookings.component.scss +38 -0
  211. package/src/bookings/daily/daily-bookings.component.tsx +138 -0
  212. package/src/bookings/daily/daily-bookings.resource.ts +28 -0
  213. package/src/bookings/daily/filters/daily-bookings-filter.component.scss +15 -0
  214. package/src/bookings/daily/filters/daily-bookings-filter.component.tsx +80 -0
  215. package/src/bookings/daily/patient-list/daily-bookings-patient-list.component.tsx +97 -0
  216. package/src/bookings/types/index.ts +68 -0
  217. package/src/claims/claims.component.tsx +142 -0
  218. package/src/claims/claims.resource.ts +168 -0
  219. package/src/claims/index.ts +206 -0
  220. package/src/claims/interventions.resource.ts +49 -0
  221. package/src/claims/utils.ts +59 -0
  222. package/src/config-schema.ts +215 -0
  223. package/src/createDashboardLink.tsx +9 -0
  224. package/src/dashboard/charts/chart.component.scss +13 -0
  225. package/src/dashboard/charts/chart.component.tsx +56 -0
  226. package/src/dashboard/dashboard.component.scss +7 -0
  227. package/src/dashboard/dashboard.component.tsx +51 -0
  228. package/src/dashboard/overview/overview.component.scss +150 -0
  229. package/src/dashboard/overview/overview.component.tsx +134 -0
  230. package/src/dashboard/patient-list/patient-list.component.tsx +41 -0
  231. package/src/dashboard-meta/accounting-dashboard.meta.ts +6 -0
  232. package/src/dashboard-meta/admissions-dashboard.meta.ts +6 -0
  233. package/src/dashboard-meta/billing-dashboard.meta.ts +6 -0
  234. package/src/dashboard-meta/bookings-dashboard.meta.ts +6 -0
  235. package/src/dashboard-meta/consultation-dashboard.meta.ts +6 -0
  236. package/src/dashboard-meta/dha-workflow-dashboard.meta.ts +6 -0
  237. package/src/dashboard-meta/inpatient-admissions.meta.ts +6 -0
  238. package/src/dashboard-meta/maternity-dashboard.meta.ts +6 -0
  239. package/src/dashboard-meta/mch-dashboard.meta.ts +6 -0
  240. package/src/dashboard-meta/pharmacy-dashboard.meta.ts +6 -0
  241. package/src/dashboard-meta/queue-dashboard.meta.ts +6 -0
  242. package/src/dashboard-meta/registers-dashboard.meta.ts +6 -0
  243. package/src/dashboard-meta/registry-dashboard.meta.ts +6 -0
  244. package/src/dashboard-meta/service-queue-admin.meta.ts +6 -0
  245. package/src/dashboard-meta/triage-dashboard.meta.ts +6 -0
  246. package/src/declarations.d.ts +5 -0
  247. package/src/hooks/useActions.ts +165 -0
  248. package/src/index.ts +161 -0
  249. package/src/laboratory/laboratory.component.tsx +13 -0
  250. package/src/left-panel/left-panel.component.tsx +20 -0
  251. package/src/left-panel/left-panel.scss +42 -0
  252. package/src/mnch/maternity/maternity-queues.component.scss +7 -0
  253. package/src/mnch/maternity/maternity-queues.component.tsx +28 -0
  254. package/src/mnch/maternity/queues/triage/maternity-triage.tsx +15 -0
  255. package/src/mnch/mch/mch-queues.component.scss +7 -0
  256. package/src/mnch/mch/mch-queues.component.tsx +32 -0
  257. package/src/mnch/mch/queues/consultation/mch-consultation.tsx +18 -0
  258. package/src/mnch/mch/queues/triage/mch-triage.tsx +15 -0
  259. package/src/mnch/mnch-root.tsx +17 -0
  260. package/src/modals/sign-off-modal.scss +7 -0
  261. package/src/modals/sign-off-modal.tsx +52 -0
  262. package/src/mortuary/mortuary.component.tsx +13 -0
  263. package/src/pharmacy/pharmacy.component.tsx +13 -0
  264. package/src/registers/registers.component.tsx +8 -0
  265. package/src/registry/client-details/client-details.tsx +40 -0
  266. package/src/registry/eligibility/eligibility-details/eligibility-details.scss +38 -0
  267. package/src/registry/eligibility/eligibility-details/eligibility-details.tsx +101 -0
  268. package/src/registry/eligibility/eliigibility-tags/eligibility-tags.scss +6 -0
  269. package/src/registry/eligibility/eliigibility-tags/eligibility-tags.tsx +120 -0
  270. package/src/registry/eligibility/modal/eligibility-details.modal.scss +0 -0
  271. package/src/registry/eligibility/modal/eligibility-details.modal.tsx +45 -0
  272. package/src/registry/modal/client-details-modal/client-details-modal.scss +28 -0
  273. package/src/registry/modal/client-details-modal/client-details-modal.tsx +119 -0
  274. package/src/registry/modal/otp-verification-modal/otp-verification-modal.scss +43 -0
  275. package/src/registry/modal/otp-verification-modal/otp-verification-modal.tsx +280 -0
  276. package/src/registry/modal/send-to-triage/send-to-triage.modal.scss +39 -0
  277. package/src/registry/modal/send-to-triage/send-to-triage.modal.tsx +921 -0
  278. package/src/registry/payment-details/payment-options/payment-options.scss +13 -0
  279. package/src/registry/payment-details/payment-options/payment-options.tsx +68 -0
  280. package/src/registry/registry.component.scss +89 -0
  281. package/src/registry/registry.component.tsx +490 -0
  282. package/src/registry/registry.resource.ts +62 -0
  283. package/src/registry/types/index.ts +401 -0
  284. package/src/registry/utils/error-handler.ts +37 -0
  285. package/src/registry/utils/format-dependant-display-data.ts +8 -0
  286. package/src/registry/utils/hie-adapter.ts +56 -0
  287. package/src/registry/utils/hie-client-adapter.ts +312 -0
  288. package/src/registry/utils/mask-data.ts +32 -0
  289. package/src/registry/utils/phone-number-formatter.ts +11 -0
  290. package/src/resources/dashboard.resource.ts +20 -0
  291. package/src/resources/hie-amrs-automatic-registration.service.ts +16 -0
  292. package/src/resources/identifier-types.ts +29 -0
  293. package/src/resources/patient-resource.ts +62 -0
  294. package/src/resources/patient-search.resource.ts +22 -0
  295. package/src/resources/queue.resource.ts +60 -0
  296. package/src/resources/superset.resource.ts +21 -0
  297. package/src/resources/visit.resource.ts +38 -0
  298. package/src/root.component.tsx +48 -0
  299. package/src/root.scss +11 -0
  300. package/src/root.test.tsx +51 -0
  301. package/src/routes.json +292 -0
  302. package/src/service-queues/action-button.component.tsx +34 -0
  303. package/src/service-queues/action-overflow-menu-item.component.tsx +34 -0
  304. package/src/service-queues/admin/queue/modal/create-queue/create-queue.modal.scss +31 -0
  305. package/src/service-queues/admin/queue/modal/create-queue/create-queue.modal.tsx +143 -0
  306. package/src/service-queues/admin/queue/modal/edit-queue/edit-queue.modal.scss +0 -0
  307. package/src/service-queues/admin/queue/modal/edit-queue/edit-queue.modal.tsx +154 -0
  308. package/src/service-queues/admin/queue/queue-list/queue-list.scss +33 -0
  309. package/src/service-queues/admin/queue/queue-list/queue-list.tsx +113 -0
  310. package/src/service-queues/admin/queue-entry/queue-entry-list.component.scss +6 -0
  311. package/src/service-queues/admin/queue-entry/queue-entry-list.component.tsx +137 -0
  312. package/src/service-queues/admin/queue-room/modal/create-queue-room/create-queue-room.modal.scss +31 -0
  313. package/src/service-queues/admin/queue-room/modal/create-queue-room/create-queue-room.modal.tsx +137 -0
  314. package/src/service-queues/admin/queue-room/modal/edit-queue-room/edit-queue-room.modal.scss +0 -0
  315. package/src/service-queues/admin/queue-room/modal/edit-queue-room/edit-queue-room.modal.tsx +146 -0
  316. package/src/service-queues/admin/queue-room/queue-room-list/queue-room-list.scss +22 -0
  317. package/src/service-queues/admin/queue-room/queue-room-list/queue-room-list.tsx +120 -0
  318. package/src/service-queues/admin/service-queue-admin-dashboard.component.scss +6 -0
  319. package/src/service-queues/admin/service-queue-admin-dashboard.component.tsx +34 -0
  320. package/src/service-queues/consultation/consultation.component.scss +7 -0
  321. package/src/service-queues/consultation/consultation.component.tsx +15 -0
  322. package/src/service-queues/extensions/service-queue-patient-banner/service-queue-patient-banner.scss +30 -0
  323. package/src/service-queues/extensions/service-queue-patient-banner/service-queue-patient-banner.tsx +108 -0
  324. package/src/service-queues/metrics/metrics-cards/attended-patients.extension.tsx +38 -0
  325. package/src/service-queues/metrics/metrics-cards/metrics-card.component.tsx +86 -0
  326. package/src/service-queues/metrics/metrics-cards/metrics-card.scss +106 -0
  327. package/src/service-queues/metrics/metrics-cards/waiting-patients.extension.tsx +34 -0
  328. package/src/service-queues/metrics/metrics-container.component.tsx +23 -0
  329. package/src/service-queues/metrics/metrics-container.scss +36 -0
  330. package/src/service-queues/metrics/metrics.resource.ts +65 -0
  331. package/src/service-queues/modals/daily-report-patient-list/daily-report-patient-list.scss +26 -0
  332. package/src/service-queues/modals/daily-report-patient-list/daily-report-patient-list.tsx +110 -0
  333. package/src/service-queues/modals/move/move-patient.component.scss +35 -0
  334. package/src/service-queues/modals/move/move-patient.component.tsx +152 -0
  335. package/src/service-queues/modals/serve/serve-patient.comppnent.scss +0 -0
  336. package/src/service-queues/modals/serve/serve-patient.comppnent.tsx +80 -0
  337. package/src/service-queues/modals/sign-off/sign-off.modal.scss +0 -0
  338. package/src/service-queues/modals/sign-off/sign-off.modal.tsx +79 -0
  339. package/src/service-queues/modals/transition/transition-patient.component.scss +0 -0
  340. package/src/service-queues/modals/transition/transition-patient.component.tsx +122 -0
  341. package/src/service-queues/queue-list/check-in.service.ts +26 -0
  342. package/src/service-queues/queue-list/queue-list.component.scss +56 -0
  343. package/src/service-queues/queue-list/queue-list.component.tsx +311 -0
  344. package/src/service-queues/queue-room.component.tsx +39 -0
  345. package/src/service-queues/room/room.component.tsx +58 -0
  346. package/src/service-queues/service-queue/service-queue.component.scss +14 -0
  347. package/src/service-queues/service-queue/service-queue.component.tsx +387 -0
  348. package/src/service-queues/service-queue/stats/stat-details/stat-details.component.scss +12 -0
  349. package/src/service-queues/service-queue/stats/stat-details/stat-details.component.tsx +63 -0
  350. package/src/service-queues/service-queue.scss +27 -0
  351. package/src/service-queues/service-queue.tsx +37 -0
  352. package/src/service-queues/service-queues.resource.ts +361 -0
  353. package/src/service-queues/service.resource.ts +28 -0
  354. package/src/shared/constants/civil-status.ts +29 -0
  355. package/src/shared/constants/concepts.ts +35 -0
  356. package/src/shared/constants/index.ts +1 -0
  357. package/src/shared/constants/patient-category.ts +6 -0
  358. package/src/shared/constants/patient-type.ts +10 -0
  359. package/src/shared/constants/person-attributes.ts +33 -0
  360. package/src/shared/constants/visit-types.ts +7 -0
  361. package/src/shared/services/billing.resource.ts +38 -0
  362. package/src/shared/services/client-payment-mode.resource.ts +27 -0
  363. package/src/shared/services/client-payment.resource.ts +17 -0
  364. package/src/shared/services/eligibility.resource.ts +20 -0
  365. package/src/shared/services/encounters.resource.ts +27 -0
  366. package/src/shared/services/location.resource.ts +9 -0
  367. package/src/shared/types/index.ts +153 -0
  368. package/src/shared/ui/aggregate-stat-card/aggregate-stat-card.scss +42 -0
  369. package/src/shared/ui/aggregate-stat-card/aggregate-stat-card.tsx +42 -0
  370. package/src/shared/ui/confirm-modal/confirm.modal.scss +18 -0
  371. package/src/shared/ui/confirm-modal/confirm.modal.tsx +38 -0
  372. package/src/shared/ui/otp-input/otp-input.component.scss +14 -0
  373. package/src/shared/ui/otp-input/otp-input.component.tsx +90 -0
  374. package/src/shared/ui/stat-card/stat-card.component.scss +10 -0
  375. package/src/shared/ui/stat-card/stat-card.component.tsx +26 -0
  376. package/src/shared/ui/timer/timer.component.scss +5 -0
  377. package/src/shared/ui/timer/timer.component.tsx +40 -0
  378. package/src/shared/utils/get-base-url.ts +27 -0
  379. package/src/shared/utils/get-tag-type.ts +66 -0
  380. package/src/side-nav/side-menu.scss +38 -0
  381. package/src/side-nav-menu/mnch-nav-link-config.ts +16 -0
  382. package/src/side-nav-menu/mnch-nav-links.tsx +42 -0
  383. package/src/side-nav-menu/nav-link-config.ts +78 -0
  384. package/src/side-nav-menu/nav-links.tsx +42 -0
  385. package/src/triage/metrics/attended-patients.extension.tsx +42 -0
  386. package/src/triage/metrics/metrics.scss +36 -0
  387. package/src/triage/metrics/triage-metrics.component.tsx +21 -0
  388. package/src/triage/metrics/waiting-patients.extension.tsx +39 -0
  389. package/src/triage/room/room.scss +29 -0
  390. package/src/triage/triage.component.tsx +15 -0
  391. package/src/triage/triage.resource.ts +19 -0
  392. package/src/triage/types.ts +16 -0
  393. package/src/types/types.ts +180 -0
  394. package/src/widgets/workflow-registry-link.extension.tsx +12 -0
  395. package/tools/i18next-parser.config.js +89 -0
  396. package/tools/setup-tests.ts +1 -0
  397. package/tools/update-openmrs-deps.mjs +43 -0
  398. package/translations/am.json +24 -0
  399. package/translations/en.json +24 -0
  400. package/translations/es.json +24 -0
  401. package/translations/fr.json +24 -0
  402. package/translations/he.json +24 -0
  403. package/translations/km.json +24 -0
  404. package/tsconfig.json +24 -0
  405. package/webpack.config.js +1 -0
@@ -0,0 +1,1441 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ Grid,
4
+ Column,
5
+ Tile,
6
+ DataTable,
7
+ Table,
8
+ TableHead,
9
+ TableRow,
10
+ TableHeader,
11
+ TableBody,
12
+ TableCell,
13
+ Breadcrumb,
14
+ BreadcrumbItem,
15
+ Button,
16
+ Modal,
17
+ TextInput,
18
+ NumberInput,
19
+ Tag,
20
+ Stack,
21
+ Checkbox,
22
+ OverflowMenu,
23
+ OverflowMenuItem,
24
+ SkeletonText,
25
+ SkeletonPlaceholder,
26
+ } from '@carbon/react';
27
+ import { navigate, showSnackbar } from '@openmrs/esm-framework';
28
+
29
+ import {
30
+ Receipt,
31
+ DocumentAdd,
32
+ Subtract,
33
+ Money,
34
+ View,
35
+ CheckmarkFilled,
36
+ WarningAlt,
37
+ PendingFilled,
38
+ ResultNew,
39
+ } from '@carbon/react/icons';
40
+ import {
41
+ fetchBillById,
42
+ processPayment,
43
+ fetchPaymentModes,
44
+ checkClaimStatus,
45
+ raiseSHAClaim,
46
+ UpdateBillItemStatus,
47
+ finalizeBill,
48
+ } from '../api/billing.api';
49
+ import EligibilityTags from '../../registry/eligibility/eliigibility-tags/eligibility-tags';
50
+
51
+ type LineItemStatus = 'PENDING' | 'PAID' | 'CLAIMED' | 'WAIVED';
52
+
53
+
54
+ type LineItem = {
55
+ id: string;
56
+ service: string;
57
+ dept: string;
58
+ price: number;
59
+ payerType: 'CASH' | 'SHA';
60
+ supportsSha: boolean;
61
+ quantity: number;
62
+ paidQuantity?: number;
63
+ waivedAmount?: number;
64
+ claimedAmount?: number;
65
+ waiverAllowed: boolean;
66
+ status: LineItemStatus;
67
+ paymentReference?: string;
68
+ selected?: boolean;
69
+ };
70
+
71
+ type PaymentLog = {
72
+ id: string;
73
+ type: 'CASH' | 'WAIVER' | 'SHA';
74
+ items: LineItem[];
75
+ totalAmount: number;
76
+ reference?: string;
77
+ claimNumber?: string;
78
+ timestamp: string;
79
+ };
80
+
81
+ const headers = [
82
+ { key: 'service', header: 'Bill Item' },
83
+ { key: 'payerType', header: 'Payer' },
84
+ { key: 'quantity', header: 'Quantity' },
85
+ { key: 'price', header: 'Price' },
86
+ { key: 'balance', header: 'Total' },
87
+ { key: 'status', header: 'Status' },
88
+ { key: 'select', header: 'Select' },
89
+ { key: 'actions', header: 'Actions' },
90
+ ];
91
+
92
+ const INSURANCE_MODES = ['PHC', 'SHIF', 'ECCIF', 'SHA'];
93
+ const CASH_MODES = ['CASH', 'MPESA'];
94
+
95
+ const formatBillDate = (dateString?: string) => {
96
+ if (!dateString) return '';
97
+
98
+ const date = new Date(dateString);
99
+ const now = new Date();
100
+
101
+ const isToday =
102
+ date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate();
103
+
104
+ const yesterday = new Date();
105
+ yesterday.setDate(now.getDate() - 1);
106
+ const isYesterday =
107
+ date.getFullYear() === yesterday.getFullYear() &&
108
+ date.getMonth() === yesterday.getMonth() &&
109
+ date.getDate() === yesterday.getDate();
110
+
111
+ const timeStr = date.toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric', hour12: true });
112
+
113
+ if (isToday) return `Today, ${timeStr}`;
114
+ if (isYesterday) return `Yesterday, ${timeStr}`;
115
+
116
+ return date.toLocaleString(undefined, {
117
+ year: 'numeric',
118
+ month: 'short',
119
+ day: 'numeric',
120
+ hour: 'numeric',
121
+ minute: 'numeric',
122
+ hour12: true,
123
+ });
124
+ };
125
+
126
+ const formatCurrency = (value: number) => `Ksh ${value.toLocaleString()}`;
127
+
128
+ // Add headers for transaction logs
129
+ const logHeaders = [
130
+ { key: 'timestamp', header: 'Timestamp' },
131
+ { key: 'type', header: 'Type' },
132
+ { key: 'items', header: 'Items' },
133
+ { key: 'totalAmount', header: 'Amount' },
134
+ { key: 'reference', header: 'Reference / Claim #' },
135
+ ];
136
+
137
+ const navigateToBillingPage = () => {
138
+ navigate({ to: `${window.spaBase}/home/billing`, templateParams: {} });
139
+ };
140
+
141
+ const getBillIdFromUrl = () => {
142
+ const parts = window.location.pathname.split('/');
143
+ return parts[parts.length - 1];
144
+ };
145
+
146
+ const BillDetails: React.FC = () => {
147
+ const [bill, setBill] = useState<any>(null);
148
+ const [items, setItems] = useState<LineItem[]>([]);
149
+ const [loading, setLoading] = useState(true);
150
+
151
+ const billId = getBillIdFromUrl();
152
+
153
+ useEffect(() => {
154
+ async function loadBill() {
155
+ try {
156
+ const data = await fetchBillById(billId);
157
+
158
+ setBill(data);
159
+
160
+ const mappedItems: LineItem[] =
161
+ data?.lineItems?.map((li: any) => ({
162
+ id: li.uuid,
163
+ service: li.billableService || 'Service',
164
+ payerType: (li.priceName && li.priceName.toUpperCase()) || 'CASH',
165
+ supportsSha: li.supportsSha || false,
166
+ waiverAllowed: li.waiverAllowed || false,
167
+ quantity: li.quantity || 1,
168
+ price: li.price || 0,
169
+ status: (li.paymentStatus?.toUpperCase() as LineItemStatus) || 'PENDING',
170
+ })) || [];
171
+
172
+ setItems(mappedItems);
173
+ } catch (error) {
174
+ console.error('Failed to fetch bill', error);
175
+ } finally {
176
+ setLoading(false);
177
+ }
178
+ }
179
+
180
+ loadBill();
181
+ }, [billId, setItems]);
182
+
183
+ const billNumber = bill?.receiptNumber || bill?.id || '';
184
+ const cashPoint = bill?.cashPoint?.name || '';
185
+ const billState = bill?.status || '';
186
+ const patientName = bill?.patient?.display || '';
187
+
188
+ const [logs, setLogs] = useState<PaymentLog[]>([]);
189
+ const [modalType, setModalType] = useState<
190
+ 'CASH' | 'WAIVER' | 'SHA' | 'CHECK_SHA' | 'EDIT_QUANTITY' | 'CONFIRM_DELETE' | null
191
+ >(null);
192
+ const [reference, setReference] = useState('');
193
+ const [waiverType, setWaiverType] = useState<'PERCENT' | 'AMOUNT'>('AMOUNT');
194
+
195
+ const [waiverPercent, setWaiverPercent] = useState('');
196
+ const [waiverAmount, setWaiverAmount] = useState('');
197
+ const [editItem, setEditItem] = useState<LineItem | null>(null);
198
+ const [editQuantity, setEditQuantity] = useState<number>(1);
199
+
200
+ const [claimResponse, setClaimResponse] = useState<any | null>(null);
201
+ const [checkingClaim, setCheckingClaim] = useState(false);
202
+
203
+ const percent = Number(waiverPercent || 0);
204
+ const amount = Number(waiverAmount || 0);
205
+
206
+ useEffect(() => {
207
+ setWaiverPercent('');
208
+ setWaiverAmount('');
209
+ }, [waiverType]);
210
+
211
+ const getBalance = (i: LineItem) =>
212
+ i.price * i.quantity - (i.paidQuantity || 0) * i.price - (i.waivedAmount || 0) - (i.claimedAmount || 0);
213
+
214
+ const toggleSelect = (id: string) =>
215
+ setItems((prev) => prev.map((i) => (i.id === id ? { ...i, selected: !i.selected } : i)));
216
+ const deleteItem = (id: string) => setItems((prev) => prev.filter((i) => i.id !== id));
217
+
218
+ const addLog = (type: 'CASH' | 'WAIVER' | 'SHA', affectedItems: LineItem[], totalAmount: number) => {
219
+ setLogs((prev) => [
220
+ ...prev,
221
+ {
222
+ id: `${type}-${prev.length + 1}`,
223
+ type,
224
+ items: affectedItems,
225
+ totalAmount,
226
+ reference: type === 'CASH' ? reference : undefined,
227
+ timestamp: new Date().toLocaleString(),
228
+ },
229
+ ]);
230
+ };
231
+
232
+ // SHA flags
233
+ const shaOnlyBill = items.length > 0 && items.every((i) => (i.payerType || '').toUpperCase() === 'SHA');
234
+
235
+ const pendingShaItems = items.filter(
236
+ (i) => i.status === 'PENDING' && INSURANCE_MODES.includes(i.payerType.toUpperCase()),
237
+ );
238
+
239
+ const hasPendingSha = pendingShaItems.length > 0;
240
+
241
+ const isSha = (i: LineItem) => INSURANCE_MODES.includes((i.payerType ?? '').toUpperCase());
242
+ const status = (i: LineItem) => (i.status ?? '').toUpperCase();
243
+
244
+ const claimedShaItems = items.filter((i) => isSha(i) && ['CLAIMED', 'PAID'].includes(status(i)));
245
+
246
+ const hasClaimedSha = claimedShaItems.length > 0;
247
+
248
+ // All Cash items fully paid
249
+ const allCashSettled = items
250
+ .filter((i) =>
251
+ CASH_MODES.includes((i.payerType ?? '').toUpperCase())
252
+ )
253
+ .every((i) => (i.status ?? '').toUpperCase() === 'PAID');
254
+
255
+ const selectedCashItems = items.filter(
256
+ (i) =>
257
+ i.selected &&
258
+ CASH_MODES.includes((i.payerType ?? '').toUpperCase()) &&
259
+ i.status === 'PENDING'
260
+ );
261
+
262
+ // const totalBalance = items.reduce((acc, i) => acc + getBalance(i), 0);
263
+ const totalPaid = items.reduce((acc, i) => {
264
+ const payer = (i.payerType ?? '').toString().trim().toUpperCase();
265
+ const status = (i.status ?? '').toString().trim().toUpperCase();
266
+ const qty = Number(i.quantity ?? 0);
267
+ const price = Number(i.price ?? 0);
268
+
269
+ if (CASH_MODES.includes(payer) && status === 'PAID') {
270
+ return acc + qty * price;
271
+ }
272
+
273
+ return acc;
274
+ }, 0);
275
+
276
+ const billTotal = items.reduce((acc, i) => acc + i.price * i.quantity, 0);
277
+ const totalWaived = items.reduce((acc, i) => acc + (i.waivedAmount || 0), 0);
278
+
279
+ const totalClaimed = items.reduce((acc, i) => {
280
+ const payer = (i.payerType ?? '').toUpperCase();
281
+ const status = (i.status ?? '').toUpperCase();
282
+
283
+ if (INSURANCE_MODES.includes(payer) && (status === 'CLAIMED' || status === 'PAID')) {
284
+ return acc + (i.claimedAmount ?? i.price * i.quantity);
285
+ }
286
+
287
+ return acc;
288
+ }, 0);
289
+
290
+ const schemes = [...new Set(pendingShaItems.map((i) => i.payerType))].join(', ');
291
+
292
+ const showAlert = (type: string, title: string, subTitle: string) => {
293
+ showSnackbar({
294
+ kind: type,
295
+ title: title,
296
+ subtitle: subTitle,
297
+ });
298
+ };
299
+
300
+ // Balance per item: remaining amount to be settled
301
+ const totalBalance = billTotal - (totalPaid + totalClaimed + totalWaived);
302
+
303
+ // Auto-select SHA items
304
+ useEffect(() => {
305
+ setItems((prev) => {
306
+ if (prev.length === 0) return prev;
307
+
308
+ return prev.map((i) => {
309
+ if (shaOnlyBill) return { ...i, selected: true };
310
+
311
+ if (i.status === 'PENDING' && i.payerType.toUpperCase() === 'SHA') {
312
+ return { ...i, selected: true };
313
+ }
314
+
315
+ return i;
316
+ });
317
+ });
318
+ }, [shaOnlyBill]);
319
+
320
+ const crId =
321
+ bill?.patient?.identifiers?.find((id: any) => id.identifierType?.display?.toLowerCase().includes('cr'))
322
+ ?.identifier || '';
323
+
324
+ const locationUuid = bill?.cashPoint?.location?.uuid || '';
325
+
326
+ // ----- Render Raise SHA Claim Button -----
327
+ const showShaClaimButton = () => {
328
+ // Show Raise Claim if pending SHA exists
329
+ if (hasPendingSha && (shaOnlyBill || allCashSettled)) {
330
+ return 'RAISE';
331
+ }
332
+
333
+ // Show Check Status if all SHA items are already claimed/paid
334
+ if (!hasPendingSha && hasClaimedSha) {
335
+ return 'STATUS';
336
+ }
337
+
338
+ return null;
339
+ };
340
+
341
+ const statusTag = (status: LineItemStatus) => {
342
+ switch (status) {
343
+ case 'PAID':
344
+ return <Tag type="green">Paid (Cash)</Tag>;
345
+ case 'CLAIMED':
346
+ return <Tag type="blue">Claimed</Tag>;
347
+ case 'WAIVED':
348
+ return <Tag type="purple">Waived</Tag>;
349
+ default:
350
+ return <Tag type="red">Pending</Tag>;
351
+ }
352
+ };
353
+
354
+ // Prepare transaction rows
355
+ const transactionRows = logs.map((l) => ({
356
+ id: l.id,
357
+ timestamp: l.timestamp,
358
+ type: l.type,
359
+ items: l.items.map((i) => i.service).join(', '),
360
+ totalAmount: l.totalAmount,
361
+ reference: l.reference ?? l.claimNumber ?? '-',
362
+ }));
363
+
364
+ const billStatus = () => {
365
+ if (totalBalance === 0) return 'Fully Settled';
366
+ if (totalBalance < billTotal) return 'Partially Settled';
367
+ return 'Unpaid';
368
+ };
369
+
370
+ const processCashPayment = async () => {
371
+ if (!reference.trim()) return showAlert('error', 'Cash Payment', 'Payment reference required');
372
+
373
+ if (selectedCashItems.length === 0) showAlert('error', 'Cash Payment', 'No items selected');
374
+
375
+ try {
376
+ setCashLoading(true);
377
+ // 1️⃣ Fetch payment modes and find Cash UUID
378
+ const paymentModesResponse = await fetchPaymentModes();
379
+
380
+ // Find the UUID for Cash
381
+
382
+ // const cashMode = paymentModesResponse.results.find((mode) => mode.name.toLowerCase() === 'cash');
383
+ // if (!cashMode) {
384
+ // setCashLoading(false);
385
+ // showAlert('error', 'Cash Payment', 'Cash payment mode not found');
386
+ // return;
387
+ // }
388
+ // const cashUuid = cashMode.uuid;
389
+
390
+ const selectedPaymentType = selectedCashItems[0]?.payerType?.toUpperCase();
391
+
392
+ const paymentMode = paymentModesResponse.results.find(
393
+ (mode) =>
394
+ (mode.name ?? '').toUpperCase() === selectedPaymentType,
395
+ );
396
+
397
+ if (!paymentMode) {
398
+ setCashLoading(false);
399
+
400
+ showAlert(
401
+ 'error',
402
+ selectedPaymentType || 'Payment',
403
+ `${selectedPaymentType || 'Selected'} payment mode not found`,
404
+ );
405
+
406
+ return;
407
+ }
408
+
409
+ const paymentUuid = paymentMode.uuid;
410
+
411
+ const selectedPaymentName =
412
+ selectedCashItems.length > 0
413
+ ? selectedCashItems[0]?.payerType || 'Payment'
414
+ : 'Payment';
415
+
416
+ // 2️⃣ Prepare payload for line item payments
417
+ const payload = {
418
+ instanceType: paymentUuid,
419
+ amount: selectedCashItems.reduce((acc, i) => acc + getBalance(i), 0),
420
+ amountTendered: selectedCashItems.reduce((acc, i) => acc + getBalance(i), 0),
421
+ };
422
+
423
+ // 3️⃣ Call payment endpoint
424
+ const response = await processPayment(billId, payload);
425
+ if (!response.ok) {
426
+ setCashLoading(false);
427
+ showAlert('error', 'Cash Payment', 'Payment processing failed');
428
+ return;
429
+ }
430
+
431
+ const statusPayload = {
432
+ billUuid: billId,
433
+ cashModeUuid: paymentUuid,
434
+ billItemsUuid: selectedCashItems.map((item) => item.id),
435
+ };
436
+
437
+ const statusResponse = await UpdateBillItemStatus(statusPayload);
438
+ if (!statusResponse?.success) {
439
+ setCashLoading(false);
440
+ showAlert('error', 'Cash Payment', statusResponse?.message ?? 'Status update failed');
441
+ return;
442
+ }
443
+
444
+ showAlert('success', 'Cash Payment', 'Payment processed successfully');
445
+
446
+ // 4️⃣ Re-fetch bill and update local state instead of manual update
447
+ const refreshedBill = await fetchBillById(billId);
448
+
449
+ setBill(refreshedBill);
450
+
451
+ const mappedItems: LineItem[] =
452
+ refreshedBill?.lineItems?.map((li: any) => ({
453
+ id: li.uuid,
454
+ service: li.billableService || 'Service',
455
+ payerType: (li.priceName && li.priceName.toUpperCase()) || 'CASH',
456
+ supportsSha: li.supportsSha || false,
457
+ waiverAllowed: li.waiverAllowed || false,
458
+ quantity: li.quantity || 1,
459
+ price: li.price || 0,
460
+ status: (li.paymentStatus && li.paymentStatus.toUpperCase()) || 'CASH',
461
+ })) || [];
462
+
463
+ setItems(mappedItems);
464
+
465
+ addLog('CASH', selectedCashItems, payload.amountTendered);
466
+
467
+ setReference('');
468
+ setModalType(null);
469
+ setCashLoading(false);
470
+ } catch (error) {
471
+ console.error('Payment failed', error);
472
+ setCashLoading(false);
473
+ showAlert('error', 'Cash Payment', 'Payment failed. Please try again.');
474
+ }
475
+ };
476
+
477
+ const paymentName =
478
+ selectedCashItems.length > 0
479
+ ? selectedCashItems[0].payerType
480
+ : 'Payment';
481
+
482
+ const handleCheckSHAClaim = async () => {
483
+ if (!billId) return showSnackbar({ kind: 'error', title: 'SHA Claim Status', subtitle: 'Bill ID is required' });
484
+
485
+ setCheckingClaim(true);
486
+
487
+ const res = await checkClaimStatus(billId);
488
+
489
+ if (!res.success) {
490
+ setCheckingClaim(false);
491
+ return showSnackbar({
492
+ kind: 'error',
493
+ title: 'SHA Claim Status',
494
+ subtitle: res.message,
495
+ });
496
+ }
497
+
498
+ setClaimResponse(res.data);
499
+ setModalType('CHECK_SHA');
500
+ setCheckingClaim(false);
501
+ };
502
+
503
+ const applyWaiver = async () => {
504
+ return showAlert(
505
+ 'error',
506
+ 'Waiver Application',
507
+ 'Waiver functionality is currently unavailable. Please contact support.',
508
+ );
509
+
510
+ setWaiverLoading(true);
511
+
512
+ const appliedAmount = waiverType === 'PERCENT' ? Math.round((totalBalance * percent) / 100) : waiverAmount;
513
+ if (amount <= 0) return;
514
+ showAlert('error', 'Waiver Application', 'Waiver amount must be greater than zero');
515
+
516
+ // 1️⃣ Fetch payment modes and find Cash UUID
517
+ const waiverModesResponse = await fetchPaymentModes();
518
+
519
+ // Find the UUID for Cash
520
+ const waiverMode = waiverModesResponse.results.find((mode) => mode.name.toLowerCase() === 'waiver');
521
+ if (!waiverMode) showAlert('error', 'Waiver Application', 'Waiver payment mode not found. Please contact support.');
522
+
523
+ const waiverUuid = waiverMode.uuid;
524
+
525
+ // 2️⃣ Prepare payload for line item payments
526
+ const payload = {
527
+ instanceType: waiverUuid,
528
+ amount: appliedAmount,
529
+ amountTendered: appliedAmount,
530
+ };
531
+
532
+ // 3️⃣ Call payment endpoint
533
+ const response = await processPayment(billId, payload);
534
+ if (!response.ok)
535
+ showAlert('error', 'Waiver Application', 'Failed to apply waiver. Please try again or contact support.');
536
+
537
+ showAlert('success', 'Waiver Application', 'Waiver applied successfully');
538
+
539
+ // 4️⃣ Re-fetch bill and update local state instead of manual update
540
+ const refreshedBill = await fetchBillById(billId);
541
+
542
+ setBill(refreshedBill);
543
+
544
+ const mappedItems: LineItem[] =
545
+ refreshedBill?.lineItems?.map((li: any) => ({
546
+ id: li.uuid,
547
+ service: li.billableService || 'Service',
548
+ payerType: (li.priceName && li.priceName.toUpperCase()) || 'CASH',
549
+ supportsSha: li.supportsSha || false,
550
+ waiverAllowed: li.waiverAllowed || false,
551
+ quantity: li.quantity || 1,
552
+ price: li.price || 0,
553
+ status: (li.paymentStatus && li.paymentStatus.toUpperCase()) || 'CASH',
554
+ })) || [];
555
+
556
+ setItems(mappedItems);
557
+
558
+ addLog(
559
+ 'WAIVER',
560
+ items.filter((i) => i.waiverAllowed),
561
+ amount,
562
+ );
563
+ setWaiverAmount('');
564
+ setWaiverPercent('');
565
+ setModalType(null);
566
+ setWaiverLoading(false);
567
+ };
568
+
569
+ const shaButtonState = showShaClaimButton();
570
+
571
+ const applySHAClaim = async () => {
572
+ try {
573
+ setShaLoading(true);
574
+
575
+ const paymentModesResponse = await fetchPaymentModes();
576
+
577
+ const shaMode = paymentModesResponse.results.find((mode) =>
578
+ INSURANCE_MODES.includes(mode.name.toUpperCase()),
579
+ );
580
+
581
+ if (!shaMode) {
582
+ showAlert('error', 'SHA Claim', 'SHA payment mode not found. Please contact support.');
583
+ setShaLoading(false);
584
+ return;
585
+ }
586
+
587
+ // 1️⃣ Check if claim already exists
588
+ const claimStatus = await checkClaimStatus(billId);
589
+ let claimExists = false;
590
+
591
+ if (claimStatus.success && claimStatus.data) {
592
+ claimExists = true;
593
+ }
594
+
595
+ // 2️⃣ Raise claim if not existing
596
+ if (!claimExists) {
597
+ const claimResponse = await raiseSHAClaim(billId);
598
+
599
+ if (!claimResponse.success) {
600
+ showAlert('error', 'SHA Claim', claimResponse.message || 'Failed to raise SHA claim.');
601
+ setShaLoading(false);
602
+ return;
603
+ }
604
+ }
605
+
606
+ // 3️⃣ Process SHA payment
607
+ const totalAmount = pendingShaItems.reduce((acc, i) => acc + getBalance(i), 0);
608
+
609
+ const payload = {
610
+ instanceType: shaMode.uuid,
611
+ amount: totalAmount,
612
+ amountTendered: totalAmount,
613
+ };
614
+
615
+ const paymentResponse = await processPayment(billId, payload);
616
+
617
+ if (!paymentResponse.ok) {
618
+ showAlert('error', 'SHA Claim', 'Payment failed after claim creation.');
619
+ setShaLoading(false);
620
+ return;
621
+ }
622
+
623
+ // 4️⃣ Refresh bill
624
+ const refreshedBill = await fetchBillById(billId);
625
+ setBill(refreshedBill);
626
+
627
+ const mappedItems: LineItem[] =
628
+ refreshedBill?.lineItems?.map((li: any) => ({
629
+ id: li.uuid,
630
+ service: li.billableService || 'Service',
631
+ payerType: (li.priceName && li.priceName.toUpperCase()) || 'CASH',
632
+ supportsSha: li.supportsSha || false,
633
+ waiverAllowed: li.waiverAllowed || false,
634
+ quantity: li.quantity || 1,
635
+ price: li.price || 0,
636
+ status: (li.paymentStatus?.toUpperCase() as LineItemStatus) || 'PENDING',
637
+ })) || [];
638
+
639
+ setItems(mappedItems);
640
+
641
+ // 5️⃣ Add transaction log
642
+ addLog('SHA', pendingShaItems, totalAmount);
643
+
644
+ showAlert('success', 'SHA Claim', 'SHA claim processed successfully');
645
+
646
+ setModalType(null);
647
+ } catch (error) {
648
+ console.error('SHA claim flow failed', error);
649
+ showAlert('error', 'SHA Claim', 'Unexpected error while processing SHA claim.');
650
+ } finally {
651
+ setShaLoading(false);
652
+ }
653
+ };
654
+
655
+ const openEditQuantity = (item: LineItem) => {
656
+ setEditItem(item);
657
+ setEditQuantity(item.quantity);
658
+ setModalType('EDIT_QUANTITY');
659
+ };
660
+ const saveEditQuantity = () => {
661
+ if (!editItem) return;
662
+ if (editQuantity < 1) return alert('Quantity must be at least 1');
663
+ setItems((prev) => prev.map((i) => (i.id === editItem.id ? { ...i, quantity: editQuantity } : i)));
664
+ setModalType(null);
665
+ };
666
+
667
+ const statusText = `${billState}${billStatus() ? ` (${billStatus()})` : ''}`;
668
+
669
+ const [cashLoading, setCashLoading] = useState(false);
670
+ const [waiverLoading, setWaiverLoading] = useState(false);
671
+ const [shaLoading, setShaLoading] = useState(false);
672
+
673
+ const handlePrintInvoice = () => {
674
+ // For partially paid / unpaid bills
675
+ const billUrl = `/billing/print-invoice/${billId}`;
676
+ window.open(billUrl, '_blank');
677
+ };
678
+
679
+ const handlePrintReceipt = () => {
680
+ // For fully paid bills
681
+ const receiptUrl = `/billing/print-receipt/${billId}`;
682
+ window.open(receiptUrl, '_blank');
683
+ };
684
+
685
+ const finalizePatientBill = () => {
686
+ const confirmed = window.confirm('Finalize bill? This action cannot be undone.');
687
+ if (confirmed) {
688
+ setLoading(true);
689
+ finalizeBill(billId)
690
+ .then(() => {
691
+ showAlert('success', 'Finalize Bill', 'Bill finalized successfully');
692
+ // Refresh bill details
693
+ return fetchBillById(billId);
694
+ })
695
+ .then((refreshedBill) => {
696
+ setBill(refreshedBill);
697
+ const mappedItems: LineItem[] =
698
+ refreshedBill?.lineItems?.map((li: any) => ({
699
+ id: li.uuid,
700
+ service: li.billableService || 'Service',
701
+ payerType: (li.priceName && li.priceName.toUpperCase()) || 'CASH',
702
+ supportsSha: li.supportsSha || false,
703
+ waiverAllowed: li.waiverAllowed || false,
704
+ quantity: li.quantity || 1,
705
+ price: li.price || 0,
706
+ status: (li.paymentStatus?.toUpperCase() as LineItemStatus) || 'PENDING',
707
+ })) || [];
708
+ setItems(mappedItems);
709
+ })
710
+ .catch((error) => {
711
+ console.error('Failed to finalize bill', error);
712
+ showAlert('error', 'Finalize Bill', 'Failed to finalize bill. Please try again.');
713
+ })
714
+ .finally(() => setLoading(false));
715
+ }
716
+ };
717
+
718
+ const getClaimTagType = (status?: string) => {
719
+ const s = status?.toLowerCase() || '';
720
+
721
+ if (s.includes('approved') || s.includes('paid')) return 'green';
722
+ if (s.includes('pending')) return 'warm-gray';
723
+ if (s.includes('rejected') || s.includes('failed')) return 'red';
724
+
725
+ return 'cool-gray';
726
+ };
727
+
728
+ const handleRaiseShaConfirm = () => {
729
+ const confirmed = window.confirm(
730
+ 'Raise SHA claim for this bill?\n\nConfirm that all cash items are settled if any and that there are no additional items to add to the claim.',
731
+ );
732
+
733
+ if (confirmed) {
734
+ setModalType('SHA');
735
+ }
736
+ };
737
+
738
+ return (
739
+ <Grid fullWidth style={{ padding: '0' }}>
740
+ {/* Breadcrumb & Bill Info */}
741
+ <Column lg={16} style={{ marginTop: '1rem' }}>
742
+ <Breadcrumb style={{ marginBottom: '1rem' }}>
743
+ <BreadcrumbItem onClick={navigateToBillingPage}>Billing Dashboard</BreadcrumbItem>
744
+ <BreadcrumbItem isCurrentPage>{loading ? <SkeletonText width="120px" /> : billNumber}</BreadcrumbItem>
745
+ </Breadcrumb>
746
+
747
+ <Tile style={{ paddingTop: '1rem' }}>
748
+ {/* Patient Name on Top */}
749
+ {loading ? (
750
+ <SkeletonText width="200px" />
751
+ ) : (
752
+ <>
753
+ <h5 style={{ marginBottom: '0.5rem' }}>{patientName}</h5>
754
+
755
+ {crId && locationUuid && <EligibilityTags crId={crId} locationUuid={locationUuid} />}
756
+ </>
757
+ )}
758
+
759
+ {/* Row with three columns */}
760
+ <Grid fullWidth>
761
+ <Column sm={4} md={4} lg={4}>
762
+ <p>
763
+ <strong>Bill No:</strong> {loading ? <SkeletonText width="100px" /> : billNumber}
764
+ </p>
765
+ </Column>
766
+ <Column sm={4} md={4} lg={4}>
767
+ <p>
768
+ <strong>Cash Point:</strong> {loading ? <SkeletonText width="120px" /> : cashPoint}
769
+ </p>
770
+ </Column>
771
+ <Column sm={4} md={4} lg={4}>
772
+ <p>
773
+ <strong>Bill Date:</strong>{' '}
774
+ {loading ? <SkeletonText width="150px" /> : formatBillDate(bill?.dateCreated)}
775
+ </p>
776
+ </Column>
777
+ </Grid>
778
+ </Tile>
779
+ </Column>
780
+
781
+ {/* ===== Bordered Main Section ===== */}
782
+ <Column lg={16}>
783
+ <div
784
+ style={{
785
+ borderTop: '1px solid #e0e0e0',
786
+ paddingTop: '1rem',
787
+ marginTop: '1rem',
788
+ background: '#fff',
789
+ }}
790
+ >
791
+ <Grid fullWidth>
792
+ {/* Left Section */}
793
+ <Column lg={12}>
794
+ <Tile>
795
+ {!allCashSettled && (
796
+ <Tile
797
+ style={{
798
+ background: '#fff5f5',
799
+ border: '1px solid #f5c2c2',
800
+ borderRadius: '8px',
801
+ padding: '1rem',
802
+ marginBottom: '1rem',
803
+ width: '100%',
804
+ }}
805
+ >
806
+ <p
807
+ style={{
808
+ color: '#da1e28',
809
+ margin: 0,
810
+ fontSize: '0.9rem',
811
+ lineHeight: 1.5,
812
+ }}
813
+ >
814
+ Please select cash/M-Pesa item(s) to continue. SHA claims (if any) will be available after all cash
815
+ payments are completed.
816
+ </p>
817
+ </Tile>
818
+ )}
819
+
820
+ <div
821
+ style={{
822
+ display: 'flex',
823
+ justifyContent: 'space-between',
824
+ alignItems: 'center',
825
+ marginBottom: '1rem',
826
+ }}
827
+ >
828
+ <h4>
829
+ <u>Bill Items</u>
830
+ </h4>
831
+ <Stack orientation="horizontal" gap={2}>
832
+ {selectedCashItems.length > 0 && (
833
+ <Button disabled={loading} renderIcon={Money} size="sm" onClick={() => setModalType('CASH')}>
834
+ Pay Selected {paymentName} Items
835
+ </Button>
836
+ )}
837
+
838
+ {shaButtonState === 'RAISE' && (
839
+ <Button
840
+ disabled={loading || checkingClaim}
841
+ size="sm"
842
+ kind="secondary"
843
+ renderIcon={DocumentAdd}
844
+ onClick={handleRaiseShaConfirm}
845
+ >
846
+ Raise SHA Claim
847
+ </Button>
848
+ )}
849
+
850
+ {shaButtonState === 'STATUS' && (
851
+ <Button
852
+ disabled={loading || checkingClaim}
853
+ size="sm"
854
+ kind="tertiary"
855
+ renderIcon={View}
856
+ onClick={handleCheckSHAClaim}
857
+ >
858
+ {checkingClaim ? 'Checking Status...' : 'Check SHA Claim Status'}
859
+ </Button>
860
+ )}
861
+
862
+ {totalBalance > 0 && (
863
+ <Button
864
+ disabled={loading}
865
+ renderIcon={Subtract}
866
+ size="sm"
867
+ kind="tertiary"
868
+ onClick={() => setModalType('WAIVER')}
869
+ >
870
+ Apply Waiver
871
+ </Button>
872
+ )}
873
+
874
+ {totalBalance === 0 && billState === 'PENDING' && (
875
+ <Button size="sm" kind="primary" renderIcon={ResultNew} onClick={finalizePatientBill}>
876
+ Finalize Bill
877
+ </Button>
878
+ )}
879
+
880
+ {totalBalance === 0 && billState === 'PAID' && (
881
+ <Button disabled size="sm" kind="tertiary" renderIcon={Receipt} onClick={handlePrintReceipt}>
882
+ Print Receipt
883
+ </Button>
884
+ )}
885
+ </Stack>
886
+ </div>
887
+
888
+ {/* Bill Items Table */}
889
+ {loading ? (
890
+ <SkeletonPlaceholder style={{ height: '300px', width: '100%' }} />
891
+ ) : (
892
+ <DataTable rows={items} headers={headers}>
893
+ {({ rows, headers, getHeaderProps, getRowProps }) => (
894
+ <Table>
895
+ <TableHead>
896
+ <TableRow>
897
+ {headers.map((h) => (
898
+ <TableHeader key={h.key} {...getHeaderProps({ header: h })}>
899
+ {h.header}
900
+ </TableHeader>
901
+ ))}
902
+ </TableRow>
903
+ </TableHead>
904
+
905
+ <TableBody>
906
+ {rows.map((row) => {
907
+ const i = items.find((it) => it.id === row.id)!;
908
+
909
+ return (
910
+ <TableRow key={i.id} {...getRowProps({ row })}>
911
+ <TableCell>{i.service}</TableCell>
912
+ <TableCell>{i.payerType}</TableCell>
913
+ <TableCell>{i.quantity}</TableCell>
914
+ <TableCell>Ksh {i.price}</TableCell>
915
+ <TableCell>Ksh {getBalance(i)}</TableCell>
916
+ <TableCell>
917
+ {i.payerType.toUpperCase() === 'SHA' && i.status.toUpperCase() === 'PAID'
918
+ ? statusTag('CLAIMED')
919
+ : statusTag(i.status)}
920
+ </TableCell>
921
+
922
+ <TableCell>
923
+ <Checkbox
924
+ id={`select-${i.id}`}
925
+ labelText=""
926
+ checked={shaOnlyBill ? true : !!i.selected}
927
+ disabled={
928
+ i.status !== 'PENDING' ||
929
+ shaOnlyBill ||
930
+ !CASH_MODES.includes((i.payerType ?? '').toUpperCase())
931
+ }
932
+ onChange={() => toggleSelect(i.id)}
933
+ />
934
+ </TableCell>
935
+
936
+ <TableCell>
937
+ <OverflowMenu ariaLabel="Actions" size="sm" disabled={i.status !== 'PENDING'}>
938
+ <OverflowMenuItem itemText="Edit Quantity" onClick={() => openEditQuantity(i)} />
939
+ <OverflowMenuItem
940
+ itemText="Delete Item"
941
+ onClick={() => {
942
+ setEditItem(i);
943
+ setModalType('CONFIRM_DELETE');
944
+ }}
945
+ />
946
+ </OverflowMenu>
947
+ </TableCell>
948
+ </TableRow>
949
+ );
950
+ })}
951
+ </TableBody>
952
+ </Table>
953
+ )}
954
+ </DataTable>
955
+ )}
956
+ </Tile>
957
+
958
+ {bill?.payments?.length > 0 && (
959
+ <Tile style={{ marginTop: '1rem' }}>
960
+ <h4 style={{ marginBottom: '1rem' }}>
961
+ <u>Payment Details</u>
962
+ </h4>
963
+
964
+ <DataTable
965
+ rows={bill.payments.map((p: any) => ({
966
+ id: p.uuid,
967
+ type: p.instanceType?.name || '-',
968
+ amount: formatCurrency(p.amount),
969
+ amountTendered: p.amountTendered ? formatCurrency(p.amountTendered) : '-',
970
+ dateCreated: formatBillDate(p.dateCreated),
971
+ }))}
972
+ headers={[
973
+ { key: 'type', header: 'Payment Type' },
974
+ { key: 'amount', header: 'Amount' },
975
+ { key: 'amountTendered', header: 'Amount Tendered' },
976
+ { key: 'dateCreated', header: 'Date / Time' },
977
+ ]}
978
+ >
979
+ {({ rows, headers, getHeaderProps, getRowProps }) => (
980
+ <Table>
981
+ <TableHead>
982
+ <TableRow>
983
+ {headers.map((h) => (
984
+ <TableHeader key={h.key} {...getHeaderProps({ header: h })}>
985
+ {h.header}
986
+ </TableHeader>
987
+ ))}
988
+ </TableRow>
989
+ </TableHead>
990
+
991
+ <TableBody>
992
+ {rows.map((row) => (
993
+ <TableRow key={row.id} {...getRowProps({ row })}>
994
+ {row.cells.map((cell) => (
995
+ <TableCell key={cell.id}>{cell.value || '-'}</TableCell>
996
+ ))}
997
+ </TableRow>
998
+ ))}
999
+ </TableBody>
1000
+ </Table>
1001
+ )}
1002
+ </DataTable>
1003
+ </Tile>
1004
+ )}
1005
+ </Column>
1006
+
1007
+ {/* Right Summary */}
1008
+ <Column lg={4}>
1009
+ <Tile style={{ padding: '1.5rem' }}>
1010
+ <h4 style={{ marginBottom: '1rem' }}>
1011
+ <u>Bill Summary</u>
1012
+ </h4>
1013
+
1014
+ <Stack gap={3}>
1015
+ {/* Status */}
1016
+ {loading ? (
1017
+ <SkeletonText width="100%" />
1018
+ ) : (
1019
+ <Tag type={totalBalance === 0 ? 'green' : totalBalance < billTotal ? 'teal' : 'red'}>
1020
+ {totalBalance === 0 && <CheckmarkFilled size={12} style={{ marginRight: 4 }} />}
1021
+ {totalBalance > 0 && totalBalance < billTotal && (
1022
+ <WarningAlt size={12} style={{ marginRight: 4 }} />
1023
+ )}
1024
+ {totalBalance === billTotal && <PendingFilled size={12} style={{ marginRight: 4 }} />}
1025
+ {statusText}
1026
+ </Tag>
1027
+ )}
1028
+
1029
+ {/* Divider */}
1030
+ <hr style={{ border: 'none', borderTop: '1px solid #e0e0e0' }} />
1031
+
1032
+ {/* Total */}
1033
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
1034
+ <span style={{ fontWeight: 'bold' }}>Total</span>
1035
+ <strong>
1036
+ {loading ? (
1037
+ <SkeletonText width="100px" />
1038
+ ) : (
1039
+ `Ksh ${Number(billTotal).toLocaleString(undefined, {
1040
+ minimumFractionDigits: 2,
1041
+ maximumFractionDigits: 2,
1042
+ })}`
1043
+ )}
1044
+ </strong>
1045
+ </div>
1046
+
1047
+ {/* Paid */}
1048
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
1049
+ <span style={{ color: '#24a148', fontWeight: 'bold' }}>Paid</span>
1050
+ <strong style={{ color: '#24a148' }}>
1051
+ {loading ? (
1052
+ <SkeletonText width="100px" />
1053
+ ) : (
1054
+ `Ksh ${Number(totalPaid).toLocaleString(undefined, {
1055
+ minimumFractionDigits: 2,
1056
+ maximumFractionDigits: 2,
1057
+ })}`
1058
+ )}
1059
+ </strong>
1060
+ </div>
1061
+
1062
+ {/* Claimed */}
1063
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
1064
+ <span style={{ color: '#0f62fe', fontWeight: 'bold' }}>Claimed</span>
1065
+ <strong style={{ color: '#0f62fe' }}>
1066
+ {loading ? (
1067
+ <SkeletonText width="100px" />
1068
+ ) : (
1069
+ `Ksh ${Number(totalClaimed).toLocaleString(undefined, {
1070
+ minimumFractionDigits: 2,
1071
+ maximumFractionDigits: 2,
1072
+ })}`
1073
+ )}
1074
+ </strong>
1075
+ </div>
1076
+
1077
+ {/* Waived */}
1078
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
1079
+ <span style={{ color: '#8a3ffc', fontWeight: 'bold' }}>Waived</span>
1080
+ <strong style={{ color: '#8a3ffc' }}>
1081
+ {loading ? (
1082
+ <SkeletonText width="100px" />
1083
+ ) : (
1084
+ `Ksh ${Number(totalWaived).toLocaleString(undefined, {
1085
+ minimumFractionDigits: 2,
1086
+ maximumFractionDigits: 2,
1087
+ })}`
1088
+ )}
1089
+ </strong>
1090
+ </div>
1091
+
1092
+ {/* Divider */}
1093
+ <hr style={{ border: 'none', borderTop: '1px solid #e0e0e0' }} />
1094
+
1095
+ {/* Balance */}
1096
+ <div style={{ display: 'flex', justifyContent: 'space-between' }}>
1097
+ <span style={{ fontWeight: 'bold' }}>Balance</span>
1098
+ <strong
1099
+ style={{
1100
+ color: totalBalance > 0 ? '#da1e28' : '#24a148',
1101
+ fontSize: '1.1rem',
1102
+ }}
1103
+ >
1104
+ {loading ? (
1105
+ <SkeletonText width="100px" />
1106
+ ) : (
1107
+ `Ksh ${Number(totalBalance).toLocaleString(undefined, {
1108
+ minimumFractionDigits: 2,
1109
+ maximumFractionDigits: 2,
1110
+ })}`
1111
+ )}
1112
+ </strong>
1113
+ </div>
1114
+ </Stack>
1115
+ </Tile>
1116
+ </Column>
1117
+ </Grid>
1118
+ </div>
1119
+ </Column>
1120
+
1121
+ {/* ================= MODALS ================= */}
1122
+ {/* Cash Payment */}
1123
+ <Modal
1124
+ open={modalType === 'CASH'}
1125
+ modalHeading={`Confirm Payment for ${paymentName} Items`}
1126
+ primaryButtonText={cashLoading ? 'Processing...' : 'Submit'}
1127
+ secondaryButtonText="Cancel"
1128
+ primaryButtonDisabled={cashLoading}
1129
+ onRequestClose={() => setModalType(null)}
1130
+ onRequestSubmit={processCashPayment}
1131
+ >
1132
+ <div style={{ marginBottom: '1rem' }}>
1133
+ <TextInput
1134
+ id="cash-payment-ref"
1135
+ labelText="Payment Reference"
1136
+ value={reference}
1137
+ onChange={(e) => setReference(e.target.value)}
1138
+ />
1139
+ </div>
1140
+
1141
+ <ul>
1142
+ {selectedCashItems.map((i) => (
1143
+ <li key={i.id} style={{ marginBottom: '0.5rem' }}>
1144
+ {i.service} - Ksh {getBalance(i)}
1145
+ </li>
1146
+ ))}
1147
+ </ul>
1148
+
1149
+ <p style={{ marginTop: '1rem' }}>
1150
+ <strong>Total: Ksh {selectedCashItems.reduce((acc, i) => acc + getBalance(i), 0)}</strong>
1151
+ </p>
1152
+ </Modal>
1153
+
1154
+ {/* Waiver */}
1155
+ <Modal
1156
+ open={modalType === 'WAIVER'}
1157
+ modalHeading="Apply Waiver"
1158
+ primaryButtonText={waiverLoading ? 'Applying...' : 'Submit'}
1159
+ secondaryButtonText="Cancel"
1160
+ primaryButtonDisabled={
1161
+ waiverLoading ||
1162
+ (waiverType === 'PERCENT'
1163
+ ? waiverPercent === '' || Number(waiverPercent) < 1 || Number(waiverPercent) > 100
1164
+ : waiverAmount === '' || Number(waiverAmount) < 1)
1165
+ }
1166
+ onRequestClose={() => setModalType(null)}
1167
+ onRequestSubmit={applyWaiver}
1168
+ >
1169
+ <div style={{ marginBottom: '1rem' }}>
1170
+ <label htmlFor="waiver-type-amount">
1171
+ <input
1172
+ type="radio"
1173
+ id="waiver-type-amount"
1174
+ name="waiver-type"
1175
+ value="AMOUNT"
1176
+ checked={waiverType === 'AMOUNT'}
1177
+ onChange={() => setWaiverType('AMOUNT')}
1178
+ />{' '}
1179
+ Amount
1180
+ </label>
1181
+
1182
+ <label htmlFor="waiver-type-percent" style={{ marginLeft: '1rem' }}>
1183
+ <input
1184
+ type="radio"
1185
+ id="waiver-type-percent"
1186
+ name="waiver-type"
1187
+ value="PERCENT"
1188
+ checked={waiverType === 'PERCENT'}
1189
+ onChange={() => setWaiverType('PERCENT')}
1190
+ />{' '}
1191
+ Percentage
1192
+ </label>
1193
+ </div>
1194
+
1195
+ {waiverType === 'PERCENT' ? (
1196
+ <NumberInput
1197
+ id="waiver-percent"
1198
+ label="Waiver Percentage"
1199
+ min={0}
1200
+ max={100}
1201
+ value={waiverPercent}
1202
+ invalid={waiverPercent !== '' && (Number(waiverPercent) < 1 || Number(waiverPercent) > 100)}
1203
+ invalidText="Percentage must be between 1 and 100"
1204
+ onChange={(_e, state) => setWaiverPercent(String(state.value))}
1205
+ />
1206
+ ) : (
1207
+ <NumberInput
1208
+ id="waiver-amount"
1209
+ label="Waiver Amount"
1210
+ min={0}
1211
+ value={waiverAmount}
1212
+ invalid={waiverAmount !== '' && Number(waiverAmount) < 1}
1213
+ invalidText="Amount cannot be less than 1"
1214
+ onChange={(_e, state) => setWaiverAmount(String(state.value))}
1215
+ />
1216
+ )}
1217
+ </Modal>
1218
+
1219
+ {/* SHA Claim */}
1220
+ <Modal
1221
+ open={modalType === 'SHA'}
1222
+ modalHeading={`Confirm ${schemes} Claim`}
1223
+ primaryButtonText={shaLoading ? 'Submitting...' : 'Submit Claim'}
1224
+ secondaryButtonText="Cancel"
1225
+ primaryButtonDisabled={shaLoading}
1226
+ onRequestClose={() => setModalType(null)}
1227
+ onRequestSubmit={applySHAClaim}
1228
+ >
1229
+ <Stack gap={4}>
1230
+ {/* ===== Info ===== */}
1231
+ <Tile>
1232
+ <p style={{ marginBottom: '0.5rem' }}>
1233
+ You are about to submit the following items for <strong>{schemes} claim</strong>.
1234
+ </p>
1235
+ <p style={{ color: '#6f6f6f', marginBottom: 0 }}>
1236
+ Please confirm all services are correct before proceeding.
1237
+ </p>
1238
+ </Tile>
1239
+
1240
+ {/* ===== Items Table ===== */}
1241
+ <Tile>
1242
+ <h5 style={{ marginBottom: '1rem' }}>Claim Items</h5>
1243
+
1244
+ <DataTable
1245
+ rows={pendingShaItems.map((i) => ({
1246
+ id: i.id,
1247
+ service: i.service,
1248
+ quantity: i.quantity,
1249
+ unitPrice: `Ksh ${Number(i.price).toLocaleString()}`,
1250
+ total: `Ksh ${getBalance(i).toLocaleString()}`,
1251
+ }))}
1252
+ headers={[
1253
+ { key: 'service', header: 'Service' },
1254
+ { key: 'quantity', header: 'Qty' },
1255
+ { key: 'unitPrice', header: 'Unit Price' },
1256
+ { key: 'total', header: 'Total' },
1257
+ ]}
1258
+ >
1259
+ {({ rows, headers, getHeaderProps, getRowProps }) => (
1260
+ <Table size="sm" useZebraStyles>
1261
+ <TableHead>
1262
+ <TableRow>
1263
+ {headers.map((h) => (
1264
+ <TableHeader key={h.key} {...getHeaderProps({ header: h })}>
1265
+ {h.header}
1266
+ </TableHeader>
1267
+ ))}
1268
+ </TableRow>
1269
+ </TableHead>
1270
+
1271
+ <TableBody>
1272
+ {rows.map((row) => (
1273
+ <TableRow key={row.id} {...getRowProps({ row })}>
1274
+ {row.cells.map((cell) => (
1275
+ <TableCell key={cell.id}>{cell.value}</TableCell>
1276
+ ))}
1277
+ </TableRow>
1278
+ ))}
1279
+ </TableBody>
1280
+ </Table>
1281
+ )}
1282
+ </DataTable>
1283
+ </Tile>
1284
+
1285
+ {/* ===== Summary ===== */}
1286
+ <Tile>
1287
+ <Grid fullWidth>
1288
+ <Column lg={8}>
1289
+ <p>
1290
+ <strong>Items:</strong> {pendingShaItems.length}
1291
+ </p>
1292
+ </Column>
1293
+
1294
+ <Column lg={8} style={{ textAlign: 'right' }}>
1295
+ <p style={{ fontSize: '1.1rem', fontWeight: 600 }}>
1296
+ Total: Ksh {pendingShaItems.reduce((acc, i) => acc + getBalance(i), 0).toLocaleString()}
1297
+ </p>
1298
+ </Column>
1299
+ </Grid>
1300
+ </Tile>
1301
+ </Stack>
1302
+ </Modal>
1303
+
1304
+ {/* claim status */}
1305
+ <Modal
1306
+ open={modalType === 'CHECK_SHA'}
1307
+ modalHeading="SHA Claim Status"
1308
+ primaryButtonText="Close"
1309
+ secondaryButtonText={null}
1310
+ onRequestClose={() => setModalType(null)}
1311
+ onRequestSubmit={() => setModalType(null)}
1312
+ >
1313
+ {claimResponse ? (
1314
+ <Stack gap={4}>
1315
+ <Tile>
1316
+ <h5 style={{ marginBottom: '0.5rem' }}>Patient Information</h5>
1317
+ <p>
1318
+ <strong>Name:</strong> {claimResponse.patientFullName}
1319
+ </p>
1320
+ <p>
1321
+ <strong>Gender:</strong> {claimResponse.gender}
1322
+ </p>
1323
+
1324
+ <h5 style={{ marginTop: '1rem', marginBottom: '0.5rem' }}>Facility</h5>
1325
+ <p>
1326
+ {claimResponse.facilityName} ({claimResponse.facilityLevel})
1327
+ </p>
1328
+ </Tile>
1329
+
1330
+ <Tile>
1331
+ <h5 style={{ marginBottom: '0.5rem' }}>Claim Details</h5>
1332
+
1333
+ <p>
1334
+ <strong>Status:</strong>{' '}
1335
+ <Tag type={getClaimTagType(claimResponse.claim_Status)}>{claimResponse.claim_Status}</Tag>
1336
+ </p>
1337
+
1338
+ <p>
1339
+ <strong>Response:</strong> {claimResponse.claim_Response || '-'}
1340
+ </p>
1341
+ </Tile>
1342
+
1343
+ {claimResponse.services?.length > 0 && (
1344
+ <Tile>
1345
+ <h5 style={{ marginBottom: '1rem' }}>Services</h5>
1346
+
1347
+ <DataTable
1348
+ rows={claimResponse.services.map((s: any) => ({
1349
+ id: s.serviceCode,
1350
+ name: s.serviceDisplay,
1351
+ code: s.serviceCode,
1352
+ quantity: s.quantity,
1353
+ amount: `Ksh ${Number(s.totalAmount).toLocaleString()}`,
1354
+ }))}
1355
+ headers={[
1356
+ { key: 'name', header: 'Service' },
1357
+ { key: 'code', header: 'Code' },
1358
+ { key: 'quantity', header: 'Qty' },
1359
+ { key: 'amount', header: 'Total Amount' },
1360
+ ]}
1361
+ >
1362
+ {({ rows, headers, getHeaderProps, getRowProps }) => (
1363
+ <Table size="sm">
1364
+ <TableHead>
1365
+ <TableRow>
1366
+ {headers.map((h) => (
1367
+ <TableHeader key={h.key} {...getHeaderProps({ header: h })}>
1368
+ {h.header}
1369
+ </TableHeader>
1370
+ ))}
1371
+ </TableRow>
1372
+ </TableHead>
1373
+
1374
+ <TableBody>
1375
+ {rows.map((row) => (
1376
+ <TableRow key={row.id} {...getRowProps({ row })}>
1377
+ {row.cells.map((cell) => (
1378
+ <TableCell key={cell.id}>{cell.value}</TableCell>
1379
+ ))}
1380
+ </TableRow>
1381
+ ))}
1382
+ </TableBody>
1383
+ </Table>
1384
+ )}
1385
+ </DataTable>
1386
+ </Tile>
1387
+ )}
1388
+
1389
+ {claimResponse.diagnoses?.length > 0 && (
1390
+ <Tile>
1391
+ <h5 style={{ marginBottom: '1rem' }}>Diagnoses</h5>
1392
+
1393
+ <DataTable
1394
+ rows={claimResponse.diagnoses.map((d: any, index: number) => ({
1395
+ id: `${d.Code}-${index}`,
1396
+ name: d.Display,
1397
+ code: d.Code,
1398
+ }))}
1399
+ headers={[
1400
+ { key: 'name', header: 'Diagnosis' },
1401
+ { key: 'code', header: 'Code' },
1402
+ ]}
1403
+ >
1404
+ {({ rows, headers, getHeaderProps, getRowProps }) => (
1405
+ <Table size="sm">
1406
+ <TableHead>
1407
+ <TableRow>
1408
+ {headers.map((h) => (
1409
+ <TableHeader key={h.key} {...getHeaderProps({ header: h })}>
1410
+ {h.header}
1411
+ </TableHeader>
1412
+ ))}
1413
+ </TableRow>
1414
+ </TableHead>
1415
+
1416
+ <TableBody>
1417
+ {rows.map((row) => (
1418
+ <TableRow key={row.id} {...getRowProps({ row })}>
1419
+ {row.cells.map((cell) => (
1420
+ <TableCell key={cell.id}>{cell.value}</TableCell>
1421
+ ))}
1422
+ </TableRow>
1423
+ ))}
1424
+ </TableBody>
1425
+ </Table>
1426
+ )}
1427
+ </DataTable>
1428
+ </Tile>
1429
+ )}
1430
+ </Stack>
1431
+ ) : (
1432
+ <Tile>
1433
+ <p>No claim data available.</p>
1434
+ </Tile>
1435
+ )}
1436
+ </Modal>
1437
+ </Grid>
1438
+ );
1439
+ };
1440
+
1441
+ export default BillDetails;