@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,1454 @@
1
+ /**
2
+ * Linelist Report Builder Modal
3
+ *
4
+ * Multi-panel modal for creating and editing linelist reports.
5
+ * Follows the same pattern as create-base-indicator-modal.component.tsx
6
+ *
7
+ * Panels:
8
+ * 1. Basics - Name, description, code
9
+ * 2. Cohort - Patient selection SQL query
10
+ * 3. Columns - Column selection and configuration
11
+ * 4. Preview - SQL preview and validation
12
+ * 5. Review - Review and save
13
+ */
14
+
15
+ import React, { useState, useCallback, useEffect, useMemo } from 'react';
16
+ import {
17
+ Modal,
18
+ Stack,
19
+ SideNav,
20
+ SideNavItems,
21
+ SideNavLink,
22
+ Content,
23
+ Button,
24
+ ButtonSet,
25
+ InlineNotification,
26
+ ProgressBar,
27
+ TextInput,
28
+ TextArea,
29
+ Select,
30
+ SelectItem,
31
+ DataTable,
32
+ Table,
33
+ TableContainer,
34
+ TableHead,
35
+ TableHeader,
36
+ TableRow,
37
+ TableBody,
38
+ TableCell,
39
+ Toggle,
40
+ } from '@carbon/react';
41
+
42
+ import type {
43
+ LinelistReportDraft,
44
+ LinelistBuilderPanel,
45
+ LinelistColumnDraft,
46
+ LinelistReportDefinitionConfig,
47
+ LinelistRowGrain,
48
+ LinelistParameterDraft,
49
+ VisualFilterState,
50
+ FilterFieldType,
51
+ // LinelistDataDefinitionMap,
52
+ } from '../../../types/linelist-types';
53
+ import {
54
+ draftToConfig,
55
+ validateLinelistDraft,
56
+ isLinelistDraftValid,
57
+ isLinelistDraftReadyToCompile,
58
+ // isLinelistDraftReadyToPublish,
59
+ generateLinelistWarnings,
60
+ } from '../../../types/linelist-types';
61
+
62
+ import {
63
+ createLinelistReport,
64
+ updateLinelistReport,
65
+ configToSavePayload,
66
+ compileLinelistReport,
67
+ } from '../../../resources/linelist/linelist-reports.api';
68
+ import type { LinelistReportDto } from '../../../types/linelist-types';
69
+ import { listReportCategories, type ReportCategoryDto } from '../../../resources/report-category/report-category.api';
70
+ import { useETLTables } from '../../../hooks/theme';
71
+ import { enhanceConfigForPreview } from '../../../utils/config-enhancer';
72
+
73
+ import CohortSQLEditor from '../design/panels/sql-builder-panel.component';
74
+ import ColumnSelector from '../definition/panels/column-selector-panel.component';
75
+ import ColumnCategorySelector from '../design/panels/column-category-selector.component';
76
+ import SortConfiguration from '../design/panels/sort-config-panel.component';
77
+ import ParameterEditor from '../definition/panels/parameter-panel.component';
78
+ import DisplayExportSettings from '../design/panels/display-settings-panel.component';
79
+ import styles from './linelist-report-builder-modal.scss';
80
+
81
+ type Props = {
82
+ open: boolean;
83
+ mode: 'create' | 'edit';
84
+ initialReport?: LinelistReportDto | null;
85
+ onClose: () => void;
86
+ onSaved: () => void;
87
+ };
88
+
89
+ /**
90
+ * Default draft state
91
+ */
92
+ const defaultDraft: LinelistReportDraft = {
93
+ version: 2,
94
+ name: '',
95
+ description: '',
96
+ code: '',
97
+ currentPanel: 'basics',
98
+ categoryUuid: '',
99
+ dataSourceUuid: '', // @deprecated - use dataSources instead
100
+ dataSources: [],
101
+ populationSources: [], // v3 - population sources for SQL/Hybrid modes
102
+ rowGrain: 'PATIENT',
103
+ templateId: '',
104
+ cohortSql: '', // @deprecated - use population.sqlTemplate instead
105
+ population: {
106
+ baseDataSourceUuid: '',
107
+ buildMethod: 'SQL_BUILDER',
108
+ sqlTemplate: '',
109
+ parameterReferences: [],
110
+ visualFilter: {
111
+ rootGroup: {
112
+ id: 'root',
113
+ logicalOperator: 'AND',
114
+ conditions: [],
115
+ nestedGroups: [],
116
+ },
117
+ useVisualBuilder: false,
118
+ },
119
+ buildHistory: [],
120
+ },
121
+ visualFilter: {
122
+ rootGroup: {
123
+ id: 'root',
124
+ logicalOperator: 'AND',
125
+ conditions: [],
126
+ },
127
+ useVisualBuilder: false,
128
+ },
129
+ columns: [],
130
+ parameters: [],
131
+ dataSetName: 'PATIENT_LIST', // @deprecated
132
+ sortConfig: [],
133
+ limit: 1000,
134
+ displaySettings: {
135
+ defaultPageSize: 25,
136
+ freezeFirstColumn: true,
137
+ freezeHeader: true,
138
+ dateDisplayFormat: 'dd MMM yyyy',
139
+ nullDisplayValue: '—',
140
+ maxInteractiveRows: 500,
141
+ maxExportRows: 100000,
142
+ allowedExports: ['CSV', 'XLSX', 'PDF'],
143
+ includeParametersInExportHeader: true,
144
+ includeGeneratedTimestamp: true,
145
+ },
146
+ validation: {
147
+ errors: {},
148
+ warnings: {},
149
+ lastValidated: new Date().toISOString(),
150
+ },
151
+ metadata: {
152
+ createdAt: new Date().toISOString(),
153
+ lastModified: new Date().toISOString(),
154
+ buildMethod: 'NEW',
155
+ version: 2,
156
+ status: 'DRAFT',
157
+ },
158
+ errors: {}, // Embedded validation errors
159
+ };
160
+
161
+ /**
162
+ * Extract table and field from a SQL reference
163
+ * Parses patterns like `table.field`, `table`.`field`, table.field
164
+ */
165
+ function extractTableAndFieldFromSql(sql: string): { table: string; field: string } | null {
166
+ if (!sql) return null;
167
+
168
+ // Match patterns like: `table`.`field`, table.field, `table`.field, table.`field`
169
+ const match = sql.match(/`?(\w+)`?\.\s*`?(\w+)`?/);
170
+ if (match) {
171
+ return { table: match[1], field: match[2] };
172
+ }
173
+
174
+ // For custom SQL (SELECT statements), return null
175
+ if (/^\s*SELECT\s/i.test(sql) || /:patientId\b/i.test(sql)) {
176
+ return null;
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ /**
183
+ * Normalize a column to ensure it has required fields (id, sortOrder)
184
+ * This ensures backward compatibility with legacy configs
185
+ */
186
+ function normalizeColumn(column: any, index: number, existingIds: Set<string>): string {
187
+ // Generate a unique ID if missing or duplicate
188
+ let columnId = column.id;
189
+
190
+ if (!columnId) {
191
+ // Use index + timestamp for uniqueness
192
+ const timestamp = Date.now().toString(36);
193
+ columnId = `col-${timestamp}-${index}`;
194
+ // If still duplicate, add random suffix
195
+ if (existingIds.has(columnId)) {
196
+ columnId = `col-${timestamp}-${index}-${Math.random().toString(36).substr(2, 5)}`;
197
+ }
198
+ } else if (existingIds.has(columnId)) {
199
+ // Duplicate ID - generate new one
200
+ const timestamp = Date.now().toString(36);
201
+ columnId = `col-${timestamp}-${index}-dup`;
202
+ }
203
+
204
+ existingIds.add(columnId);
205
+
206
+ // Ensure sortOrder is set on source if it exists
207
+ if (column && typeof column.sortOrder !== 'number') {
208
+ column.sortOrder = index;
209
+ }
210
+
211
+ return columnId;
212
+ }
213
+
214
+ /**
215
+ * Convert LinelistReportDto to LinelistReportDraft for editing
216
+ */
217
+ function reportDtoToDraft(report: LinelistReportDto): LinelistReportDraft {
218
+ let config: LinelistReportDefinitionConfig | null = null;
219
+
220
+ try {
221
+ config = report.configJson ? JSON.parse(report.configJson) : null;
222
+ } catch (e) {
223
+ console.error('Failed to parse report config:', e);
224
+ }
225
+
226
+ if (!config || config.type !== 'LINE_LIST') {
227
+ // Return default draft for invalid config
228
+ return {
229
+ ...defaultDraft,
230
+ name: report.name || '',
231
+ description: report.description || '',
232
+ code: report.code || '',
233
+ };
234
+ }
235
+
236
+ // Extract columns from dataset definitions
237
+ const columns: LinelistColumnDraft[] = [];
238
+ const dataSet = config.dataSetDefinitions?.[0];
239
+ const existingIds = new Set<string>(); // Track IDs to ensure uniqueness
240
+
241
+ if (dataSet?.columns) {
242
+ dataSet.columns.forEach((col, idx) => {
243
+ const dataDef = (col.dataDefinition as any) || {};
244
+ const dataDefConfig = dataDef.config || {};
245
+ const dataDefType = dataDef.type || 'SQL';
246
+
247
+ // Extract table and field from SQL if available
248
+ const sql = dataDefConfig.sql || '';
249
+ const tableFieldInfo = extractTableAndFieldFromSql(sql);
250
+
251
+ // Determine data source info
252
+ const dataSourceUuid = tableFieldInfo?.table || (config as any).dataSourceUuid || '';
253
+ const dataSourceName = tableFieldInfo?.table || (config as any).dataSourceUuid || '';
254
+ const table = tableFieldInfo?.table || 'unknown';
255
+ const field = tableFieldInfo?.field || col.name;
256
+
257
+ // Map data definition type to field type
258
+ const fieldTypeMap: Record<string, string> = {
259
+ 'IDENTIFIER': 'IDENTIFIER',
260
+ 'PERSON_NAME': 'TEXT',
261
+ 'PERSON_ATTRIBUTE': 'TEXT',
262
+ 'CALCULATION': 'NUMBER',
263
+ 'PERSON_ADDRESS': 'TEXT',
264
+ 'SQL': 'TEXT',
265
+ 'OBSERVATION': 'CODED',
266
+ 'ENCOUNTER_DIAGNOSIS': 'CODED',
267
+ };
268
+ const fieldType = fieldTypeMap[dataDefType] || 'TEXT';
269
+
270
+ // Set data source name based on dataDefinitionType
271
+ let dataSourceNameToUse = dataSourceName;
272
+ let tableToUse = table;
273
+ let fieldToUse = field;
274
+
275
+ // Override source info for known column types
276
+ if (dataDefType === 'OBSERVATION') {
277
+ dataSourceNameToUse = 'Observations';
278
+ tableToUse = 'observations';
279
+ fieldToUse = dataDefConfig.conceptUuid || field;
280
+ } else if (dataDefType === 'ENCOUNTER_DIAGNOSIS') {
281
+ dataSourceNameToUse = 'Encounter Diagnoses';
282
+ tableToUse = 'encounter_diagnoses';
283
+ fieldToUse = dataDefConfig.conceptUuid || '*';
284
+ } else if (dataDefType === 'PERSON_NAME') {
285
+ dataSourceNameToUse = 'Person Names';
286
+ tableToUse = 'person_name';
287
+ } else if (dataDefType === 'PERSON_ADDRESS') {
288
+ dataSourceNameToUse = 'Person Address';
289
+ tableToUse = 'person_address';
290
+ } else if (dataDefType === 'IDENTIFIER') {
291
+ dataSourceNameToUse = 'Patient Identifiers';
292
+ tableToUse = 'patient_identifier';
293
+ } else if (dataDefType === 'PERSON_ATTRIBUTE') {
294
+ dataSourceNameToUse = 'Person Attributes';
295
+ tableToUse = 'person_attribute';
296
+ }
297
+
298
+ // Normalize ID and sortOrder
299
+ const columnId = normalizeColumn(col, idx, existingIds);
300
+
301
+ columns.push({
302
+ id: columnId,
303
+ name: col.name,
304
+ description: (col as any).description || '',
305
+ dataDefinitionType: dataDefType,
306
+ dataDefinitionConfig: dataDefConfig,
307
+ config: dataDefConfig, // @deprecated
308
+ source: {
309
+ dataSourceUuid: dataSourceUuid || (dataDefType === 'OBSERVATION' ? 'observations' : dataDefType === 'ENCOUNTER_DIAGNOSIS' ? 'encounter_diagnoses' : ''),
310
+ dataSourceName: dataSourceNameToUse,
311
+ table: tableToUse,
312
+ field: fieldToUse,
313
+ fieldType,
314
+ conceptUuid: dataDefConfig.conceptUuid,
315
+ attributeTypeUuid: dataDefConfig.attributeTypeUuid,
316
+ identifierTypeUuid: dataDefConfig.identifierTypeUuid,
317
+ },
318
+ additionInfo: {
319
+ addedVia: 'IMPORT',
320
+ addedAt: new Date().toISOString(),
321
+ orderAdded: idx,
322
+ },
323
+ display: {
324
+ width: 150,
325
+ align: 'left',
326
+ sortable: true,
327
+ filterable: true,
328
+ format: dataDefType === 'OBSERVATION' || dataDefType === 'ENCOUNTER_DIAGNOSIS' ? 'coded' : 'text',
329
+ },
330
+ sortOrder: idx,
331
+ repeatResolution: (col as any).repeatResolution,
332
+ });
333
+ });
334
+ }
335
+
336
+ // Extract parameters from config
337
+ const parameters: LinelistParameterDraft[] = [];
338
+ if (config.parameters && config.parameters.length > 0) {
339
+ config.parameters.forEach((param, idx) => {
340
+ parameters.push({
341
+ id: `param-${idx}`,
342
+ name: param.name,
343
+ label: param.label,
344
+ type: param.type,
345
+ required: param.required || false,
346
+ defaultValue: (param as any).defaultValue || '',
347
+ displayOrder: (param as any).displayOrder || idx,
348
+ config: (param as any).config || {},
349
+ });
350
+ });
351
+ }
352
+
353
+ return {
354
+ ...defaultDraft,
355
+ name: report.name || '',
356
+ description: report.description || '',
357
+ code: report.code || '',
358
+ categoryUuid: (config as any).categoryUuid || '',
359
+ dataSourceUuid: (config as any).dataSourceUuid || '',
360
+ rowGrain: (config as any).rowGrain || 'PATIENT',
361
+ templateId: (config as any).templateId || '',
362
+ cohortSql: config.baseCohortDefinition?.config?.sql || '',
363
+ columns,
364
+ parameters,
365
+ dataSetName: dataSet?.name || 'PATIENT_LIST',
366
+ sortConfig: config.orderBy
367
+ ? [
368
+ {
369
+ id: `sort-0`,
370
+ columnId: config.orderBy,
371
+ columnName: config.orderBy,
372
+ direction: config.orderDirection || 'ASC',
373
+ nulls: 'LAST',
374
+ sortOrder: 0,
375
+ },
376
+ ]
377
+ : [],
378
+ limit: config.limit,
379
+ };
380
+ }
381
+
382
+ export default function LinelistReportBuilderModal({ open, mode, initialReport, onClose, onSaved }: Props) {
383
+ const [draft, setDraft] = useState<LinelistReportDraft>(defaultDraft);
384
+ const [saving, setSaving] = useState(false);
385
+ const [saveError, setSaveError] = useState<string | null>(null);
386
+ const [panelValidation, setPanelValidation] = useState<Record<string, boolean>>({});
387
+ const [compiling, setCompiling] = useState(false);
388
+ const [compileError, setCompileError] = useState<string | null>(null);
389
+ const [compileSuccess, setCompileSuccess] = useState<string | null>(null);
390
+ const [savedReportUuid, setSavedReportUuid] = useState<string | null>(null);
391
+
392
+ // Data for dropdowns
393
+ const [categories, setCategories] = useState<ReportCategoryDto[]>([]);
394
+ const { tables } = useETLTables(open);
395
+
396
+ // Load categories on mount
397
+ useEffect(() => {
398
+ const loadData = async () => {
399
+ try {
400
+ const cats = await listReportCategories();
401
+ setCategories(cats);
402
+ } catch (error) {
403
+ console.error('Failed to load reference data:', error);
404
+ }
405
+ };
406
+ loadData();
407
+ }, []);
408
+
409
+ // Initialize draft when opening
410
+ useEffect(() => {
411
+ if (open) {
412
+ if (mode === 'edit' && initialReport) {
413
+ setDraft(reportDtoToDraft(initialReport));
414
+ setSavedReportUuid(initialReport.uuid); // Initialize savedReportUuid in edit mode
415
+ } else {
416
+ setDraft({ ...defaultDraft });
417
+ setSavedReportUuid(null); // Reset for create mode
418
+ }
419
+ setSaveError(null);
420
+ setPanelValidation({});
421
+ setCompileError(null);
422
+ setCompileSuccess(null);
423
+ }
424
+ }, [open, mode, initialReport]);
425
+
426
+ /**
427
+ * Update draft field
428
+ */
429
+ const updateDraft = useCallback((updates: Partial<LinelistReportDraft>) => {
430
+ setDraft((prev) => ({ ...prev, ...updates }));
431
+ }, []);
432
+
433
+ /**
434
+ * Navigate to panel
435
+ */
436
+ const goToPanel = useCallback((panel: LinelistBuilderPanel) => {
437
+ setDraft((prev) => ({ ...prev, currentPanel: panel }));
438
+ }, []);
439
+
440
+ /**
441
+ * Validate current panel
442
+ */
443
+ const validatePanel = useCallback((panel: LinelistBuilderPanel): boolean => {
444
+ const errors = validateLinelistDraft(draft);
445
+
446
+ let valid = true;
447
+
448
+ switch (panel) {
449
+ case 'basics':
450
+ valid = !errors.name && !errors.categoryUuid && !errors.themeUuid && !errors.dataSources && !errors.rowGrain;
451
+ break;
452
+ case 'cohort':
453
+ valid = !errors.population;
454
+ break;
455
+ case 'columns':
456
+ valid = !errors.columns;
457
+ break;
458
+ default:
459
+ valid = true;
460
+ }
461
+
462
+ setPanelValidation((prev) => ({ ...prev, [panel]: valid }));
463
+ return valid;
464
+ }, [draft]);
465
+
466
+ /**
467
+ * Check if can advance to next panel
468
+ */
469
+ const canAdvance = useCallback(() => {
470
+ return validatePanel(draft.currentPanel);
471
+ }, [draft.currentPanel, validatePanel]);
472
+
473
+ /**
474
+ * Go to next panel
475
+ */
476
+ const goToNextPanel = useCallback(() => {
477
+ if (!canAdvance()) return;
478
+
479
+ const panels: LinelistBuilderPanel[] = ['basics', 'cohort', 'columns', 'sort', 'parameters', 'display-export', 'preview', 'review'];
480
+ const currentIndex = panels.indexOf(draft.currentPanel);
481
+
482
+ if (currentIndex < panels.length - 1) {
483
+ goToPanel(panels[currentIndex + 1]);
484
+ }
485
+ }, [draft.currentPanel, canAdvance, goToPanel]);
486
+
487
+ /**
488
+ * Go to previous panel
489
+ */
490
+ const goToPrevPanel = useCallback(() => {
491
+ const panels: LinelistBuilderPanel[] = ['basics', 'cohort', 'columns', 'sort', 'parameters', 'display-export', 'preview', 'review'];
492
+ const currentIndex = panels.indexOf(draft.currentPanel);
493
+
494
+ if (currentIndex > 0) {
495
+ goToPanel(panels[currentIndex - 1]);
496
+ }
497
+ }, [draft.currentPanel, goToPanel]);
498
+
499
+ /**
500
+ * Save the report (stays open for compile)
501
+ */
502
+ const handleSave = useCallback(async () => {
503
+ if (!isLinelistDraftValid(draft)) {
504
+ setSaveError('Please fix all validation errors before saving');
505
+ return;
506
+ }
507
+
508
+ setSaving(true);
509
+ setSaveError(null);
510
+
511
+ try {
512
+ const config = draftToConfig(draft);
513
+ const payload = configToSavePayload(config, {
514
+ name: draft.name,
515
+ description: draft.description,
516
+ code: draft.code,
517
+ });
518
+
519
+ let result;
520
+ if (mode === 'create') {
521
+ result = await createLinelistReport(payload);
522
+ } else {
523
+ result = await updateLinelistReport(initialReport?.uuid || '', payload);
524
+ }
525
+
526
+ setSavedReportUuid(result?.uuid || null);
527
+ } catch (error) {
528
+ setSaveError(error instanceof Error ? error.message : 'Failed to save report');
529
+ } finally {
530
+ setSaving(false);
531
+ }
532
+ }, [draft, mode, initialReport?.uuid]);
533
+
534
+ /**
535
+ * Save and close (original behavior)
536
+ */
537
+ const handleSaveAndClose = useCallback(async () => {
538
+ if (!isLinelistDraftValid(draft)) {
539
+ setSaveError('Please fix all validation errors before saving');
540
+ return;
541
+ }
542
+
543
+ setSaving(true);
544
+ setSaveError(null);
545
+
546
+ try {
547
+ const config = draftToConfig(draft);
548
+ const payload = configToSavePayload(config, {
549
+ name: draft.name,
550
+ description: draft.description,
551
+ code: draft.code,
552
+ });
553
+
554
+ if (mode === 'create') {
555
+ await createLinelistReport(payload);
556
+ } else {
557
+ await updateLinelistReport(initialReport?.uuid || '', payload);
558
+ }
559
+
560
+ onSaved();
561
+ onClose();
562
+ } catch (error) {
563
+ setSaveError(error instanceof Error ? error.message : 'Failed to save report');
564
+ } finally {
565
+ setSaving(false);
566
+ }
567
+ }, [draft, mode, initialReport?.uuid, onSaved, onClose]);
568
+
569
+ /**
570
+ * Compile the report (only available after save)
571
+ */
572
+ const handleCompile = useCallback(async () => {
573
+ if (!savedReportUuid) {
574
+ setCompileError('Please save the report first before compiling');
575
+ return;
576
+ }
577
+
578
+ setCompiling(true);
579
+ setCompileError(null);
580
+ setCompileSuccess(null);
581
+
582
+ try {
583
+ // Get category name from the selected category UUID
584
+ const category = categories.find((c) => c.uuid === draft.categoryUuid)?.name;
585
+
586
+ console.log('Linelist Modal - Compiling with:', {
587
+ reportUuid: savedReportUuid,
588
+ categoryUuid: draft.categoryUuid,
589
+ categoryName: category,
590
+ categoriesCount: categories.length,
591
+ });
592
+
593
+ // Pass category UUID instead of name to avoid "category is required" error
594
+ const result = await compileLinelistReport(savedReportUuid, draft.categoryUuid);
595
+
596
+ setCompileSuccess(
597
+ result?.reportDefinitionUuid
598
+ ? `Compiled successfully. Runtime report UUID: ${result.reportDefinitionUuid}`
599
+ : 'Compiled successfully.'
600
+ );
601
+ } catch (error) {
602
+ setCompileError(error instanceof Error ? error.message : 'Failed to compile report');
603
+ } finally {
604
+ setCompiling(false);
605
+ }
606
+ }, [savedReportUuid, draft.categoryUuid, categories]);
607
+
608
+ /**
609
+ * Get panel step number
610
+ */
611
+ const getPanelStep = useCallback((panel: LinelistBuilderPanel): number => {
612
+ const panels: LinelistBuilderPanel[] = ['basics', 'cohort', 'columns', 'sort', 'parameters', 'display-export', 'preview', 'review'];
613
+ return panels.indexOf(panel) + 1;
614
+ }, []);
615
+
616
+ /**
617
+ * Get total steps
618
+ */
619
+ const totalSteps = 8;
620
+
621
+ /**
622
+ * Render panel content
623
+ */
624
+ const renderPanel = () => {
625
+ const errors = validateLinelistDraft(draft);
626
+
627
+ switch (draft.currentPanel) {
628
+ case 'basics':
629
+ return (
630
+ <BasicsPanel
631
+ draft={draft}
632
+ onChange={updateDraft}
633
+ error={errors.name}
634
+ categories={categories}
635
+ tables={tables}
636
+ />
637
+ );
638
+
639
+ case 'cohort':
640
+ return (
641
+ <CohortPanel
642
+ draft={draft}
643
+ onChange={updateDraft}
644
+ error={errors.population}
645
+ />
646
+ );
647
+
648
+ case 'columns':
649
+ return (
650
+ <ColumnsPanel
651
+ draft={draft}
652
+ onChange={updateDraft}
653
+ error={errors.columns}
654
+ />
655
+ );
656
+
657
+ case 'sort':
658
+ return (
659
+ <SortPanel
660
+ draft={draft}
661
+ onChange={updateDraft}
662
+ />
663
+ );
664
+
665
+ case 'parameters':
666
+ return (
667
+ <ParametersPanel
668
+ draft={draft}
669
+ onChange={updateDraft}
670
+ />
671
+ );
672
+
673
+ case 'display-export':
674
+ return (
675
+ <DisplayExportPanel
676
+ draft={draft}
677
+ onChange={updateDraft}
678
+ />
679
+ );
680
+
681
+ case 'preview':
682
+ return (
683
+ <PreviewPanel
684
+ draft={draft}
685
+ />
686
+ );
687
+
688
+ case 'review':
689
+ return (
690
+ <ReviewPanel
691
+ draft={draft}
692
+ onEdit={goToPanel}
693
+ isValid={isLinelistDraftValid(draft)}
694
+ categories={categories}
695
+ />
696
+ );
697
+
698
+ default:
699
+ return null;
700
+ }
701
+ };
702
+
703
+ if (!open) return null;
704
+
705
+ const currentStep = getPanelStep(draft.currentPanel);
706
+ const canGoNext = canAdvance();
707
+ const canGoPrev = currentStep > 1;
708
+ const isLastPanel = draft.currentPanel === 'review';
709
+
710
+ return (
711
+ <Modal
712
+ open={open}
713
+ onRequestClose={onClose}
714
+ modalHeading={mode === 'create' ? 'Create Linelist Report' : 'Edit Linelist Report'}
715
+ modalLabel="Report Builder"
716
+ size="lg"
717
+ className={styles.modal}
718
+ preventCloseOnClickOutside={saving}
719
+ >
720
+ <ProgressBar
721
+ value={currentStep}
722
+ max={totalSteps}
723
+ label={`Step ${currentStep} of ${totalSteps}`}
724
+ className={styles.progressBar}
725
+ />
726
+
727
+ <div className={styles.content}>
728
+ {/* Side Navigation */}
729
+ <SideNav className={styles.sideNav}>
730
+ <SideNavItems>
731
+ <SideNavLink
732
+ onClick={() => goToPanel('basics')}
733
+ isActive={draft.currentPanel === 'basics'}
734
+ renderIcon={panelValidation.basics === false ? undefined : undefined}
735
+ >
736
+ Basics
737
+ </SideNavLink>
738
+ <SideNavLink
739
+ onClick={() => goToPanel('cohort')}
740
+ isActive={draft.currentPanel === 'cohort'}
741
+ >
742
+ Cohort SQL
743
+ </SideNavLink>
744
+ <SideNavLink
745
+ onClick={() => goToPanel('columns')}
746
+ isActive={draft.currentPanel === 'columns'}
747
+ >
748
+ Columns
749
+ </SideNavLink>
750
+ <SideNavLink
751
+ onClick={() => goToPanel('sort')}
752
+ isActive={draft.currentPanel === 'sort'}
753
+ >
754
+ Sort
755
+ </SideNavLink>
756
+ <SideNavLink
757
+ onClick={() => goToPanel('parameters')}
758
+ isActive={draft.currentPanel === 'parameters'}
759
+ >
760
+ Parameters
761
+ </SideNavLink>
762
+ <SideNavLink
763
+ onClick={() => goToPanel('display-export')}
764
+ isActive={draft.currentPanel === 'display-export'}
765
+ >
766
+ Display & Export
767
+ </SideNavLink>
768
+ <SideNavLink
769
+ onClick={() => goToPanel('preview')}
770
+ isActive={draft.currentPanel === 'preview'}
771
+ >
772
+ Preview
773
+ </SideNavLink>
774
+ <SideNavLink
775
+ onClick={() => goToPanel('review')}
776
+ isActive={draft.currentPanel === 'review'}
777
+ >
778
+ Review
779
+ </SideNavLink>
780
+ </SideNavItems>
781
+ </SideNav>
782
+
783
+ {/* Main Content */}
784
+ <Content className={styles.panelContent}>
785
+ {saveError && (
786
+ <InlineNotification
787
+ kind="error"
788
+ title="Save Error"
789
+ subtitle={saveError}
790
+ onClose={() => setSaveError(null)}
791
+ className={styles.notification}
792
+ />
793
+ )}
794
+ {compileError && (
795
+ <InlineNotification
796
+ kind="error"
797
+ title="Compile Error"
798
+ subtitle={compileError}
799
+ onClose={() => setCompileError(null)}
800
+ className={styles.notification}
801
+ />
802
+ )}
803
+ {compileSuccess && (
804
+ <InlineNotification
805
+ kind="success"
806
+ title="Compiled"
807
+ subtitle={compileSuccess}
808
+ onClose={() => setCompileSuccess(null)}
809
+ className={styles.notification}
810
+ />
811
+ )}
812
+
813
+ {renderPanel()}
814
+ </Content>
815
+ </div>
816
+
817
+ {/* Footer Actions */}
818
+ <ButtonSet className={styles.footer}>
819
+ <Button kind="secondary" onClick={onClose} disabled={saving || compiling}>
820
+ {savedReportUuid ? 'Close' : 'Cancel'}
821
+ </Button>
822
+ <Button
823
+ kind="ghost"
824
+ onClick={goToPrevPanel}
825
+ disabled={!canGoPrev || saving || compiling}
826
+ >
827
+ Previous
828
+ </Button>
829
+ {isLastPanel ? (
830
+ <>
831
+ <Button
832
+ kind="primary"
833
+ onClick={handleSaveAndClose}
834
+ disabled={!isLinelistDraftValid(draft) || saving || compiling}
835
+ >
836
+ {saving ? 'Saving...' : (mode === 'create' ? 'Create Report' : 'Save and Close')}
837
+ </Button>
838
+ <Button
839
+ kind="secondary"
840
+ onClick={handleSave}
841
+ disabled={!isLinelistDraftValid(draft) || saving || compiling}
842
+ >
843
+ {saving ? 'Saving...' : 'Save'}
844
+ </Button>
845
+ {savedReportUuid && (
846
+ <Button
847
+ kind="primary"
848
+ onClick={handleCompile}
849
+ disabled={compiling || !isLinelistDraftReadyToCompile(draft)}
850
+ >
851
+ {compiling ? 'Compiling...' : 'Compile'}
852
+ </Button>
853
+ )}
854
+ </>
855
+ ) : (
856
+ <Button
857
+ kind="primary"
858
+ onClick={goToNextPanel}
859
+ disabled={!canGoNext || saving}
860
+ >
861
+ Next
862
+ </Button>
863
+ )}
864
+ </ButtonSet>
865
+ </Modal>
866
+ );
867
+ }
868
+
869
+ /**
870
+ * Basics Panel
871
+ */
872
+ interface BasicsPanelProps {
873
+ draft: LinelistReportDraft;
874
+ onChange: (updates: Partial<LinelistReportDraft>) => void;
875
+ error?: string;
876
+ categories: ReportCategoryDto[];
877
+ tables: string[];
878
+ }
879
+
880
+ function BasicsPanel({
881
+ draft,
882
+ onChange,
883
+ error,
884
+ categories,
885
+ tables,
886
+ }: BasicsPanelProps) {
887
+ const rowGrainOptions: Array<{ value: LinelistRowGrain; label: string; description: string }> = [
888
+ { value: 'PATIENT', label: 'Patient', description: 'One row per patient' },
889
+ { value: 'ENCOUNTER', label: 'Encounter', description: 'One row per encounter/visit' },
890
+ { value: 'OBSERVATION', label: 'Observation/Lab Result', description: 'One row per observation or test' },
891
+ { value: 'PROGRAM_ENROLLMENT', label: 'Program Enrollment', description: 'One row per program enrollment' },
892
+ { value: 'APPOINTMENT', label: 'Appointment', description: 'One row per appointment' },
893
+ { value: 'ORDER', label: 'Order', description: 'One row per order' },
894
+ ];
895
+
896
+ const templateOptions = [
897
+ { value: '', label: 'Blank Report' },
898
+ { value: 'appointment-list', label: 'Appointment List' },
899
+ { value: 'patient-demographic', label: 'Patient Demographic List' },
900
+ { value: 'missed-appointment', label: 'Missed Appointment List' },
901
+ { value: 'laboratory-result', label: 'Laboratory Result List' },
902
+ ];
903
+
904
+ return (
905
+ <Stack gap={4}>
906
+ <h2 className={styles.panelTitle}>Basic Information</h2>
907
+ <p className={styles.panelDescription}>
908
+ Enter the basic information for your linelist report. This information will be displayed in
909
+ the report library.
910
+ </p>
911
+
912
+ <TextInput
913
+ id="linelist-name"
914
+ labelText="Report Name *"
915
+ placeholder="e.g., Patients on ART"
916
+ value={draft.name}
917
+ onChange={(e) => onChange({ name: (e.target as HTMLInputElement).value })}
918
+ invalid={!!error}
919
+ invalidText={error}
920
+ />
921
+
922
+ <TextInput
923
+ id="linelist-code"
924
+ labelText="Code (optional)"
925
+ placeholder="e.g., ART_PATIENTS"
926
+ value={draft.code}
927
+ onChange={(e) => onChange({ code: (e.target as HTMLInputElement).value })}
928
+ />
929
+
930
+ <TextArea
931
+ id="linelist-description"
932
+ labelText="Description (optional)"
933
+ placeholder="Describe what this linelist report shows..."
934
+ value={draft.description}
935
+ onChange={(e) => onChange({ description: (e.target as HTMLTextAreaElement).value })}
936
+ rows={3}
937
+ />
938
+
939
+ <Select
940
+ id="linelist-category"
941
+ labelText="Category *"
942
+ value={draft.categoryUuid}
943
+ onChange={(e) => onChange({ categoryUuid: (e.target as HTMLSelectElement).value })}
944
+ invalid={!!draft.errors.categoryUuid}
945
+ invalidText={draft.errors.categoryUuid}
946
+ >
947
+ <SelectItem value="" text="Select a category" />
948
+ {categories.map((cat) => (
949
+ <SelectItem key={cat.uuid} value={cat.uuid} text={cat.name} />
950
+ ))}
951
+ </Select>
952
+
953
+ <Select
954
+ id="linelist-source"
955
+ labelText="Data Source *"
956
+ value={draft.dataSourceUuid}
957
+ onChange={(e) => onChange({ dataSourceUuid: (e.target as HTMLSelectElement).value })}
958
+ invalid={!!draft.errors.dataSourceUuid}
959
+ invalidText={draft.errors.dataSourceUuid}
960
+ helperText="Select the ETL data source table"
961
+ >
962
+ <SelectItem value="" text="Select a data source" />
963
+ {tables.map((table) => (
964
+ <SelectItem key={table} value={table} text={table} />
965
+ ))}
966
+ </Select>
967
+
968
+ <Select
969
+ id="linelist-row-grain"
970
+ labelText="One Row Represents *"
971
+ value={draft.rowGrain}
972
+ onChange={(e) => onChange({ rowGrain: (e.target as HTMLSelectElement).value as LinelistRowGrain })}
973
+ invalid={!!draft.errors.rowGrain}
974
+ invalidText={draft.errors.rowGrain}
975
+ helperText="What each row in the report represents"
976
+ >
977
+ {rowGrainOptions.map((option) => (
978
+ <SelectItem
979
+ key={option.value}
980
+ value={option.value}
981
+ text={option.label}
982
+ title={option.description}
983
+ />
984
+ ))}
985
+ </Select>
986
+
987
+ <Select
988
+ id="linelist-template"
989
+ labelText="Template (optional)"
990
+ value={draft.templateId}
991
+ onChange={(e) => onChange({ templateId: (e.target as HTMLSelectElement).value })}
992
+ helperText="Start with a pre-configured template"
993
+ >
994
+ {templateOptions.map((option) => (
995
+ <SelectItem key={option.value} value={option.value} text={option.label} />
996
+ ))}
997
+ </Select>
998
+
999
+ <TextInput
1000
+ id="linelist-dataset-name"
1001
+ labelText="Dataset Name"
1002
+ value={draft.dataSetName}
1003
+ onChange={(e) => onChange({ dataSetName: (e.target as HTMLInputElement).value })}
1004
+ helperText="The name for the dataset in the report output"
1005
+ />
1006
+ </Stack>
1007
+ );
1008
+ }
1009
+
1010
+ /**
1011
+ * Cohort Panel
1012
+ */
1013
+ function CohortPanel({
1014
+ draft,
1015
+ onChange,
1016
+ error,
1017
+ }: {
1018
+ draft: LinelistReportDraft;
1019
+ onChange: (updates: Partial<LinelistReportDraft>) => void;
1020
+ error?: string;
1021
+ }) {
1022
+ // Initialize visual filter if not present
1023
+ const visualFilter = draft.visualFilter || {
1024
+ rootGroup: {
1025
+ id: 'root',
1026
+ logicalOperator: 'AND',
1027
+ conditions: [],
1028
+ },
1029
+ useVisualBuilder: false,
1030
+ };
1031
+
1032
+ // Available fields for the visual filter builder
1033
+ // In a real implementation, this would come from the ETL schema
1034
+ const availableFields: Array<{ name: string; label: string; type: FilterFieldType }> = [
1035
+ { name: 'appointment_date', label: 'Appointment Date', type: 'DATE' },
1036
+ { name: 'gender', label: 'Gender', type: 'CODED' },
1037
+ { name: 'age', label: 'Age', type: 'NUMBER' },
1038
+ { name: 'voided', label: 'Voided', type: 'BOOLEAN' },
1039
+ { name: 'location_id', label: 'Facility', type: 'LOCATION' },
1040
+ { name: 'given_name', label: 'First Name', type: 'TEXT' },
1041
+ { name: 'family_name', label: 'Last Name', type: 'TEXT' },
1042
+ ];
1043
+
1044
+ const handleVisualFilterChange = (newVisualFilter: VisualFilterState) => {
1045
+ onChange({ visualFilter: newVisualFilter });
1046
+ };
1047
+
1048
+ return (
1049
+ <Stack gap={4}>
1050
+ <h2 className={styles.panelTitle}>Patient Selection</h2>
1051
+ <p className={styles.panelDescription}>
1052
+ Define the SQL query that selects patients for this linelist report. The query should return
1053
+ distinct patient IDs and use the :startDate and :endDate parameters.
1054
+ </p>
1055
+
1056
+ <CohortSQLEditor
1057
+ sql={draft.cohortSql}
1058
+ onChange={(sql) => onChange({ cohortSql: sql })}
1059
+ error={error}
1060
+ visualFilter={visualFilter}
1061
+ onVisualFilterChange={handleVisualFilterChange}
1062
+ availableFields={availableFields}
1063
+ />
1064
+ </Stack>
1065
+ );
1066
+ }
1067
+
1068
+ /**
1069
+ * Columns Panel
1070
+ */
1071
+ function ColumnsPanel({
1072
+ draft,
1073
+ onChange,
1074
+ error,
1075
+ }: {
1076
+ draft: LinelistReportDraft;
1077
+ onChange: (updates: Partial<LinelistReportDraft>) => void;
1078
+ error?: string;
1079
+ }) {
1080
+ const [useCategorySelector, setUseCategorySelector] = useState(true);
1081
+
1082
+ return (
1083
+ <Stack gap={4}>
1084
+ <div className={styles.panelHeaderRow}>
1085
+ <div>
1086
+ <h2 className={styles.panelTitle}>Report Columns</h2>
1087
+ <p className={styles.panelDescription}>
1088
+ Select and configure the columns to include in your linelist report. These columns will be
1089
+ displayed for each patient returned by your cohort query.
1090
+ </p>
1091
+ </div>
1092
+ <Toggle
1093
+ id="use-category-selector"
1094
+ labelText="Use category selector"
1095
+ labelA="Advanced"
1096
+ labelB="Simple"
1097
+ toggled={useCategorySelector}
1098
+ onToggle={setUseCategorySelector}
1099
+ className={styles.categoryToggle}
1100
+ />
1101
+ </div>
1102
+
1103
+ {useCategorySelector ? (
1104
+ <ColumnCategorySelector
1105
+ columns={draft.columns}
1106
+ onChange={(columns) => onChange({ columns })}
1107
+ error={error}
1108
+ />
1109
+ ) : (
1110
+ <ColumnSelector
1111
+ columns={draft.columns}
1112
+ onChange={(columns) => onChange({ columns })}
1113
+ error={error}
1114
+ />
1115
+ )}
1116
+ </Stack>
1117
+ );
1118
+ }
1119
+
1120
+ /**
1121
+ * Sort Panel
1122
+ */
1123
+ function SortPanel({
1124
+ draft,
1125
+ onChange,
1126
+ }: {
1127
+ draft: LinelistReportDraft;
1128
+ onChange: (updates: Partial<LinelistReportDraft>) => void;
1129
+ }) {
1130
+ return (
1131
+ <Stack gap={4}>
1132
+ <h2 className={styles.panelTitle}>Sort Configuration</h2>
1133
+ <p className={styles.panelDescription}>
1134
+ Define how rows should be ordered in your linelist report. Sort rules are applied in priority
1135
+ order from top to bottom.
1136
+ </p>
1137
+
1138
+ <SortConfiguration
1139
+ sortConfig={draft.sortConfig || []}
1140
+ columns={draft.columns || []}
1141
+ onChange={(sortConfig) => onChange({ sortConfig })}
1142
+ disabled={false}
1143
+ />
1144
+ </Stack>
1145
+ );
1146
+ }
1147
+
1148
+ /**
1149
+ * Parameters Panel
1150
+ */
1151
+ function ParametersPanel({
1152
+ draft,
1153
+ onChange,
1154
+ }: {
1155
+ draft: LinelistReportDraft;
1156
+ onChange: (updates: Partial<LinelistReportDraft>) => void;
1157
+ }) {
1158
+ return (
1159
+ <Stack gap={4}>
1160
+ <h2 className={styles.panelTitle}>Report Parameters</h2>
1161
+ <p className={styles.panelDescription}>
1162
+ Define runtime parameters for your linelist report. Parameters allow users to customize
1163
+ report output when running the report (e.g., date range, location, program).
1164
+ </p>
1165
+
1166
+ <ParameterEditor
1167
+ parameters={draft.parameters || []}
1168
+ onChange={(parameters) => onChange({ parameters })}
1169
+ disabled={false}
1170
+ />
1171
+ </Stack>
1172
+ );
1173
+ }
1174
+
1175
+ /**
1176
+ * Display & Export Panel
1177
+ */
1178
+ function DisplayExportPanel({
1179
+ draft,
1180
+ onChange,
1181
+ }: {
1182
+ draft: LinelistReportDraft;
1183
+ onChange: (updates: Partial<LinelistReportDraft>) => void;
1184
+ }) {
1185
+ return (
1186
+ <Stack gap={4}>
1187
+ <h2 className={styles.panelTitle}>Display & Export Settings</h2>
1188
+ <p className={styles.panelDescription}>
1189
+ Configure how your linelist report is displayed in the browser and exported.
1190
+ </p>
1191
+
1192
+ <DisplayExportSettings
1193
+ draft={draft}
1194
+ onChange={onChange}
1195
+ disabled={false}
1196
+ />
1197
+ </Stack>
1198
+ );
1199
+ }
1200
+
1201
+ /**
1202
+ * Preview Panel
1203
+ */
1204
+ interface PreviewPanelProps {
1205
+ draft: LinelistReportDraft;
1206
+ }
1207
+
1208
+ function PreviewPanel({ draft }: PreviewPanelProps) {
1209
+ const [sampleSize, setSampleSize] = useState<number>(25);
1210
+ const [showJson, setShowJson] = useState<boolean>(false);
1211
+
1212
+ const config = draftToConfig(draft);
1213
+ const warnings = generateLinelistWarnings(draft);
1214
+ const hasWarnings = Object.keys(warnings).length > 0;
1215
+ const columns = draft.columns;
1216
+
1217
+ // Generate mock data for preview (in real implementation, this would come from API)
1218
+ const mockRows = useMemo(() => {
1219
+ if (columns.length === 0) return [];
1220
+ const rows = [];
1221
+ for (let i = 0; i < Math.min(sampleSize, 5); i++) {
1222
+ const row: Record<string, string> = {};
1223
+ columns.forEach((col) => {
1224
+ row[col.name] = `[${col.name}]`; // Placeholder value
1225
+ });
1226
+ rows.push(row);
1227
+ }
1228
+ return rows;
1229
+ }, [columns, sampleSize]);
1230
+
1231
+ const sampleSizeOptions = [25, 50, 100, 500];
1232
+
1233
+ return (
1234
+ <Stack gap={4}>
1235
+ <h2 className={styles.panelTitle}>Report Preview</h2>
1236
+ <p className={styles.panelDescription}>
1237
+ Preview how your linelist report will look with the current configuration.
1238
+ </p>
1239
+
1240
+ {/* Preview toolbar */}
1241
+ <div className={styles.previewToolbar}>
1242
+ <ButtonSet>
1243
+ <Button
1244
+ kind="primary"
1245
+ size="sm"
1246
+ onClick={() => {/* TODO: Implement preview API call */}}
1247
+ disabled={columns.length === 0}
1248
+ >
1249
+ Run Preview
1250
+ </Button>
1251
+ <Button
1252
+ kind="ghost"
1253
+ size="sm"
1254
+ onClick={() => setShowJson(!showJson)}
1255
+ >
1256
+ {showJson ? 'Hide' : 'Show'} JSON
1257
+ </Button>
1258
+ </ButtonSet>
1259
+
1260
+ <Select
1261
+ id="sample-size"
1262
+ labelText="Sample Size"
1263
+ size="sm"
1264
+ value={String(sampleSize)}
1265
+ onChange={(e) => setSampleSize(Number((e.target as HTMLSelectElement).value))}
1266
+ >
1267
+ {sampleSizeOptions.map((size) => (
1268
+ <SelectItem key={size} value={String(size)} text={`${size} rows`} />
1269
+ ))}
1270
+ </Select>
1271
+ </div>
1272
+
1273
+ {/* Status area */}
1274
+ <div className={styles.previewStatus}>
1275
+ <span>Columns: {columns.length}</span>
1276
+ <span>Sample size: {sampleSize}</span>
1277
+ <span>Row grain: {draft.rowGrain?.replace('_', ' ') || '-'}</span>
1278
+ </div>
1279
+
1280
+ {/* Warnings */}
1281
+ {hasWarnings && (
1282
+ <InlineNotification
1283
+ kind="warning"
1284
+ title="Preview Warnings"
1285
+ subtitle={Object.values(warnings).join('; ')}
1286
+ lowContrast
1287
+ />
1288
+ )}
1289
+
1290
+ {/* Empty state */}
1291
+ {columns.length === 0 ? (
1292
+ <div className={styles.emptyState}>
1293
+ <p>No columns defined. Add columns in the Columns panel to see a preview.</p>
1294
+ </div>
1295
+ ) : (
1296
+ /* Data table preview */
1297
+ <DataTable
1298
+ rows={mockRows.map((row, idx) => ({ id: idx, ...row }))}
1299
+ headers={columns.map((col) => ({ key: col.name, header: col.name }))}
1300
+ >
1301
+ {({ headers, rows }) => (
1302
+ <TableContainer className={styles.previewTableContainer}>
1303
+ <Table>
1304
+ <TableHead>
1305
+ <TableRow>
1306
+ {headers.map((header) => (
1307
+ <TableHeader key={header.key}>{header.header}</TableHeader>
1308
+ ))}
1309
+ </TableRow>
1310
+ </TableHead>
1311
+ <TableBody>
1312
+ {rows.map((row) => (
1313
+ <TableRow key={row.id}>
1314
+ {columns.map((col) => (
1315
+ <TableCell key={col.name}>{(row as any)[col.name]}</TableCell>
1316
+ ))}
1317
+ </TableRow>
1318
+ ))}
1319
+ </TableBody>
1320
+ </Table>
1321
+ </TableContainer>
1322
+ )}
1323
+ </DataTable>
1324
+ )}
1325
+
1326
+ {/* JSON preview (collapsible) */}
1327
+ {showJson && (
1328
+ <div className={styles.sqlPreview}>
1329
+ <h4>Generated Configuration</h4>
1330
+ <pre className={styles.jsonPreview}>{JSON.stringify(enhanceConfigForPreview(config, draft), null, 2)}</pre>
1331
+ </div>
1332
+ )}
1333
+ </Stack>
1334
+ );
1335
+ }
1336
+
1337
+ /**
1338
+ * Review Panel
1339
+ */
1340
+ interface ReviewPanelProps {
1341
+ draft: LinelistReportDraft;
1342
+ onEdit: (panel: LinelistBuilderPanel) => void;
1343
+ isValid: boolean;
1344
+ categories: ReportCategoryDto[];
1345
+ }
1346
+
1347
+ function ReviewPanel({
1348
+ draft,
1349
+ onEdit,
1350
+ isValid,
1351
+ categories,
1352
+ }: ReviewPanelProps) {
1353
+ const warnings = generateLinelistWarnings(draft);
1354
+ const isReadyToCompile = isLinelistDraftReadyToCompile(draft);
1355
+
1356
+ // Helper to get display names
1357
+ const getCategoryName = () => categories.find((c) => c.uuid === draft.categoryUuid)?.name || '-';
1358
+ const getDataSourceName = () => draft.dataSourceUuid || '-';
1359
+ const getRowGrainName = () => draft.rowGrain?.replace('_', ' ') || '-';
1360
+
1361
+ const hasWarnings = Object.keys(warnings).length > 0;
1362
+
1363
+ return (
1364
+ <Stack gap={4}>
1365
+ <h2 className={styles.panelTitle}>Review & Save</h2>
1366
+ <p className={styles.panelDescription}>
1367
+ Review your linelist report configuration and save when ready.
1368
+ </p>
1369
+
1370
+ <div className={styles.reviewGrid}>
1371
+ <ReviewItem label="Report Name" value={draft.name} onEdit={() => onEdit('basics')} />
1372
+ <ReviewItem label="Code" value={draft.code || '-'} onEdit={() => onEdit('basics')} />
1373
+ <ReviewItem
1374
+ label="Description"
1375
+ value={draft.description || '-'}
1376
+ onEdit={() => onEdit('basics')}
1377
+ />
1378
+ <ReviewItem label="Category" value={getCategoryName()} onEdit={() => onEdit('basics')} />
1379
+ <ReviewItem label="Data Source" value={getDataSourceName()} onEdit={() => onEdit('basics')} />
1380
+ <ReviewItem label="Row Grain" value={getRowGrainName()} onEdit={() => onEdit('basics')} />
1381
+ <ReviewItem
1382
+ label="Dataset Name"
1383
+ value={draft.dataSetName}
1384
+ onEdit={() => onEdit('basics')}
1385
+ />
1386
+ <ReviewItem
1387
+ label="Columns"
1388
+ value={`${draft.columns.length} column(s)`}
1389
+ onEdit={() => onEdit('columns')}
1390
+ />
1391
+ <ReviewItem label="Row Limit" value={draft.limit?.toString() || '-'} onEdit={() => onEdit('basics')} />
1392
+ </div>
1393
+
1394
+ {!isValid && (
1395
+ <InlineNotification
1396
+ kind="error"
1397
+ title="Validation Errors"
1398
+ subtitle="Please fix all validation errors before saving."
1399
+ lowContrast
1400
+ />
1401
+ )}
1402
+
1403
+ {isValid && !isReadyToCompile && (
1404
+ <InlineNotification
1405
+ kind="warning"
1406
+ title="Not Ready to Compile"
1407
+ subtitle="Report has validation issues that must be resolved before compiling."
1408
+ lowContrast
1409
+ />
1410
+ )}
1411
+
1412
+ {isValid && isReadyToCompile && (
1413
+ <InlineNotification
1414
+ kind="success"
1415
+ title="Ready to Compile"
1416
+ subtitle="Your linelist report configuration is complete and valid."
1417
+ lowContrast
1418
+ />
1419
+ )}
1420
+
1421
+ {hasWarnings && (
1422
+ <InlineNotification
1423
+ kind="warning"
1424
+ title="Warnings"
1425
+ subtitle={Object.values(warnings).join('; ')}
1426
+ lowContrast
1427
+ />
1428
+ )}
1429
+ </Stack>
1430
+ );
1431
+ }
1432
+
1433
+ /**
1434
+ * Review item component
1435
+ */
1436
+ function ReviewItem({
1437
+ label,
1438
+ value,
1439
+ onEdit,
1440
+ }: {
1441
+ label: string;
1442
+ value: string;
1443
+ onEdit: () => void;
1444
+ }) {
1445
+ return (
1446
+ <div className={styles.reviewItem}>
1447
+ <span className={styles.reviewLabel}>{label}:</span>
1448
+ <span className={styles.reviewValue}>{value}</span>
1449
+ <Button kind="ghost" size="sm" onClick={onEdit}>
1450
+ Edit
1451
+ </Button>
1452
+ </div>
1453
+ );
1454
+ }