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

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 (302) hide show
  1. package/dist/0.css +1 -0
  2. package/dist/0.css.map +1 -0
  3. package/dist/0.js +1 -0
  4. package/dist/0.js.map +1 -0
  5. package/dist/197.js +1 -1
  6. package/dist/282.js +1 -1
  7. package/dist/282.js.map +1 -1
  8. package/dist/295.css +1 -0
  9. package/dist/295.css.map +1 -0
  10. package/dist/295.js +1 -0
  11. package/dist/295.js.map +1 -0
  12. package/dist/299.js +2 -0
  13. package/dist/299.js.LICENSE.txt +3 -0
  14. package/dist/299.js.map +1 -0
  15. package/dist/300.js +1 -1
  16. package/dist/311.js +1 -1
  17. package/dist/311.js.map +1 -1
  18. package/dist/335.js +1 -1
  19. package/dist/353.js +1 -1
  20. package/dist/353.js.map +1 -1
  21. package/dist/354.js +2 -0
  22. package/dist/354.js.LICENSE.txt +20 -0
  23. package/dist/354.js.map +1 -0
  24. package/dist/410.js +1 -0
  25. package/dist/410.js.map +1 -0
  26. package/dist/457.js +2 -0
  27. package/dist/457.js.LICENSE.txt +261 -0
  28. package/dist/457.js.map +1 -0
  29. package/dist/478.js +1 -1
  30. package/dist/478.js.map +1 -1
  31. package/dist/480.js +1 -0
  32. package/dist/540.js +1 -1
  33. package/dist/540.js.map +1 -1
  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 +1332 -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.tsx +149 -0
  251. package/src/reports/moh-740/registers/moh-270-permanent-register/moh-270-permanent-register.tsx +123 -0
  252. package/src/reports/moh-740/registers/moh-740-patient-list.scss +0 -0
  253. package/src/reports/moh-740/registers/moh-740-patient-list.tsx +60 -0
  254. package/src/reports/moh-740/registers/moh-740.columns.ts +54 -0
  255. package/src/reports/moh-740/shared/data-cell/data-cell.scss +6 -0
  256. package/src/reports/moh-740/shared/data-cell/data-cell.tsx +23 -0
  257. package/src/reports/moh-740/shared/utils/indicator-register-map.ts +159 -0
  258. package/src/reports/moh-740/types/index.ts +223 -0
  259. package/src/reports/moh-745/moh-745.component.tsx +2058 -0
  260. package/src/reports/moh-745/moh-745.scss +142 -0
  261. package/src/reports/moh-745/registers/moh-412-register.component.tsx +249 -0
  262. package/src/reports/moh-745/registers/moh-412.columns.ts +52 -0
  263. package/src/reports/moh-745/registers/type.ts +223 -0
  264. package/src/reports/table-wrapper/table-row-mapper.component.tsx +20 -5
  265. package/src/reports/table-wrapper/table-wrapper.component.tsx +23 -14
  266. package/src/reports/table-wrapper/table-wrapper.scss +1 -16
  267. package/src/resources/moh-267.resource.ts +27 -0
  268. package/src/resources/moh-362.resource.ts +27 -0
  269. package/src/resources/moh-505.resource.ts +69 -0
  270. package/src/resources/moh-705.resource.ts +118 -0
  271. package/src/resources/moh-706.resource.ts +80 -0
  272. package/src/resources/moh-710.resource.ts +60 -9
  273. package/src/resources/moh-711.resource.ts +206 -9
  274. package/src/resources/moh-717.resource.ts +26 -9
  275. package/src/resources/moh-729b.resource.ts +25 -0
  276. package/src/resources/moh-731.resource.ts +90 -0
  277. package/src/resources/moh-745.resource.ts +76 -0
  278. package/src/resources/patient-list-page.ts +66 -0
  279. package/src/resources/presumptive-tb.resource.ts +27 -0
  280. package/src/root.component.tsx +79 -8
  281. package/src/root.scss +36 -0
  282. package/src/routes.json +3 -1
  283. package/src/utils/format-indicator.ts +39 -0
  284. package/src/utils/get-base-url.ts +39 -7
  285. package/src/utils/utils.ts +30 -1
  286. package/tsconfig.json +1 -1
  287. package/webpack.config.js +22 -1
  288. package/dist/138.js +0 -2
  289. package/dist/138.js.LICENSE.txt +0 -20
  290. package/dist/138.js.map +0 -1
  291. package/dist/184.js +0 -2
  292. package/dist/184.js.LICENSE.txt +0 -14
  293. package/dist/184.js.map +0 -1
  294. package/dist/205.js +0 -1
  295. package/dist/205.js.map +0 -1
  296. package/dist/812.js +0 -1
  297. package/dist/812.js.map +0 -1
  298. package/dist/879.js +0 -1
  299. package/dist/879.js.map +0 -1
  300. package/dist/ampath-esm-reports-app.js.buildmanifest.json +0 -539
  301. package/dist/main.js +0 -1
  302. package/dist/main.js.map +0 -1
@@ -0,0 +1,265 @@
1
+ import React from 'react';
2
+ import { Breadcrumb, BreadcrumbItem, Button, Layer, Pagination } from '@carbon/react';
3
+ import { Link, useSearchParams } from 'react-router-dom';
4
+ import { ArrowLeft, Calendar, Document, Download, Location } from '@carbon/react/icons';
5
+ import { formatDate } from '@openmrs/esm-framework';
6
+
7
+ import { formatIndicatorName } from '../../utils/format-indicator';
8
+ import { showReportError } from '../report-error';
9
+ import { columnsToCsv, downloadCsv, rowsToCsv } from './rows-to-csv';
10
+ import { type RegisterColumn } from './register-columns';
11
+ import { REPORT_PARENTS } from './report-parents';
12
+
13
+ import { RegisterSkeleton } from '../report-skeleton';
14
+ import styles from './register-layout.scss';
15
+
16
+ interface RegisterLayoutProps {
17
+ /** The report this register was opened from, e.g. `MOH-731 Report` */
18
+ parentLabel: string;
19
+ /** Route back to that report, e.g. `/moh-731` */
20
+ parentPath?: string;
21
+ /**
22
+ * Returns to the report in place, for a register that is a view on the
23
+ * report's own route rather than a route of its own.
24
+ *
25
+ * MOH-740 toggles its registers in place, so there is nowhere to navigate:
26
+ * a breadcrumb would link the page to itself. Given this, the trail is
27
+ * replaced by a button that puts the report back.
28
+ */
29
+ onBack?: () => void;
30
+ /** Register name, e.g. `HIV Care Treatment Daily Activity Register MOH 366` */
31
+ title: string;
32
+ /**
33
+ * The page heading, when the register can name the figure itself. Without
34
+ * it the heading is built from the link's `label` and `gender`.
35
+ */
36
+ heading?: string;
37
+ /** The location the register was generated for, shown beside the period */
38
+ location?: string;
39
+ isLoading: boolean;
40
+ /** `true` once loaded with nothing to show */
41
+ isEmpty: boolean;
42
+ /** Paging state from `usePatientList` */
43
+ page?: number;
44
+ pageSize?: number;
45
+ total?: number;
46
+ /** `false` when the total is a lower bound the endpoint did not confirm */
47
+ isTotalExact?: boolean;
48
+ onPageChange?: (change: { page: number; pageSize: number }) => void;
49
+ /** Every row across every page, used to build the export */
50
+ fetchAll?: () => Promise<Array<Record<string, any>>>;
51
+ /** The register's own columns. Without them the export falls back to the
52
+ response's raw fields, which will not match the printed form. */
53
+ columns?: RegisterColumn[];
54
+ /**
55
+ * The printed form's own sub-headings (e.g. its column letters), written as
56
+ * a second CSV row so exports stay line-numbered against the paper form.
57
+ */
58
+ subHeaders?: string[];
59
+ /** The register table. Optional: MOH 510 has no table implemented yet. */
60
+ children?: React.ReactNode;
61
+ }
62
+
63
+ /**
64
+ * The frame every drill-down register renders inside.
65
+ *
66
+ * Registers are opened from a value box on a report, so the trail back matters
67
+ * more than a bare Back button: the breadcrumb shows where the figure came
68
+ * from and lets the user step to either level. The table itself is wide -- the
69
+ * MOH 366 register runs to 41 columns -- so it scrolls within the page rather
70
+ * than forcing the whole layout sideways.
71
+ */
72
+ const GENDERS: Record<string, string> = { M: 'Male', F: 'Female' };
73
+
74
+ const RegisterLayout: React.FC<RegisterLayoutProps> = ({
75
+ parentLabel,
76
+ parentPath,
77
+ onBack,
78
+ title,
79
+ heading: headingProp,
80
+ location,
81
+ isLoading,
82
+ isEmpty,
83
+ page,
84
+ pageSize,
85
+ total,
86
+ isTotalExact = true,
87
+ onPageChange,
88
+ fetchAll,
89
+ columns,
90
+ subHeaders,
91
+ children,
92
+ }) => {
93
+ /* The same register serves every indicator that links to it, so without this
94
+ each drill-down looks identical no matter which box was clicked. */
95
+ const [params] = useSearchParams();
96
+ const [isExporting, setIsExporting] = React.useState(false);
97
+ const indicator = params.get('indicator') ?? params.get('indicators');
98
+ /* The report passes the row label it displayed, so the crumb reads the way
99
+ the figure was labelled on the form. `formatIndicatorName` is the fallback
100
+ for links made before the label was carried through. */
101
+ const label = params.get('label');
102
+ const gender = params.get('gender');
103
+ const startDate = params.get('startDate');
104
+ const endDate = params.get('endDate');
105
+ const locationUuids = params.get('locationUuids');
106
+
107
+ /* Where the drill-down came from. Registers shared by two reports cannot
108
+ name their parent as a constant, so the link says which one it was; the
109
+ props remain the fallback for links made before it was carried. */
110
+ const from = params.get('from');
111
+ const parent = (from && REPORT_PARENTS[from]) || { label: parentLabel, path: parentPath ?? '' };
112
+
113
+ /* The crumb has to carry the period, not just the path: a report that does
114
+ not keep its figures in session storage regenerates from its URL, and
115
+ linking to the bare path would land the user on the placeholder instead of
116
+ the figures the register was opened from. */
117
+ const parentQuery = new URLSearchParams();
118
+ if (locationUuids) parentQuery.append('locationUuids', locationUuids);
119
+ if (startDate) parentQuery.append('startDate', startDate);
120
+ if (endDate) parentQuery.append('endDate', endDate);
121
+ /* back to the report tab the register was opened from, e.g. a summary view */
122
+ const view = params.get('view');
123
+ if (view) parentQuery.append('view', view);
124
+ const section = params.get('section');
125
+ if (section) parentQuery.append('section', section);
126
+ const parentLink = parentQuery.toString() ? `${parent.path}?${parentQuery}` : parent.path;
127
+
128
+ const heading =
129
+ headingProp ??
130
+ (label
131
+ ? `${label}${gender && GENDERS[gender] ? ` (${GENDERS[gender]})` : ''}`
132
+ : formatIndicatorName(indicator ?? ''));
133
+
134
+ /* Exports every page, not the one on screen: the DOM only holds the current
135
+ page once paginated, so a scrape would silently export 50 of 2,000. */
136
+ const exportCsv = async () => {
137
+ if (!fetchAll) {
138
+ return;
139
+ }
140
+
141
+ setIsExporting(true);
142
+
143
+ try {
144
+ const all = await fetchAll();
145
+ const slug = [title, label ?? indicator, startDate, endDate]
146
+ .filter(Boolean)
147
+ .join('-')
148
+ .replace(/[^a-z0-9]+/gi, '-')
149
+ .replace(/^-|-$/g, '')
150
+ .toLowerCase();
151
+
152
+ downloadCsv(columns ? columnsToCsv(all, columns, { subHeaderRow: subHeaders }) : rowsToCsv(all), `${slug}.csv`);
153
+ } catch (error) {
154
+ showReportError('the register export', error);
155
+ } finally {
156
+ setIsExporting(false);
157
+ }
158
+ };
159
+
160
+ const canExport = !isLoading && !isEmpty;
161
+ const pageSizeOrDefault = pageSize ?? 50;
162
+
163
+ const period =
164
+ startDate && endDate
165
+ ? `${formatDate(new Date(startDate), { mode: 'standard', time: false, noToday: true })} to ${formatDate(
166
+ new Date(endDate),
167
+ { mode: 'standard', time: false, noToday: true },
168
+ )}`
169
+ : null;
170
+
171
+ return (
172
+ <div className={styles.register}>
173
+ {onBack ? (
174
+ <div className={styles.breadcrumb}>
175
+ <Button kind="ghost" size="sm" renderIcon={ArrowLeft} onClick={onBack} className={styles.back}>
176
+ {parent.label}
177
+ </Button>
178
+ <h3 className={styles.title}>{heading || title}</h3>
179
+ </div>
180
+ ) : (
181
+ <Breadcrumb noTrailingSlash className={styles.breadcrumb}>
182
+ <BreadcrumbItem>
183
+ <Link to="/">All reports</Link>
184
+ </BreadcrumbItem>
185
+ <BreadcrumbItem>
186
+ <Link to={parentLink}>{parent.label}</Link>
187
+ </BreadcrumbItem>
188
+ <BreadcrumbItem isCurrentPage>
189
+ <h3 className={styles.title}>{heading || title}</h3>
190
+ </BreadcrumbItem>
191
+ </Breadcrumb>
192
+ )}
193
+
194
+ {/* Register, period and the export sit on one line: they all describe the
195
+ table below, and splitting them stacked two sparse rows above it. */}
196
+ {(indicator || period || canExport) && (
197
+ <div className={styles.context}>
198
+ {/* Register and period read as one caption for the table, so they
199
+ share a panel rather than sitting as two loose fragments. */}
200
+ <span className={styles.summary}>
201
+ <span className={styles.summaryTitle}>{title}</span>
202
+ <span className={styles.period}>
203
+ {period && (
204
+ <>
205
+ <Calendar size={14} />
206
+ {period}
207
+ </>
208
+ )}
209
+ {location && (
210
+ <span className={styles.location}>
211
+ <Location size={14} />
212
+ {location}
213
+ </span>
214
+ )}
215
+ {typeof total === 'number' && total > 0 && (
216
+ <span>{isTotalExact ? `${total.toLocaleString()} patients` : `${total.toLocaleString()}+ patients`}</span>
217
+ )}
218
+ </span>
219
+ </span>
220
+ {canExport && fetchAll && (
221
+ <Button
222
+ kind="tertiary"
223
+ size="sm"
224
+ renderIcon={Download}
225
+ onClick={exportCsv}
226
+ disabled={isExporting}
227
+ className={styles.export}
228
+ >
229
+ {isExporting ? 'Preparing…' : 'Export CSV'}
230
+ </Button>
231
+ )}
232
+ </div>
233
+ )}
234
+
235
+ {isLoading && <RegisterSkeleton />}
236
+
237
+ {!isLoading && isEmpty && (
238
+ <Layer className={styles.empty}>
239
+ <Document size={32} className={styles.emptyIcon} />
240
+ <h4>No records to list</h4>
241
+ <p>No clients matched this indicator for the selected period and facility.</p>
242
+ </Layer>
243
+ )}
244
+
245
+ {canExport && (
246
+ <>
247
+ <div className={styles.tableScroll}>{children}</div>
248
+ {onPageChange && typeof total === 'number' && total > pageSizeOrDefault && (
249
+ <Pagination
250
+ className={styles.pagination}
251
+ page={page}
252
+ pageSize={pageSizeOrDefault}
253
+ pageSizes={[25, 50, 100, 200]}
254
+ totalItems={total}
255
+ size="sm"
256
+ onChange={onPageChange}
257
+ />
258
+ )}
259
+ </>
260
+ )}
261
+ </div>
262
+ );
263
+ };
264
+
265
+ export default RegisterLayout;
@@ -0,0 +1,150 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .register {
6
+ padding: layout.$spacing-05 0 layout.$spacing-07;
7
+ }
8
+
9
+ .breadcrumb {
10
+ margin-bottom: layout.$spacing-05;
11
+
12
+ :global(.cds--breadcrumb-item) {
13
+ display: inline-flex;
14
+ align-items: baseline;
15
+ }
16
+ }
17
+
18
+ /* The register name doubles as the page heading, so it stays an `h3` rather
19
+ than being repeated in a title row of its own. */
20
+
21
+ .title {
22
+ @include type.type-style('productive-heading-03');
23
+ display: inline;
24
+ color: $color-gray-100;
25
+ }
26
+
27
+ /* Registers run to dozens of columns, so the table scrolls inside the page
28
+ instead of pushing the whole layout sideways. */
29
+
30
+ .tableScroll {
31
+ overflow-x: auto;
32
+ padding: layout.$spacing-05;
33
+ background-color: $ui-02;
34
+ border: 1px solid $ui-03;
35
+ border-top: none;
36
+
37
+ /* Each register carries its own table margins -- 1rem here, 0.5rem there,
38
+ 0.75rem elsewhere -- which land inside this frame and give every register a
39
+ different gap to the border. The padding above is the spacing now, so those
40
+ are levelled out. */
41
+
42
+ table,
43
+ > div {
44
+ margin: 0;
45
+ }
46
+ }
47
+
48
+
49
+ .empty {
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: center;
53
+ gap: layout.$spacing-03;
54
+ padding: layout.$spacing-07 layout.$spacing-05;
55
+ text-align: center;
56
+ background-color: $ui-02;
57
+ border: 1px solid $ui-03;
58
+ border-top: none;
59
+
60
+ h4 {
61
+ @include type.type-style('productive-heading-02');
62
+ color: $color-gray-100;
63
+ }
64
+
65
+ p {
66
+ @include type.type-style('body-long-01');
67
+ color: $text-02;
68
+ }
69
+ }
70
+
71
+ .emptyIcon {
72
+ fill: $ui-04;
73
+ }
74
+
75
+ /* The caption and the table below it are one card: the bar carries the register
76
+ name, the period it covers and the export, and sits flush on whatever follows
77
+ -- skeleton, empty state or table -- so the page reads as a single object
78
+ rather than three stacked fragments. */
79
+
80
+ .context {
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ align-items: center;
84
+ justify-content: space-between;
85
+ gap: layout.$spacing-03 layout.$spacing-05;
86
+ padding: layout.$spacing-04 layout.$spacing-05;
87
+ background-color: $ui-02;
88
+ border: 1px solid $ui-03;
89
+ border-bottom: none;
90
+ }
91
+
92
+ /* Register name over the period it covers: side by side they made one long
93
+ line that crowded the action; stacked, the name leads and the period reads
94
+ as its caption. */
95
+
96
+ .summary {
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: layout.$spacing-02;
100
+ min-width: 0;
101
+ }
102
+
103
+ .summaryTitle {
104
+ @include type.type-style('body-short-01');
105
+ font-weight: 600;
106
+ color: $color-gray-100;
107
+ }
108
+
109
+ .period {
110
+ @include type.type-style('label-01');
111
+ display: inline-flex;
112
+ flex-wrap: wrap;
113
+ align-items: center;
114
+ gap: layout.$spacing-02;
115
+ color: $text-02;
116
+
117
+ svg {
118
+ fill: var(--brand-01);
119
+ transform: translateY(0.125rem);
120
+ }
121
+ }
122
+
123
+ /* Location sits after the period as one unit, so on a narrow screen it wraps
124
+ to its own line whole rather than leaving its pin behind */
125
+ .location {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ gap: layout.$spacing-02;
129
+ margin-left: layout.$spacing-03;
130
+ }
131
+
132
+ .export {
133
+ flex-shrink: 0;
134
+ margin-left: auto;
135
+ }
136
+
137
+ /* Sits directly under the table, completing the card */
138
+
139
+ .pagination {
140
+ border: 1px solid $ui-03;
141
+ border-top: none;
142
+ }
143
+
144
+ /* A register rendered as a view on its report's own route has nowhere to
145
+ navigate, so the trail is a button and the register name beside it. */
146
+
147
+ .back {
148
+ padding-inline: 0.25rem;
149
+ min-block-size: auto;
150
+ }
@@ -0,0 +1,71 @@
1
+ @use '@openmrs/esm-styleguide/src/vars' as *;
2
+
3
+ /* The one stylesheet every register table uses.
4
+ *
5
+ * Registers previously borrowed their parent report's sheet, so a report sized
6
+ * for an A4 page dragged its paper typography into its line lists, and the same
7
+ * table rules were restated in half a dozen files that drifted apart. These are
8
+ * screen tables: dense, scrollable, and independent of how their report prints.
9
+ */
10
+
11
+ .tableContainer {
12
+ width: 100%;
13
+ }
14
+
15
+ /* Sized to content rather than to the frame, so a long value -- a full name,
16
+ say -- widens its own column instead of wrapping. `.tableScroll` in the
17
+ layout scrolls when the table outgrows the page. */
18
+
19
+ .table {
20
+ width: auto;
21
+ min-width: 100%;
22
+ margin: 0;
23
+ font-size: 0.75rem;
24
+ border-collapse: collapse;
25
+ }
26
+
27
+ .tableBordered td,
28
+ .tableBordered th {
29
+ padding: 0.125rem 0.5rem;
30
+ font-size: 0.75rem;
31
+ line-height: 1.3;
32
+ border: 1px solid $ui-04;
33
+ }
34
+
35
+ .tableBordered th {
36
+ font-weight: 600;
37
+ color: $color-gray-100;
38
+ background-color: $ui-01;
39
+ /* headings carry deliberate line breaks, so they wrap where the markup says */
40
+ white-space: normal;
41
+ }
42
+
43
+ .tableBordered td {
44
+ white-space: nowrap;
45
+ }
46
+
47
+ /* Long prose columns (diagnoses, referrals, comments) wrap inside a bounded
48
+ width instead of stretching the row past the fold. */
49
+ .tableBordered td.wrapCell {
50
+ white-space: normal;
51
+ min-width: 8rem;
52
+ max-width: 16rem;
53
+ overflow-wrap: anywhere;
54
+ }
55
+
56
+ .tableStriped tbody tr:nth-of-type(odd) {
57
+ background-color: $ui-01;
58
+ }
59
+
60
+ .tableStriped tbody tr:hover {
61
+ background-color: $brand-primary-10;
62
+ }
63
+
64
+ .textCentre {
65
+ text-align: center;
66
+ }
67
+
68
+ .verticalText {
69
+ writing-mode: vertical-rl;
70
+ transform: rotate(180deg);
71
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The report each register can be opened from.
3
+ *
4
+ * Several registers serve more than one report -- MOH 333, 405 and 406 are
5
+ * reached from both MOH-711 and MOH-717, and MOH 412 from both MOH-711 and
6
+ * MOH-745. A register cannot name its parent as a constant, then: whichever
7
+ * report it named, the trail would be wrong for everyone arriving from the
8
+ * other one. The link carries `from`, and the trail is resolved from it.
9
+ */
10
+ export interface ReportParent {
11
+ label: string;
12
+ path: string;
13
+ }
14
+
15
+ export const REPORT_PARENTS: Record<string, ReportParent> = {
16
+ 'acf-summary': { label: 'ACF Facility Summary Tool', path: '/acf-summary' },
17
+ 'moh-240': { label: 'MOH-240 Register', path: '/moh-240' },
18
+ 'moh-505': { label: 'MOH-505 Report', path: '/moh-505' },
19
+ 'moh-705a': { label: 'MOH-705A Report', path: '/moh-705a' },
20
+ 'moh-705b': { label: 'MOH-705B Report', path: '/moh-705b' },
21
+ 'moh-706': { label: 'MOH-706 Report', path: '/moh-706' },
22
+ 'moh-710': { label: 'MOH-710 Report', path: '/moh-710' },
23
+ 'moh-711': { label: 'MOH-711 Report', path: '/moh-711' },
24
+ 'moh-717': { label: 'MOH-717 Report', path: '/moh-717' },
25
+ 'moh-729b': { label: 'MOH 729B F-MAPS Report', path: '/moh-729b' },
26
+ 'moh-731': { label: 'MOH-731 Report', path: '/moh-731' },
27
+ 'moh-740': { label: 'MOH-740 Report', path: '/moh-740' },
28
+ 'moh-745': { label: 'MOH-745 Report', path: '/moh-745' },
29
+ };
@@ -0,0 +1,89 @@
1
+ import { type RegisterColumn } from './register-columns';
2
+
3
+ /**
4
+ * Serialises line-list rows to CSV.
5
+ *
6
+ * Built from the data rather than from the rendered table: with pagination the
7
+ * DOM only ever holds one page, so scraping it would quietly export 50 rows
8
+ * while looking like it exported all 2,000 -- the worst way for an export to
9
+ * fail. The trade-off is that the file carries the API's own fields rather
10
+ * than the register's printed column headings, which is what a spreadsheet
11
+ * wants anyway.
12
+ */
13
+
14
+ /**
15
+ * Forces text Excel cannot mangle. A cell starting with `=`, `+`, `-` or `@`
16
+ * is parsed as a formula (`+2547...` becomes `#NAME?`), and a long digit-only
17
+ * cell is reinterpreted as a number (leading zeros dropped, switched to
18
+ * scientific notation), so both are emitted as an explicit text formula
19
+ * (`"..."` quoted inside `=`, which Excel renders as literal text).
20
+ */
21
+ const FORMULA_PREFIX = /^[=+\-@]/;
22
+ const LONG_DIGITS = /^\d{9,}$/;
23
+
24
+ function escapeCell(value: unknown, forceText = false): string {
25
+ const text = value === null || value === undefined ? '' : String(value).replace(/\s+/g, ' ').trim();
26
+
27
+ if (forceText && text !== '') {
28
+ return `="${text.replace(/"/g, '""')}"`;
29
+ }
30
+
31
+ if (text !== '' && (FORMULA_PREFIX.test(text) || LONG_DIGITS.test(text))) {
32
+ return `="${text.replace(/"/g, '""')}"`;
33
+ }
34
+
35
+ return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
36
+ }
37
+
38
+ /** Serialises using the register's own columns, so the file matches the form */
39
+ export function columnsToCsv(
40
+ rows: Array<Record<string, any>>,
41
+ columns: RegisterColumn[],
42
+ options?: {
43
+ /** A second header row (e.g. the printed form's column letters) */
44
+ subHeaderRow?: string[];
45
+ },
46
+ ): string {
47
+ const headerRows = [
48
+ columns.map((column) => escapeCell(column.header)).join(','),
49
+ ...(options?.subHeaderRow ? [options.subHeaderRow.map((cell) => escapeCell(cell)).join(',')] : []),
50
+ ];
51
+
52
+ return [
53
+ ...headerRows,
54
+ ...rows.map((row, index) => columns.map((column) => escapeCell(column.value(row, index), column.text)).join(',')),
55
+ ].join('\r\n');
56
+ }
57
+
58
+ /** Fallback for registers with no column spec yet: the response's own fields */
59
+ export function rowsToCsv(rows: Array<Record<string, any>>): string {
60
+ if (!rows.length) {
61
+ return '';
62
+ }
63
+
64
+ /* Union of keys, not just the first row's: endpoints omit fields that are
65
+ null for a given client, so keying off row one drops columns. */
66
+ const keys = new Set<string>();
67
+ rows.forEach((row) => Object.keys(row).forEach((key) => keys.add(key)));
68
+ const columns = Array.from(keys);
69
+
70
+ return [
71
+ columns.map((column) => escapeCell(column)).join(','),
72
+ ...rows.map((row) => columns.map((column) => escapeCell(row[column])).join(',')),
73
+ ].join('\r\n');
74
+ }
75
+
76
+ /** Prompts the browser to save `csv` under `filename` */
77
+ export function downloadCsv(csv: string, filename: string): void {
78
+ // the BOM is what makes Excel read the file as UTF-8 rather than latin-1
79
+ const blob = new Blob([`${csv}`], { type: 'text/csv;charset=utf-8;' });
80
+ const url = URL.createObjectURL(blob);
81
+ const link = document.createElement('a');
82
+
83
+ link.href = url;
84
+ link.download = filename;
85
+ document.body.appendChild(link);
86
+ link.click();
87
+ document.body.removeChild(link);
88
+ URL.revokeObjectURL(url);
89
+ }
@@ -0,0 +1,54 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { openmrsFetch, useSession } from '@openmrs/esm-framework';
3
+
4
+ /**
5
+ * The display name of the location(s) a register was generated for.
6
+ *
7
+ * Read from the link's `locationUuids` rather than the session: the user may
8
+ * have switched location since generating the report, and the register must
9
+ * name the location its rows came from. The session location answers without
10
+ * a request when it is the one asked for; any other is looked up. Several
11
+ * uuids (comma-separated) are named in order.
12
+ */
13
+ export function useLocationName(locationUuids: string | null | undefined): string {
14
+ const session = useSession();
15
+ const uuids = (locationUuids ?? '')
16
+ .split(',')
17
+ .map((uuid) => uuid.trim())
18
+ .filter(Boolean);
19
+ const key = uuids.join(',');
20
+ const sessionUuid = session?.sessionLocation?.uuid;
21
+ const sessionName = session?.sessionLocation?.display;
22
+
23
+ const [names, setNames] = useState<Record<string, string>>({});
24
+
25
+ useEffect(() => {
26
+ const missing = uuids.filter((uuid) => uuid !== sessionUuid);
27
+ if (missing.length === 0) {
28
+ return;
29
+ }
30
+
31
+ const abortController = new AbortController();
32
+
33
+ Promise.all(
34
+ missing.map((uuid) =>
35
+ openmrsFetch(`/ws/rest/v1/location/${uuid}?v=custom:(display)`, { signal: abortController.signal })
36
+ .then((response) => response.json())
37
+ .then((location) => [uuid, location?.display ?? ''] as const)
38
+ .catch(() => [uuid, ''] as const),
39
+ ),
40
+ ).then((found) => {
41
+ if (!abortController.signal.aborted) {
42
+ setNames(Object.fromEntries(found.filter(([, name]) => name)));
43
+ }
44
+ });
45
+
46
+ return () => abortController.abort();
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, [key, sessionUuid]);
49
+
50
+ return uuids
51
+ .map((uuid) => (uuid === sessionUuid ? sessionName : names[uuid]))
52
+ .filter(Boolean)
53
+ .join(', ');
54
+ }