@ampath/esm-reports-app 1.0.0-next.8 → 1.0.0-next.81

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 (305) hide show
  1. package/dist/197.js +1 -1
  2. package/dist/282.js +1 -1
  3. package/dist/282.js.map +1 -1
  4. package/dist/295.css +1 -0
  5. package/dist/295.css.map +1 -0
  6. package/dist/295.js +1 -0
  7. package/dist/295.js.map +1 -0
  8. package/dist/299.js +2 -0
  9. package/dist/299.js.LICENSE.txt +3 -0
  10. package/dist/299.js.map +1 -0
  11. package/dist/300.js +1 -1
  12. package/dist/311.js +1 -1
  13. package/dist/311.js.map +1 -1
  14. package/dist/335.js +1 -1
  15. package/dist/353.js +1 -1
  16. package/dist/353.js.map +1 -1
  17. package/dist/354.js +2 -0
  18. package/dist/354.js.LICENSE.txt +20 -0
  19. package/dist/354.js.map +1 -0
  20. package/dist/410.js +1 -0
  21. package/dist/410.js.map +1 -0
  22. package/dist/457.js +2 -0
  23. package/dist/457.js.LICENSE.txt +261 -0
  24. package/dist/457.js.map +1 -0
  25. package/dist/478.js +1 -1
  26. package/dist/478.js.map +1 -1
  27. package/dist/480.js +1 -0
  28. package/dist/540.js +1 -1
  29. package/dist/540.js.map +1 -1
  30. package/dist/549.css +1 -0
  31. package/dist/549.css.map +1 -0
  32. package/dist/549.js +1 -0
  33. package/dist/549.js.map +1 -0
  34. package/dist/55.js +1 -1
  35. package/dist/600.js +2 -0
  36. package/dist/600.js.LICENSE.txt +35 -0
  37. package/dist/600.js.map +1 -0
  38. package/dist/652.js +1 -1
  39. package/dist/757.js +1 -0
  40. package/dist/757.js.map +1 -0
  41. package/dist/781.js +1 -0
  42. package/dist/781.js.map +1 -0
  43. package/dist/961.js +1 -1
  44. package/dist/961.js.map +1 -1
  45. package/dist/99.js +1 -1
  46. package/dist/99.js.map +1 -0
  47. package/dist/ampath-esm-reports-app.js +1 -1
  48. package/dist/ampath-esm-reports-app.js.map +1 -1
  49. package/dist/routes.json +1 -1
  50. package/package.json +4 -2
  51. package/src/common/daily-summary-table.tsx +55 -0
  52. package/src/common/linelist-table/index.ts +3 -0
  53. package/src/common/linelist-table/linelist-columns.ts +48 -0
  54. package/src/common/linelist-table/linelist-table.component.tsx +442 -0
  55. package/src/common/linelist-table/linelist-table.scss +306 -0
  56. package/src/common/linelist-table/linelist-table.test.tsx +120 -0
  57. package/src/common/linelist-table/use-all-rows.ts +93 -0
  58. package/src/common/linelist-table/use-column-layout.ts +123 -0
  59. package/src/common/report-error/index.ts +1 -0
  60. package/src/common/report-error/show-report-error.ts +25 -0
  61. package/src/common/report-filters/date-range.ts +137 -0
  62. package/src/common/report-filters/index.ts +4 -0
  63. package/src/common/report-filters/report-filters.component.tsx +317 -99
  64. package/src/common/report-filters/report-filters.scss +264 -32
  65. package/src/common/report-filters/types.ts +79 -0
  66. package/src/common/report-filters/use-restore-period.ts +43 -0
  67. package/src/common/report-header/index.ts +1 -0
  68. package/src/common/report-header/use-location-details.ts +58 -0
  69. package/src/common/report-page/download-sheet-pdf.ts +225 -0
  70. package/src/common/report-page/index.ts +3 -0
  71. package/src/common/report-page/report-page.component.tsx +32 -0
  72. package/src/common/report-page/report-page.scss +45 -0
  73. package/src/common/report-page/use-page-scale.ts +60 -0
  74. package/src/common/report-placeholder/has-report-data.ts +15 -0
  75. package/src/common/report-placeholder/index.ts +3 -0
  76. package/src/common/report-placeholder/report-placeholder.component.tsx +81 -0
  77. package/src/common/report-placeholder/report-placeholder.scss +48 -0
  78. package/src/common/report-placeholder/use-generated-location.ts +62 -0
  79. package/src/common/report-register/index.ts +6 -0
  80. package/src/common/report-register/register-columns.ts +46 -0
  81. package/src/common/report-register/register-layout.component.tsx +265 -0
  82. package/src/common/report-register/register-layout.scss +150 -0
  83. package/src/common/report-register/register-table.scss +71 -0
  84. package/src/common/report-register/report-parents.ts +29 -0
  85. package/src/common/report-register/rows-to-csv.ts +89 -0
  86. package/src/common/report-register/use-location-name.ts +54 -0
  87. package/src/common/report-register/use-patient-list.ts +92 -0
  88. package/src/common/report-skeleton/index.ts +1 -0
  89. package/src/common/report-skeleton/report-skeleton.component.tsx +46 -0
  90. package/src/common/report-skeleton/report-skeleton.scss +29 -0
  91. package/src/config-schema.ts +9 -1
  92. package/src/dashboard/report-catalog.ts +191 -0
  93. package/src/dashboard/reports-dasboard.tsx +189 -18
  94. package/src/dashboard/reports-dashboard.module.scss +357 -14
  95. package/src/index.ts +13 -1
  96. package/src/registers/moh-240/moh-240-register.scss +41 -0
  97. package/src/registers/moh-240/moh-240.component.test.tsx +166 -0
  98. package/src/registers/moh-240/moh-240.component.tsx +164 -0
  99. package/src/registers/moh-267/moh-267-register.component.tsx +407 -0
  100. package/src/registers/moh-267/moh-267-register.scss +298 -0
  101. package/src/registers/moh-267/moh-267-summary.ts +102 -0
  102. package/src/registers/moh-267/moh-267.component.tsx +94 -0
  103. package/src/registers/moh-267/moh-267.types.ts +47 -0
  104. package/src/registers/moh-362/moh-362-lab-register.component.tsx +551 -0
  105. package/src/registers/moh-362/moh-362-lab-register.scss +412 -0
  106. package/src/registers/moh-362/moh-362-linkage-register.component.tsx +215 -0
  107. package/src/registers/moh-362/moh-362-linkage-register.scss +91 -0
  108. package/src/registers/moh-362/moh-362.component.tsx +117 -0
  109. package/src/registers/moh-362/moh-362.scss +11 -0
  110. package/src/registers/moh-362/moh-362.types.ts +87 -0
  111. package/src/registers/presumptive-tb/presumptive-tb-register.component.tsx +391 -0
  112. package/src/registers/presumptive-tb/presumptive-tb-register.scss +262 -0
  113. package/src/registers/presumptive-tb/presumptive-tb.component.tsx +92 -0
  114. package/src/registers/presumptive-tb/presumptive-tb.types.ts +88 -0
  115. package/src/registers/register-catalog.ts +202 -0
  116. package/src/registers/register-file-name.ts +18 -0
  117. package/src/registers/tb-lab-register/tb-lab-register-table.component.tsx +116 -0
  118. package/src/registers/tb-lab-register/tb-lab-register-table.scss +123 -0
  119. package/src/registers/tb-lab-register/tb-lab-register.columns.ts +164 -0
  120. package/src/registers/tb-lab-register/tb-lab-register.component.test.tsx +232 -0
  121. package/src/registers/tb-lab-register/tb-lab-register.component.tsx +153 -0
  122. package/src/registers/tb4/tb4-parts.tsx +164 -0
  123. package/src/registers/tb4/tb4-patient-sheet.component.tsx +418 -0
  124. package/src/registers/tb4/tb4-register.scss +282 -0
  125. package/src/registers/tb4/tb4-treatment-sheet.component.tsx +349 -0
  126. package/src/registers/tb4/tb4.component.tsx +120 -0
  127. package/src/registers/tb4/tb4.types.ts +129 -0
  128. package/src/reports/acf-summary/acf-summary-form.component.tsx +176 -0
  129. package/src/reports/acf-summary/acf-summary-linelist.component.tsx +86 -0
  130. package/src/reports/acf-summary/acf-summary.component.tsx +97 -0
  131. package/src/reports/acf-summary/acf-summary.counts.ts +37 -0
  132. package/src/reports/acf-summary/acf-summary.scss +162 -0
  133. package/src/reports/datatable-wrapper/datatable-wrapper.component.tsx +63 -48
  134. package/src/reports/moh-240/moh-240-header.component.tsx +62 -0
  135. package/src/reports/moh-240/moh-240.columns.ts +113 -0
  136. package/src/reports/moh-240/moh-240.component.tsx +72 -9
  137. package/src/reports/moh-240/moh-240.scss +40 -0
  138. package/src/reports/moh-240/sub-reports/moh-240-register.component.tsx +31 -37
  139. package/src/reports/moh-240/sub-reports/page-summary.component.tsx +143 -106
  140. package/src/reports/moh-505/moh-505-header.component.tsx +67 -0
  141. package/src/reports/moh-505/moh-505.component.tsx +441 -0
  142. package/src/reports/moh-505/moh-505.scss +117 -0
  143. package/src/reports/moh-705B/moh-204b-register.component.tsx +246 -0
  144. package/src/reports/moh-705B/moh-204b.columns.ts +39 -0
  145. package/src/reports/moh-705B/moh-705b.component.tsx +659 -0
  146. package/src/reports/moh-705B/moh-705b.scss +172 -0
  147. package/src/reports/moh-705a/moh-705a.component.tsx +661 -0
  148. package/src/reports/moh-705a/moh-705a.scss +172 -0
  149. package/src/reports/moh-705a/registers/moh-204a-register.component.tsx +331 -0
  150. package/src/reports/moh-705a/registers/moh-204a.columns.ts +57 -0
  151. package/src/reports/moh-705a/type.ts +6 -0
  152. package/src/reports/moh-706/moh-706-header.component.tsx +66 -0
  153. package/src/reports/moh-706/moh-706.component.tsx +150 -24
  154. package/src/reports/moh-706/moh-706.scss +119 -11
  155. package/src/reports/moh-706/sub-reports/bacteriology/bacteriology.component.tsx +35 -28
  156. package/src/reports/moh-706/sub-reports/blood-chemistry/blood-chemistry.component.tsx +229 -305
  157. package/src/reports/moh-706/sub-reports/haematology/haematology.component.tsx +26 -19
  158. package/src/reports/moh-706/sub-reports/histology-and-cytology/histology-and-cytology.component.tsx +23 -23
  159. package/src/reports/moh-706/sub-reports/parasitology/parasitology.component.tsx +25 -18
  160. package/src/reports/moh-706/sub-reports/serology/serology.component.tsx +21 -14
  161. package/src/reports/moh-706/sub-reports/specimen-referral-to-higher-levels/specimen-referral-to-higher-levels.component.tsx +2 -2
  162. package/src/reports/moh-706/sub-reports/urine-analysis/urine-analysis.component.tsx +27 -20
  163. package/src/reports/moh-710/moh-710.component.tsx +815 -632
  164. package/src/reports/moh-710/moh-710.scss +142 -21
  165. package/src/reports/moh-710/registers/moh-413/moh-413-columns.ts +22 -0
  166. package/src/reports/moh-710/registers/moh-413/moh-413.component.tsx +140 -0
  167. package/src/reports/moh-710/registers/moh-413/moh-413.scss +64 -0
  168. package/src/reports/moh-710/registers/moh-510/moh-510.columns.ts +40 -0
  169. package/src/reports/moh-710/registers/moh-510/moh-510.component.tsx +191 -0
  170. package/src/reports/moh-710/registers/moh-510/moh-510.scss +130 -0
  171. package/src/reports/moh-710/registers/moh-511-710-register.component.tsx +268 -0
  172. package/src/reports/moh-710/registers/moh-511-710.columns.ts +52 -0
  173. package/src/reports/moh-710/registers/type.ts +31 -0
  174. package/src/reports/moh-711/moh-711.component.tsx +153 -1269
  175. package/src/reports/moh-711/moh711.scss +135 -20
  176. package/src/reports/moh-711/registers/moh-333-register.component.tsx +680 -0
  177. package/src/reports/moh-711/registers/moh-333.columns.ts +119 -0
  178. package/src/reports/moh-711/registers/moh-405-register.component.tsx +566 -0
  179. package/src/reports/moh-711/registers/moh-405.columns.ts +120 -0
  180. package/src/reports/moh-711/registers/moh-406-register.component.tsx +581 -0
  181. package/src/reports/moh-711/registers/moh-406.columns.ts +133 -0
  182. package/src/reports/moh-711/registers/moh-407-register.component.tsx +157 -0
  183. package/src/reports/moh-711/registers/moh-407.columns.ts +24 -0
  184. package/src/reports/moh-711/registers/moh-407.scss +64 -0
  185. package/src/reports/moh-711/registers/moh-510-register.component.tsx +55 -0
  186. package/src/reports/moh-711/registers/moh-511-register.component.tsx +270 -0
  187. package/src/reports/moh-711/registers/moh-511.columns.ts +59 -0
  188. package/src/reports/moh-711/registers/moh-512-columns.ts +94 -0
  189. package/src/reports/moh-711/registers/moh-512.component.tsx +191 -0
  190. package/src/reports/moh-711/registers/moh-512.scss +64 -0
  191. package/src/reports/moh-711/registers/tb-register-columns.ts +17 -0
  192. package/src/reports/moh-711/registers/tb-register.component.tsx +96 -0
  193. package/src/reports/moh-711/registers/tb-register.scss +64 -0
  194. package/src/reports/moh-711/registers/type.ts +192 -0
  195. package/src/reports/moh-711/sections/anc.component.tsx +159 -0
  196. package/src/reports/moh-711/sections/cervical-cancer.component.tsx +148 -0
  197. package/src/reports/moh-711/sections/chanis.component.tsx +389 -0
  198. package/src/reports/moh-711/sections/family-planning.component.tsx +299 -0
  199. package/src/reports/moh-711/sections/gbv.component.tsx +115 -0
  200. package/src/reports/moh-711/sections/maternity.component.tsx +321 -0
  201. package/src/reports/moh-711/sections/medical-social-work.component.tsx +73 -0
  202. package/src/reports/moh-711/sections/other.component.tsx +47 -0
  203. package/src/reports/moh-711/sections/physiotherapy.component.tsx +61 -0
  204. package/src/reports/moh-711/sections/pnc.component.tsx +127 -0
  205. package/src/reports/moh-711/sections/post-abortion.component.tsx +42 -0
  206. package/src/reports/moh-711/sections/rehabilitation.component.tsx +57 -0
  207. package/src/reports/moh-711/sections/report-compiled-by.component.tsx +50 -0
  208. package/src/reports/moh-711/sections/tb-screening.component.tsx +94 -0
  209. package/src/reports/moh-717/moh-717.component.tsx +149 -985
  210. package/src/reports/moh-717/moh717.scss +217 -21
  211. package/src/reports/moh-717/registers/nutrition-register.component.tsx +48 -0
  212. package/src/reports/moh-717/sections/finance.component.tsx +42 -0
  213. package/src/reports/moh-717/sections/inpatient.component.tsx +374 -0
  214. package/src/reports/moh-717/sections/maternity.component.tsx +126 -0
  215. package/src/reports/moh-717/sections/medical-records.component.tsx +35 -0
  216. package/src/reports/moh-717/sections/mortuary.component.tsx +43 -0
  217. package/src/reports/moh-717/sections/operations.component.tsx +53 -0
  218. package/src/reports/moh-717/sections/orthopaedic-trauma.component.tsx +98 -0
  219. package/src/reports/moh-717/sections/outpatient.component.tsx +597 -0
  220. package/src/reports/moh-717/sections/pharmacy.component.tsx +43 -0
  221. package/src/reports/moh-717/sections/preparedby.component.tsx +52 -0
  222. package/src/reports/moh-717/sections/special-services.component.tsx +114 -0
  223. package/src/reports/moh-729b/moh-729b-form.component.tsx +385 -0
  224. package/src/reports/moh-729b/moh-729b-linelist.component.tsx +205 -0
  225. package/src/reports/moh-729b/moh-729b-summary.component.tsx +258 -0
  226. package/src/reports/moh-729b/moh-729b-summary.config.ts +229 -0
  227. package/src/reports/moh-729b/moh-729b.component.tsx +157 -0
  228. package/src/reports/moh-729b/moh-729b.config.ts +235 -0
  229. package/src/reports/moh-729b/moh-729b.scss +276 -0
  230. package/src/reports/moh-729b/moh-729b.types.ts +111 -0
  231. package/src/reports/moh-731/linelist/moh-731-indicators.ts +1122 -0
  232. package/src/reports/moh-731/linelist/moh-731-linelist-sections.ts +472 -0
  233. package/src/reports/moh-731/linelist/moh-731-linelist.component.tsx +102 -0
  234. package/src/reports/moh-731/linelist/use-open-moh-731-linelist.ts +50 -0
  235. package/src/reports/moh-731/moh-731.component.tsx +269 -0
  236. package/src/reports/moh-731/moh-731.scss +496 -0
  237. package/src/reports/moh-731/registers/moh-366.columns.ts +38 -0
  238. package/src/reports/moh-731/registers/moh-366.component.tsx +471 -0
  239. package/src/reports/moh-731/registers/prep-register.component.tsx +284 -0
  240. package/src/reports/moh-731/registers/prep.columns.ts +34 -0
  241. package/src/reports/moh-731/sections/hiv-and-tb-treatment.component.tsx +1206 -0
  242. package/src/reports/moh-731/sections/hiv-testing-and-pre-exposure.component.tsx +727 -0
  243. package/src/reports/moh-731/sections/pmtct.component.tsx +572 -0
  244. package/src/reports/moh-731/summary/moh-731-summary.component.tsx +313 -0
  245. package/src/reports/moh-731/summary/moh-731-summary.config.ts +527 -0
  246. package/src/reports/moh-731/summary/moh-731-summary.scss +247 -0
  247. package/src/reports/moh-740/moh-740.component.scss +202 -0
  248. package/src/reports/moh-740/moh-740.component.tsx +1339 -0
  249. package/src/reports/moh-740/moh-740.resource.ts +54 -0
  250. package/src/reports/moh-740/registers/moh-222-daily-register/moh-222-daily-register.scss +20 -0
  251. package/src/reports/moh-740/registers/moh-222-daily-register/moh-222-daily-register.tsx +183 -0
  252. package/src/reports/moh-740/registers/moh-270-permanent-register/moh-270-permanent-register.scss +6 -0
  253. package/src/reports/moh-740/registers/moh-270-permanent-register/moh-270-permanent-register.tsx +151 -0
  254. package/src/reports/moh-740/registers/moh-740-patient-list.scss +0 -0
  255. package/src/reports/moh-740/registers/moh-740-patient-list.tsx +60 -0
  256. package/src/reports/moh-740/registers/moh-740.columns.ts +54 -0
  257. package/src/reports/moh-740/shared/data-cell/data-cell.scss +6 -0
  258. package/src/reports/moh-740/shared/data-cell/data-cell.tsx +23 -0
  259. package/src/reports/moh-740/shared/utils/indicator-register-map.ts +159 -0
  260. package/src/reports/moh-740/types/index.ts +223 -0
  261. package/src/reports/moh-745/moh-745.component.tsx +2058 -0
  262. package/src/reports/moh-745/moh-745.scss +142 -0
  263. package/src/reports/moh-745/registers/moh-412-register.component.scss +6 -0
  264. package/src/reports/moh-745/registers/moh-412-register.component.tsx +275 -0
  265. package/src/reports/moh-745/registers/moh-412.columns.ts +52 -0
  266. package/src/reports/moh-745/registers/type.ts +223 -0
  267. package/src/reports/table-wrapper/table-row-mapper.component.tsx +20 -5
  268. package/src/reports/table-wrapper/table-wrapper.component.tsx +23 -14
  269. package/src/reports/table-wrapper/table-wrapper.scss +1 -16
  270. package/src/resources/moh-267.resource.ts +27 -0
  271. package/src/resources/moh-362.resource.ts +27 -0
  272. package/src/resources/moh-505.resource.ts +69 -0
  273. package/src/resources/moh-705.resource.ts +118 -0
  274. package/src/resources/moh-706.resource.ts +80 -0
  275. package/src/resources/moh-710.resource.ts +60 -9
  276. package/src/resources/moh-711.resource.ts +206 -9
  277. package/src/resources/moh-717.resource.ts +26 -9
  278. package/src/resources/moh-729b.resource.ts +25 -0
  279. package/src/resources/moh-731.resource.ts +90 -0
  280. package/src/resources/moh-745.resource.ts +76 -0
  281. package/src/resources/patient-list-page.ts +66 -0
  282. package/src/resources/presumptive-tb.resource.ts +27 -0
  283. package/src/root.component.tsx +79 -8
  284. package/src/root.scss +36 -0
  285. package/src/routes.json +3 -1
  286. package/src/utils/format-indicator.ts +39 -0
  287. package/src/utils/get-base-url.ts +39 -7
  288. package/src/utils/utils.ts +30 -1
  289. package/tsconfig.json +1 -1
  290. package/webpack.config.js +22 -1
  291. package/dist/138.js +0 -2
  292. package/dist/138.js.LICENSE.txt +0 -20
  293. package/dist/138.js.map +0 -1
  294. package/dist/184.js +0 -2
  295. package/dist/184.js.LICENSE.txt +0 -14
  296. package/dist/184.js.map +0 -1
  297. package/dist/205.js +0 -1
  298. package/dist/205.js.map +0 -1
  299. package/dist/812.js +0 -1
  300. package/dist/812.js.map +0 -1
  301. package/dist/879.js +0 -1
  302. package/dist/879.js.map +0 -1
  303. package/dist/ampath-esm-reports-app.js.buildmanifest.json +0 -539
  304. package/dist/main.js +0 -1
  305. package/dist/main.js.map +0 -1
@@ -0,0 +1,92 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ import { PAGE_SIZE, type PatientListPage } from '../../resources/patient-list-page';
4
+ import { showReportError } from '../report-error';
5
+
6
+ type Fetcher = (paging: { startIndex: number; limit: number }) => Promise<PatientListPage>;
7
+
8
+ /**
9
+ * Page state and fetching for a register's line list.
10
+ *
11
+ * Registers used to ask for a hardcoded 300 rows and render every one of them.
12
+ * At 41 columns that is 12,300 cells, and anything past 300 was silently
13
+ * dropped. This fetches a page at a time, so the DOM stays flat however large
14
+ * the list is, and reports the total so truncation is never invisible.
15
+ */
16
+ export function usePatientList(fetcher: Fetcher, deps: React.DependencyList) {
17
+ const [rows, setRows] = useState<Array<Record<string, any>>>([]);
18
+ const [total, setTotal] = useState(0);
19
+ const [isTotalExact, setIsTotalExact] = useState(true);
20
+ const [isLoading, setIsLoading] = useState(false);
21
+ const [page, setPage] = useState(1);
22
+ const [pageSize, setPageSize] = useState(PAGE_SIZE);
23
+
24
+ /* Guards against an earlier page landing after a later one and overwriting
25
+ it, which is easy to trigger by clicking through pages quickly. */
26
+ const request = useRef(0);
27
+
28
+ const load = useCallback(
29
+ async (nextPage: number, size: number) => {
30
+ const id = ++request.current;
31
+
32
+ setIsLoading(true);
33
+
34
+ try {
35
+ const result = await fetcher({ startIndex: (nextPage - 1) * size, limit: size });
36
+
37
+ if (id !== request.current) {
38
+ return;
39
+ }
40
+
41
+ setRows(result.results);
42
+ setTotal(result.total);
43
+ setIsTotalExact(result.isTotalExact);
44
+ } catch (error) {
45
+ if (id === request.current) {
46
+ setRows([]);
47
+ showReportError('the register', error);
48
+ }
49
+ } finally {
50
+ if (id === request.current) {
51
+ setIsLoading(false);
52
+ }
53
+ }
54
+ },
55
+ // eslint-disable-next-line react-hooks/exhaustive-deps
56
+ deps,
57
+ );
58
+
59
+ /** Back to page one whenever the query behind the list changes */
60
+ useEffect(() => {
61
+ setPage(1);
62
+ load(1, pageSize);
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ }, deps);
65
+
66
+ const onPageChange = useCallback(
67
+ ({ page: nextPage, pageSize: nextSize }: { page: number; pageSize: number }) => {
68
+ setPage(nextPage);
69
+ setPageSize(nextSize);
70
+ load(nextPage, nextSize);
71
+ },
72
+ [load],
73
+ );
74
+
75
+ /** Every row across every page, for the CSV export */
76
+ const fetchAll = useCallback(async () => {
77
+ const all: Array<Record<string, any>> = [];
78
+ const size = 500;
79
+
80
+ for (let index = 0; ; index += size) {
81
+ const result = await fetcher({ startIndex: index, limit: size });
82
+ all.push(...result.results);
83
+
84
+ if (result.results.length < size) {
85
+ return all;
86
+ }
87
+ }
88
+ // eslint-disable-next-line react-hooks/exhaustive-deps
89
+ }, deps);
90
+
91
+ return { rows, total, isTotalExact, isLoading, page, pageSize, onPageChange, fetchAll };
92
+ }
@@ -0,0 +1 @@
1
+ export * from './report-skeleton.component';
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { DataTableSkeleton, SkeletonText } from '@carbon/react';
3
+
4
+ import styles from './report-skeleton.scss';
5
+
6
+ interface ReportSkeletonProps {
7
+ /** How many indicator blocks to outline. Roughly the sections a form has. */
8
+ blocks?: number;
9
+ }
10
+
11
+ /**
12
+ * Placeholder in the shape of a generated report.
13
+ *
14
+ * Reports replaced their whole body while fetching, so a centred spinner left
15
+ * the page blank and shifted everything when the data landed. Outlining the
16
+ * masthead and the indicator tables keeps the layout stable and shows how much
17
+ * is coming.
18
+ */
19
+ export const ReportSkeleton: React.FC<ReportSkeletonProps> = ({ blocks = 2 }) => (
20
+ <div className={styles.skeleton} aria-busy="true" aria-label="Loading report">
21
+ <div className={styles.header}>
22
+ <SkeletonText heading width="45%" />
23
+ <SkeletonText width="70%" />
24
+ </div>
25
+
26
+ {Array.from({ length: blocks }, (_, index) => (
27
+ <div key={index} className={styles.block}>
28
+ <SkeletonText heading width="30%" />
29
+ <DataTableSkeleton showHeader={false} showToolbar={false} columnCount={4} rowCount={5} size="sm" />
30
+ </div>
31
+ ))}
32
+ </div>
33
+ );
34
+
35
+ /**
36
+ * Placeholder for the register and patient-list pages, which are a single
37
+ * table rather than a form.
38
+ */
39
+ export const RegisterSkeleton: React.FC<{ columnCount?: number; rowCount?: number }> = ({
40
+ columnCount = 6,
41
+ rowCount = 8,
42
+ }) => (
43
+ <div className={styles.skeleton} aria-busy="true" aria-label="Loading register">
44
+ <DataTableSkeleton showHeader={false} showToolbar={false} columnCount={columnCount} rowCount={rowCount} size="sm" />
45
+ </div>
46
+ );
@@ -0,0 +1,29 @@
1
+ @use '@carbon/layout';
2
+ @use '@openmrs/esm-styleguide/src/vars' as *;
3
+
4
+ .skeleton {
5
+ padding: layout.$spacing-05;
6
+ margin-bottom: layout.$spacing-05;
7
+ background-color: $ui-02;
8
+ border: 1px solid $ui-03;
9
+ }
10
+
11
+ /* When it stands in for a register table it sits under the caption bar */
12
+ .skeleton:not(:first-child) {
13
+ margin-bottom: 0;
14
+ border-top: none;
15
+ }
16
+
17
+ .header {
18
+ padding-bottom: layout.$spacing-05;
19
+ margin-bottom: layout.$spacing-05;
20
+ border-bottom: 1px solid $ui-03;
21
+ }
22
+
23
+ .block {
24
+ margin-bottom: layout.$spacing-06;
25
+
26
+ &:last-child {
27
+ margin-bottom: 0;
28
+ }
29
+ }
@@ -3,10 +3,18 @@ export const configSchema = {
3
3
  etlBaseUrl: {
4
4
  _type: Type.String,
5
5
  _default: '',
6
- _description: 'ETL endpoint',
6
+ _description: 'Base URL of the ETL service every report reads from, e.g. "/etl"',
7
+ },
8
+ /* Declared because the deployment sets it in this section. Nothing reads it
9
+ yet; without it the config validator reports it as an unknown key. */
10
+ subDomainUrl: {
11
+ _type: Type.String,
12
+ _default: '',
13
+ _description: 'Origin this instance is served from',
7
14
  },
8
15
  };
9
16
 
10
17
  export type Config = {
11
18
  etlBaseUrl: string;
19
+ subDomainUrl: string;
12
20
  };
@@ -0,0 +1,191 @@
1
+ import {
2
+ Activity,
3
+ ChartColumn,
4
+ Document,
5
+ DocumentTasks,
6
+ GenderFemale,
7
+ Growth,
8
+ HealthCross,
9
+ Medication,
10
+ Microscope,
11
+ Stethoscope,
12
+ UserMultiple,
13
+ Warning,
14
+ type CarbonIconType,
15
+ } from '@carbon/react/icons';
16
+
17
+ export type ReportTone = 'violet' | 'magenta' | 'teal' | 'blue';
18
+
19
+ export interface ReportDefinition {
20
+ /** The MOH form number, e.g. `MOH 731` */
21
+ code: string;
22
+ /** Human readable name of the form */
23
+ name: string;
24
+ description: string;
25
+ /** Route registered in root.component.tsx */
26
+ path: string;
27
+ icon: CarbonIconType;
28
+ /** Extra terms matched by the search field */
29
+ keywords: string[];
30
+ }
31
+
32
+ export interface ReportCategory {
33
+ id: string;
34
+ title: string;
35
+ description: string;
36
+ tone: ReportTone;
37
+ reports: ReportDefinition[];
38
+ }
39
+
40
+ export const reportCategories: ReportCategory[] = [
41
+ {
42
+ id: 'hiv-and-chronic-care',
43
+ title: 'HIV & chronic care',
44
+ description: 'Comprehensive care programme summaries',
45
+ tone: 'violet',
46
+ reports: [
47
+ {
48
+ code: 'MOH 731',
49
+ name: 'Comprehensive HIV/AIDS',
50
+ description: 'NASCOP monthly summary for HIV testing, EMTCT, care and treatment.',
51
+ path: '/moh-731',
52
+ icon: HealthCross,
53
+ keywords: ['hiv', 'nascop', 'art', 'prep', 'emtct', 'pmtct', 'tb'],
54
+ },
55
+ {
56
+ code: 'MOH 729B',
57
+ name: 'Facility ARV patient summary (F-MAPS)',
58
+ description: 'Monthly count of current active patients on each ARV and OI regimen.',
59
+ path: '/moh-729b',
60
+ icon: Medication,
61
+ keywords: ['fmaps', 'f-maps', 'arv', 'regimen', 'art', 'pmtct', 'pep', 'prep', 'tpt', 'pharmacy'],
62
+ },
63
+ {
64
+ code: 'MOH 740',
65
+ name: 'Diabetes & hypertension',
66
+ description: 'Monthly summary for the NCD comprehensive care clinic.',
67
+ path: '/moh-740',
68
+ icon: Activity,
69
+ keywords: ['ncd', 'diabetes', 'hypertension', 'chronic'],
70
+ },
71
+ ],
72
+ },
73
+ {
74
+ id: 'reproductive-and-child-health',
75
+ title: 'Reproductive & child health',
76
+ description: 'RMNCAH service delivery summaries',
77
+ tone: 'magenta',
78
+ reports: [
79
+ {
80
+ code: 'MOH 710',
81
+ name: 'Immunization',
82
+ description: 'Child immunization and vitamin A supplementation summary.',
83
+ path: '/moh-710',
84
+ icon: Growth,
85
+ keywords: ['immunization', 'vaccine', 'child', 'khis', 'bcg', 'polio'],
86
+ },
87
+ {
88
+ code: 'MOH 711',
89
+ name: 'Integrated programme summary',
90
+ description: 'Reproductive and child health, medical and rehabilitation services.',
91
+ path: '/moh-711',
92
+ icon: UserMultiple,
93
+ keywords: ['rmncah', 'anc', 'pnc', 'maternity', 'family planning', 'gbv', 'physiotherapy'],
94
+ },
95
+ {
96
+ code: 'MOH 745',
97
+ name: 'Cancer screening',
98
+ description: 'Monthly cervical and breast cancer screening summary.',
99
+ path: '/moh-745',
100
+ icon: GenderFemale,
101
+ keywords: ['cancer', 'cervical', 'screening', 'via', 'hpv'],
102
+ },
103
+ ],
104
+ },
105
+ {
106
+ id: 'outpatient-and-workload',
107
+ title: 'Outpatient & workload',
108
+ description: 'Morbidity and facility workload returns',
109
+ tone: 'teal',
110
+ reports: [
111
+ {
112
+ code: 'MOH 705A',
113
+ name: 'Outpatient morbidity, under 5',
114
+ description: 'Summary of diagnoses for children under five years.',
115
+ path: '/moh-705a',
116
+ icon: DocumentTasks,
117
+ keywords: ['morbidity', 'outpatient', 'under five', 'children', 'khis'],
118
+ },
119
+ {
120
+ code: 'MOH 705B',
121
+ name: 'Outpatient morbidity, over 5',
122
+ description: 'Summary of diagnoses for clients aged five years and above.',
123
+ path: '/moh-705b',
124
+ icon: Document,
125
+ keywords: ['morbidity', 'outpatient', 'above five', 'adults', 'khis'],
126
+ },
127
+ {
128
+ code: 'MOH 717',
129
+ name: 'Service workload',
130
+ description: 'Monthly service workload across facility departments.',
131
+ path: '/moh-717',
132
+ icon: ChartColumn,
133
+ keywords: ['workload', 'inpatient', 'pharmacy', 'mortuary', 'theatre', 'maternity'],
134
+ },
135
+ ],
136
+ },
137
+ {
138
+ id: 'diagnostics-and-surveillance',
139
+ title: 'Diagnostics & surveillance',
140
+ description: 'Laboratory and disease surveillance returns',
141
+ tone: 'blue',
142
+ reports: [
143
+ {
144
+ code: 'MOH 706',
145
+ name: 'Laboratory services',
146
+ description: 'Monthly summary of laboratory investigations and results.',
147
+ path: '/moh-706',
148
+ icon: Microscope,
149
+ keywords: ['laboratory', 'lab', 'urine', 'parasitology', 'blood chemistry'],
150
+ },
151
+ {
152
+ code: 'MOH 505',
153
+ name: 'IDSR epidemic monitoring',
154
+ description: 'Weekly monitoring form for priority notifiable diseases.',
155
+ path: '/moh-505',
156
+ icon: Warning,
157
+ keywords: ['idsr', 'surveillance', 'epidemic', 'notifiable', 'outbreak'],
158
+ },
159
+ {
160
+ code: 'MOH/F/DNTLDP/02',
161
+ name: 'TB active case finding summary',
162
+ description: 'Monthly facility summary of TB screening, investigation, diagnosis and treatment by department.',
163
+ path: '/acf-summary',
164
+ icon: Stethoscope,
165
+ keywords: ['tb', 'tuberculosis', 'acf', 'active case finding', 'screening', 'presumptive', 'dntldp'],
166
+ },
167
+ ],
168
+ },
169
+ ];
170
+
171
+ export const totalReportCount = reportCategories.reduce((count, category) => count + category.reports.length, 0);
172
+
173
+ export function filterCategories(categories: ReportCategory[], searchTerm: string): ReportCategory[] {
174
+ const term = searchTerm.trim().toLowerCase();
175
+
176
+ if (!term) {
177
+ return categories;
178
+ }
179
+
180
+ return categories
181
+ .map((category) => ({
182
+ ...category,
183
+ reports: category.reports.filter((report) =>
184
+ [report.code, report.name, report.description, category.title, ...report.keywords]
185
+ .join(' ')
186
+ .toLowerCase()
187
+ .includes(term),
188
+ ),
189
+ }))
190
+ .filter((category) => category.reports.length > 0);
191
+ }
@@ -1,28 +1,199 @@
1
- import React from 'react';
2
- import { useNavigate } from 'react-router-dom';
3
- import { ClickableTile } from '@carbon/react';
1
+ import React, { useState } from 'react';
2
+ import { useNavigate, useSearchParams } from 'react-router-dom';
3
+ import { Button, ClickableTile, Layer, Search, Tab, TabList, TabPanel, TabPanels, Tabs, Tag } from '@carbon/react';
4
+ import { ArrowRight, DocumentMultiple_01, Report, type CarbonIconType } from '@carbon/react/icons';
5
+ import { formatDate, useSession } from '@openmrs/esm-framework';
4
6
 
7
+ import { registerCategories, totalRegisterCount } from '../registers/register-catalog';
8
+ import {
9
+ filterCategories,
10
+ reportCategories,
11
+ totalReportCount,
12
+ type ReportCategory,
13
+ type ReportDefinition,
14
+ } from './report-catalog';
5
15
  import styles from './reports-dashboard.module.scss';
16
+
6
17
  interface ReportsDashboardProps {}
18
+
7
19
  const ReportsDashboard: React.FC<ReportsDashboardProps> = () => {
8
20
  const navigate = useNavigate();
21
+ const session = useSession();
22
+ /* each tab keeps its own search */
23
+ const [reportSearch, setReportSearch] = useState<string>('');
24
+ const [registerSearch, setRegisterSearch] = useState<string>('');
25
+
26
+ /* Reports first, Registers second; the open tab is kept in the URL */
27
+ const [searchParams, setSearchParams] = useSearchParams();
28
+ const selectedTab = searchParams.get('tab') === 'registers' ? 1 : 0;
29
+ const selectTab = ({ selectedIndex }: { selectedIndex: number }) => {
30
+ const next = new URLSearchParams(searchParams);
31
+ if (selectedIndex === 1) {
32
+ next.set('tab', 'registers');
33
+ } else {
34
+ next.delete('tab');
35
+ }
36
+ setSearchParams(next, { replace: true });
37
+ };
38
+
39
+ const facilityName = session?.sessionLocation?.display;
40
+ const today = formatDate(new Date(), { mode: 'standard', time: false, noToday: true });
41
+
42
+ const renderReportTile = (report: ReportDefinition, tone: string) => {
43
+ const Icon = report.icon;
44
+
45
+ return (
46
+ <ClickableTile
47
+ key={report.path}
48
+ className={styles.tile}
49
+ data-tone={tone}
50
+ onClick={() => navigate(report.path)}
51
+ aria-label={`${report.code} ${report.name}`}
52
+ >
53
+ <div className={styles.tileIcon}>
54
+ <Icon size={20} />
55
+ </div>
56
+ <div className={styles.tileCopy}>
57
+ <p className={styles.tileCode}>{report.code}</p>
58
+ <h4 className={styles.tileName}>{report.name}</h4>
59
+ </div>
60
+ <ArrowRight size={16} className={styles.tileArrow} />
61
+ </ClickableTile>
62
+ );
63
+ };
64
+
65
+ const renderCategory = (category: ReportCategory) => (
66
+ <section key={category.id} className={styles.category}>
67
+ <div className={styles.categoryHeader} data-tone={category.tone}>
68
+ <h5 className={styles.categoryTitle}>{category.title}</h5>
69
+ <Tag size="sm" type="outline">
70
+ {category.reports.length}
71
+ </Tag>
72
+ </div>
73
+ <div className={styles.grid}>{category.reports.map((report) => renderReportTile(report, category.tone))}</div>
74
+ </section>
75
+ );
76
+
77
+ /** A tab's list: search row, then its categories, or a message when nothing matches */
78
+ const renderCatalog = ({
79
+ id,
80
+ noun,
81
+ categories,
82
+ total,
83
+ term,
84
+ setTerm,
85
+ icon: Icon,
86
+ hint,
87
+ }: {
88
+ id: string;
89
+ noun: string;
90
+ categories: ReportCategory[];
91
+ total: number;
92
+ term: string;
93
+ setTerm: (term: string) => void;
94
+ icon: CarbonIconType;
95
+ hint: string;
96
+ }) => {
97
+ const visible = filterCategories(categories, term);
98
+ const matches = visible.reduce((count, category) => count + category.reports.length, 0);
99
+ const isSearching = term.trim().length > 0;
100
+ const label = `Search ${noun}`;
101
+ /* "1 register", "10 reports" */
102
+ const counted = (n: number) => (n === 1 ? noun.replace(/s$/, '') : noun);
103
+
104
+ return (
105
+ <>
106
+ <div className={styles.toolbar}>
107
+ <Layer className={styles.searchLayer}>
108
+ <Search
109
+ id={id}
110
+ labelText={label}
111
+ placeholder={label}
112
+ size="md"
113
+ value={term}
114
+ onChange={(event) => setTerm(event.target.value)}
115
+ onClear={() => setTerm('')}
116
+ />
117
+ </Layer>
118
+ <p className={styles.resultCount}>
119
+ {isSearching ? `${matches} of ${total} ${counted(total)}` : `${total} ${counted(total)} available`}
120
+ </p>
121
+ </div>
122
+
123
+ {visible.length > 0 ? (
124
+ visible.map(renderCategory)
125
+ ) : (
126
+ <Layer className={styles.emptyState}>
127
+ <Icon size={32} className={styles.emptyStateIcon} />
128
+ <h5>
129
+ No {noun} match &ldquo;{term}&rdquo;
130
+ </h5>
131
+ <p>{hint}</p>
132
+ <Button kind="ghost" size="sm" onClick={() => setTerm('')}>
133
+ Clear search
134
+ </Button>
135
+ </Layer>
136
+ )}
137
+ </>
138
+ );
139
+ };
140
+
9
141
  return (
10
- <>
11
- <div className={styles.container}>
12
- <ClickableTile onClick={() => navigate('/moh-710')} className={styles.tile}>
13
- MOH-710 REPORT
14
- </ClickableTile>
15
- <ClickableTile onClick={() => navigate('/moh-711')} className={styles.tile}>
16
- MOH-711 REPORT
17
- </ClickableTile>
18
- <ClickableTile onClick={() => navigate('/moh-717')} className={styles.tile}>
19
- MOH-717 REPORT
20
- </ClickableTile>
21
- <ClickableTile onClick={() => navigate('/moh-706')} className={styles.tile}>
22
- MOH-706 REPORT
23
- </ClickableTile>
142
+ <div className={styles.dashboard}>
143
+ {/* The facility and date sit at the right end of the tab row */}
144
+ <div className={styles.tabsArea}>
145
+ <p className={styles.headerMeta}>{[facilityName, today].filter(Boolean).join(' · ')}</p>
146
+ <Tabs selectedIndex={selectedTab} onChange={selectTab}>
147
+ <TabList aria-label="Reports and registers" className={styles.tabList}>
148
+ <Tab>
149
+ <span className={styles.tabLabel}>
150
+ <Report size={16} />
151
+ Reports
152
+ <Tag size="sm" type="gray" className={styles.tabCount}>
153
+ {totalReportCount}
154
+ </Tag>
155
+ </span>
156
+ </Tab>
157
+ <Tab>
158
+ <span className={styles.tabLabel}>
159
+ <DocumentMultiple_01 size={16} />
160
+ Registers
161
+ <Tag size="sm" type="gray" className={styles.tabCount}>
162
+ {totalRegisterCount}
163
+ </Tag>
164
+ </span>
165
+ </Tab>
166
+ </TabList>
167
+ <TabPanels>
168
+ <TabPanel className={styles.tabPanel}>
169
+ {renderCatalog({
170
+ id: 'reports-search',
171
+ noun: 'reports',
172
+ categories: reportCategories,
173
+ total: totalReportCount,
174
+ term: reportSearch,
175
+ setTerm: setReportSearch,
176
+ icon: Report,
177
+ hint: 'Try a form number such as 731, or a service such as immunization.',
178
+ })}
179
+ </TabPanel>
180
+ <TabPanel className={styles.tabPanel}>
181
+ {/* Registers, from src/registers, listed and searched as the reports are */}
182
+ {renderCatalog({
183
+ id: 'registers-search',
184
+ noun: 'registers',
185
+ categories: registerCategories,
186
+ total: totalRegisterCount,
187
+ term: registerSearch,
188
+ setTerm: setRegisterSearch,
189
+ icon: DocumentMultiple_01,
190
+ hint: 'Try a register number such as 362, or a service such as HTS.',
191
+ })}
192
+ </TabPanel>
193
+ </TabPanels>
194
+ </Tabs>
24
195
  </div>
25
- </>
196
+ </div>
26
197
  );
27
198
  };
28
199