@openmrs/esm-report-builder 0.1.1-pre.101

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 (670) hide show
  1. package/.eslintignore +5 -0
  2. package/.eslintrc.js +19 -0
  3. package/.prettierrc +8 -0
  4. package/.tx/config +12 -0
  5. package/LICENSE +401 -0
  6. package/README.md +336 -0
  7. package/__mocks__/react-i18next.js +12 -0
  8. package/dist/128.css +1 -0
  9. package/dist/128.css.map +1 -0
  10. package/dist/128.js +338 -0
  11. package/dist/128.js.map +1 -0
  12. package/dist/1480.js +1 -0
  13. package/dist/1480.js.map +1 -0
  14. package/dist/1646.js +1 -0
  15. package/dist/1646.js.map +1 -0
  16. package/dist/1869.js +1 -0
  17. package/dist/1869.js.map +1 -0
  18. package/dist/1877.js +1 -0
  19. package/dist/1877.js.map +1 -0
  20. package/dist/2131.js +1 -0
  21. package/dist/2131.js.map +1 -0
  22. package/dist/2317.js +1 -0
  23. package/dist/2317.js.map +1 -0
  24. package/dist/2408.js +1 -0
  25. package/dist/2408.js.map +1 -0
  26. package/dist/2416.js +1 -0
  27. package/dist/2416.js.map +1 -0
  28. package/dist/282.js +1 -0
  29. package/dist/282.js.map +1 -0
  30. package/dist/2881.js +1 -0
  31. package/dist/2881.js.map +1 -0
  32. package/dist/2997.js +1 -0
  33. package/dist/2997.js.map +1 -0
  34. package/dist/3378.js +1 -0
  35. package/dist/3378.js.map +1 -0
  36. package/dist/3963.js +1 -0
  37. package/dist/3963.js.map +1 -0
  38. package/dist/4106.js +1 -0
  39. package/dist/4106.js.map +1 -0
  40. package/dist/4111.js +1 -0
  41. package/dist/4111.js.map +1 -0
  42. package/dist/4171.css +1 -0
  43. package/dist/4171.css.map +1 -0
  44. package/dist/4171.js +43 -0
  45. package/dist/4171.js.map +1 -0
  46. package/dist/434.js +1 -0
  47. package/dist/434.js.map +1 -0
  48. package/dist/4348.js +1 -0
  49. package/dist/4348.js.map +1 -0
  50. package/dist/4383.js +1 -0
  51. package/dist/4383.js.map +1 -0
  52. package/dist/4658.js +1 -0
  53. package/dist/4658.js.map +1 -0
  54. package/dist/4668.js +1 -0
  55. package/dist/4668.js.map +1 -0
  56. package/dist/4928.js +1 -0
  57. package/dist/4928.js.map +1 -0
  58. package/dist/5098.js +1 -0
  59. package/dist/5098.js.map +1 -0
  60. package/dist/5117.js +1 -0
  61. package/dist/5117.js.map +1 -0
  62. package/dist/5132.js +1 -0
  63. package/dist/5132.js.map +1 -0
  64. package/dist/5145.js +1 -0
  65. package/dist/5145.js.map +1 -0
  66. package/dist/550.js +1 -0
  67. package/dist/550.js.map +1 -0
  68. package/dist/5503.js +1 -0
  69. package/dist/5503.js.map +1 -0
  70. package/dist/556.js +1 -0
  71. package/dist/556.js.map +1 -0
  72. package/dist/5644.js +1 -0
  73. package/dist/5644.js.map +1 -0
  74. package/dist/5940.js +1 -0
  75. package/dist/5940.js.map +1 -0
  76. package/dist/6047.js +1 -0
  77. package/dist/6047.js.map +1 -0
  78. package/dist/6237.js +1 -0
  79. package/dist/6237.js.map +1 -0
  80. package/dist/6371.js +1 -0
  81. package/dist/6371.js.map +1 -0
  82. package/dist/6377.js +1 -0
  83. package/dist/6377.js.map +1 -0
  84. package/dist/6444.js +1 -0
  85. package/dist/6444.js.map +1 -0
  86. package/dist/6508.js +1 -0
  87. package/dist/6508.js.map +1 -0
  88. package/dist/6652.js +1 -0
  89. package/dist/6652.js.map +1 -0
  90. package/dist/6686.js +1 -0
  91. package/dist/6686.js.map +1 -0
  92. package/dist/6724.js +1 -0
  93. package/dist/6724.js.map +1 -0
  94. package/dist/6867.js +19 -0
  95. package/dist/6867.js.map +1 -0
  96. package/dist/6904.js +1 -0
  97. package/dist/6904.js.map +1 -0
  98. package/dist/7045.js +1 -0
  99. package/dist/7045.js.map +1 -0
  100. package/dist/7175.js +1 -0
  101. package/dist/7175.js.map +1 -0
  102. package/dist/7182.js +1 -0
  103. package/dist/7182.js.map +1 -0
  104. package/dist/7475.js +1 -0
  105. package/dist/7475.js.map +1 -0
  106. package/dist/7742.js +1 -0
  107. package/dist/7742.js.map +1 -0
  108. package/dist/7912.js +1 -0
  109. package/dist/7912.js.map +1 -0
  110. package/dist/8251.js +1 -0
  111. package/dist/8251.js.map +1 -0
  112. package/dist/8358.js +1 -0
  113. package/dist/8358.js.map +1 -0
  114. package/dist/8359.js +1 -0
  115. package/dist/8359.js.map +1 -0
  116. package/dist/8695.js +1 -0
  117. package/dist/8695.js.map +1 -0
  118. package/dist/903.js +1 -0
  119. package/dist/903.js.map +1 -0
  120. package/dist/9072.js +1 -0
  121. package/dist/9072.js.map +1 -0
  122. package/dist/9806.js +1 -0
  123. package/dist/9806.js.map +1 -0
  124. package/dist/997.js +1 -0
  125. package/dist/997.js.map +1 -0
  126. package/dist/openmrs-esm-report-builder.js +3 -0
  127. package/dist/openmrs-esm-report-builder.js.buildmanifest.json +1433 -0
  128. package/dist/openmrs-esm-report-builder.js.map +1 -0
  129. package/dist/routes.json +1 -0
  130. package/e2e/core/env.ts +5 -0
  131. package/e2e/core/global-setup.ts +34 -0
  132. package/e2e/core/test.ts +21 -0
  133. package/e2e/pages/app-shell.page.ts +35 -0
  134. package/e2e/pages/etl-sources.page.ts +36 -0
  135. package/e2e/pages/report-sections.page.ts +21 -0
  136. package/e2e/pages/section-preview.modal.ts +29 -0
  137. package/e2e/specs/etl-sources.spec.ts +51 -0
  138. package/e2e/specs/navigation.spec.ts +13 -0
  139. package/e2e/specs/section-preview-validation.spec.ts +38 -0
  140. package/e2e/specs/section-preview.spec.ts +94 -0
  141. package/e2e/support/github/Dockerfile +32 -0
  142. package/e2e/support/github/Dockerfile.dockerignore +13 -0
  143. package/e2e/support/github/docker-compose.yml +31 -0
  144. package/e2e/support/github/run-e2e-docker-env.sh +131 -0
  145. package/e2e/support/github/spa-assemble-config.json +6 -0
  146. package/e2e/tsconfig.json +11 -0
  147. package/example.env +5 -0
  148. package/linelist_report_builder_design_spec.md +816 -0
  149. package/package.json +98 -0
  150. package/playwright.config.ts +28 -0
  151. package/rspack.config.js +2 -0
  152. package/src/__debug__/all-designs.test.tsx +33 -0
  153. package/src/admin-card-link.extension.module.scss +55 -0
  154. package/src/admin-card-link.extension.tsx +24 -0
  155. package/src/components/admin/admin-page.component.tsx +75 -0
  156. package/src/components/admin/age-categories-page.component.tsx +109 -0
  157. package/src/components/admin/age-groups-page.component.tsx +146 -0
  158. package/src/components/admin/etl-browser/columns-tab.component.tsx +80 -0
  159. package/src/components/admin/etl-browser/etl-browser-page.component.tsx +309 -0
  160. package/src/components/admin/etl-browser/etl-browser.component.scss +424 -0
  161. package/src/components/admin/etl-browser/metadata-tab.component.tsx +65 -0
  162. package/src/components/admin/etl-browser/preview-grid.component.tsx +189 -0
  163. package/src/components/admin/etl-browser/relationships-tab.component.tsx +21 -0
  164. package/src/components/admin/etl-browser/sources-tree.component.tsx +118 -0
  165. package/src/components/admin/etl-browser/table-detail.component.tsx +124 -0
  166. package/src/components/admin/etl-browser/table-info-strip.component.tsx +51 -0
  167. package/src/components/admin/etl-browser/table-list.component.tsx +136 -0
  168. package/src/components/admin/etl-browser/use-table-preview.ts +47 -0
  169. package/src/components/admin/etl-sources-page.component.tsx +355 -0
  170. package/src/components/admin/etl-tasks-page.component.scss +370 -0
  171. package/src/components/admin/etl-tasks-page.component.tsx +768 -0
  172. package/src/components/admin/report-categories-page.component.tsx +267 -0
  173. package/src/components/admin/report-library-page.component.tsx +551 -0
  174. package/src/components/ai-support/ai-assist-button.component.tsx +30 -0
  175. package/src/components/ai-support/ai-assist-modal.component.tsx +81 -0
  176. package/src/components/ai-support/ai.service.ts +21 -0
  177. package/src/components/ai-support/ai.types.ts +12 -0
  178. package/src/components/app-shell/report-builder-shell.component.tsx +242 -0
  179. package/src/components/app-shell/report-builder-shell.scss +18 -0
  180. package/src/components/dashboards/dashboard-config.schema.ts +97 -0
  181. package/src/components/dashboards/dashboard-form-modal.component.tsx +150 -0
  182. package/src/components/dashboards/dashboards-admin.scss +62 -0
  183. package/src/components/dashboards/dashboards-page.component.tsx +153 -0
  184. package/src/components/dashboards/dashboards-table.component.tsx +130 -0
  185. package/src/components/dashboards/sections/dashboard-autoinclude.section.tsx +75 -0
  186. package/src/components/dashboards/sections/dashboard-basics.section.tsx +80 -0
  187. package/src/components/dashboards/sections/dashboard-json.section.tsx +79 -0
  188. package/src/components/dashboards/sections/dashboard-sections-editor.section.tsx +130 -0
  189. package/src/components/dashboards/sections/dashboard-widgets-editor.section.tsx +184 -0
  190. package/src/components/data-themes/data-theme-form.component.tsx +59 -0
  191. package/src/components/data-themes/data-theme-modal.component.tsx +349 -0
  192. package/src/components/data-themes/data-themes-page.component.tsx +139 -0
  193. package/src/components/data-themes/data-themes-table.component.tsx +98 -0
  194. package/src/components/data-themes/icon-registry.ts +35 -0
  195. package/src/components/data-themes/sections/data-theme-basics.section.tsx +63 -0
  196. package/src/components/data-themes/sections/data-theme-condition-columns.section.tsx +397 -0
  197. package/src/components/data-themes/sections/data-theme-fields-editor.section.tsx +125 -0
  198. package/src/components/data-themes/sections/data-theme-metadata.section.tsx +245 -0
  199. package/src/components/data-themes/sections/data-theme-preview.section.tsx +115 -0
  200. package/src/components/data-themes/sections/data-theme-source.section.tsx +184 -0
  201. package/src/components/data-visualizer/components/cqi-components/cqi-data-table.component.tsx +214 -0
  202. package/src/components/data-visualizer/components/cqi-components/cqi-elements.ts +203 -0
  203. package/src/components/data-visualizer/components/cqi-components/detail.component.tsx +110 -0
  204. package/src/components/data-visualizer/components/cqi-components/detail.scss +22 -0
  205. package/src/components/data-visualizer/components/data-table/data-table.component.tsx +208 -0
  206. package/src/components/data-visualizer/components/data-table/data-tables.scss +206 -0
  207. package/src/components/data-visualizer/components/empty-state/empty-state-illustration.component.tsx +53 -0
  208. package/src/components/data-visualizer/components/header/header.component.tsx +43 -0
  209. package/src/components/data-visualizer/components/header/header.scss +71 -0
  210. package/src/components/data-visualizer/components/model/confirm.component.tsx +40 -0
  211. package/src/components/data-visualizer/components/panel/panel.component.tsx +20 -0
  212. package/src/components/data-visualizer/components/panel/panel.scss +25 -0
  213. package/src/components/data-visualizer/components/popover/modifier-panel.tsx +53 -0
  214. package/src/components/data-visualizer/constants.ts +445 -0
  215. package/src/components/data-visualizer/data-visualizer-illustration.component.tsx +31 -0
  216. package/src/components/data-visualizer/data-visualizer.component.tsx +1935 -0
  217. package/src/components/data-visualizer/data-visualizer.resource.ts +462 -0
  218. package/src/components/data-visualizer/data-visualizer.scss +448 -0
  219. package/src/components/data-visualizer/declarations.d.ts +131 -0
  220. package/src/components/data-visualizer/parameter-inputs/boolean-parameter-input.component.tsx +34 -0
  221. package/src/components/data-visualizer/parameter-inputs/concept-parameter-input.component.tsx +81 -0
  222. package/src/components/data-visualizer/parameter-inputs/date-parameter-input.component.tsx +110 -0
  223. package/src/components/data-visualizer/parameter-inputs/date-range-input.component.scss +47 -0
  224. package/src/components/data-visualizer/parameter-inputs/date-range-input.component.tsx +228 -0
  225. package/src/components/data-visualizer/parameter-inputs/identifier-type-parameter-input.component.tsx +69 -0
  226. package/src/components/data-visualizer/parameter-inputs/list-parameter-input.component.tsx +73 -0
  227. package/src/components/data-visualizer/parameter-inputs/location-parameter-input.component.tsx +81 -0
  228. package/src/components/data-visualizer/parameter-inputs/number-parameter-input.component.tsx +40 -0
  229. package/src/components/data-visualizer/parameter-inputs/person-attribute-parameter-input.component.tsx +71 -0
  230. package/src/components/data-visualizer/parameter-inputs/text-parameter-input.component.tsx +52 -0
  231. package/src/components/data-visualizer/report-parameter-modal.component.tsx +383 -0
  232. package/src/components/data-visualizer/types.ts +206 -0
  233. package/src/components/etl-monitor/builder/builder-state-machine.ts +381 -0
  234. package/src/components/etl-monitor/builder/design-registry.tsx +139 -0
  235. package/src/components/etl-monitor/builder/etl-monitor-builder-context.tsx +702 -0
  236. package/src/components/etl-monitor/builder/etl-monitor-builder-modal.component.tsx +461 -0
  237. package/src/components/etl-monitor/builder/etl-monitor-builder-modal.scss +256 -0
  238. package/src/components/etl-monitor/builder/etl-monitor-builder-page.component.tsx +387 -0
  239. package/src/components/etl-monitor/builder/etl-monitor-builder-page.scss +976 -0
  240. package/src/components/etl-monitor/builder/generated-config-panel.component.tsx +168 -0
  241. package/src/components/etl-monitor/builder/generated-config-panel.scss +198 -0
  242. package/src/components/etl-monitor/builder/index.ts +5 -0
  243. package/src/components/etl-monitor/builder/monitor-preview.tsx +124 -0
  244. package/src/components/etl-monitor/builder/steps/data-source-step.component.tsx +667 -0
  245. package/src/components/etl-monitor/builder/steps/data-source-step.scss +284 -0
  246. package/src/components/etl-monitor/builder/steps/design-samples.ts +278 -0
  247. package/src/components/etl-monitor/builder/steps/design-step.component.tsx +299 -0
  248. package/src/components/etl-monitor/builder/steps/design-step.scss +479 -0
  249. package/src/components/etl-monitor/builder/steps/fields-step.component.tsx +859 -0
  250. package/src/components/etl-monitor/builder/steps/fields-step.scss +459 -0
  251. package/src/components/etl-monitor/builder/steps/general-step.component.tsx +288 -0
  252. package/src/components/etl-monitor/builder/steps/general-step.scss +103 -0
  253. package/src/components/etl-monitor/builder/steps/index.ts +5 -0
  254. package/src/components/etl-monitor/builder/steps/preview-step.component.tsx +245 -0
  255. package/src/components/etl-monitor/builder/steps/preview-step.scss +236 -0
  256. package/src/components/etl-monitor/etl-monitor-card-shell.component.tsx +154 -0
  257. package/src/components/etl-monitor/etl-monitor-card-shell.scss +103 -0
  258. package/src/components/etl-monitor/etl-monitor-dashboard.component.tsx +227 -0
  259. package/src/components/etl-monitor/etl-monitor-dashboard.scss +148 -0
  260. package/src/components/etl-monitor/etl-monitor-status-card.component.tsx +192 -0
  261. package/src/components/etl-monitor/etl-monitors-page.component.tsx +621 -0
  262. package/src/components/etl-monitor/renderers/DetailsRenderer.tsx +2 -0
  263. package/src/components/etl-monitor/renderers/ErrorLogRenderer.tsx +2 -0
  264. package/src/components/etl-monitor/renderers/EtlMonitorWidget.tsx +97 -0
  265. package/src/components/etl-monitor/renderers/MetricsGridRenderer.tsx +2 -0
  266. package/src/components/etl-monitor/renderers/MonitorRenderer.tsx +175 -0
  267. package/src/components/etl-monitor/renderers/ProgressRenderer.tsx +2 -0
  268. package/src/components/etl-monitor/renderers/StatusCardRenderer.tsx +2 -0
  269. package/src/components/etl-monitor/renderers/SummaryCardRenderer.tsx +2 -0
  270. package/src/components/etl-monitor/renderers/TableRenderer.tsx +2 -0
  271. package/src/components/etl-monitor/renderers/component-icons.tsx +33 -0
  272. package/src/components/etl-monitor/renderers/components/details-renderer.tsx +195 -0
  273. package/src/components/etl-monitor/renderers/components/error-log-renderer.tsx +104 -0
  274. package/src/components/etl-monitor/renderers/components/index.ts +6 -0
  275. package/src/components/etl-monitor/renderers/components/log-renderer.tsx +86 -0
  276. package/src/components/etl-monitor/renderers/components/metrics-grid-renderer.tsx +133 -0
  277. package/src/components/etl-monitor/renderers/components/progress-renderer.tsx +188 -0
  278. package/src/components/etl-monitor/renderers/components/status-card-renderer.tsx +247 -0
  279. package/src/components/etl-monitor/renderers/components/summary-card-renderer.tsx +160 -0
  280. package/src/components/etl-monitor/renderers/components/table-renderer.tsx +175 -0
  281. package/src/components/etl-monitor/renderers/components/time-series-renderer.tsx +310 -0
  282. package/src/components/etl-monitor/renderers/data-transformer.ts +361 -0
  283. package/src/components/etl-monitor/renderers/index.ts +11 -0
  284. package/src/components/etl-monitor/renderers/legacy-monitor-renderer.tsx +69 -0
  285. package/src/components/etl-monitor/renderers/monitor-renderer.tsx +6 -0
  286. package/src/components/etl-monitor/renderers/monitor-renderers.scss +1621 -0
  287. package/src/components/etl-monitor/renderers/monitor-widget-card.component.tsx +54 -0
  288. package/src/components/etl-monitor/renderers/monitor-widget-card.scss +16 -0
  289. package/src/components/etl-monitor/renderers/v2-monitor-renderer.tsx +227 -0
  290. package/src/components/import-export/artifact-groups.ts +153 -0
  291. package/src/components/import-export/export-report-modal.component.tsx +265 -0
  292. package/src/components/import-export/import-export-page.component.tsx +610 -0
  293. package/src/components/import-export/import-export.styles.scss +825 -0
  294. package/src/components/import-export/import-package-modal.component.tsx +401 -0
  295. package/src/components/import-export/index.ts +3 -0
  296. package/src/components/indicators/README.md +24 -0
  297. package/src/components/indicators/base-indicator-basic-fields.component.tsx +18 -0
  298. package/src/components/indicators/base-indicator-basics.section.tsx +54 -0
  299. package/src/components/indicators/base-indicator-countby.section.tsx +25 -0
  300. package/src/components/indicators/base-indicator-criteria.section.tsx +83 -0
  301. package/src/components/indicators/base-indicator-details-form.component.tsx +80 -0
  302. package/src/components/indicators/base-indicator-theme-unit-fields.component.tsx +39 -0
  303. package/src/components/indicators/create-base-indicator-modal.component.tsx +623 -0
  304. package/src/components/indicators/create-composite-base-indicator-modal.component.tsx +295 -0
  305. package/src/components/indicators/create-custom-indicator-modal.component.tsx +548 -0
  306. package/src/components/indicators/create-final-indicator-modal.component.tsx +285 -0
  307. package/src/components/indicators/custom-condition-input.component.tsx +310 -0
  308. package/src/components/indicators/diagnosis-filters-form.component.tsx +101 -0
  309. package/src/components/indicators/disaggregation-panel.component.tsx +93 -0
  310. package/src/components/indicators/handler/concept-search-multiselect.component.tsx +153 -0
  311. package/src/components/indicators/handler/question-answer-concept-search.component.tsx +336 -0
  312. package/src/components/indicators/indicator-run-preview-modal.component.tsx +391 -0
  313. package/src/components/indicators/indicator-search-select.component.tsx +297 -0
  314. package/src/components/indicators/indicator-status-tag.component.tsx +9 -0
  315. package/src/components/indicators/indicators-page.component.tsx +673 -0
  316. package/src/components/indicators/indicators-page.scss +121 -0
  317. package/src/components/indicators/indicators-table.component.tsx +188 -0
  318. package/src/components/indicators/preview-table.component.tsx +31 -0
  319. package/src/components/indicators/sections/composite-indicator-basics.section.tsx +36 -0
  320. package/src/components/indicators/sections/composite-indicator-picker.section.tsx +116 -0
  321. package/src/components/indicators/sections/composite-indicator-sql-preview.section.tsx +31 -0
  322. package/src/components/indicators/sections/custom-indicator-basics.section.tsx +65 -0
  323. package/src/components/indicators/sections/custom-indicator-disaggregation.section.tsx +93 -0
  324. package/src/components/indicators/sections/custom-indicator-filters.section.tsx +203 -0
  325. package/src/components/indicators/sections/custom-indicator-population.section.tsx +107 -0
  326. package/src/components/indicators/sections/custom-indicator-sql-preview.section.tsx +108 -0
  327. package/src/components/indicators/sections/final-indicator-basics.section.tsx +46 -0
  328. package/src/components/indicators/sections/final-indicator-disaggregation.section.tsx +47 -0
  329. package/src/components/indicators/sections/final-indicator-picker.section.tsx +60 -0
  330. package/src/components/indicators/sections/final-indicator-preview.section.tsx +20 -0
  331. package/src/components/indicators/sections/final-indicator-results-preview.section.tsx +65 -0
  332. package/src/components/indicators/sections/indicator-basics.section.tsx +40 -0
  333. package/src/components/indicators/sections/indicator-conditions.section.tsx +279 -0
  334. package/src/components/indicators/sections/indicator-sql-preview.section.tsx +63 -0
  335. package/src/components/indicators/sections/indicator-theme.section.tsx +48 -0
  336. package/src/components/indicators/sql-preview.component.tsx +19 -0
  337. package/src/components/indicators/tag-input.component.tsx +177 -0
  338. package/src/components/indicators/types/base-indicator-form.types.ts +17 -0
  339. package/src/components/indicators/types/base-indicator-theme.constants.ts +8 -0
  340. package/src/components/indicators/types/composite-indicator.types.ts +27 -0
  341. package/src/components/indicators/types/condition-ui.types.ts +11 -0
  342. package/src/components/indicators/types/custom-indicator.types.ts +161 -0
  343. package/src/components/indicators/types/data-theme-config.types.ts +88 -0
  344. package/src/components/indicators/types/indicator-types.ts +57 -0
  345. package/src/components/indicators/types/sql-builders.ts +43 -0
  346. package/src/components/indicators/utils/composite-indicator-sql.utils.test.ts +143 -0
  347. package/src/components/indicators/utils/composite-indicator-sql.utils.ts +426 -0
  348. package/src/components/indicators/utils/concept-preload.utils.ts +21 -0
  349. package/src/components/indicators/utils/custom-indicator-interpreter.test.ts +493 -0
  350. package/src/components/indicators/utils/custom-indicator-interpreter.ts +702 -0
  351. package/src/components/indicators/utils/final-indicator-sql.utils.ts +190 -0
  352. package/src/components/indicators/utils/indicator-conditions-hydration.utils.ts +254 -0
  353. package/src/components/indicators/utils/indicator-sql-resolution.utils.ts +204 -0
  354. package/src/components/indicators/utils/indicator-sql.utils.ts +721 -0
  355. package/src/components/indicators/utils/population-sql.compiler.test.ts +985 -0
  356. package/src/components/indicators/utils/population-sql.compiler.ts +858 -0
  357. package/src/components/landing/landing-action-item.component.tsx +50 -0
  358. package/src/components/landing/landing-action-list.component.tsx +12 -0
  359. package/src/components/landing/landing-activity-list.component.tsx +47 -0
  360. package/src/components/landing/landing-footer-grid.component.tsx +45 -0
  361. package/src/components/landing/landing-hero.component.tsx +18 -0
  362. package/src/components/landing/landing-section-card.component.tsx +33 -0
  363. package/src/components/landing/report-builder-landing-page.component.tsx +186 -0
  364. package/src/components/landing/report-builder-landing-page.scss +131 -0
  365. package/src/components/legacy-reports/legacy-report-detail-page.component.tsx +300 -0
  366. package/src/components/legacy-reports/legacy-report-editor-page.component.tsx +324 -0
  367. package/src/components/legacy-reports/legacy-report-upload-modal.component.tsx +307 -0
  368. package/src/components/legacy-reports/legacy-reports-page.component.tsx +206 -0
  369. package/src/components/legacy-reports/legacy-reports-table.component.tsx +154 -0
  370. package/src/components/legacy-reports/tabs/legacy-report-datasets-tab.component.tsx +344 -0
  371. package/src/components/legacy-reports/tabs/legacy-report-dimensions-tab.component.tsx +590 -0
  372. package/src/components/legacy-reports/tabs/legacy-report-indicators-tab.component.tsx +490 -0
  373. package/src/components/legacy-reports/tabs/legacy-report-json-preview-tab.component.tsx +272 -0
  374. package/src/components/legacy-reports/tabs/legacy-report-overview-tab.component.tsx +218 -0
  375. package/src/components/legacy-reports/tabs/legacy-report-parameters-tab.component.tsx +279 -0
  376. package/src/components/linelist/definition/indicator-to-sql.utils.ts +281 -0
  377. package/src/components/linelist/definition/linelist-definition-editor.component.tsx +1676 -0
  378. package/src/components/linelist/definition/linelist-definition-editor.scss +461 -0
  379. package/src/components/linelist/definition/linelist-definition.types.ts +108 -0
  380. package/src/components/linelist/definition/panels/column-properties-panel.component.tsx +328 -0
  381. package/src/components/linelist/definition/panels/column-selector-panel.component.tsx +1082 -0
  382. package/src/components/linelist/definition/panels/column-selector-panel.scss +100 -0
  383. package/src/components/linelist/definition/panels/custom-sql-column-modal.component.tsx +248 -0
  384. package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.component.tsx +300 -0
  385. package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.scss +21 -0
  386. package/src/components/linelist/definition/panels/multi-datasource-selector.component.tsx +332 -0
  387. package/src/components/linelist/definition/panels/multi-datasource-selector.scss +161 -0
  388. package/src/components/linelist/definition/panels/observation-column-modal.component.tsx +450 -0
  389. package/src/components/linelist/definition/panels/observation-column-modal.scss +29 -0
  390. package/src/components/linelist/definition/panels/parameter-panel.component.tsx +659 -0
  391. package/src/components/linelist/definition/panels/parameter-panel.scss +64 -0
  392. package/src/components/linelist/definition/panels/population-source-selector.component.tsx +407 -0
  393. package/src/components/linelist/definition/panels/population-source-selector.scss +187 -0
  394. package/src/components/linelist/design/panels/column-category-selector.component.tsx +433 -0
  395. package/src/components/linelist/design/panels/column-category-selector.scss +324 -0
  396. package/src/components/linelist/design/panels/column-debug-panel.component.tsx +197 -0
  397. package/src/components/linelist/design/panels/column-debug-panel.scss +141 -0
  398. package/src/components/linelist/design/panels/data-catalogue.component.tsx +1062 -0
  399. package/src/components/linelist/design/panels/data-catalogue.scss +263 -0
  400. package/src/components/linelist/design/panels/display-settings-panel.component.tsx +405 -0
  401. package/src/components/linelist/design/panels/display-settings-panel.scss +78 -0
  402. package/src/components/linelist/design/panels/filtermap-editor.component.tsx +434 -0
  403. package/src/components/linelist/design/panels/filtermap-editor.scss +176 -0
  404. package/src/components/linelist/design/panels/json-preview.component.tsx +114 -0
  405. package/src/components/linelist/design/panels/json-preview.scss +85 -0
  406. package/src/components/linelist/design/panels/query-config-panel.component.tsx +1137 -0
  407. package/src/components/linelist/design/panels/query-config-panel.scss +341 -0
  408. package/src/components/linelist/design/panels/sort-config-panel.component.tsx +330 -0
  409. package/src/components/linelist/design/panels/sort-config-panel.scss +77 -0
  410. package/src/components/linelist/design/panels/sql-builder-panel.component.tsx +475 -0
  411. package/src/components/linelist/design/panels/sql-builder-panel.scss +226 -0
  412. package/src/components/linelist/design/panels/transformation-panel.component.tsx +687 -0
  413. package/src/components/linelist/design/panels/transformation-panel.scss +81 -0
  414. package/src/components/linelist/design/panels/visual-filter-panel.component.tsx +595 -0
  415. package/src/components/linelist/design/panels/visual-filter-panel.scss +76 -0
  416. package/src/components/linelist/linelist-dashboard.page.component.tsx +756 -0
  417. package/src/components/linelist/linelist-dashboard.page.scss +240 -0
  418. package/src/components/linelist/linelist-editor.page.component.tsx +134 -0
  419. package/src/components/linelist/linelist-editor.page.scss +28 -0
  420. package/src/components/linelist/linelist-run-report.page.component.tsx +715 -0
  421. package/src/components/linelist/linelist-run-report.page.scss +136 -0
  422. package/src/components/linelist/modals/linelist-report-builder-modal.component.tsx +1454 -0
  423. package/src/components/linelist/modals/linelist-report-builder-modal.scss +164 -0
  424. package/src/components/linelist/modals/preview-parameter-modal.component.tsx +361 -0
  425. package/src/components/report/dashboard/reports-table.component.tsx +186 -0
  426. package/src/components/report/definition/panels/properties-panel.component.tsx +38 -0
  427. package/src/components/report/definition/panels/report-structure-panel.component.tsx +61 -0
  428. package/src/components/report/definition/panels/selection-panel.component.tsx +58 -0
  429. package/src/components/report/definition/report-definition-editor.component.tsx +99 -0
  430. package/src/components/report/definition/report-definition.types.ts +66 -0
  431. package/src/components/report/design/panels/IndicatorPropertiesPanel.scss +33 -0
  432. package/src/components/report/design/panels/IndicatorPropertiesPanel.tsx +389 -0
  433. package/src/components/report/design/panels/JsonPreviewPanel.scss +32 -0
  434. package/src/components/report/design/panels/JsonPreviewPanel.tsx +104 -0
  435. package/src/components/report/design/panels/mapping-preview-panel.component.tsx +82 -0
  436. package/src/components/report/design/panels/mapping-preview-panel.scss +55 -0
  437. package/src/components/report/design/panels/template-structure-panel.component.tsx +296 -0
  438. package/src/components/report/design/panels/template-structure-panel.scss +81 -0
  439. package/src/components/report/design/report-design-editor.component.tsx +1170 -0
  440. package/src/components/report/design/report-design.types.ts +48 -0
  441. package/src/components/report/design/report-design.utils.ts +86 -0
  442. package/src/components/report/panels/report-detail.component.tsx +71 -0
  443. package/src/components/report/panels/report-panel-shell.component.tsx +60 -0
  444. package/src/components/report/report-dashboard.page.component.tsx +554 -0
  445. package/src/components/report/report-editor.page.component.tsx +590 -0
  446. package/src/components/report/report.routes.tsx +32 -0
  447. package/src/components/report-sections/create-report-section-modal.component.tsx +388 -0
  448. package/src/components/report-sections/hooks/useAgeCategories.ts +25 -0
  449. package/src/components/report-sections/hooks/useSectionEditorState.ts +449 -0
  450. package/src/components/report-sections/panels/Dhis2MappingPanel.tsx +157 -0
  451. package/src/components/report-sections/panels/DisaggregationPanel.tsx +97 -0
  452. package/src/components/report-sections/panels/IndicatorPickerPanel.tsx +201 -0
  453. package/src/components/report-sections/panels/SectionBasicsPanel.tsx +27 -0
  454. package/src/components/report-sections/report-section-preview-modal.component.tsx +548 -0
  455. package/src/components/report-sections/report-sections-page.component.tsx +325 -0
  456. package/src/components/report-sections/section-disaggregation.utils.ts +637 -0
  457. package/src/components/report-sections/section-types.ts +55 -0
  458. package/src/components/report-sections/section-utils.ts +50 -0
  459. package/src/components/report-sections/sections-page.scss +79 -0
  460. package/src/components/run-reports/run-reports-page.component.tsx +8 -0
  461. package/src/components/security/access-denied-page.component.tsx +64 -0
  462. package/src/components/security/access-denied-page.scss +39 -0
  463. package/src/components/security/privilege-route.component.test.tsx +76 -0
  464. package/src/components/security/privilege-route.component.tsx +39 -0
  465. package/src/components/shared/compile-setup-modal.component.tsx +182 -0
  466. package/src/components/shared/concept-selector.component.tsx +245 -0
  467. package/src/components/shared/concept-selector.scss +121 -0
  468. package/src/components/shared/header/builder-header-actions.component.tsx +38 -0
  469. package/src/components/shared/header/header.component.tsx +85 -0
  470. package/src/components/shared/header/header.scss +151 -0
  471. package/src/components/shared/header/illustration.component.tsx +34 -0
  472. package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.scss +33 -0
  473. package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.tsx +389 -0
  474. package/src/components/shared/json-preview-panel/JsonPreviewPanel.scss +32 -0
  475. package/src/components/shared/json-preview-panel/JsonPreviewPanel.tsx +104 -0
  476. package/src/components/shared/mapping-preview-panel/mapping-preview-panel.component.tsx +82 -0
  477. package/src/components/shared/mapping-preview-panel/mapping-preview-panel.scss +55 -0
  478. package/src/components/shared/preview/query-results-preview.component.tsx +204 -0
  479. package/src/components/shared/template-structure-panel/template-structure-panel.component.tsx +296 -0
  480. package/src/components/shared/template-structure-panel/template-structure-panel.scss +81 -0
  481. package/src/config-schema.ts +43 -0
  482. package/src/constants/privileges.test.ts +54 -0
  483. package/src/constants/privileges.ts +118 -0
  484. package/src/features/dashboards/DashboardRendererPage.tsx +174 -0
  485. package/src/features/dashboards/DashboardsPage.tsx +164 -0
  486. package/src/features/dashboards/EtlDashboardPage.tsx +32 -0
  487. package/src/features/dashboards/components/DashboardEmptyState.tsx +41 -0
  488. package/src/features/dashboards/components/DashboardGrid.tsx +48 -0
  489. package/src/features/dashboards/components/DashboardHeader.tsx +58 -0
  490. package/src/features/dashboards/components/DashboardSection.tsx +46 -0
  491. package/src/features/dashboards/components/ReportLaunchCard.tsx +51 -0
  492. package/src/features/dashboards/components/WidgetSlotDispatcher.tsx +61 -0
  493. package/src/features/dashboards/components/WidgetUnavailableCard.tsx +36 -0
  494. package/src/features/dashboards/dashboards.scss +196 -0
  495. package/src/features/dashboards/hooks/useDashboard.ts +61 -0
  496. package/src/features/dashboards/hooks/useDashboardWidgets.ts +74 -0
  497. package/src/features/dashboards/hooks/useDashboards.ts +56 -0
  498. package/src/features/dashboards/hooks/useMonitorDataMap.ts +84 -0
  499. package/src/features/dashboards/index.ts +11 -0
  500. package/src/features/dashboards/utils/dashboard-config.util.test.ts +286 -0
  501. package/src/features/dashboards/utils/dashboard-config.util.ts +382 -0
  502. package/src/features/report-visualizer/ReportVisualizerPage.tsx +448 -0
  503. package/src/features/report-visualizer/components/Export/ExportMenu.tsx +141 -0
  504. package/src/features/report-visualizer/components/Export/index.ts +2 -0
  505. package/src/features/report-visualizer/components/ReportExplorer/QuickFilters.tsx +152 -0
  506. package/src/features/report-visualizer/components/ReportExplorer/ReportCategoryFilter.tsx +54 -0
  507. package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerPanel.tsx +201 -0
  508. package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerRail.tsx +202 -0
  509. package/src/features/report-visualizer/components/ReportExplorer/ReportList.tsx +287 -0
  510. package/src/features/report-visualizer/components/ReportExplorer/ReportSearch.tsx +151 -0
  511. package/src/features/report-visualizer/components/ReportExplorer/ReportTagFilter.tsx +100 -0
  512. package/src/features/report-visualizer/components/ReportExplorer/RunReportButton.tsx +42 -0
  513. package/src/features/report-visualizer/components/ReportExplorer/RuntimeParameterForm.tsx +259 -0
  514. package/src/features/report-visualizer/components/ReportExplorer/index.ts +8 -0
  515. package/src/features/report-visualizer/components/ReportWorkspace/ReportLayer.tsx +33 -0
  516. package/src/features/report-visualizer/components/ReportWorkspace/ReportSummary.tsx +88 -0
  517. package/src/features/report-visualizer/components/ReportWorkspace/ReportViewTabs.tsx +72 -0
  518. package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspaceHeader.tsx +92 -0
  519. package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspacePanel.tsx +242 -0
  520. package/src/features/report-visualizer/components/ReportWorkspace/index.ts +5 -0
  521. package/src/features/report-visualizer/components/TableView/ColumnVisibilityMenu.tsx +206 -0
  522. package/src/features/report-visualizer/components/TableView/ReportTable.tsx +253 -0
  523. package/src/features/report-visualizer/components/TableView/index.ts +2 -0
  524. package/src/features/report-visualizer/hooks/index.ts +4 -0
  525. package/src/features/report-visualizer/hooks/useExplorerPreferences.ts +103 -0
  526. package/src/features/report-visualizer/hooks/useReportCapabilities.ts +112 -0
  527. package/src/features/report-visualizer/hooks/useReportParameters.ts +152 -0
  528. package/src/features/report-visualizer/hooks/useReports.ts +135 -0
  529. package/src/features/report-visualizer/index.ts +6 -0
  530. package/src/features/report-visualizer/report-visualizer.scss +593 -0
  531. package/src/features/report-visualizer/types/index.ts +1 -0
  532. package/src/features/report-visualizer/types/report-visualizer.types.ts +104 -0
  533. package/src/features/report-visualizer/utils/status-colors.ts +117 -0
  534. package/src/globals.d.ts +9 -0
  535. package/src/hooks/address-template.ts +86 -0
  536. package/src/hooks/concept.ts +112 -0
  537. package/src/hooks/indicator/index.ts +1 -0
  538. package/src/hooks/indicator/useIndicatorsByTheme.ts +41 -0
  539. package/src/hooks/openmrs-reference-data.ts +432 -0
  540. package/src/hooks/theme/index.ts +4 -0
  541. package/src/hooks/theme/useDataTheme.ts +61 -0
  542. package/src/hooks/theme/useDataThemes.ts +28 -0
  543. package/src/hooks/theme/useMambaTableMeta.ts +34 -0
  544. package/src/hooks/theme/useMambaTables.ts +95 -0
  545. package/src/hooks/use-report-builder-privileges.test.tsx +92 -0
  546. package/src/hooks/use-report-builder-privileges.ts +58 -0
  547. package/src/index.ts +29 -0
  548. package/src/resources/address-template/address-template.api.ts +180 -0
  549. package/src/resources/agegroup/agegroups.api.ts +58 -0
  550. package/src/resources/concept/concept.api.ts +116 -0
  551. package/src/resources/concepts/concept-mapper.ts +21 -0
  552. package/src/resources/concepts/concept-types.ts +31 -0
  553. package/src/resources/concepts/concepts.resource.ts +44 -0
  554. package/src/resources/concepts/useConceptSearch.ts +41 -0
  555. package/src/resources/dashboard/dashboard.api.ts +107 -0
  556. package/src/resources/etl-monitor/etl-monitor-test.api.ts +409 -0
  557. package/src/resources/etl-monitor/etl-monitor.api.ts +381 -0
  558. package/src/resources/etl-source/etl-source.api.ts +65 -0
  559. package/src/resources/etl-task/etl-task.api.ts +96 -0
  560. package/src/resources/etl-task/etl-task.types.ts +31 -0
  561. package/src/resources/indicator/indicators.api.ts +125 -0
  562. package/src/resources/legacy-reports/legacy-reports.api.ts +144 -0
  563. package/src/resources/linelist/linelist-reports.api.ts +616 -0
  564. package/src/resources/location/location.api.ts +135 -0
  565. package/src/resources/openmrs-api.ts +40 -0
  566. package/src/resources/patient-identifier-type/patient-identifier-type.api.ts +65 -0
  567. package/src/resources/person-attribute-type/person-attribute-type.api.ts +71 -0
  568. package/src/resources/preview/sql-preview.api.ts +54 -0
  569. package/src/resources/report/reports.api.ts +140 -0
  570. package/src/resources/report-category/report-category.api.ts +41 -0
  571. package/src/resources/report-import-export/import-export-api.ts +164 -0
  572. package/src/resources/report-library/report-library.api.ts +63 -0
  573. package/src/resources/report-section/report-sections.api.ts +73 -0
  574. package/src/resources/report-section/section-preview.api.ts +25 -0
  575. package/src/resources/theme/data-theme.api.ts +88 -0
  576. package/src/resources/theme/etl-schema.api.ts +101 -0
  577. package/src/resources/theme/etl-table-meta.api.ts +45 -0
  578. package/src/root.component.tsx +20 -0
  579. package/src/routes/report-builder.routes.tsx +160 -0
  580. package/src/routes/report-builder.scss +56 -0
  581. package/src/routes.json +31 -0
  582. package/src/sample-template.ts +121 -0
  583. package/src/template-utils.ts +110 -0
  584. package/src/types/carbon-react-inert.d.ts +13 -0
  585. package/src/types/condition-operators.ts +63 -0
  586. package/src/types/dashboard/dashboard.types.ts +137 -0
  587. package/src/types/dashboard/index.ts +4 -0
  588. package/src/types/etl/etl-types.ts +238 -0
  589. package/src/types/etl-monitor/etl-monitor-builder.types.ts +555 -0
  590. package/src/types/etl-monitor/etl-monitor-v2.types.ts +397 -0
  591. package/src/types/etl-monitor/etl-monitor.types.ts +188 -0
  592. package/src/types/etl-monitor/index.ts +6 -0
  593. package/src/types/linelist/compile-config.ts +455 -0
  594. package/src/types/linelist-types.ts +2059 -0
  595. package/src/types/theme/data-theme.types.ts +88 -0
  596. package/src/utils/api-error.utils.test.ts +147 -0
  597. package/src/utils/api-error.utils.ts +131 -0
  598. package/src/utils/config-enhancer.ts +62 -0
  599. package/src/utils/etl-browser/etl-browser.util.test.ts +151 -0
  600. package/src/utils/etl-browser/export-csv.util.ts +43 -0
  601. package/src/utils/etl-browser/group-tables.util.ts +84 -0
  602. package/src/utils/etl-browser/index.ts +4 -0
  603. package/src/utils/etl-browser/summarize-columns.util.ts +27 -0
  604. package/src/utils/etl-browser/table-sql.util.ts +20 -0
  605. package/src/utils/etl-monitor/config-v2-adapter.util.ts +460 -0
  606. package/src/utils/etl-monitor/endpoint-config-normalizer.util.ts +383 -0
  607. package/src/utils/etl-monitor/index.ts +10 -0
  608. package/src/utils/etl-monitor/jsonpath-resolver.util.ts +118 -0
  609. package/src/utils/etl-monitor/normalization-examples.ts +0 -0
  610. package/src/utils/etl-monitor/schema-inspector.util.ts +76 -0
  611. package/src/utils/etl-monitor/semantic-type-detector.util.ts +76 -0
  612. package/src/utils/etl-monitor/status-mapper.util.ts +105 -0
  613. package/src/utils/etl-monitor/value-formatters.util.ts +167 -0
  614. package/src/utils/etl-task/etl-task-card.util.test.ts +240 -0
  615. package/src/utils/etl-task/etl-task-card.util.ts +276 -0
  616. package/src/utils/etl-task/index.ts +1 -0
  617. package/src/utils/html-entities.utils.ts +34 -0
  618. package/src/utils/parameter-resolution.ts +218 -0
  619. package/src/views/etl-monitor-builder/add-monitor-modal.component.scss +0 -0
  620. package/src/views/etl-monitor-builder/add-monitor-modal.component.tsx +210 -0
  621. package/src/views/etl-monitor-builder/etl-monitor-builder.component.scss +157 -0
  622. package/src/views/etl-monitor-builder/etl-monitor-builder.component.tsx +252 -0
  623. package/src/views/etl-monitor-builder/etl-monitor-builder.store.ts +126 -0
  624. package/tools/i18next-parser.config.js +0 -0
  625. package/tools/setup-tests.ts +1 -0
  626. package/tools/update-openmrs-deps.mjs +42 -0
  627. package/translations/am.json +65 -0
  628. package/translations/ar.json +65 -0
  629. package/translations/ar_SY.json +65 -0
  630. package/translations/bn.json +65 -0
  631. package/translations/cs.json +65 -0
  632. package/translations/de.json +65 -0
  633. package/translations/en.json +65 -0
  634. package/translations/en_US.json +65 -0
  635. package/translations/es.json +65 -0
  636. package/translations/es_MX.json +65 -0
  637. package/translations/fr.json +65 -0
  638. package/translations/he.json +65 -0
  639. package/translations/hi.json +65 -0
  640. package/translations/hi_IN.json +65 -0
  641. package/translations/id.json +65 -0
  642. package/translations/it.json +65 -0
  643. package/translations/ka.json +65 -0
  644. package/translations/km.json +65 -0
  645. package/translations/ku.json +65 -0
  646. package/translations/ky.json +65 -0
  647. package/translations/lg.json +65 -0
  648. package/translations/ne.json +65 -0
  649. package/translations/pl.json +65 -0
  650. package/translations/pt.json +65 -0
  651. package/translations/pt_BR.json +65 -0
  652. package/translations/qu.json +65 -0
  653. package/translations/ro_RO.json +65 -0
  654. package/translations/ru_RU.json +65 -0
  655. package/translations/si.json +65 -0
  656. package/translations/sq.json +65 -0
  657. package/translations/sw.json +65 -0
  658. package/translations/sw_KE.json +65 -0
  659. package/translations/tr.json +65 -0
  660. package/translations/tr_TR.json +65 -0
  661. package/translations/uk.json +65 -0
  662. package/translations/uz.json +65 -0
  663. package/translations/uz@Latn.json +65 -0
  664. package/translations/uz_UZ.json +65 -0
  665. package/translations/vi.json +65 -0
  666. package/translations/zh.json +65 -0
  667. package/translations/zh_CN.json +65 -0
  668. package/translations/zh_TW.json +65 -0
  669. package/tsconfig.json +24 -0
  670. package/vitest.config.mts +42 -0
@@ -0,0 +1,1935 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from "react";
2
+ import PivotTableUI from "react-pivottable/PivotTableUI";
3
+ import TableRenderers from "react-pivottable/TableRenderers";
4
+ import Plot from "react-plotly.js";
5
+ import createPlotlyRenderers from "react-pivottable/PlotlyRenderers";
6
+ import Illustration from "./data-visualizer-illustration.component";
7
+ import type { Indicator, Item, ReportType, ReportCategory, RenderType } from "./types";
8
+ import {
9
+ ArrowLeft,
10
+ ArrowRight,
11
+ Catalog,
12
+ ChevronDown,
13
+ ChevronUp,
14
+ CrossTab,
15
+ Intersect,
16
+ ImageService,
17
+ SendAlt,
18
+ DocumentDownload,
19
+ Save,
20
+ List as ListIcon,
21
+ } from "@carbon/react/icons";
22
+ import {
23
+ RadioButton,
24
+ RadioButtonGroup,
25
+ InlineLoading,
26
+ Select,
27
+ SelectItem,
28
+ Accordion,
29
+ AccordionItem,
30
+ Button,
31
+ ButtonSet,
32
+ ComboBox,
33
+ ContentSwitcher,
34
+ DataTableSkeleton,
35
+ DatePicker,
36
+ DatePickerInput,
37
+ Form,
38
+ FormGroup,
39
+ FormLabel,
40
+ Layer,
41
+ Modal,
42
+ Stack,
43
+ Switch,
44
+ TextInput,
45
+ TextArea,
46
+ Tile,
47
+ } from "@carbon/react";
48
+ import ReportingHomeHeader from "./components/header/header.component";
49
+ import {
50
+ CQIReportHeaders,
51
+ reportIndicators,
52
+ dynamicReportOptions,
53
+ personNames,
54
+ Address,
55
+ Demographics,
56
+ AppointmentIndicators,
57
+ } from "./constants";
58
+ import DataList from "./components/data-table/data-table.component";
59
+ import CQIDataList from "./components/cqi-components/cqi-data-table.component";
60
+ import EmptyStateIllustration from "./components/empty-state/empty-state-illustration.component";
61
+ import Panel from "./components/panel/panel.component";
62
+ import pivotTableStyles from "!!raw-loader!react-pivottable/pivottable.css";
63
+ import styles from "./data-visualizer.scss";
64
+ import {
65
+ createColumns,
66
+ downloadReport,
67
+ extractDate,
68
+ formatDate,
69
+ getCategoryIndicator,
70
+ getCohortCategory,
71
+ getDateRange,
72
+ getReport,
73
+ mapDataElements,
74
+ mapOrderDataElements,
75
+ saveReport,
76
+ sendReportToDHIS2,
77
+ useGetEncounterType,
78
+ useGetOrderTypes,
79
+ useGetReportCategories,
80
+ useGetReportLibrary,
81
+ } from "./data-visualizer.resource";
82
+ import dayjs from "dayjs";
83
+ import { showModal, showNotification, showToast } from "@openmrs/esm-framework";
84
+ import ModifierComponent from "./components/popover/modifier-panel";
85
+ import ReportParameterModal from "./report-parameter-modal.component";
86
+ import type { LinelistParameter } from "../../types/linelist-types";
87
+ import {
88
+ RELATIVE_PERIOD_OPTIONS,
89
+ type RelativePeriod,
90
+ resolveRelativePeriod,
91
+ } from "../../utils/parameter-resolution";
92
+
93
+ // Import parameter input components
94
+ import DateParameterInput from "./parameter-inputs/date-parameter-input.component";
95
+ import NumberParameterInput from "./parameter-inputs/number-parameter-input.component";
96
+ import BooleanParameterInput from "./parameter-inputs/boolean-parameter-input.component";
97
+ import ListParameterInput from "./parameter-inputs/list-parameter-input.component";
98
+ import LocationParameterInput from "./parameter-inputs/location-parameter-input.component";
99
+ import ConceptParameterInput from "./parameter-inputs/concept-parameter-input.component";
100
+ import IdentifierTypeParameterInput from "./parameter-inputs/identifier-type-parameter-input.component";
101
+ import PersonAttributeParameterInput from "./parameter-inputs/person-attribute-parameter-input.component";
102
+ import TextParameterInput from "./parameter-inputs/text-parameter-input.component";
103
+
104
+ type ChartType = "list" | "pivot" | "aggregate" | "linelist";
105
+ type ReportingDuration = "fixed" | "relative";
106
+ export type CQIReportingCohort =
107
+ | "Patients with encounters"
108
+ | "Patients on appointment";
109
+
110
+ type ReportLibraryItem = {
111
+ uuid: string;
112
+ name: string;
113
+ description?: string;
114
+ code?: string;
115
+ sourceType?: string;
116
+ reportDefinitionUuid?: string;
117
+ reportBuilderReportUuid?: string;
118
+ reportType?: string;
119
+ migrated?: boolean;
120
+ retired?: boolean;
121
+ category?: {
122
+ uuid?: string;
123
+ name?: string;
124
+ display?: string;
125
+ description?: string;
126
+ };
127
+ metaJson?: string;
128
+ };
129
+
130
+ const DataVisualizer: React.FC = () => {
131
+ const PlotlyRenderers = createPlotlyRenderers(Plot);
132
+
133
+ const [tableHeaders, setTableHeaders] = useState([]);
134
+ const [data, setData] = useState([]);
135
+ const [pivotTableData, setPivotTableData] = useState<any[]>([]);
136
+ const [chartType, setChartType] = useState<ChartType>("list");
137
+ const [reportType, setReportType] = useState<ReportType>("fixed");
138
+ const [reportCategory, setReportCategory] = useState<{
139
+ category?: ReportCategory;
140
+ renderType?: RenderType;
141
+ categoryUuid?: string;
142
+ categoryName?: string;
143
+ }>({
144
+ category: undefined,
145
+ renderType: undefined,
146
+ categoryUuid: undefined,
147
+ categoryName: undefined,
148
+ });
149
+ const [reportingDuration, setReportingDuration] =
150
+ useState<ReportingDuration>("fixed");
151
+
152
+ const { reportLibrary } = useGetReportLibrary();
153
+ const { reportCategories } = useGetReportCategories();
154
+
155
+ const reportTypes = useMemo(
156
+ () =>
157
+ (reportCategories ?? []).map((category) => ({
158
+ id: category?.uuid,
159
+ key: category?.uuid,
160
+ label: category?.name,
161
+ name: category?.name,
162
+ uuid: category?.uuid,
163
+ })),
164
+ [reportCategories]
165
+ );
166
+
167
+ const reportPeriod = [
168
+ { id: "today", label: "Today" },
169
+ { id: "week", label: "This Week" },
170
+ { id: "month", label: "This Month" },
171
+ { id: "quarter", label: "This Quarter" },
172
+ { id: "lastQuarter", label: "Last Quarter" },
173
+ ];
174
+
175
+ const toReportOption = (report: ReportLibraryItem) => ({
176
+ id:
177
+ report?.reportDefinitionUuid ||
178
+ report?.reportBuilderReportUuid ||
179
+ report?.uuid,
180
+ uuid: report?.uuid,
181
+ label: report?.name,
182
+ name: report?.name,
183
+ sourceType: report?.sourceType,
184
+ reportType: report?.reportType,
185
+ code: report?.code,
186
+ category: report?.category?.name,
187
+ metaJson: report?.metaJson,
188
+ });
189
+
190
+ const getReportsByCategoryName = useCallback(
191
+ (categoryName: string) => {
192
+ return (reportLibrary ?? [])
193
+ .filter(
194
+ (report: ReportLibraryItem) =>
195
+ report?.category?.name?.toLowerCase() === categoryName?.toLowerCase()
196
+ )
197
+ .map(toReportOption);
198
+ },
199
+ [reportLibrary]
200
+ );
201
+
202
+ const facilityReports = useMemo(
203
+ () => getReportsByCategoryName("FACILITY REPORTS"),
204
+ [getReportsByCategoryName]
205
+ );
206
+
207
+ const donorReports = useMemo(
208
+ () => getReportsByCategoryName("MER INDICATOR REPORTS"),
209
+ [getReportsByCategoryName]
210
+ );
211
+
212
+ const nationalReports = useMemo(
213
+ () => getReportsByCategoryName("NATIONAL REPORTS"),
214
+ [getReportsByCategoryName]
215
+ );
216
+
217
+ const cqiReports = useMemo(
218
+ () => getReportsByCategoryName("CQI REPORTS"),
219
+ [getReportsByCategoryName]
220
+ );
221
+
222
+ const integrationDataExports = useMemo(
223
+ () => getReportsByCategoryName("Integration Data Exports"),
224
+ [getReportsByCategoryName]
225
+ );
226
+
227
+ const [reportingPeriod, setReportingPeriod] = useState<Item | null>(null);
228
+ const [selectedIndicators, setSelectedIndicators] =
229
+ useState<Indicator | null>(null);
230
+ const [selectedReport, setSelectedReport] = useState<Item | null>(null);
231
+ const [cqiReportingCohort, setCQIReportingCohort] =
232
+ useState<CQIReportingCohort>("Patients with encounters");
233
+
234
+ const [startDate, setStartDate] = useState(new Date());
235
+ const [endDate, setEndDate] = useState(new Date());
236
+ const [loading, setLoading] = useState(true);
237
+ const [showLineList, setShowLineList] = useState(false);
238
+ const [availableParameters, setAvailableParameters] = useState([]);
239
+ const [selectedParameters, setSelectedParameters] = useState<
240
+ Array<Indicator>
241
+ >([]);
242
+ const [showFilters, setShowFilters] = useState(true);
243
+ const [reportName, setReportName] = useState("Patient List");
244
+ const { encounterTypes } = useGetEncounterType();
245
+ const { orderTypes } = useGetOrderTypes();
246
+ const [saveReportModal, setSaveReportModal] = useState(false);
247
+ const [reportTitle, setReportTitle] = useState("");
248
+ const [reportDescription, setReportDescription] = useState("");
249
+ const [htmlContent, setHTML] = useState("");
250
+ const [isDownloading, setIsDownloading] = useState(false);
251
+ const [isSendingReport, setIsSendingReport] = useState(false);
252
+ const [dhisJson, setDhisJson] = useState({});
253
+ const [parameterModalOpen, setParameterModalOpen] = useState(false);
254
+ const [reportParameters, setReportParameters] = useState<LinelistParameter[]>([]);
255
+ const [parameterValues, setParameterValues] = useState<Record<string, any>>({});
256
+
257
+ // Parameter state for inline rendering
258
+ const [paramErrors, setParamErrors] = useState<Record<string, string>>({});
259
+ const [searchQueries, setSearchQueries] = useState<Record<string, string>>({});
260
+ const [dateMode, setDateMode] = useState<'FIXED' | 'RELATIVE'>('FIXED');
261
+ const [relativePeriod, setRelativePeriod] = useState<RelativePeriod | ''>('');
262
+
263
+ const [selectedDynamicReportType, setSelectedDynamicReportType] =
264
+ useState<Item | null>(null);
265
+ const [dynamicReportTypes, setDynamicReportTypes] = useState([]);
266
+
267
+ const hasFixedCategory = !!reportCategory.categoryUuid;
268
+ const hasDynamicCategory = !!selectedDynamicReportType;
269
+
270
+ useEffect(() => {
271
+ setSelectedReport(null);
272
+ setData([]);
273
+ setPivotTableData([]);
274
+ setHTML("");
275
+ setShowLineList(false);
276
+ }, [reportCategory, reportType]);
277
+
278
+ const handleSelectedReport = ({ selectedItem }) => {
279
+ console.log('=== handleSelectedReport called ===');
280
+ console.log('selectedItem:', selectedItem);
281
+
282
+ setSelectedReport(selectedItem ?? null);
283
+
284
+ // Parse parameters from metaJson and initialize default values
285
+ if (selectedItem?.metaJson) {
286
+ try {
287
+ const meta = JSON.parse(selectedItem.metaJson);
288
+ console.log('Parsed metaJson:', meta);
289
+
290
+ if (meta?.parameters && Array.isArray(meta.parameters)) {
291
+ console.log('Found parameters in metaJson:', meta.parameters);
292
+ console.log('Number of parameters:', meta.parameters.length);
293
+ meta.parameters.forEach((param, idx) => {
294
+ console.log(` Param ${idx}:`, param);
295
+ });
296
+ setReportParameters(meta.parameters);
297
+
298
+ // Initialize parameter values with defaults
299
+ const defaults: Record<string, any> = {};
300
+ meta.parameters.forEach((param) => {
301
+ if (param.defaultValue) {
302
+ defaults[param.name] = param.defaultValue;
303
+ } else if (param.type === 'DATE' || param.type === 'DATETIME') {
304
+ // Set default dates for date parameters without defaults
305
+ if (param.name === 'startDate') {
306
+ const startDate = new Date();
307
+ startDate.setMonth(startDate.getMonth() - 6);
308
+ defaults[param.name] = startDate.toISOString().split('T')[0];
309
+ } else if (param.name === 'endDate') {
310
+ defaults[param.name] = new Date().toISOString().split('T')[0];
311
+ }
312
+ }
313
+ });
314
+ setParameterValues(defaults);
315
+ } else {
316
+ console.log('No parameters found in metaJson, setting to empty array');
317
+ setReportParameters([]);
318
+ setParameterValues({});
319
+ }
320
+ } catch (error) {
321
+ console.error('Failed to parse metaJson:', error);
322
+ setReportParameters([]);
323
+ setParameterValues({});
324
+ }
325
+ } else {
326
+ console.log('No metaJson on selected report, setting parameters to empty array');
327
+ setReportParameters([]);
328
+ setParameterValues({});
329
+ }
330
+
331
+ // Reset date mode and errors for new selection
332
+ setDateMode('FIXED');
333
+ setRelativePeriod('');
334
+ setParamErrors({});
335
+ setSearchQueries({});
336
+
337
+ // Detect linelist reports by reportType
338
+ if (selectedItem?.reportType === 'LINE_LIST' || selectedItem?.reportType === 'linelist') {
339
+ setChartType('linelist');
340
+ } else if (chartType === 'linelist') {
341
+ // Reset chart type if not a linelist report
342
+ setChartType('list');
343
+ }
344
+ };
345
+
346
+ const handleChartTypeChange = ({ name }) => {
347
+ setChartType(name);
348
+ };
349
+
350
+ const handleReportTypeChange = ({ name }) => {
351
+ setReportType(name);
352
+ setSelectedReport(null);
353
+ setSelectedIndicators(null);
354
+ setAvailableParameters([]);
355
+ setSelectedParameters([]);
356
+ setData([]);
357
+ setPivotTableData([]);
358
+ setHTML("");
359
+ setShowLineList(false);
360
+ setReportCategory({
361
+ category: undefined,
362
+ renderType: undefined,
363
+ categoryUuid: undefined,
364
+ categoryName: undefined,
365
+ });
366
+ setSelectedDynamicReportType(null);
367
+ setDynamicReportTypes([]);
368
+ setChartType("list");
369
+ };
370
+
371
+ const handleReportingDurationChange = (period) => {
372
+ setReportingDuration(period);
373
+ };
374
+
375
+ const handleCohortChange = (cohort) => {
376
+ setCQIReportingCohort(cohort);
377
+ };
378
+
379
+ const showSaveReportModal = () => {
380
+ setSaveReportModal(true);
381
+ };
382
+
383
+ const closeReportModal = () => {
384
+ setSaveReportModal(false);
385
+ };
386
+
387
+ const confirmSendReport = () => {
388
+ if (!selectedReport) return;
389
+
390
+ const dispose = showModal("confirm-modal", {
391
+ close: () => dispose(),
392
+ submit: () => {
393
+ handleSendToDHIS2();
394
+ dispose();
395
+ },
396
+ report: selectedReport.label,
397
+ });
398
+ };
399
+
400
+ const handleSendToDHIS2 = useCallback(() => {
401
+ if (!selectedReport) return;
402
+
403
+ setIsSendingReport(true);
404
+
405
+ sendReportToDHIS2(selectedReport.id, dhisJson).then(
406
+ (response) => {
407
+ if (response.status === 200) {
408
+ showToast({
409
+ critical: true,
410
+ title: "Sending Report To DHIS2",
411
+ kind: "success",
412
+ description: `Report ${selectedReport.label} sent Successfully`,
413
+ });
414
+ } else {
415
+ showNotification({
416
+ title: "Error sending report to DHIS2",
417
+ kind: "error",
418
+ critical: true,
419
+ description: `Failed with error code ${response.status}, Contact System Administrator`,
420
+ });
421
+ }
422
+
423
+ setIsSendingReport(false);
424
+ },
425
+ (error) => {
426
+ showNotification({
427
+ title: "Error sending report to DHIS2",
428
+ kind: "error",
429
+ critical: true,
430
+ description: error?.message,
431
+ });
432
+ setIsSendingReport(false);
433
+ }
434
+ );
435
+ }, [selectedReport, dhisJson]);
436
+
437
+ const handleDownloadReport = useCallback(() => {
438
+ if (!selectedReport) return;
439
+
440
+ setIsDownloading(true);
441
+
442
+ downloadReport({
443
+ uuid: selectedReport.id,
444
+ startDate: formatDate(startDate),
445
+ endDate: formatDate(endDate),
446
+ reportCategory: reportCategory.category,
447
+ reportingCohort: cqiReportingCohort,
448
+ }).then(
449
+ async (response) => {
450
+ try {
451
+ const blob = await response.blob();
452
+ const url = window.URL.createObjectURL(blob);
453
+ const filename = response?.headers
454
+ ?.get("Content-Disposition")
455
+ ?.match(/filename=(.+)/)?.[1];
456
+
457
+ const a = document.createElement("a");
458
+ a.href = url;
459
+ a.download = filename ?? `${selectedReport.label}.csv`;
460
+ a.click();
461
+ window.URL.revokeObjectURL(url);
462
+ } catch (error) {
463
+ showNotification({
464
+ title: "Error downloading Report",
465
+ kind: "error",
466
+ critical: true,
467
+ description: `Error downloading file: ${error?.message}`,
468
+ });
469
+ }
470
+ setIsDownloading(false);
471
+ },
472
+ (error) => {
473
+ showNotification({
474
+ title: "Error downloading Report",
475
+ kind: "error",
476
+ critical: true,
477
+ description: error?.message,
478
+ });
479
+ setIsDownloading(false);
480
+ }
481
+ );
482
+ }, [startDate, endDate, selectedReport, reportCategory, cqiReportingCohort]);
483
+
484
+ const handleSaveReport = useCallback(() => {
485
+ saveReport({
486
+ reportName: reportTitle,
487
+ reportDescription: reportDescription,
488
+ reportType: pivotTableData?.["rendererName"],
489
+ columns: "",
490
+ rows: "",
491
+ report_request_object: JSON.stringify(pivotTableData),
492
+ }).then(
493
+ (response) => {
494
+ if (response.status === 201) {
495
+ showToast({
496
+ critical: true,
497
+ title: "Saving Report",
498
+ kind: "success",
499
+ description: `Report ${reportTitle} saved Successfully`,
500
+ });
501
+ setSaveReportModal(false);
502
+ }
503
+ },
504
+ (error) => {
505
+ showNotification({
506
+ title: "Error saving report",
507
+ kind: "error",
508
+ critical: true,
509
+ description: error?.message,
510
+ });
511
+ }
512
+ );
513
+ }, [reportDescription, pivotTableData, reportTitle]);
514
+
515
+ const handleReportTitleChange = (event) => {
516
+ setReportTitle(event.target.value);
517
+ };
518
+
519
+ const handleReportDescChange = (event) => {
520
+ setReportDescription(event.target.value);
521
+ };
522
+
523
+ const moveAllFromLeftToRight = (selectedParameter) => {
524
+ const updatedAvailableParameters = availableParameters.filter(
525
+ (parameter) => parameter !== selectedParameter
526
+ );
527
+ setAvailableParameters(updatedAvailableParameters);
528
+ setSelectedParameters([...selectedParameters, selectedParameter]);
529
+ };
530
+
531
+ const moveAllFromRightToLeft = (selectedParameter) => {
532
+ const updatedSelectedParameters = selectedParameters.filter(
533
+ (parameter) => parameter !== selectedParameter
534
+ );
535
+ setSelectedParameters(updatedSelectedParameters);
536
+
537
+ let updatedAvailableParameters = [...availableParameters];
538
+
539
+ selectedIndicators?.attributes?.filter((parameter) => {
540
+ if (parameter === selectedParameter) {
541
+ updatedAvailableParameters = [
542
+ ...updatedAvailableParameters,
543
+ selectedParameter,
544
+ ];
545
+ }
546
+ });
547
+
548
+ setAvailableParameters(updatedAvailableParameters);
549
+ };
550
+
551
+ const moveAllParametersLeft = () => {
552
+ setAvailableParameters(selectedIndicators?.attributes ?? []);
553
+ setSelectedParameters([]);
554
+ };
555
+
556
+ const moveAllParametersRight = () => {
557
+ setSelectedParameters([...availableParameters, ...selectedParameters]);
558
+ setAvailableParameters([]);
559
+ };
560
+
561
+ const handleIndicatorChange = useCallback(
562
+ ({ selectedItem }) => {
563
+ const indicator = selectedItem;
564
+
565
+ if (!selectedItem) {
566
+ setSelectedIndicators(null);
567
+ setAvailableParameters([]);
568
+ return;
569
+ }
570
+
571
+ getCategoryIndicator(selectedItem?.id, selectedItem?.type).then(
572
+ (response) => {
573
+ let results;
574
+
575
+ switch (selectedItem.type) {
576
+ case "PersonName":
577
+ results = personNames;
578
+ break;
579
+ case "Demographics":
580
+ results = Demographics;
581
+ break;
582
+ case "Address":
583
+ results = Address;
584
+ break;
585
+ case "Appointment":
586
+ results = AppointmentIndicators;
587
+ break;
588
+ case "Condition":
589
+ results = mapDataElements(response, null, "concepts");
590
+ break;
591
+ case "Orders":
592
+ results = mapOrderDataElements(
593
+ response,
594
+ "Orders",
595
+ selectedItem.id
596
+ );
597
+ break;
598
+ case "":
599
+ results = mapDataElements(response, null, "concepts");
600
+ break;
601
+ default:
602
+ results = mapDataElements(response?.results, selectedItem.type);
603
+ break;
604
+ }
605
+
606
+ setSelectedIndicators(indicator);
607
+
608
+ const filteredArray = results?.filter(
609
+ (resultParameter) =>
610
+ !selectedParameters?.some(
611
+ (parameter) => parameter.id === resultParameter.id
612
+ )
613
+ );
614
+
615
+ indicator.attributes = filteredArray;
616
+ setAvailableParameters(indicator.attributes ?? []);
617
+ },
618
+ (error) => {
619
+ showNotification({
620
+ title: "Error fetching Indicators",
621
+ kind: "error",
622
+ critical: true,
623
+ description: error?.message,
624
+ });
625
+ }
626
+ );
627
+ },
628
+ [selectedParameters]
629
+ );
630
+
631
+ const handleSelectedReportDefinition = ({ selectedItem }) => {
632
+ setSelectedReport(selectedItem ?? null);
633
+ };
634
+
635
+ const handleSelectedDynamicReportType = ({ selectedItem }) => {
636
+ setSelectedDynamicReportType(selectedItem ?? null);
637
+ setSelectedReport(null);
638
+ setDynamicReportTypes([]);
639
+
640
+ if (!selectedItem) {
641
+ return;
642
+ }
643
+
644
+ if (selectedItem.id === "reportDefinition") {
645
+ setDynamicReportTypes(facilityReports ?? []);
646
+ } else {
647
+ getCohortCategory(selectedItem.id).then((response) => {
648
+ const responseResults =
649
+ selectedItem.id === "patientSearch" ? response : response?.results;
650
+
651
+ const reports =
652
+ responseResults?.map((responseItem) => ({
653
+ id: responseItem?.uuid,
654
+ label: responseItem?.name,
655
+ })) ?? [];
656
+
657
+ setDynamicReportTypes(reports);
658
+ });
659
+ }
660
+ };
661
+
662
+ const handleFiltersToggle = () => {
663
+ setShowFilters((prev) => !prev);
664
+ };
665
+
666
+ const handleStartDateChange = (selectedDate) => {
667
+ setStartDate(selectedDate[0]);
668
+ };
669
+
670
+ const handleEndDateChange = (selectedDate) => {
671
+ setEndDate(selectedDate[0]);
672
+ };
673
+
674
+ const handleReportCategoryChange = ({ selectedItem }) => {
675
+ if (!selectedItem) {
676
+ setReportCategory({
677
+ category: undefined,
678
+ renderType: undefined,
679
+ categoryUuid: undefined,
680
+ categoryName: undefined,
681
+ });
682
+ setSelectedReport(null);
683
+ setChartType("list");
684
+ return;
685
+ }
686
+
687
+ const categoryName = selectedItem?.name || selectedItem?.label || "";
688
+ const categoryUuid = selectedItem?.uuid || selectedItem?.id;
689
+
690
+ setSelectedReport(null);
691
+
692
+ if (categoryName === "NATIONAL REPORTS") {
693
+ setReportCategory({
694
+ category: "national",
695
+ renderType: "html",
696
+ categoryUuid,
697
+ categoryName,
698
+ });
699
+ setChartType("aggregate");
700
+ } else if (categoryName === "CQI REPORTS") {
701
+ setReportCategory({
702
+ category: "cqi",
703
+ renderType: undefined,
704
+ categoryUuid,
705
+ categoryName,
706
+ });
707
+ setChartType("list");
708
+ } else if (categoryName === "MER INDICATOR REPORTS") {
709
+ setReportCategory({
710
+ category: "donor",
711
+ renderType: "html",
712
+ categoryUuid,
713
+ categoryName,
714
+ });
715
+ setChartType("aggregate");
716
+ } else if (categoryName === "Integration Data Exports") {
717
+ setReportCategory({
718
+ category: "integration",
719
+ renderType: undefined,
720
+ categoryUuid,
721
+ categoryName,
722
+ });
723
+ setChartType("list");
724
+ } else {
725
+ setReportCategory({
726
+ category: "facility",
727
+ renderType: "list",
728
+ categoryUuid,
729
+ categoryName,
730
+ });
731
+ setChartType("list");
732
+ }
733
+ };
734
+
735
+ const handleReportingPeriod = (selectedPeriod) => {
736
+ setReportingPeriod(selectedPeriod?.selectedItem ?? null);
737
+
738
+ if (!selectedPeriod?.selectedItem?.id) {
739
+ return;
740
+ }
741
+
742
+ const dateRange = getDateRange(selectedPeriod?.selectedItem?.id);
743
+ setStartDate(dateRange.start);
744
+ setEndDate(dateRange.end);
745
+ };
746
+
747
+ const changeModifier = (selectedParameter, type) => {
748
+ setSelectedParameters((selectedParameters) =>
749
+ selectedParameters.map((parameter) =>
750
+ parameter.id === selectedParameter.id
751
+ ? {
752
+ ...parameter,
753
+ modifier: addORSubtract(selectedParameter?.modifier, type),
754
+ }
755
+ : parameter
756
+ )
757
+ );
758
+ };
759
+
760
+ const addORSubtract = (value, type) => {
761
+ if (type === "add") {
762
+ return value + 1;
763
+ } else if (type === "subtract" && value > 1) {
764
+ return value - 1;
765
+ } else {
766
+ return value;
767
+ }
768
+ };
769
+
770
+ const showModifierPanel = (selectedParameter: Indicator) => {
771
+ setSelectedParameters((selectedParameters) =>
772
+ selectedParameters.map((parameter) =>
773
+ parameter.id === selectedParameter.id
774
+ ? {
775
+ ...parameter,
776
+ showModifierPanel: !selectedParameter?.showModifierPanel,
777
+ }
778
+ : parameter
779
+ )
780
+ );
781
+ };
782
+
783
+ // eslint-disable-next-line react-hooks/exhaustive-deps
784
+ const handleUpdateReport = useCallback(() => {
785
+ if (!selectedReport) return;
786
+
787
+ // If report has parameters, validate and run with inline values
788
+ if (reportParameters.length > 0) {
789
+ // Validate required parameters
790
+ const newErrors: Record<string, string> = {};
791
+ for (const param of reportParameters) {
792
+ const value = parameterValues[param.name];
793
+
794
+ // Skip validation for date parameters in RELATIVE mode (they'll be resolved)
795
+ if ((param.type === 'DATE' || param.type === 'DATETIME') && dateMode === 'RELATIVE') {
796
+ const isStartDate = param.name.toLowerCase() === 'startdate' || param.name.toLowerCase() === 'start_date';
797
+ const isEndDate = param.name.toLowerCase() === 'enddate' || param.name.toLowerCase() === 'end_date';
798
+ if (isStartDate || isEndDate) continue;
799
+ }
800
+
801
+ if (param.required && (!value || value.toString().trim() === '')) {
802
+ newErrors[param.name] = `${param.label} is required`;
803
+ }
804
+ }
805
+
806
+ // Check relative period is selected in RELATIVE mode
807
+ if (dateMode === 'RELATIVE' && !relativePeriod) {
808
+ newErrors.relativePeriod = 'Please select a reporting period';
809
+ }
810
+
811
+ if (Object.keys(newErrors).length > 0) {
812
+ setParamErrors(newErrors);
813
+ showNotification({
814
+ title: 'Validation Error',
815
+ kind: 'error',
816
+ critical: false,
817
+ description: 'Please fill in all required parameters',
818
+ });
819
+ return;
820
+ }
821
+
822
+ // Clear errors and run report
823
+ setParamErrors({});
824
+
825
+ // Build parameter values (resolve relative dates if needed)
826
+ const finalParams = { ...parameterValues };
827
+ if (dateMode === 'RELATIVE' && relativePeriod) {
828
+ const { start, end } = resolveRelativePeriod(relativePeriod);
829
+ const dateParams = reportParameters.filter(p => p.type === 'DATE' || p.type === 'DATETIME');
830
+ dateParams.forEach((param, index) => {
831
+ if (index === 0) {
832
+ finalParams[param.name] = formatDate(start);
833
+ } else if (index === 1) {
834
+ finalParams[param.name] = formatDate(end);
835
+ }
836
+ });
837
+ }
838
+
839
+ // Run report with parameters
840
+ handleRunReportWithParameters(finalParams);
841
+ return;
842
+ }
843
+
844
+ // Original logic for reports without parameters
845
+ setHTML("");
846
+ setShowLineList(true);
847
+ setLoading(true);
848
+ setShowFilters(false);
849
+
850
+ getReport({
851
+ uuid: selectedReport.id,
852
+ startDate: formatDate(startDate),
853
+ endDate: formatDate(endDate),
854
+ reportCategory: reportCategory as {
855
+ category: ReportCategory;
856
+ renderType?: RenderType;
857
+ },
858
+ reportingCohort: cqiReportingCohort,
859
+ }).then(
860
+ (response) => {
861
+ if (response.status === 200) {
862
+ let headers = [];
863
+ let dataForReport: any = [];
864
+ const reportData = response?.data;
865
+
866
+ if (reportType === "fixed") {
867
+ if (reportCategory.category === "cqi") {
868
+ dataForReport = response?.data?.A;
869
+ headers = CQIReportHeaders;
870
+ } else if (reportCategory.renderType === "html") {
871
+ setHTML(reportData?.html ?? "");
872
+ setDhisJson(reportData?.json ?? {});
873
+ } else if (chartType === "linelist" && reportData?._html) {
874
+ // Handle linelist HTML from backend _html key
875
+ setHTML(reportData?._html?.[0]?.html ?? "");
876
+ setDhisJson(reportData?.json ?? {});
877
+ } else {
878
+ const responseReportName = Object.keys(reportData)[0];
879
+
880
+ if (
881
+ reportData[responseReportName] &&
882
+ reportData[responseReportName][0]
883
+ ) {
884
+ let columnNames = Object.keys(reportData[responseReportName][0]);
885
+
886
+ if (
887
+ selectedReport.id === "bf79f017-8591-4eaf-88c9-1cde33226517"
888
+ ) {
889
+ columnNames = columnNames
890
+ .reverse()
891
+ .filter((column) => column !== "EDD" && column !== "Names");
892
+
893
+ headers = createColumns(columnNames);
894
+ dataForReport = reportData[responseReportName]
895
+ .filter((row) => row.PhoneNumber)
896
+ .map((row) => {
897
+ const formattedDate = extractDate(row.LastVisitDate);
898
+
899
+ if (row.PhoneNumber && row.PhoneNumber.startsWith("0")) {
900
+ return {
901
+ ...row,
902
+ PhoneNumber: "256" + row.PhoneNumber.substring(1),
903
+ LastVisitDate: formattedDate,
904
+ };
905
+ }
906
+
907
+ return row;
908
+ });
909
+ } else {
910
+ headers = createColumns(columnNames);
911
+ dataForReport = reportData[responseReportName];
912
+ }
913
+ } else {
914
+ setShowLineList(false);
915
+ }
916
+ }
917
+ } else {
918
+ if (chartType === "linelist" && reportData?._html) {
919
+ // Handle linelist HTML from backend _html key
920
+ setHTML(reportData?._html?.[0]?.html ?? "");
921
+ setDhisJson(reportData?.json ?? {});
922
+ } else if (reportData[0]) {
923
+ const columnNames = Object.keys(reportData[0]);
924
+ headers = createColumns(columnNames);
925
+ dataForReport = reportData;
926
+ } else {
927
+ setShowLineList(false);
928
+ }
929
+ }
930
+
931
+ setLoading(false);
932
+ setShowFilters(false);
933
+ setTableHeaders(headers);
934
+ setData(dataForReport);
935
+ setPivotTableData(dataForReport);
936
+ setReportName(selectedReport?.label);
937
+ }
938
+ },
939
+ (error) => {
940
+ setLoading(false);
941
+ setShowFilters(false);
942
+ showNotification({
943
+ title: "Error fetching report",
944
+ kind: "error",
945
+ critical: true,
946
+ description: error?.message,
947
+ });
948
+ }
949
+ );
950
+ // eslint-disable-next-line react-hooks/exhaustive-deps
951
+ }, [
952
+ cqiReportingCohort,
953
+ chartType,
954
+ dateMode,
955
+ endDate,
956
+ parameterValues,
957
+ relativePeriod,
958
+ reportCategory,
959
+ reportParameters,
960
+ reportType,
961
+ selectedParameters,
962
+ selectedReport,
963
+ startDate,
964
+ selectedDynamicReportType?.label,
965
+ // Note: handleRunReportWithParameters omitted to avoid circular dependency (defined below)
966
+ ]);
967
+
968
+ const handleRunReportWithParameters = useCallback((params: Record<string, any>) => {
969
+ console.log('=== handleRunReportWithParameters called ===');
970
+ console.log('chartType:', chartType);
971
+ console.log('Params from modal:', params);
972
+ console.log('selectedReport:', selectedReport);
973
+
974
+ setParameterValues(params);
975
+ setParameterModalOpen(false);
976
+ setHTML("");
977
+ setShowLineList(true);
978
+ setLoading(true);
979
+ setShowFilters(false);
980
+
981
+ // For linelist reports, only send the parameters defined in the report (from params)
982
+ // For legacy reports, send startDate/endDate as before
983
+ const requestOptions: any = {
984
+ uuid: selectedReport.id,
985
+ reportCategory: reportCategory as {
986
+ category: ReportCategory;
987
+ renderType?: RenderType;
988
+ },
989
+ reportingCohort: cqiReportingCohort,
990
+ parameters: params,
991
+ };
992
+
993
+ console.log('requestOptions before date check:', requestOptions);
994
+
995
+ // Only add startDate/endDate for non-linelist reports
996
+ if (chartType !== 'linelist') {
997
+ requestOptions.startDate = formatDate(startDate);
998
+ requestOptions.endDate = formatDate(endDate);
999
+ console.log('Added startDate/endDate (non-linelist report)');
1000
+ } else {
1001
+ console.log('NOT adding startDate/endDate (linelist report)');
1002
+ }
1003
+
1004
+ console.log('Final requestOptions:', requestOptions);
1005
+
1006
+ getReport(requestOptions).then(
1007
+ (response) => {
1008
+ if (response.status === 200) {
1009
+ let headers = [];
1010
+ let dataForReport: any = [];
1011
+ const reportData = response?.data;
1012
+
1013
+ if (reportType === "fixed") {
1014
+ if (reportCategory.category === "cqi") {
1015
+ dataForReport = response?.data?.A;
1016
+ headers = CQIReportHeaders;
1017
+ } else if (reportCategory.renderType === "html") {
1018
+ setHTML(reportData?.html ?? "");
1019
+ setDhisJson(reportData?.json ?? {});
1020
+ } else if (chartType === "linelist" && reportData?._html) {
1021
+ // Handle linelist HTML from backend _html key
1022
+ setHTML(reportData?._html?.[0]?.html ?? "");
1023
+ setDhisJson(reportData?.json ?? {});
1024
+ } else {
1025
+ const responseReportName = Object.keys(reportData)[0];
1026
+
1027
+ if (
1028
+ reportData[responseReportName] &&
1029
+ reportData[responseReportName][0]
1030
+ ) {
1031
+ let columnNames = Object.keys(reportData[responseReportName][0]);
1032
+
1033
+ if (
1034
+ selectedReport.id === "bf79f017-8591-4eaf-88c9-1cde33226517"
1035
+ ) {
1036
+ columnNames = columnNames
1037
+ .reverse()
1038
+ .filter((column) => column !== "EDD" && column !== "Names");
1039
+
1040
+ headers = createColumns(columnNames);
1041
+ dataForReport = reportData[responseReportName]
1042
+ .filter((row) => row.PhoneNumber)
1043
+ .map((row) => {
1044
+ const formattedDate = extractDate(row.LastVisitDate);
1045
+
1046
+ if (row.PhoneNumber && row.PhoneNumber.startsWith("0")) {
1047
+ return {
1048
+ ...row,
1049
+ PhoneNumber: "256" + row.PhoneNumber.substring(1),
1050
+ LastVisitDate: formattedDate,
1051
+ };
1052
+ }
1053
+
1054
+ return row;
1055
+ });
1056
+ } else {
1057
+ headers = createColumns(columnNames);
1058
+ dataForReport = reportData[responseReportName];
1059
+ }
1060
+ } else {
1061
+ setShowLineList(false);
1062
+ }
1063
+ }
1064
+ } else {
1065
+ if (chartType === "linelist" && reportData?._html) {
1066
+ // Handle linelist HTML from backend _html key
1067
+ setHTML(reportData?._html?.[0]?.html ?? "");
1068
+ setDhisJson(reportData?.json ?? {});
1069
+ } else if (reportData[0]) {
1070
+ const columnNames = Object.keys(reportData[0]);
1071
+ headers = createColumns(columnNames);
1072
+ dataForReport = reportData;
1073
+ } else {
1074
+ setShowLineList(false);
1075
+ }
1076
+ }
1077
+
1078
+ setLoading(false);
1079
+ setShowFilters(false);
1080
+ setTableHeaders(headers);
1081
+ setData(dataForReport);
1082
+ setPivotTableData(dataForReport);
1083
+ setReportName(selectedReport?.label);
1084
+ }
1085
+ },
1086
+ (error) => {
1087
+ setLoading(false);
1088
+ setShowFilters(false);
1089
+ showNotification({
1090
+ title: "Error fetching report",
1091
+ kind: "error",
1092
+ critical: true,
1093
+ description: error?.message,
1094
+ });
1095
+ }
1096
+ );
1097
+ }, [
1098
+ cqiReportingCohort,
1099
+ chartType,
1100
+ endDate,
1101
+ reportCategory,
1102
+ reportType,
1103
+ selectedReport,
1104
+ startDate,
1105
+ ]);
1106
+
1107
+ useEffect(() => {
1108
+ const styleElement = document.createElement("style");
1109
+ styleElement.textContent = `${pivotTableStyles}`;
1110
+ document.head.appendChild(styleElement);
1111
+
1112
+ return () => {
1113
+ document.head.removeChild(styleElement);
1114
+ };
1115
+ }, []);
1116
+
1117
+ const selectedReportTypeItem =
1118
+ reportTypes?.find((item) => item.uuid === reportCategory.categoryUuid) ?? null;
1119
+
1120
+ return (
1121
+ <>
1122
+ <ReportingHomeHeader illustrationComponent={<Illustration />} />
1123
+
1124
+ <div className={styles.container}>
1125
+ <Accordion className={styles.accordion}>
1126
+ <AccordionItem
1127
+ className={styles.heading}
1128
+ title="Report filters"
1129
+ open={showFilters}
1130
+ onHeadingClick={handleFiltersToggle}
1131
+ >
1132
+ <div className={styles.formContainer}>
1133
+ <div className={`${styles.form} ${styles.formFirst}`}>
1134
+ <Form>
1135
+ <Stack gap={2}>
1136
+ <FormGroup legendText={``}>
1137
+ <FormLabel className={styles.label}>
1138
+ Type of report
1139
+ </FormLabel>
1140
+ <ContentSwitcher
1141
+ size="sm"
1142
+ selectedIndex={reportType === "fixed" ? 0 : 1}
1143
+ onChange={handleReportTypeChange}
1144
+ >
1145
+ <Switch name="fixed" text="Fixed" />
1146
+ <Switch name="dynamic" text="Dynamic" />
1147
+ </ContentSwitcher>
1148
+ </FormGroup>
1149
+
1150
+ {reportType === "fixed" && (
1151
+ <>
1152
+ <FormGroup legendText={``}>
1153
+ <FormLabel className={styles.label}>
1154
+ Which kind of report do you want to show?
1155
+ </FormLabel>
1156
+ <ComboBox
1157
+ aria-label="Select Report Type"
1158
+ id="ReportTypeCombobox"
1159
+ items={reportTypes}
1160
+ onChange={handleReportCategoryChange}
1161
+ selectedItem={selectedReportTypeItem}
1162
+ itemToString={(item) => item?.label ?? ""}
1163
+ placeholder="Select report type"
1164
+ />
1165
+ </FormGroup>
1166
+
1167
+ {reportCategory.category === "facility" && (
1168
+ <FormGroup legendText={``}>
1169
+ <FormLabel className={styles.label}>
1170
+ Facility Reports
1171
+ </FormLabel>
1172
+ <ComboBox
1173
+ aria-label="Select facility report"
1174
+ id="facilityReportsCombobox"
1175
+ items={facilityReports ?? []}
1176
+ onChange={handleSelectedReport}
1177
+ selectedItem={selectedReport}
1178
+ itemToString={(item) => item?.label ?? ""}
1179
+ placeholder="Select facility report"
1180
+ disabled={!hasFixedCategory}
1181
+ />
1182
+ </FormGroup>
1183
+ )}
1184
+
1185
+ {reportCategory.category === "national" && (
1186
+ <FormGroup legendText={``}>
1187
+ <FormLabel className={styles.label}>
1188
+ National Reports
1189
+ </FormLabel>
1190
+ <ComboBox
1191
+ aria-label="Select national report"
1192
+ id="nationalReportsCombobox"
1193
+ items={nationalReports ?? []}
1194
+ onChange={handleSelectedReport}
1195
+ selectedItem={selectedReport}
1196
+ itemToString={(item) => item?.label ?? ""}
1197
+ placeholder="Select national report"
1198
+ disabled={!hasFixedCategory}
1199
+ />
1200
+ </FormGroup>
1201
+ )}
1202
+
1203
+ {reportCategory.category === "donor" && (
1204
+ <FormGroup legendText={``}>
1205
+ <FormLabel className={styles.label}>
1206
+ Donor Reports
1207
+ </FormLabel>
1208
+ <ComboBox
1209
+ aria-label="Select donor report"
1210
+ id="donorReportsCombobox"
1211
+ items={donorReports ?? []}
1212
+ onChange={handleSelectedReport}
1213
+ selectedItem={selectedReport}
1214
+ itemToString={(item) => item?.label ?? ""}
1215
+ placeholder="Select donor report"
1216
+ disabled={!hasFixedCategory}
1217
+ />
1218
+ </FormGroup>
1219
+ )}
1220
+
1221
+ {reportCategory.category === "cqi" && (
1222
+ <FormGroup legendText={``}>
1223
+ <FormLabel className={styles.label}>
1224
+ CQI Reports
1225
+ </FormLabel>
1226
+ <ComboBox
1227
+ aria-label="Select CQI report"
1228
+ id="CQIReportsCombobox"
1229
+ items={cqiReports ?? []}
1230
+ onChange={handleSelectedReport}
1231
+ selectedItem={selectedReport}
1232
+ itemToString={(item) => item?.label ?? ""}
1233
+ placeholder="Select CQI report"
1234
+ disabled={!hasFixedCategory}
1235
+ />
1236
+ </FormGroup>
1237
+ )}
1238
+
1239
+ {reportCategory.category === "integration" && (
1240
+ <FormGroup legendText={``}>
1241
+ <FormLabel className={styles.label}>
1242
+ Integration Data Exports
1243
+ </FormLabel>
1244
+ <ComboBox
1245
+ aria-label="Select Integration Data Exports"
1246
+ id="integrationDataExportCombobox"
1247
+ items={integrationDataExports ?? []}
1248
+ onChange={handleSelectedReport}
1249
+ selectedItem={selectedReport}
1250
+ itemToString={(item) => item?.label ?? ""}
1251
+ placeholder="Select integration export"
1252
+ disabled={!hasFixedCategory}
1253
+ />
1254
+ </FormGroup>
1255
+ )}
1256
+
1257
+ {reportCategory.category === "cqi" && (
1258
+ <FormGroup legendText={``}>
1259
+ <FormLabel className={styles.label}>
1260
+ Select your cohort of interest
1261
+ </FormLabel>
1262
+ <RadioButtonGroup
1263
+ legendText=""
1264
+ name="patientCohort"
1265
+ onChange={handleCohortChange}
1266
+ defaultSelected="Patients with encounters"
1267
+ >
1268
+ <RadioButton
1269
+ id="patient_with_encounters"
1270
+ labelText="Patient with encounters"
1271
+ value="Patients with encounters"
1272
+ />
1273
+ <RadioButton
1274
+ id="patients_on_appointment"
1275
+ labelText="Patients on appointment"
1276
+ value="Patients on appointment"
1277
+ />
1278
+ </RadioButtonGroup>
1279
+ </FormGroup>
1280
+ )}
1281
+ </>
1282
+ )}
1283
+
1284
+ {reportType === "dynamic" && (
1285
+ <Stack gap={2}>
1286
+ <FormGroup legendText={``}>
1287
+ <FormLabel className={styles.label}>
1288
+ Which kind of dynamic report type do you want to
1289
+ base on?
1290
+ </FormLabel>
1291
+ <ComboBox
1292
+ aria-label="Select dynamic report type"
1293
+ id="dynamicReportOptions"
1294
+ items={dynamicReportOptions}
1295
+ onChange={handleSelectedDynamicReportType}
1296
+ selectedItem={selectedDynamicReportType}
1297
+ itemToString={(item) => item?.label ?? ""}
1298
+ placeholder="Select dynamic report type"
1299
+ />
1300
+ </FormGroup>
1301
+
1302
+ {selectedDynamicReportType && (
1303
+ <FormGroup legendText={``}>
1304
+ <FormLabel className={styles.label}>
1305
+ {selectedDynamicReportType?.label ?? "Report"}
1306
+ </FormLabel>
1307
+
1308
+ <ComboBox
1309
+ aria-label="Select report type"
1310
+ id="reportTypeCombobox"
1311
+ items={dynamicReportTypes ?? []}
1312
+ onChange={handleSelectedReportDefinition}
1313
+ selectedItem={selectedReport}
1314
+ itemToString={(item) => item?.label ?? ""}
1315
+ placeholder="Select report"
1316
+ disabled={!hasDynamicCategory}
1317
+ />
1318
+ </FormGroup>
1319
+ )}
1320
+ </Stack>
1321
+ )}
1322
+ </Stack>
1323
+ </Form>
1324
+ </div>
1325
+
1326
+ {/* Only show hardcoded parameters when a report is selected but has no parameters */}
1327
+ {selectedReport && reportParameters.length === 0 && (
1328
+ <div className={`${styles.form} ${styles.formRight}`}>
1329
+ <Form>
1330
+ <Stack gap={3}>
1331
+ <FormGroup legendText={``}>
1332
+ <FormLabel className={styles.label}>
1333
+ Do you want your report to cover a fixed reporting
1334
+ period or a relative one?
1335
+ </FormLabel>
1336
+ <RadioButtonGroup
1337
+ legendText=""
1338
+ name="reportingDuration"
1339
+ onChange={handleReportingDurationChange}
1340
+ defaultSelected="fixed"
1341
+ >
1342
+ <RadioButton
1343
+ id="fixedPeriod"
1344
+ labelText="Fixed period"
1345
+ value="fixed"
1346
+ />
1347
+ <RadioButton
1348
+ id="relativePeriod"
1349
+ labelText="Relative period"
1350
+ value="relative"
1351
+ />
1352
+ </RadioButtonGroup>
1353
+ </FormGroup>
1354
+
1355
+ {reportingDuration === "fixed" && (
1356
+ <FormGroup legendText={``} className={styles.dateForm}>
1357
+ <DatePicker
1358
+ datePickerType="single"
1359
+ onChange={handleStartDateChange}
1360
+ dateFormat={"d/m/Y"}
1361
+ value={startDate}
1362
+ >
1363
+ <DatePickerInput
1364
+ id="date-picker-input-id-start"
1365
+ placeholder="dd/mm/yyyy"
1366
+ labelText="Start Date"
1367
+ />
1368
+ </DatePicker>
1369
+ <br />
1370
+ <DatePicker
1371
+ datePickerType="single"
1372
+ onChange={handleEndDateChange}
1373
+ dateFormat={"d/m/Y"}
1374
+ value={endDate}
1375
+ >
1376
+ <DatePickerInput
1377
+ id="date-picker-input-id-end"
1378
+ placeholder="dd/mm/yyyy"
1379
+ labelText="End Date"
1380
+ />
1381
+ </DatePicker>
1382
+ </FormGroup>
1383
+ )}
1384
+
1385
+ {reportingDuration === "relative" && (
1386
+ <FormGroup legendText={``}>
1387
+ <FormLabel className={styles.label}>
1388
+ Select your desired reporting period
1389
+ </FormLabel>
1390
+
1391
+ <ComboBox
1392
+ ariaLabel="Select reporting period"
1393
+ id="reportingPeriodCombobox"
1394
+ items={reportPeriod}
1395
+ placeholder="Choose the reporting period"
1396
+ onChange={handleReportingPeriod}
1397
+ selectedItem={reportingPeriod}
1398
+ itemToString={(item) => item?.label ?? ""}
1399
+ />
1400
+ </FormGroup>
1401
+ )}
1402
+ </Stack>
1403
+ </Form>
1404
+ </div>
1405
+ )}
1406
+
1407
+ {/* Show inline parameters when report has parameters */}
1408
+ {selectedReport && reportParameters.length > 0 && (
1409
+ <div className={`${styles.form} ${styles.formRight}`}>
1410
+ <Stack gap={5}>
1411
+ {/* Date Mode Toggle - shown if report has date parameters */}
1412
+ {reportParameters.some(p => p.type === 'DATE' || p.type === 'DATETIME') && (
1413
+ <div style={{ marginBottom: '1rem' }}>
1414
+ <label className="cds--label">Date Selection Mode</label>
1415
+ <RadioButtonGroup
1416
+ legendText=""
1417
+ name="date-mode"
1418
+ valueSelected={dateMode}
1419
+ onChange={(mode) => {
1420
+ setDateMode(mode as 'FIXED' | 'RELATIVE');
1421
+ if (mode === 'FIXED') {
1422
+ setRelativePeriod('');
1423
+ }
1424
+ }}
1425
+ >
1426
+ <RadioButton
1427
+ id="date-mode-fixed"
1428
+ labelText="Specific dates"
1429
+ value="FIXED"
1430
+ />
1431
+ <RadioButton
1432
+ id="date-mode-relative"
1433
+ labelText="Relative period"
1434
+ value="RELATIVE"
1435
+ />
1436
+ </RadioButtonGroup>
1437
+ </div>
1438
+ )}
1439
+
1440
+ {/* Relative Period Selector - shown in RELATIVE mode */}
1441
+ {dateMode === 'RELATIVE' && reportParameters.some(p => p.type === 'DATE' || p.type === 'DATETIME') && (
1442
+ <div style={{ marginBottom: '1rem', maxWidth: '300px' }}>
1443
+ <Select
1444
+ id="relative-period-select"
1445
+ labelText="Select reporting period"
1446
+ value={relativePeriod}
1447
+ onChange={(e) => {
1448
+ setRelativePeriod(e.target.value as RelativePeriod);
1449
+ }}
1450
+ size="sm"
1451
+ >
1452
+ <SelectItem value="" text="Select period" />
1453
+ {RELATIVE_PERIOD_OPTIONS.map((option) => (
1454
+ <SelectItem
1455
+ key={option.value}
1456
+ value={option.value}
1457
+ text={option.label}
1458
+ />
1459
+ ))}
1460
+ </Select>
1461
+ {paramErrors.relativePeriod && (
1462
+ <div style={{ color: '#da1e28', fontSize: '0.875rem', marginTop: '0.5rem' }}>
1463
+ {paramErrors.relativePeriod}
1464
+ </div>
1465
+ )}
1466
+ </div>
1467
+ )}
1468
+
1469
+ {/* Render parameter inputs inline */}
1470
+ {reportParameters.map((param) => {
1471
+ const value = parameterValues[param.name] || '';
1472
+ const error = paramErrors[param.name];
1473
+
1474
+ // Skip start/end date parameters in RELATIVE mode
1475
+ if (dateMode === 'RELATIVE') {
1476
+ const isStartDate = param.name.toLowerCase() === 'startdate' || param.name.toLowerCase() === 'start_date';
1477
+ const isEndDate = param.name.toLowerCase() === 'enddate' || param.name.toLowerCase() === 'end_date';
1478
+ if ((param.type === 'DATE' || param.type === 'DATETIME') && (isStartDate || isEndDate)) {
1479
+ return null;
1480
+ }
1481
+ }
1482
+
1483
+ const commonProps = {
1484
+ key: param.name,
1485
+ parameter: param,
1486
+ value,
1487
+ error,
1488
+ onChange: (newValue: any) => {
1489
+ setParameterValues((prev) => ({ ...prev, [param.name]: newValue }));
1490
+ if (paramErrors[param.name]) {
1491
+ setParamErrors((prev) => {
1492
+ const newErrors = { ...prev };
1493
+ delete newErrors[param.name];
1494
+ return newErrors;
1495
+ });
1496
+ }
1497
+ },
1498
+ };
1499
+
1500
+ switch (param.type) {
1501
+ case 'DATE':
1502
+ case 'DATETIME':
1503
+ return <DateParameterInput {...commonProps} />;
1504
+ case 'NUMBER':
1505
+ return <NumberParameterInput {...commonProps} />;
1506
+ case 'BOOLEAN':
1507
+ return <BooleanParameterInput {...commonProps} />;
1508
+ case 'LIST':
1509
+ return <ListParameterInput {...commonProps} />;
1510
+ case 'LOCATION':
1511
+ return (
1512
+ <LocationParameterInput
1513
+ {...commonProps}
1514
+ onSearchQueryChange={(query) => setSearchQueries((prev) => ({ ...prev, [param.name]: query }))}
1515
+ />
1516
+ );
1517
+ case 'CONCEPT':
1518
+ return (
1519
+ <ConceptParameterInput
1520
+ {...commonProps}
1521
+ searchQuery={searchQueries[param.name] || ''}
1522
+ onSearchQueryChange={(query) => setSearchQueries((prev) => ({ ...prev, [param.name]: query }))}
1523
+ />
1524
+ );
1525
+ case 'IDENTIFIER_TYPE':
1526
+ return <IdentifierTypeParameterInput {...commonProps} />;
1527
+ case 'PERSON_ATTRIBUTE':
1528
+ return <PersonAttributeParameterInput {...commonProps} />;
1529
+ case 'TEXT':
1530
+ default:
1531
+ return (
1532
+ <TextParameterInput
1533
+ {...commonProps}
1534
+ onError={(error) => setParamErrors((prev) => ({ ...prev, [param.name]: error }))}
1535
+ />
1536
+ );
1537
+ }
1538
+ })}
1539
+ </Stack>
1540
+ </div>
1541
+ )}
1542
+
1543
+ {/* Show empty state when no report is selected */}
1544
+ {!selectedReport && (
1545
+ <div className={`${styles.form} ${styles.formRight}`}>
1546
+ <div style={{ padding: '1rem', color: '#525252', fontStyle: 'italic', textAlign: 'center', marginTop: '2rem' }}>
1547
+ Select a report above to configure and run
1548
+ </div>
1549
+ </div>
1550
+ )}
1551
+ </div>
1552
+
1553
+ <div>
1554
+ <Form>
1555
+ {reportType === "dynamic" && (
1556
+ <Stack gap={2}>
1557
+ <FormGroup legendText={``}>
1558
+ <FormLabel className={styles.label}>Indicators</FormLabel>
1559
+
1560
+ <ComboBox
1561
+ ariaLabel="Select indicators"
1562
+ id="indicatorCombobox"
1563
+ items={[
1564
+ ...reportIndicators,
1565
+ ...encounterTypes,
1566
+ ...orderTypes,
1567
+ ]}
1568
+ placeholder="Choose the indicators"
1569
+ onChange={handleIndicatorChange}
1570
+ selectedItem={selectedIndicators}
1571
+ itemToString={(item) => item?.label ?? ""}
1572
+ />
1573
+ </FormGroup>
1574
+
1575
+ <div className={styles.panelContainer}>
1576
+ <Panel heading="Available parameters">
1577
+ <ul className={styles.list}>
1578
+ {availableParameters.map((parameter) => (
1579
+ <li
1580
+ role="menuitem"
1581
+ className={styles.leftListItem}
1582
+ key={parameter.label}
1583
+ onClick={() => moveAllFromLeftToRight(parameter)}
1584
+ >
1585
+ {parameter.label}
1586
+ </li>
1587
+ ))}
1588
+ </ul>
1589
+ </Panel>
1590
+
1591
+ <div className={styles.paramsControlContainer}>
1592
+ <Button
1593
+ iconDescription="Move all parameters to the right"
1594
+ kind="tertiary"
1595
+ hasIconOnly
1596
+ renderIcon={ArrowRight}
1597
+ onClick={moveAllParametersRight}
1598
+ role="button"
1599
+ size="md"
1600
+ disabled={availableParameters.length < 1}
1601
+ />
1602
+ <Button
1603
+ iconDescription="Move all parameters to the left"
1604
+ kind="tertiary"
1605
+ hasIconOnly
1606
+ renderIcon={ArrowLeft}
1607
+ onClick={moveAllParametersLeft}
1608
+ role="button"
1609
+ size="md"
1610
+ disabled={selectedParameters.length < 1}
1611
+ />
1612
+ </div>
1613
+
1614
+ <Panel heading="Selected parameters">
1615
+ <ul className={styles.list}>
1616
+ {selectedParameters.map((parameter) => (
1617
+ <React.Fragment key={parameter.label}>
1618
+ <li
1619
+ className={`${styles.rightListItem} ${
1620
+ parameter?.showModifierPanel
1621
+ ? styles.openRightListItem
1622
+ : ""
1623
+ } `}
1624
+ role="menuitem"
1625
+ >
1626
+ <div className={styles.selectedListItem}>
1627
+ <div>
1628
+ <ArrowLeft
1629
+ className={styles.itemChevronUpDown}
1630
+ onClick={() =>
1631
+ moveAllFromRightToLeft(parameter)
1632
+ }
1633
+ />
1634
+ </div>
1635
+ {parameter.label}
1636
+ {![
1637
+ "PatientIdentifier",
1638
+ "PersonAttribute",
1639
+ "PersonName",
1640
+ "Demographics",
1641
+ "Address",
1642
+ "Condition",
1643
+ "Appointment",
1644
+ "Orders",
1645
+ ].includes(parameter?.type) ? (
1646
+ <div className={styles.modifierContainer}>
1647
+ <div>
1648
+ {parameter?.showModifierPanel ? (
1649
+ <ChevronUp
1650
+ className={styles.itemChevronUpDown}
1651
+ onClick={() =>
1652
+ showModifierPanel(parameter)
1653
+ }
1654
+ />
1655
+ ) : (
1656
+ <ChevronDown
1657
+ className={styles.itemChevronUpDown}
1658
+ onClick={() =>
1659
+ showModifierPanel(parameter)
1660
+ }
1661
+ />
1662
+ )}
1663
+ </div>
1664
+ </div>
1665
+ ) : null}
1666
+ </div>
1667
+ </li>
1668
+
1669
+ <div
1670
+ className={`${styles.fadeModifierContainer} ${
1671
+ parameter?.showModifierPanel
1672
+ ? styles.show
1673
+ : styles.hide
1674
+ }`}
1675
+ >
1676
+ <ModifierComponent
1677
+ listItem={parameter}
1678
+ onChangeMostRecent={changeModifier}
1679
+ />
1680
+ </div>
1681
+ </React.Fragment>
1682
+ ))}
1683
+ </ul>
1684
+ </Panel>
1685
+ </div>
1686
+ </Stack>
1687
+ )}
1688
+ </Form>
1689
+ </div>
1690
+ </AccordionItem>
1691
+ </Accordion>
1692
+ </div>
1693
+
1694
+ <section className={styles.section}>
1695
+ <div className={styles.contentSwitchContainer}>
1696
+ <ContentSwitcher
1697
+ size={`md`}
1698
+ selectedIndex={
1699
+ chartType === "linelist" ? 3 : chartType === "pivot" ? 1 : chartType === "aggregate" ? 2 : 0
1700
+ }
1701
+ onChange={handleChartTypeChange}
1702
+ >
1703
+ <Switch name="list" disabled={chartType === "aggregate" || chartType === "linelist"}>
1704
+ <div className={styles.switch}>
1705
+ <Catalog />
1706
+ <span>Patient list</span>
1707
+ </div>
1708
+ </Switch>
1709
+ <Switch name="pivot" disabled={chartType === "aggregate" || chartType === "linelist"}>
1710
+ <div className={styles.switch}>
1711
+ <CrossTab />
1712
+ <span>Pivot table</span>
1713
+ </div>
1714
+ </Switch>
1715
+ <Switch name="aggregate" disabled={chartType !== "aggregate"}>
1716
+ <div className={styles.switch}>
1717
+ <ImageService />
1718
+ <span>Aggregate Report</span>
1719
+ </div>
1720
+ </Switch>
1721
+ <Switch name="linelist" disabled={chartType !== "linelist"}>
1722
+ <div className={styles.switch}>
1723
+ <ListIcon />
1724
+ <span>Linelist</span>
1725
+ </div>
1726
+ </Switch>
1727
+ </ContentSwitcher>
1728
+ </div>
1729
+
1730
+ <div className={styles.actionButtonContainer}>
1731
+ <ButtonSet>
1732
+ <Button
1733
+ size="md"
1734
+ kind="primary"
1735
+ onClick={handleUpdateReport}
1736
+ className={styles.actionButton}
1737
+ disabled={
1738
+ !selectedReport ||
1739
+ (reportType === "fixed" && !reportCategory.category) ||
1740
+ (reportType === "dynamic" && !selectedDynamicReportType)
1741
+ }
1742
+ >
1743
+ <Intersect />
1744
+ <span>View Report</span>
1745
+ </Button>
1746
+
1747
+ {data.length > 0 || htmlContent !== "" ? (
1748
+ <>
1749
+ {chartType === "pivot" ? (
1750
+ <Button
1751
+ size="md"
1752
+ kind="secondary"
1753
+ iconDescription="Save Report"
1754
+ onClick={showSaveReportModal}
1755
+ className={styles.dsReportBtn}
1756
+ renderIcon={Save}
1757
+ hasIconOnly
1758
+ />
1759
+ ) : null}
1760
+
1761
+ {reportType === "fixed" ? (
1762
+ isDownloading ? (
1763
+ <InlineLoading />
1764
+ ) : (
1765
+ <Button
1766
+ size="md"
1767
+ kind="tertiary"
1768
+ iconDescription="Download Report"
1769
+ tooltipAlignment="end"
1770
+ onClick={handleDownloadReport}
1771
+ className={styles.dsReportBtn}
1772
+ renderIcon={DocumentDownload}
1773
+ hasIconOnly
1774
+ />
1775
+ )
1776
+ ) : null}
1777
+
1778
+ {chartType === "aggregate" ? (
1779
+ isSendingReport ? (
1780
+ <InlineLoading />
1781
+ ) : (
1782
+ <Button
1783
+ size="md"
1784
+ kind="secondary"
1785
+ iconDescription="Send Report to DHIS2"
1786
+ tooltipAlignment="end"
1787
+ onClick={confirmSendReport}
1788
+ className={styles.dsReportBtn}
1789
+ renderIcon={SendAlt}
1790
+ hasIconOnly
1791
+ />
1792
+ )
1793
+ ) : null}
1794
+ </>
1795
+ ) : null}
1796
+ </ButtonSet>
1797
+ </div>
1798
+ </section>
1799
+
1800
+ {showLineList ? (
1801
+ <>
1802
+ {loading && <DataTableSkeleton role="progressbar" />}
1803
+
1804
+ {chartType === "list" && !loading && (
1805
+ <div className={styles.reportContainer}>
1806
+ <h3 className={styles.listHeading}>
1807
+ {reportName} ({dayjs(startDate).format("DD/MM/YYYY")} -{" "}
1808
+ {dayjs(endDate).format("DD/MM/YYYY")})
1809
+ </h3>
1810
+ <div className={styles.reportDataTable}>
1811
+ {reportCategory.category === "cqi" ? (
1812
+ <CQIDataList columns={tableHeaders} data={data} />
1813
+ ) : (
1814
+ <DataList
1815
+ columns={tableHeaders}
1816
+ data={data}
1817
+ report={{
1818
+ type: reportType,
1819
+ name: selectedReport?.label ?? "",
1820
+ }}
1821
+ />
1822
+ )}
1823
+ </div>
1824
+ </div>
1825
+ )}
1826
+
1827
+ {chartType === "list" &&
1828
+ !loading &&
1829
+ selectedReport?.id === "bf79f017-8591-4eaf-88c9-1cde33226517" && (
1830
+ <div className={styles.sendReportBtn}>
1831
+ <Button
1832
+ size="md"
1833
+ kind="primary"
1834
+ className={styles.actionButton}
1835
+ >
1836
+ <SendAlt />
1837
+ <span>Send Report to Family Connect</span>
1838
+ </Button>
1839
+ </div>
1840
+ )}
1841
+
1842
+ {chartType === "pivot" && (
1843
+ <div className={styles.reportContainer}>
1844
+ <h3>Pivot Table</h3>
1845
+ <PivotTableUI
1846
+ data={pivotTableData}
1847
+ onChange={(s) => setPivotTableData(s)}
1848
+ renderers={{ ...TableRenderers, ...PlotlyRenderers }}
1849
+ {...pivotTableData}
1850
+ />
1851
+ </div>
1852
+ )}
1853
+
1854
+ {chartType === "aggregate" && !loading && (
1855
+ <div className={styles.reportTableContainer}>
1856
+ <section className={styles.reportOptions}>
1857
+ <h3 className={styles.listHeading}>
1858
+ {reportName} ({dayjs(startDate).format("DD/MM/YYYY")} -{" "}
1859
+ {dayjs(endDate).format("DD/MM/YYYY")})
1860
+ </h3>
1861
+ </section>
1862
+ <div dangerouslySetInnerHTML={{ __html: htmlContent }} />
1863
+ </div>
1864
+ )}
1865
+
1866
+ {chartType === "linelist" && !loading && (
1867
+ <div className={styles.reportTableContainer}>
1868
+ <section className={styles.reportOptions}>
1869
+ <h3 className={styles.listHeading}>
1870
+ {reportName} ({dayjs(startDate).format("DD/MM/YYYY")} -{" "}
1871
+ {dayjs(endDate).format("DD/MM/YYYY")})
1872
+ </h3>
1873
+ </section>
1874
+ <div dangerouslySetInnerHTML={{ __html: htmlContent }} />
1875
+ </div>
1876
+ )}
1877
+
1878
+ {saveReportModal && (
1879
+ <Modal
1880
+ open
1881
+ size="sm"
1882
+ preventCloseOnClickOutside={true}
1883
+ hasScrollingContent={true}
1884
+ modalHeading="ENTER REPORT DETAILS"
1885
+ secondaryButtonText="Cancel"
1886
+ primaryButtonText="Save Report"
1887
+ onRequestClose={closeReportModal}
1888
+ onRequestSubmit={handleSaveReport}
1889
+ >
1890
+ <div>
1891
+ <TextInput
1892
+ id="title"
1893
+ labelText={`Report Title`}
1894
+ onChange={handleReportTitleChange}
1895
+ maxCount={50}
1896
+ placeholder="Enter report title"
1897
+ />
1898
+ <TextArea
1899
+ id="description"
1900
+ className={styles.reportDescription}
1901
+ labelText={`Report Description`}
1902
+ onChange={handleReportDescChange}
1903
+ rows={2}
1904
+ placeholder="Enter report description"
1905
+ />
1906
+ </div>
1907
+ </Modal>
1908
+ )}
1909
+
1910
+ {/* Parameter Modal */}
1911
+ <ReportParameterModal
1912
+ open={parameterModalOpen}
1913
+ onClose={() => setParameterModalOpen(false)}
1914
+ onRun={handleRunReportWithParameters}
1915
+ parameters={reportParameters}
1916
+ initialValues={parameterValues}
1917
+ loading={loading}
1918
+ />
1919
+ </>
1920
+ ) : (
1921
+ <Layer className={styles.layer}>
1922
+ <Tile className={styles.tile}>
1923
+ <EmptyStateIllustration />
1924
+ <p className={styles.content}>No data to display</p>
1925
+ <p className={styles.explainer}>
1926
+ Use the report filters above to build your reports
1927
+ </p>
1928
+ </Tile>
1929
+ </Layer>
1930
+ )}
1931
+ </>
1932
+ );
1933
+ };
1934
+
1935
+ export default DataVisualizer;