@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,349 @@
1
+ import React from 'react';
2
+ import dayjs from 'dayjs';
3
+
4
+ import { usePageScale } from '../../common/report-page';
5
+ import {
6
+ Columns,
7
+ Heading,
8
+ KeyTable,
9
+ NutritionStack,
10
+ PAGE_WIDTH_MM,
11
+ ROWS_PER_PAGE,
12
+ Stack,
13
+ Tb4Page,
14
+ Value,
15
+ paginate,
16
+ } from './tb4-parts';
17
+ import { formDate, text, type Tb4Row, type Tb4Test } from './tb4.types';
18
+ import styles from './tb4-register.scss';
19
+
20
+ /** Initial phase drug collections ruled per patient (W) */
21
+ const INITIAL_COLLECTIONS = 8;
22
+
23
+ const MONTH_LETTERS = 'JFMAMJJASOND'.split('');
24
+
25
+ /**
26
+ * Column widths, measured off page 9 of the form: U, V, the eight initial phase
27
+ * collections (W), the 24 months of the continuation phase (X), then Y to AH
28
+ */
29
+ const COLUMN_WIDTHS = [
30
+ 92,
31
+ 93,
32
+ ...Array<number>(INITIAL_COLLECTIONS).fill(21.75),
33
+ ...Array<number>(12).fill(24.1),
34
+ ...Array<number>(12).fill(23.6),
35
+ 91,
36
+ 93,
37
+ 113,
38
+ 97,
39
+ 67,
40
+ 81,
41
+ 80,
42
+ 91,
43
+ 78,
44
+ 133,
45
+ ];
46
+
47
+ /** An entry over its date, split across the row as printed (U, Y, Z, AA, AB) */
48
+ const UpperLower: React.FC<{ upper: React.ReactNode; lower: React.ReactNode; tags?: [string, string] }> = ({
49
+ upper,
50
+ lower,
51
+ tags,
52
+ }) => (
53
+ <Stack
54
+ parts={[
55
+ { share: 0.45, boxes: [{ tag: tags?.[0], value: upper }] },
56
+ { share: 0.55, boxes: [{ tag: tags?.[1], value: lower }] },
57
+ ]}
58
+ />
59
+ );
60
+
61
+ const TestUpperLower: React.FC<{ test?: Tb4Test | null }> = ({ test }) => (
62
+ <UpperLower upper={text(test?.result)} lower={formDate(test?.date)} />
63
+ );
64
+
65
+ /**
66
+ * The continuation phase grid's two years, from the earliest collection on the
67
+ * page; blank (as printed, "Year:……") when the page has none
68
+ */
69
+ const continuationYears = (rows: Tb4Row[]): [number, number] | null => {
70
+ const years = rows
71
+ .flatMap((row) => row?.continuation_collections ?? [])
72
+ .map((date) => dayjs(date).year())
73
+ .filter((year) => Number.isFinite(year));
74
+ if (!years.length) {
75
+ return null;
76
+ }
77
+ const first = Math.min(...years);
78
+ return [first, first + 1];
79
+ };
80
+
81
+ /** One patient: U to AH across one tall row, divided inside as printed */
82
+ const TreatmentRow: React.FC<{ row?: Tb4Row; years: [number, number] | null }> = ({ row, years }) => {
83
+ const initial = row?.initial_phase_collections ?? [];
84
+ /* each month's collections, as days of the month, upper then lower */
85
+ const continuation = (year: number, month: number) =>
86
+ (row?.continuation_collections ?? [])
87
+ .map((date) => dayjs(date))
88
+ .filter((date) => date.year() === year && date.month() === month)
89
+ .map((date) => date.format('D'));
90
+
91
+ return (
92
+ <tr>
93
+ <td>
94
+ <UpperLower upper={text(row?.started_treatment)} lower={text(row?.not_started_reason)} />
95
+ </td>
96
+ <td>
97
+ <UpperLower upper={text(row?.regimen)} lower={formDate(row?.treatment_start_date)} tags={['Regimen', 'Date']} />
98
+ </td>
99
+ {Array.from({ length: INITIAL_COLLECTIONS }, (_, index) => (
100
+ <td key={`w${index}`} className={styles.collection}>
101
+ <span className={styles.vertical}>{formDate(initial[index])}</span>
102
+ </td>
103
+ ))}
104
+ {[0, 1].map((yearIndex) =>
105
+ MONTH_LETTERS.map((_, month) => {
106
+ const days = years ? continuation(years[yearIndex], month) : [];
107
+ return (
108
+ <td key={`x${yearIndex}-${month}`}>
109
+ <UpperLower upper={days[0] ?? ''} lower={days[1] ?? ''} />
110
+ </td>
111
+ );
112
+ }),
113
+ )}
114
+ <td>
115
+ <TestUpperLower test={row?.hiv} />
116
+ </td>
117
+ <td>
118
+ <TestUpperLower test={row?.partner_hiv} />
119
+ </td>
120
+ <td>
121
+ <UpperLower upper={text(row?.cpt)} lower={formDate(row?.cpt_start_date)} />
122
+ </td>
123
+ <td>
124
+ <UpperLower upper={text(row?.art)} lower={text(row?.art_start)} />
125
+ </td>
126
+ <td>
127
+ <Value>{text(row?.nutrition_support)}</Value>
128
+ </td>
129
+ <td>
130
+ <NutritionStack nutrition={row?.nutrition_end} />
131
+ </td>
132
+ <td>
133
+ <Value wrap>{text(row?.risk_factors)}</Value>
134
+ </td>
135
+ <td>
136
+ <Value wrap>{text(row?.comorbidities)}</Value>
137
+ </td>
138
+ <td>
139
+ <Stack
140
+ parts={[
141
+ { share: 0.17, boxes: [{ heading: 'Outcome' }] },
142
+ { share: 0.28, boxes: [{ value: text(row?.outcome) }] },
143
+ { share: 0.17, boxes: [{ heading: 'Date' }] },
144
+ { share: 0.38, boxes: [{ value: formDate(row?.outcome_date) }] },
145
+ ]}
146
+ />
147
+ </td>
148
+ <td>
149
+ <Value left wrap>
150
+ {text(row?.remarks)}
151
+ </Value>
152
+ </td>
153
+ </tr>
154
+ );
155
+ };
156
+
157
+ /** Keys 6b to 10, beneath the register on the right-hand pages */
158
+ const TreatmentKeys: React.FC = () => (
159
+ <div className={styles.keys}>
160
+ <KeyTable
161
+ title="Key 6 b: Molecular test results"
162
+ widthMm={69}
163
+ rows={[
164
+ ['A', 'MTB Detected; INH, FLQ, AMK, KAN, CAP, and ETH Resistance NOT Detected'],
165
+ ['B', 'MTB Detected; INH, FLQ, AMK, KAN, CAP, and ETH Resistance Detected'],
166
+ ['C', 'MTB Detected; INH Resistance Detected'],
167
+ ['D', 'MTB Detected; INH and KAN Resistance Detected; AMK and CAP Indeterminate'],
168
+ ['E', 'MTB Detected; INH and Low FLQ Resistance Detected'],
169
+ ['F', 'MTB Detected; INH, FLQ, AMK, and KAN Resistance Detected'],
170
+ ]}
171
+ />
172
+ <KeyTable
173
+ title="Key 7 a: DST"
174
+ widthMm={26}
175
+ leftTitle
176
+ rows={[['R = Rifampicin'], ['H = Isoniazid'], ['Z = Pyrazinamide'], ['E = Ethambutol']]}
177
+ />
178
+ <KeyTable
179
+ title="Key 7 b: Culture Results"
180
+ widthMm={27}
181
+ rows={[['Growth'], ['No Growth'], ['Done No Results'], ['Not done'], ['Contaminated']]}
182
+ />
183
+ <KeyTable
184
+ title="Key 8: Nutritional Support"
185
+ widthMm={51}
186
+ rows={[
187
+ ['Therapeutic feeds', 'RUTF'],
188
+ ['Supplementary Food', 'FBF'],
189
+ ['Nutritional Counseling', 'NC'],
190
+ ['Vitamin A', 'VIT A'],
191
+ ['Pyridoxine', 'Pyridoxine'],
192
+ ['Not Done', 'ND'],
193
+ ]}
194
+ />
195
+ <KeyTable
196
+ title="Key 9: Comorbidities/Risk Factors"
197
+ widthMm={70}
198
+ leftTitle
199
+ subheadings={[0, 6]}
200
+ rows={[
201
+ ['Comorbidities'],
202
+ ['Malnutrition', 'Kidney Diseases'],
203
+ ['Diabetes', 'Asthma'],
204
+ ['Hypertension', 'Liver Disease(s)'],
205
+ ['Mental Health Conditions.', 'Renal Disease(s)'],
206
+ ['Lung Cancer (If other cancers, specify)', 'Otherr (specify)'],
207
+ ['Risk Factors'],
208
+ ['Undernutrition', 'Alcoholism'],
209
+ ['Drug & Substance Abuse', 'Smoking'],
210
+ ['Pregnancy', 'Others (specify)'],
211
+ ]}
212
+ />
213
+ <KeyTable
214
+ title="Key 10: Treatment Outcome"
215
+ widthMm={48}
216
+ leftTitle
217
+ rows={[
218
+ ['Cured', 'C'],
219
+ ['Treatment completed', 'TC'],
220
+ ['Treatment failure', 'F'],
221
+ ['Lost to follow-up', 'LTFU'],
222
+ ['Died', 'D'],
223
+ ['Moved to MTB4', 'MTB4'],
224
+ ['Not Evaluated', 'NE'],
225
+ ['Not a TB Case', 'NTB'],
226
+ ]}
227
+ />
228
+ </div>
229
+ );
230
+
231
+ /**
232
+ * The right-hand pages of the TB4 register (page 9 of the form): treatment and
233
+ * follow-up, columns U to AH, for the same five patients a page as the
234
+ * left-hand pages, with Keys 6b to 10 beneath.
235
+ */
236
+ const Tb4TreatmentSheet: React.FC<{ rows: Tb4Row[] }> = ({ rows }) => {
237
+ const { ref, scale } = usePageScale<HTMLDivElement>(PAGE_WIDTH_MM);
238
+ const pages = paginate(rows);
239
+
240
+ return (
241
+ <div className={styles.viewport} ref={ref}>
242
+ {pages.map((pageRows, index) => {
243
+ const years = continuationYears(pageRows);
244
+ return (
245
+ <Tb4Page
246
+ key={index}
247
+ page={index + 1}
248
+ pages={pages.length}
249
+ side="Treatment and follow-up (U–AH)"
250
+ scale={scale}
251
+ keys={<TreatmentKeys />}
252
+ table={
253
+ <table className={styles.register}>
254
+ <Columns widths={COLUMN_WIDTHS} />
255
+ <thead>
256
+ <tr style={{ height: '15mm' }}>
257
+ <Heading letter="U" rowSpan={3} centre>
258
+ Started treatment
259
+ <br />
260
+ <br />
261
+ Yes/No
262
+ <br />
263
+ <br />
264
+ If No, provide reason (Specify)
265
+ </Heading>
266
+ <Heading letter="V" rowSpan={3} centre>
267
+ Regimen and Date treatment started+
268
+ </Heading>
269
+ <Heading letter="W" rowSpan={3} colSpan={INITIAL_COLLECTIONS} centre>
270
+ Initial phase date of drugs collection
271
+ </Heading>
272
+ <Heading letter="X" colSpan={24} centre>
273
+ Continuation phase date of drugs collection
274
+ <br />
275
+ <br />
276
+ Indicate the date when the patient collected drugs
277
+ </Heading>
278
+ <Heading letter="Y" rowSpan={3} centre>
279
+ HIV Test New Pos/ Known Pos/ Neg/ Unknown (Date done)
280
+ </Heading>
281
+ <Heading letter="Z" rowSpan={3} centre>
282
+ Partner tested for HIV Pos/ Neg/Unknown/ No Partner
283
+ <br />
284
+ <br />
285
+ (Date done)
286
+ </Heading>
287
+ <Heading letter="AA" rowSpan={3} centre>
288
+ Cotrimoxazole Preventive Therapy/ Dapsone Yes/No
289
+ <br />
290
+ <br />
291
+ (Date Started)
292
+ </Heading>
293
+ <Heading letter="AB" rowSpan={3} centre>
294
+ ART : Yes/No (Date started &amp; Regimen)
295
+ </Heading>
296
+ <Heading letter="AC" rowSpan={3} centre>
297
+ Nutrition Support (Key 8)
298
+ </Heading>
299
+ <Heading letter="AD" rowSpan={3} centre>
300
+ Nutritional status at end Treatment Height/ Length (Cm)
301
+ </Heading>
302
+ <Heading letter="AE" rowSpan={3} centre>
303
+ Risk Factors (Key 9)
304
+ </Heading>
305
+ <Heading letter="AF" rowSpan={3} centre>
306
+ Co-Morbidities
307
+ <br />
308
+ <br />
309
+ (Key 9)
310
+ </Heading>
311
+ <Heading letter="AG" rowSpan={3} centre>
312
+ Date and Outcome of treatment (Key10)
313
+ </Heading>
314
+ <Heading letter="AH" rowSpan={3} centre>
315
+ Remarks
316
+ </Heading>
317
+ </tr>
318
+ <tr style={{ height: '13.5mm' }}>
319
+ {[0, 1].map((yearIndex) => (
320
+ <th key={yearIndex} colSpan={12} className={styles.centreHeading}>
321
+ Year:{years ? years[yearIndex] : '……...'}
322
+ </th>
323
+ ))}
324
+ </tr>
325
+ <tr style={{ height: '13.5mm' }}>
326
+ {[0, 1].map((yearIndex) =>
327
+ MONTH_LETTERS.map((letter, month) => (
328
+ <th key={`${yearIndex}-${month}`} className={styles.monthHeading}>
329
+ {letter}
330
+ </th>
331
+ )),
332
+ )}
333
+ </tr>
334
+ </thead>
335
+ <tbody>
336
+ {Array.from({ length: ROWS_PER_PAGE }, (_, rowIndex) => (
337
+ <TreatmentRow key={rowIndex} row={pageRows[rowIndex]} years={years} />
338
+ ))}
339
+ </tbody>
340
+ </table>
341
+ }
342
+ />
343
+ );
344
+ })}
345
+ </div>
346
+ );
347
+ };
348
+
349
+ export default Tb4TreatmentSheet;
@@ -0,0 +1,120 @@
1
+ import React, { useRef, useState } from 'react';
2
+ import { useSearchParams } from 'react-router-dom';
3
+ import { Tab, TabList, TabPanel, TabPanels, Tabs } from '@carbon/react';
4
+ import { useSession } from '@openmrs/esm-framework';
5
+
6
+ import { ReportFiltersComponent, type ReportPeriod, useRestorePeriod } from '../../common/report-filters';
7
+ import { downloadSheetsPdf } from '../../common/report-page';
8
+ import { ReportPlaceholder, useGeneratedLocation } from '../../common/report-placeholder';
9
+ import { registerFileName } from '../register-file-name';
10
+ import Tb4PatientSheet from './tb4-patient-sheet.component';
11
+ import Tb4TreatmentSheet from './tb4-treatment-sheet.component';
12
+ import { type Tb4Row } from './tb4.types';
13
+ import styles from './tb4-register.scss';
14
+
15
+ /**
16
+ * TB4 Notification and Treatment Register (MOH/REG/DLTLDP/01), opened from the
17
+ * landing page's Registers tab, with the reports' filter bar. Each spread of
18
+ * the paper register is two facing pages, shown as tabs: the patient,
19
+ * diagnosis and investigations (A–T), and treatment and follow-up (U–AH).
20
+ *
21
+ * The template only for now: its queries are still to be written, so
22
+ * generating it shows the form ruled and blank for the period. Once the
23
+ * backend is in, `generate` fetches the patients (`Tb4Row`) instead.
24
+ */
25
+ const Tb4RegisterPage: React.FC = () => {
26
+ const [rows, setRows] = useState<Tb4Row[] | null>(null);
27
+ const [period, setPeriod] = useState<{ startDate: string; endDate: string }>({ startDate: '', endDate: '' });
28
+
29
+ const session = useSession();
30
+ const locationUuids = session?.sessionLocation?.uuid ?? '';
31
+ /* Only a register for the current location counts */
32
+ const { isForCurrentLocation, hasLocationChanged, markGenerated } = useGeneratedLocation('tb4Location');
33
+ const isGenerated = rows !== null && isForCurrentLocation;
34
+
35
+ const generate = async ({ startDate, endDate }: ReportPeriod) => {
36
+ setPeriod({ startDate, endDate });
37
+ // no backend yet: the register is drawn blank
38
+ setRows([]);
39
+ markGenerated(locationUuids);
40
+ };
41
+
42
+ useRestorePeriod(generate);
43
+
44
+ /* Download PDF saves the register as the paper book runs, whichever tab is
45
+ open: each left-hand page followed by its right-hand page, as A3
46
+ landscape pages made from copies, so the pages on screen are left as
47
+ they are */
48
+ const sheetsRef = useRef<HTMLDivElement>(null);
49
+ const downloadPdf = async () => {
50
+ const panels = Array.from(sheetsRef.current?.querySelectorAll<HTMLElement>('[data-tb4-side]') ?? []);
51
+ const [left, right] = panels.map((panel) =>
52
+ Array.from(panel.querySelectorAll<HTMLElement>('[data-register-page]')),
53
+ );
54
+ const pages = (left ?? []).flatMap((page, index) => [page, right?.[index]].filter(Boolean));
55
+ const name = registerFileName('TB4-Register', session?.sessionLocation?.display ?? '', period);
56
+ /* styled by its own stylesheets in Arial only, and may run to many pages */
57
+ await downloadSheetsPdf(pages, name, {
58
+ paperSize: 'a3',
59
+ orientation: 'landscape',
60
+ stylesheetsOnly: true,
61
+ image: 'jpeg',
62
+ });
63
+ };
64
+
65
+ /* the open side is kept in the URL */
66
+ const [searchParams, setSearchParams] = useSearchParams();
67
+ const selectedSide = searchParams.get('side') === 'treatment' ? 1 : 0;
68
+ const selectSide = ({ selectedIndex }: { selectedIndex: number }) => {
69
+ const next = new URLSearchParams(searchParams);
70
+ if (selectedIndex === 1) {
71
+ next.set('side', 'treatment');
72
+ } else {
73
+ next.delete('side');
74
+ }
75
+ setSearchParams(next, { replace: true });
76
+ };
77
+
78
+ return (
79
+ <>
80
+ <ReportFiltersComponent
81
+ reportName="TB4 Notification and Treatment Register"
82
+ mode="both"
83
+ onGenerate={generate}
84
+ isLoading={false}
85
+ isReportGenerated={isGenerated}
86
+ generatedPeriod={period}
87
+ defaultTab="custom"
88
+ backLink={{ label: 'All registers', to: '/?tab=registers' }}
89
+ onDownloadPdf={downloadPdf}
90
+ />
91
+ {!isGenerated && (
92
+ <ReportPlaceholder reportName="TB4 register" isLocationChanged={rows !== null && hasLocationChanged} />
93
+ )}
94
+ {isGenerated && (
95
+ <div ref={sheetsRef}>
96
+ <Tabs selectedIndex={selectedSide} onChange={selectSide}>
97
+ <TabList aria-label="TB4 register pages" className={styles.sheetTabs}>
98
+ <Tab>Patient, diagnosis &amp; investigations (A–T)</Tab>
99
+ <Tab>Treatment &amp; follow-up (U–AH)</Tab>
100
+ </TabList>
101
+ <TabPanels>
102
+ <TabPanel className={styles.sheetPanel}>
103
+ <div className={styles.reportFrame} data-tb4-side>
104
+ <Tb4PatientSheet rows={rows} />
105
+ </div>
106
+ </TabPanel>
107
+ <TabPanel className={styles.sheetPanel}>
108
+ <div className={styles.reportFrame} data-tb4-side>
109
+ <Tb4TreatmentSheet rows={rows} />
110
+ </div>
111
+ </TabPanel>
112
+ </TabPanels>
113
+ </Tabs>
114
+ </div>
115
+ )}
116
+ </>
117
+ );
118
+ };
119
+
120
+ export default Tb4RegisterPage;
@@ -0,0 +1,129 @@
1
+ import dayjs from 'dayjs';
2
+
3
+ /** A test result with the date it was done, and for smears the serial number */
4
+ export interface Tb4Test {
5
+ serial?: string | null;
6
+ date?: string | null;
7
+ result?: string | null;
8
+ }
9
+
10
+ /** Nutritional status: weight (kg), height/length (cm), BMI or Z score, MUAC */
11
+ export interface Tb4Nutrition {
12
+ weight?: number | string | null;
13
+ height?: number | string | null;
14
+ bmi?: number | string | null;
15
+ muac?: number | string | null;
16
+ }
17
+
18
+ /**
19
+ * One patient in the TB4 Notification and Treatment Register
20
+ * (MOH/REG/DLTLDP/01, February 2025), column by column as on the form's two
21
+ * facing pages: A to T (patient, diagnosis and investigations) and U to AH
22
+ * (treatment and follow-up).
23
+ *
24
+ * The register has no backend yet: these are the names its query is to
25
+ * return, so the page fills in once it is connected.
26
+ */
27
+ export interface Tb4Row {
28
+ /** (A) cumulative from the start of each year */
29
+ serial_number?: number | string | null;
30
+ /** (B) date the sub-county registration number was issued */
31
+ registration_date?: string | null;
32
+ /** (C) */
33
+ sub_county_tb_number?: string | null;
34
+ /** (D) three names */
35
+ full_name?: string | null;
36
+ /** (E) M, F or other */
37
+ sex?: string | null;
38
+ /** (F) years and months */
39
+ age?: string | number | null;
40
+ /** (G) */
41
+ nationality?: string | null;
42
+ /** (G) national ID, passport, UPI, NEMIS or alien ID number */
43
+ id_number?: string | null;
44
+ /** (H) neighbour, school, church or mosque */
45
+ physical_address?: string | null;
46
+ /** (H) */
47
+ patient_phone?: string | null;
48
+ /** (H) */
49
+ supporter_phone?: string | null;
50
+ /** (I) HCW, truck driver, prisoner, miner, or None */
51
+ key_population?: string | null;
52
+ /** (J) Key 1 */
53
+ referred_by?: string | null;
54
+ /** (K) at the start of treatment */
55
+ nutrition_start?: Tb4Nutrition | null;
56
+ /** (L) Severe or Non-Severe (Key 2) */
57
+ severity?: string | null;
58
+ /** (M) PTB or EPTB */
59
+ tb_type?: string | null;
60
+ /** (N) EPTB sub-type */
61
+ eptb_subtype?: string | null;
62
+ /** (N) for lymph nodes: complicated or uncomplicated (Key 3) */
63
+ lymph_node_type?: string | null;
64
+ /** (O) Key 4 */
65
+ patient_type?: string | null;
66
+ /** (P) X-ray: date, score and finding (Key 5) */
67
+ xray_date?: string | null;
68
+ xray_score?: string | null;
69
+ xray_finding?: string | null;
70
+ /** (P) other imaging, specified */
71
+ other_imaging?: string | null;
72
+ /** (Q) Pos, Neg or ND */
73
+ tb_lamp?: Tb4Test | null;
74
+ /** (R) Pos, Neg or ND */
75
+ lf_lam?: Tb4Test | null;
76
+ /** (S) molecular test at month 0 (Key 6a/6b) */
77
+ molecular?: Tb4Test | null;
78
+ /** (S) sputum smears by month of treatment: 0, 2, 3, 5 and 6 */
79
+ smears?: Partial<Record<0 | 2 | 3 | 5 | 6, Tb4Test>> | null;
80
+ /** (T) culture result (Key 7b) and the date collected */
81
+ culture?: Tb4Test | null;
82
+ /** (T) DST: R or S for each drug (Key 7a) */
83
+ dst?: { r?: string | null; h?: string | null; z?: string | null; e?: string | null } | null;
84
+
85
+ /** (U) Yes or No */
86
+ started_treatment?: string | null;
87
+ /** (U) if not started, why */
88
+ not_started_reason?: string | null;
89
+ /** (V) */
90
+ regimen?: string | null;
91
+ /** (V) */
92
+ treatment_start_date?: string | null;
93
+ /** (W) initial phase drug collection dates, up to eight */
94
+ initial_phase_collections?: string[] | null;
95
+ /** (X) continuation phase drug collection dates, placed by month */
96
+ continuation_collections?: string[] | null;
97
+ /** (Y) New Pos, Known Pos, Neg or Unknown, and the date done */
98
+ hiv?: Tb4Test | null;
99
+ /** (Z) partner tested: Pos, Neg, Unknown or No Partner, and the date done */
100
+ partner_hiv?: Tb4Test | null;
101
+ /** (AA) cotrimoxazole preventive therapy / dapsone: Yes or No */
102
+ cpt?: string | null;
103
+ /** (AA) */
104
+ cpt_start_date?: string | null;
105
+ /** (AB) Yes or No */
106
+ art?: string | null;
107
+ /** (AB) date started and regimen */
108
+ art_start?: string | null;
109
+ /** (AC) Key 8 */
110
+ nutrition_support?: string | null;
111
+ /** (AD) at the end of treatment */
112
+ nutrition_end?: Tb4Nutrition | null;
113
+ /** (AE) Key 9 */
114
+ risk_factors?: string | null;
115
+ /** (AF) Key 9 */
116
+ comorbidities?: string | null;
117
+ /** (AG) Key 10 */
118
+ outcome?: string | null;
119
+ /** (AG) */
120
+ outcome_date?: string | null;
121
+ /** (AH) */
122
+ remarks?: string | null;
123
+ }
124
+
125
+ /** Blank rather than "null" / "undefined" in a cell */
126
+ export const text = (value: unknown) => (value === null || value === undefined ? '' : String(value));
127
+
128
+ /** DD/MM/YYYY, as the form asks */
129
+ export const formDate = (value: unknown) => (value ? dayjs(String(value)).format('DD/MM/YYYY') : '');