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

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 (551) 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/162.js +1 -0
  9. package/dist/162.js.map +1 -0
  10. package/dist/184.js +2 -0
  11. package/dist/184.js.LICENSE.txt +14 -0
  12. package/dist/184.js.map +1 -0
  13. package/dist/242.js +2 -0
  14. package/dist/242.js.LICENSE.txt +21 -0
  15. package/dist/242.js.map +1 -0
  16. package/dist/253.js +1 -0
  17. package/dist/253.js.map +1 -0
  18. package/dist/300.js +1 -0
  19. package/dist/311.js +1 -0
  20. package/dist/311.js.map +1 -0
  21. package/dist/469.js +2 -0
  22. package/dist/469.js.LICENSE.txt +45 -0
  23. package/dist/469.js.map +1 -0
  24. package/dist/478.js +2 -0
  25. package/dist/478.js.LICENSE.txt +9 -0
  26. package/dist/478.js.map +1 -0
  27. package/dist/539.js +1 -0
  28. package/dist/539.js.map +1 -0
  29. package/dist/540.js +2 -0
  30. package/dist/540.js.LICENSE.txt +9 -0
  31. package/dist/540.js.map +1 -0
  32. package/dist/669.js +1 -0
  33. package/dist/669.js.map +1 -0
  34. package/dist/710.js +1 -0
  35. package/dist/710.js.map +1 -0
  36. package/dist/729.js +1 -0
  37. package/dist/729.js.map +1 -0
  38. package/dist/838.js +2 -0
  39. package/dist/838.js.LICENSE.txt +9 -0
  40. package/dist/838.js.map +1 -0
  41. package/dist/917.js +1 -0
  42. package/dist/917.js.map +1 -0
  43. package/dist/961.js +2 -0
  44. package/dist/961.js.LICENSE.txt +19 -0
  45. package/dist/961.js.map +1 -0
  46. package/dist/main.js +1 -0
  47. package/dist/main.js.map +1 -0
  48. package/dist/openmrs-esm-report-builder.js +1 -0
  49. package/dist/openmrs-esm-report-builder.js.buildmanifest.json +530 -0
  50. package/dist/openmrs-esm-report-builder.js.map +1 -0
  51. package/dist/routes.json +1 -0
  52. package/e2e/core/env.ts +5 -0
  53. package/e2e/core/global-setup.ts +34 -0
  54. package/e2e/core/test.ts +21 -0
  55. package/e2e/pages/app-shell.page.ts +34 -0
  56. package/e2e/pages/etl-sources.page.ts +35 -0
  57. package/e2e/pages/report-sections.page.ts +20 -0
  58. package/e2e/pages/section-preview.modal.ts +29 -0
  59. package/e2e/specs/etl-sources.spec.ts +51 -0
  60. package/e2e/specs/navigation.spec.ts +13 -0
  61. package/e2e/specs/section-preview-validation.spec.ts +38 -0
  62. package/e2e/specs/section-preview.spec.ts +94 -0
  63. package/e2e/support/github/Dockerfile +22 -0
  64. package/e2e/support/github/Dockerfile.dockerignore +13 -0
  65. package/e2e/support/github/docker-compose.yml +31 -0
  66. package/e2e/support/github/run-e2e-docker-env.sh +131 -0
  67. package/e2e/support/github/spa-assemble-config.json +9 -0
  68. package/e2e/tsconfig.json +11 -0
  69. package/example.env +5 -0
  70. package/jest.config.js +28 -0
  71. package/linelist_report_builder_design_spec.md +816 -0
  72. package/package.json +111 -0
  73. package/playwright.config.ts +28 -0
  74. package/routes.json +22 -0
  75. package/src/__debug__/all-designs.test.tsx +30 -0
  76. package/src/admin-card-link.extension.module.scss +55 -0
  77. package/src/admin-card-link.extension.tsx +24 -0
  78. package/src/components/admin/admin-page.component.tsx +75 -0
  79. package/src/components/admin/age-categories-page.component.tsx +109 -0
  80. package/src/components/admin/age-groups-page.component.tsx +146 -0
  81. package/src/components/admin/etl-browser/columns-tab.component.tsx +80 -0
  82. package/src/components/admin/etl-browser/etl-browser-page.component.tsx +309 -0
  83. package/src/components/admin/etl-browser/etl-browser.component.scss +424 -0
  84. package/src/components/admin/etl-browser/metadata-tab.component.tsx +65 -0
  85. package/src/components/admin/etl-browser/preview-grid.component.tsx +189 -0
  86. package/src/components/admin/etl-browser/relationships-tab.component.tsx +21 -0
  87. package/src/components/admin/etl-browser/sources-tree.component.tsx +118 -0
  88. package/src/components/admin/etl-browser/table-detail.component.tsx +124 -0
  89. package/src/components/admin/etl-browser/table-info-strip.component.tsx +51 -0
  90. package/src/components/admin/etl-browser/table-list.component.tsx +136 -0
  91. package/src/components/admin/etl-browser/use-table-preview.ts +47 -0
  92. package/src/components/admin/etl-sources-page.component.tsx +355 -0
  93. package/src/components/admin/etl-tasks-page.component.scss +370 -0
  94. package/src/components/admin/etl-tasks-page.component.tsx +768 -0
  95. package/src/components/admin/report-categories-page.component.tsx +267 -0
  96. package/src/components/admin/report-library-page.component.tsx +551 -0
  97. package/src/components/ai-support/ai-assist-button.component.tsx +30 -0
  98. package/src/components/ai-support/ai-assist-modal.component.tsx +81 -0
  99. package/src/components/ai-support/ai.service.ts +21 -0
  100. package/src/components/ai-support/ai.types.ts +12 -0
  101. package/src/components/app-shell/report-builder-shell.component.tsx +242 -0
  102. package/src/components/app-shell/report-builder-shell.scss +18 -0
  103. package/src/components/dashboards/dashboard-config.schema.ts +97 -0
  104. package/src/components/dashboards/dashboard-form-modal.component.tsx +150 -0
  105. package/src/components/dashboards/dashboards-admin.scss +62 -0
  106. package/src/components/dashboards/dashboards-page.component.tsx +153 -0
  107. package/src/components/dashboards/dashboards-table.component.tsx +130 -0
  108. package/src/components/dashboards/sections/dashboard-autoinclude.section.tsx +75 -0
  109. package/src/components/dashboards/sections/dashboard-basics.section.tsx +80 -0
  110. package/src/components/dashboards/sections/dashboard-json.section.tsx +79 -0
  111. package/src/components/dashboards/sections/dashboard-sections-editor.section.tsx +130 -0
  112. package/src/components/dashboards/sections/dashboard-widgets-editor.section.tsx +184 -0
  113. package/src/components/data-themes/data-theme-form.component.tsx +59 -0
  114. package/src/components/data-themes/data-theme-modal.component.tsx +349 -0
  115. package/src/components/data-themes/data-themes-page.component.tsx +139 -0
  116. package/src/components/data-themes/data-themes-table.component.tsx +98 -0
  117. package/src/components/data-themes/icon-registry.ts +35 -0
  118. package/src/components/data-themes/sections/data-theme-basics.section.tsx +63 -0
  119. package/src/components/data-themes/sections/data-theme-condition-columns.section.tsx +397 -0
  120. package/src/components/data-themes/sections/data-theme-fields-editor.section.tsx +125 -0
  121. package/src/components/data-themes/sections/data-theme-metadata.section.tsx +245 -0
  122. package/src/components/data-themes/sections/data-theme-preview.section.tsx +115 -0
  123. package/src/components/data-themes/sections/data-theme-source.section.tsx +184 -0
  124. package/src/components/data-visualizer/components/cqi-components/cqi-data-table.component.tsx +214 -0
  125. package/src/components/data-visualizer/components/cqi-components/cqi-elements.ts +203 -0
  126. package/src/components/data-visualizer/components/cqi-components/detail.component.tsx +110 -0
  127. package/src/components/data-visualizer/components/cqi-components/detail.scss +22 -0
  128. package/src/components/data-visualizer/components/data-table/data-table.component.tsx +208 -0
  129. package/src/components/data-visualizer/components/data-table/data-tables.scss +206 -0
  130. package/src/components/data-visualizer/components/empty-state/empty-state-illustration.component.tsx +53 -0
  131. package/src/components/data-visualizer/components/header/header.component.tsx +43 -0
  132. package/src/components/data-visualizer/components/header/header.scss +71 -0
  133. package/src/components/data-visualizer/components/model/confirm.component.tsx +40 -0
  134. package/src/components/data-visualizer/components/panel/panel.component.tsx +20 -0
  135. package/src/components/data-visualizer/components/panel/panel.scss +25 -0
  136. package/src/components/data-visualizer/components/popover/modifier-panel.tsx +53 -0
  137. package/src/components/data-visualizer/constants.ts +445 -0
  138. package/src/components/data-visualizer/data-visualizer-illustration.component.tsx +31 -0
  139. package/src/components/data-visualizer/data-visualizer.component.tsx +1935 -0
  140. package/src/components/data-visualizer/data-visualizer.resource.ts +462 -0
  141. package/src/components/data-visualizer/data-visualizer.scss +449 -0
  142. package/src/components/data-visualizer/declarations.d.ts +131 -0
  143. package/src/components/data-visualizer/parameter-inputs/boolean-parameter-input.component.tsx +34 -0
  144. package/src/components/data-visualizer/parameter-inputs/concept-parameter-input.component.tsx +81 -0
  145. package/src/components/data-visualizer/parameter-inputs/date-parameter-input.component.tsx +110 -0
  146. package/src/components/data-visualizer/parameter-inputs/date-range-input.component.scss +47 -0
  147. package/src/components/data-visualizer/parameter-inputs/date-range-input.component.tsx +228 -0
  148. package/src/components/data-visualizer/parameter-inputs/identifier-type-parameter-input.component.tsx +69 -0
  149. package/src/components/data-visualizer/parameter-inputs/list-parameter-input.component.tsx +73 -0
  150. package/src/components/data-visualizer/parameter-inputs/location-parameter-input.component.tsx +81 -0
  151. package/src/components/data-visualizer/parameter-inputs/number-parameter-input.component.tsx +40 -0
  152. package/src/components/data-visualizer/parameter-inputs/person-attribute-parameter-input.component.tsx +71 -0
  153. package/src/components/data-visualizer/parameter-inputs/text-parameter-input.component.tsx +52 -0
  154. package/src/components/data-visualizer/report-parameter-modal.component.tsx +383 -0
  155. package/src/components/data-visualizer/types.ts +206 -0
  156. package/src/components/etl-monitor/builder/builder-state-machine.ts +381 -0
  157. package/src/components/etl-monitor/builder/design-registry.tsx +139 -0
  158. package/src/components/etl-monitor/builder/etl-monitor-builder-context.tsx +702 -0
  159. package/src/components/etl-monitor/builder/etl-monitor-builder-modal.component.tsx +461 -0
  160. package/src/components/etl-monitor/builder/etl-monitor-builder-modal.scss +256 -0
  161. package/src/components/etl-monitor/builder/etl-monitor-builder-page.component.tsx +387 -0
  162. package/src/components/etl-monitor/builder/etl-monitor-builder-page.scss +976 -0
  163. package/src/components/etl-monitor/builder/generated-config-panel.component.tsx +168 -0
  164. package/src/components/etl-monitor/builder/generated-config-panel.scss +198 -0
  165. package/src/components/etl-monitor/builder/index.ts +5 -0
  166. package/src/components/etl-monitor/builder/monitor-preview.tsx +124 -0
  167. package/src/components/etl-monitor/builder/steps/data-source-step.component.tsx +667 -0
  168. package/src/components/etl-monitor/builder/steps/data-source-step.scss +284 -0
  169. package/src/components/etl-monitor/builder/steps/design-samples.ts +278 -0
  170. package/src/components/etl-monitor/builder/steps/design-step.component.tsx +299 -0
  171. package/src/components/etl-monitor/builder/steps/design-step.scss +479 -0
  172. package/src/components/etl-monitor/builder/steps/fields-step.component.tsx +859 -0
  173. package/src/components/etl-monitor/builder/steps/fields-step.scss +459 -0
  174. package/src/components/etl-monitor/builder/steps/general-step.component.tsx +288 -0
  175. package/src/components/etl-monitor/builder/steps/general-step.scss +103 -0
  176. package/src/components/etl-monitor/builder/steps/index.ts +5 -0
  177. package/src/components/etl-monitor/builder/steps/preview-step.component.tsx +245 -0
  178. package/src/components/etl-monitor/builder/steps/preview-step.scss +236 -0
  179. package/src/components/etl-monitor/etl-monitor-card-shell.component.tsx +154 -0
  180. package/src/components/etl-monitor/etl-monitor-card-shell.scss +103 -0
  181. package/src/components/etl-monitor/etl-monitor-dashboard.component.tsx +227 -0
  182. package/src/components/etl-monitor/etl-monitor-dashboard.scss +148 -0
  183. package/src/components/etl-monitor/etl-monitor-status-card.component.tsx +192 -0
  184. package/src/components/etl-monitor/etl-monitors-page.component.tsx +621 -0
  185. package/src/components/etl-monitor/renderers/DetailsRenderer.tsx +2 -0
  186. package/src/components/etl-monitor/renderers/ErrorLogRenderer.tsx +2 -0
  187. package/src/components/etl-monitor/renderers/EtlMonitorWidget.tsx +97 -0
  188. package/src/components/etl-monitor/renderers/MetricsGridRenderer.tsx +2 -0
  189. package/src/components/etl-monitor/renderers/MonitorRenderer.tsx +175 -0
  190. package/src/components/etl-monitor/renderers/ProgressRenderer.tsx +2 -0
  191. package/src/components/etl-monitor/renderers/StatusCardRenderer.tsx +2 -0
  192. package/src/components/etl-monitor/renderers/SummaryCardRenderer.tsx +2 -0
  193. package/src/components/etl-monitor/renderers/TableRenderer.tsx +2 -0
  194. package/src/components/etl-monitor/renderers/component-icons.tsx +33 -0
  195. package/src/components/etl-monitor/renderers/components/details-renderer.tsx +195 -0
  196. package/src/components/etl-monitor/renderers/components/error-log-renderer.tsx +104 -0
  197. package/src/components/etl-monitor/renderers/components/index.ts +6 -0
  198. package/src/components/etl-monitor/renderers/components/log-renderer.tsx +86 -0
  199. package/src/components/etl-monitor/renderers/components/metrics-grid-renderer.tsx +133 -0
  200. package/src/components/etl-monitor/renderers/components/progress-renderer.tsx +188 -0
  201. package/src/components/etl-monitor/renderers/components/status-card-renderer.tsx +247 -0
  202. package/src/components/etl-monitor/renderers/components/summary-card-renderer.tsx +160 -0
  203. package/src/components/etl-monitor/renderers/components/table-renderer.tsx +175 -0
  204. package/src/components/etl-monitor/renderers/components/time-series-renderer.tsx +310 -0
  205. package/src/components/etl-monitor/renderers/data-transformer.ts +361 -0
  206. package/src/components/etl-monitor/renderers/index.ts +11 -0
  207. package/src/components/etl-monitor/renderers/legacy-monitor-renderer.tsx +69 -0
  208. package/src/components/etl-monitor/renderers/monitor-renderer.tsx +6 -0
  209. package/src/components/etl-monitor/renderers/monitor-renderers.scss +1621 -0
  210. package/src/components/etl-monitor/renderers/monitor-widget-card.component.tsx +54 -0
  211. package/src/components/etl-monitor/renderers/monitor-widget-card.scss +16 -0
  212. package/src/components/etl-monitor/renderers/v2-monitor-renderer.tsx +227 -0
  213. package/src/components/import-export/artifact-groups.ts +153 -0
  214. package/src/components/import-export/export-report-modal.component.tsx +265 -0
  215. package/src/components/import-export/import-export-page.component.tsx +610 -0
  216. package/src/components/import-export/import-export.styles.scss +825 -0
  217. package/src/components/import-export/import-package-modal.component.tsx +401 -0
  218. package/src/components/import-export/index.ts +3 -0
  219. package/src/components/indicators/README.md +24 -0
  220. package/src/components/indicators/base-indicator-basic-fields.component.tsx +18 -0
  221. package/src/components/indicators/base-indicator-basics.section.tsx +54 -0
  222. package/src/components/indicators/base-indicator-countby.section.tsx +25 -0
  223. package/src/components/indicators/base-indicator-criteria.section.tsx +83 -0
  224. package/src/components/indicators/base-indicator-details-form.component.tsx +80 -0
  225. package/src/components/indicators/base-indicator-theme-unit-fields.component.tsx +39 -0
  226. package/src/components/indicators/create-base-indicator-modal.component.tsx +623 -0
  227. package/src/components/indicators/create-composite-base-indicator-modal.component.tsx +295 -0
  228. package/src/components/indicators/create-custom-indicator-modal.component.tsx +548 -0
  229. package/src/components/indicators/create-final-indicator-modal.component.tsx +285 -0
  230. package/src/components/indicators/custom-condition-input.component.tsx +310 -0
  231. package/src/components/indicators/diagnosis-filters-form.component.tsx +101 -0
  232. package/src/components/indicators/disaggregation-panel.component.tsx +93 -0
  233. package/src/components/indicators/handler/concept-search-multiselect.component.tsx +153 -0
  234. package/src/components/indicators/handler/question-answer-concept-search.component.tsx +336 -0
  235. package/src/components/indicators/indicator-run-preview-modal.component.tsx +391 -0
  236. package/src/components/indicators/indicator-search-select.component.tsx +297 -0
  237. package/src/components/indicators/indicator-status-tag.component.tsx +9 -0
  238. package/src/components/indicators/indicators-page.component.tsx +673 -0
  239. package/src/components/indicators/indicators-page.scss +121 -0
  240. package/src/components/indicators/indicators-table.component.tsx +188 -0
  241. package/src/components/indicators/preview-table.component.tsx +31 -0
  242. package/src/components/indicators/sections/composite-indicator-basics.section.tsx +36 -0
  243. package/src/components/indicators/sections/composite-indicator-picker.section.tsx +116 -0
  244. package/src/components/indicators/sections/composite-indicator-sql-preview.section.tsx +31 -0
  245. package/src/components/indicators/sections/custom-indicator-basics.section.tsx +65 -0
  246. package/src/components/indicators/sections/custom-indicator-disaggregation.section.tsx +93 -0
  247. package/src/components/indicators/sections/custom-indicator-filters.section.tsx +203 -0
  248. package/src/components/indicators/sections/custom-indicator-population.section.tsx +107 -0
  249. package/src/components/indicators/sections/custom-indicator-sql-preview.section.tsx +108 -0
  250. package/src/components/indicators/sections/final-indicator-basics.section.tsx +46 -0
  251. package/src/components/indicators/sections/final-indicator-disaggregation.section.tsx +47 -0
  252. package/src/components/indicators/sections/final-indicator-picker.section.tsx +60 -0
  253. package/src/components/indicators/sections/final-indicator-preview.section.tsx +20 -0
  254. package/src/components/indicators/sections/final-indicator-results-preview.section.tsx +65 -0
  255. package/src/components/indicators/sections/indicator-basics.section.tsx +40 -0
  256. package/src/components/indicators/sections/indicator-conditions.section.tsx +279 -0
  257. package/src/components/indicators/sections/indicator-sql-preview.section.tsx +63 -0
  258. package/src/components/indicators/sections/indicator-theme.section.tsx +48 -0
  259. package/src/components/indicators/sql-preview.component.tsx +19 -0
  260. package/src/components/indicators/tag-input.component.tsx +177 -0
  261. package/src/components/indicators/types/base-indicator-form.types.ts +17 -0
  262. package/src/components/indicators/types/base-indicator-theme.constants.ts +8 -0
  263. package/src/components/indicators/types/composite-indicator.types.ts +27 -0
  264. package/src/components/indicators/types/condition-ui.types.ts +11 -0
  265. package/src/components/indicators/types/custom-indicator.types.ts +161 -0
  266. package/src/components/indicators/types/data-theme-config.types.ts +88 -0
  267. package/src/components/indicators/types/indicator-types.ts +57 -0
  268. package/src/components/indicators/types/sql-builders.ts +43 -0
  269. package/src/components/indicators/utils/composite-indicator-sql.utils.test.ts +143 -0
  270. package/src/components/indicators/utils/composite-indicator-sql.utils.ts +426 -0
  271. package/src/components/indicators/utils/concept-preload.utils.ts +21 -0
  272. package/src/components/indicators/utils/custom-indicator-interpreter.test.ts +493 -0
  273. package/src/components/indicators/utils/custom-indicator-interpreter.ts +702 -0
  274. package/src/components/indicators/utils/final-indicator-sql.utils.ts +190 -0
  275. package/src/components/indicators/utils/indicator-conditions-hydration.utils.ts +254 -0
  276. package/src/components/indicators/utils/indicator-sql-resolution.utils.ts +204 -0
  277. package/src/components/indicators/utils/indicator-sql.utils.ts +721 -0
  278. package/src/components/indicators/utils/population-sql.compiler.test.ts +999 -0
  279. package/src/components/indicators/utils/population-sql.compiler.ts +858 -0
  280. package/src/components/landing/landing-action-item.component.tsx +50 -0
  281. package/src/components/landing/landing-action-list.component.tsx +12 -0
  282. package/src/components/landing/landing-activity-list.component.tsx +47 -0
  283. package/src/components/landing/landing-footer-grid.component.tsx +45 -0
  284. package/src/components/landing/landing-hero.component.tsx +18 -0
  285. package/src/components/landing/landing-section-card.component.tsx +33 -0
  286. package/src/components/landing/report-builder-landing-page.component.tsx +186 -0
  287. package/src/components/landing/report-builder-landing-page.scss +131 -0
  288. package/src/components/legacy-reports/legacy-report-detail-page.component.tsx +300 -0
  289. package/src/components/legacy-reports/legacy-report-editor-page.component.tsx +324 -0
  290. package/src/components/legacy-reports/legacy-report-upload-modal.component.tsx +307 -0
  291. package/src/components/legacy-reports/legacy-reports-page.component.tsx +206 -0
  292. package/src/components/legacy-reports/legacy-reports-table.component.tsx +154 -0
  293. package/src/components/legacy-reports/tabs/legacy-report-datasets-tab.component.tsx +344 -0
  294. package/src/components/legacy-reports/tabs/legacy-report-dimensions-tab.component.tsx +590 -0
  295. package/src/components/legacy-reports/tabs/legacy-report-indicators-tab.component.tsx +490 -0
  296. package/src/components/legacy-reports/tabs/legacy-report-json-preview-tab.component.tsx +272 -0
  297. package/src/components/legacy-reports/tabs/legacy-report-overview-tab.component.tsx +218 -0
  298. package/src/components/legacy-reports/tabs/legacy-report-parameters-tab.component.tsx +279 -0
  299. package/src/components/linelist/definition/indicator-to-sql.utils.ts +281 -0
  300. package/src/components/linelist/definition/linelist-definition-editor.component.tsx +1676 -0
  301. package/src/components/linelist/definition/linelist-definition-editor.scss +461 -0
  302. package/src/components/linelist/definition/linelist-definition.types.ts +108 -0
  303. package/src/components/linelist/definition/panels/column-properties-panel.component.tsx +328 -0
  304. package/src/components/linelist/definition/panels/column-selector-panel.component.tsx +1082 -0
  305. package/src/components/linelist/definition/panels/column-selector-panel.scss +100 -0
  306. package/src/components/linelist/definition/panels/custom-sql-column-modal.component.tsx +248 -0
  307. package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.component.tsx +300 -0
  308. package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.scss +21 -0
  309. package/src/components/linelist/definition/panels/multi-datasource-selector.component.tsx +332 -0
  310. package/src/components/linelist/definition/panels/multi-datasource-selector.scss +161 -0
  311. package/src/components/linelist/definition/panels/observation-column-modal.component.tsx +450 -0
  312. package/src/components/linelist/definition/panels/observation-column-modal.scss +29 -0
  313. package/src/components/linelist/definition/panels/parameter-panel.component.tsx +659 -0
  314. package/src/components/linelist/definition/panels/parameter-panel.scss +64 -0
  315. package/src/components/linelist/definition/panels/population-source-selector.component.tsx +407 -0
  316. package/src/components/linelist/definition/panels/population-source-selector.scss +187 -0
  317. package/src/components/linelist/design/panels/column-category-selector.component.tsx +433 -0
  318. package/src/components/linelist/design/panels/column-category-selector.scss +329 -0
  319. package/src/components/linelist/design/panels/column-debug-panel.component.tsx +197 -0
  320. package/src/components/linelist/design/panels/column-debug-panel.scss +141 -0
  321. package/src/components/linelist/design/panels/data-catalogue.component.tsx +1062 -0
  322. package/src/components/linelist/design/panels/data-catalogue.scss +263 -0
  323. package/src/components/linelist/design/panels/display-settings-panel.component.tsx +405 -0
  324. package/src/components/linelist/design/panels/display-settings-panel.scss +78 -0
  325. package/src/components/linelist/design/panels/filtermap-editor.component.tsx +434 -0
  326. package/src/components/linelist/design/panels/filtermap-editor.scss +176 -0
  327. package/src/components/linelist/design/panels/json-preview.component.tsx +114 -0
  328. package/src/components/linelist/design/panels/json-preview.scss +85 -0
  329. package/src/components/linelist/design/panels/query-config-panel.component.tsx +1137 -0
  330. package/src/components/linelist/design/panels/query-config-panel.scss +342 -0
  331. package/src/components/linelist/design/panels/sort-config-panel.component.tsx +330 -0
  332. package/src/components/linelist/design/panels/sort-config-panel.scss +77 -0
  333. package/src/components/linelist/design/panels/sql-builder-panel.component.tsx +475 -0
  334. package/src/components/linelist/design/panels/sql-builder-panel.scss +226 -0
  335. package/src/components/linelist/design/panels/transformation-panel.component.tsx +687 -0
  336. package/src/components/linelist/design/panels/transformation-panel.scss +81 -0
  337. package/src/components/linelist/design/panels/visual-filter-panel.component.tsx +595 -0
  338. package/src/components/linelist/design/panels/visual-filter-panel.scss +76 -0
  339. package/src/components/linelist/linelist-dashboard.page.component.tsx +756 -0
  340. package/src/components/linelist/linelist-dashboard.page.scss +240 -0
  341. package/src/components/linelist/linelist-editor.page.component.tsx +134 -0
  342. package/src/components/linelist/linelist-editor.page.scss +28 -0
  343. package/src/components/linelist/linelist-run-report.page.component.tsx +715 -0
  344. package/src/components/linelist/linelist-run-report.page.scss +136 -0
  345. package/src/components/linelist/modals/linelist-report-builder-modal.component.tsx +1454 -0
  346. package/src/components/linelist/modals/linelist-report-builder-modal.scss +164 -0
  347. package/src/components/linelist/modals/preview-parameter-modal.component.tsx +361 -0
  348. package/src/components/report/dashboard/reports-table.component.tsx +186 -0
  349. package/src/components/report/definition/panels/properties-panel.component.tsx +38 -0
  350. package/src/components/report/definition/panels/report-structure-panel.component.tsx +61 -0
  351. package/src/components/report/definition/panels/selection-panel.component.tsx +58 -0
  352. package/src/components/report/definition/report-definition-editor.component.tsx +99 -0
  353. package/src/components/report/definition/report-definition.types.ts +66 -0
  354. package/src/components/report/design/panels/IndicatorPropertiesPanel.scss +33 -0
  355. package/src/components/report/design/panels/IndicatorPropertiesPanel.tsx +389 -0
  356. package/src/components/report/design/panels/JsonPreviewPanel.scss +32 -0
  357. package/src/components/report/design/panels/JsonPreviewPanel.tsx +104 -0
  358. package/src/components/report/design/panels/mapping-preview-panel.component.tsx +82 -0
  359. package/src/components/report/design/panels/mapping-preview-panel.scss +55 -0
  360. package/src/components/report/design/panels/template-structure-panel.component.tsx +296 -0
  361. package/src/components/report/design/panels/template-structure-panel.scss +81 -0
  362. package/src/components/report/design/report-design-editor.component.tsx +1170 -0
  363. package/src/components/report/design/report-design.types.ts +48 -0
  364. package/src/components/report/design/report-design.utils.ts +86 -0
  365. package/src/components/report/panels/report-detail.component.tsx +71 -0
  366. package/src/components/report/panels/report-panel-shell.component.tsx +60 -0
  367. package/src/components/report/report-dashboard.page.component.tsx +554 -0
  368. package/src/components/report/report-editor.page.component.tsx +590 -0
  369. package/src/components/report/report.routes.tsx +32 -0
  370. package/src/components/report-sections/create-report-section-modal.component.tsx +388 -0
  371. package/src/components/report-sections/hooks/useAgeCategories.ts +25 -0
  372. package/src/components/report-sections/hooks/useSectionEditorState.ts +449 -0
  373. package/src/components/report-sections/panels/Dhis2MappingPanel.tsx +157 -0
  374. package/src/components/report-sections/panels/DisaggregationPanel.tsx +97 -0
  375. package/src/components/report-sections/panels/IndicatorPickerPanel.tsx +201 -0
  376. package/src/components/report-sections/panels/SectionBasicsPanel.tsx +27 -0
  377. package/src/components/report-sections/report-section-preview-modal.component.tsx +548 -0
  378. package/src/components/report-sections/report-sections-page.component.tsx +325 -0
  379. package/src/components/report-sections/section-disaggregation.utils.ts +637 -0
  380. package/src/components/report-sections/section-types.ts +55 -0
  381. package/src/components/report-sections/section-utils.ts +50 -0
  382. package/src/components/report-sections/sections-page.scss +79 -0
  383. package/src/components/run-reports/run-reports-page.component.tsx +8 -0
  384. package/src/components/security/access-denied-page.component.tsx +69 -0
  385. package/src/components/security/access-denied-page.scss +39 -0
  386. package/src/components/security/privilege-route.component.test.tsx +79 -0
  387. package/src/components/security/privilege-route.component.tsx +39 -0
  388. package/src/components/shared/compile-setup-modal.component.tsx +182 -0
  389. package/src/components/shared/concept-selector.component.tsx +245 -0
  390. package/src/components/shared/concept-selector.scss +121 -0
  391. package/src/components/shared/header/builder-header-actions.component.tsx +38 -0
  392. package/src/components/shared/header/header.component.tsx +85 -0
  393. package/src/components/shared/header/header.scss +150 -0
  394. package/src/components/shared/header/illustration.component.tsx +34 -0
  395. package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.scss +33 -0
  396. package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.tsx +389 -0
  397. package/src/components/shared/json-preview-panel/JsonPreviewPanel.scss +32 -0
  398. package/src/components/shared/json-preview-panel/JsonPreviewPanel.tsx +104 -0
  399. package/src/components/shared/mapping-preview-panel/mapping-preview-panel.component.tsx +82 -0
  400. package/src/components/shared/mapping-preview-panel/mapping-preview-panel.scss +55 -0
  401. package/src/components/shared/preview/query-results-preview.component.tsx +204 -0
  402. package/src/components/shared/template-structure-panel/template-structure-panel.component.tsx +296 -0
  403. package/src/components/shared/template-structure-panel/template-structure-panel.scss +81 -0
  404. package/src/config-schema.ts +43 -0
  405. package/src/constants/privileges.test.ts +53 -0
  406. package/src/constants/privileges.ts +118 -0
  407. package/src/features/dashboards/DashboardRendererPage.tsx +174 -0
  408. package/src/features/dashboards/DashboardsPage.tsx +164 -0
  409. package/src/features/dashboards/EtlDashboardPage.tsx +32 -0
  410. package/src/features/dashboards/components/DashboardEmptyState.tsx +41 -0
  411. package/src/features/dashboards/components/DashboardGrid.tsx +48 -0
  412. package/src/features/dashboards/components/DashboardHeader.tsx +58 -0
  413. package/src/features/dashboards/components/DashboardSection.tsx +46 -0
  414. package/src/features/dashboards/components/ReportLaunchCard.tsx +51 -0
  415. package/src/features/dashboards/components/WidgetSlotDispatcher.tsx +61 -0
  416. package/src/features/dashboards/components/WidgetUnavailableCard.tsx +36 -0
  417. package/src/features/dashboards/dashboards.scss +196 -0
  418. package/src/features/dashboards/hooks/useDashboard.ts +61 -0
  419. package/src/features/dashboards/hooks/useDashboardWidgets.ts +74 -0
  420. package/src/features/dashboards/hooks/useDashboards.ts +56 -0
  421. package/src/features/dashboards/hooks/useMonitorDataMap.ts +84 -0
  422. package/src/features/dashboards/index.ts +11 -0
  423. package/src/features/dashboards/utils/dashboard-config.util.test.ts +255 -0
  424. package/src/features/dashboards/utils/dashboard-config.util.ts +382 -0
  425. package/src/features/report-visualizer/ReportVisualizerPage.tsx +448 -0
  426. package/src/features/report-visualizer/components/Export/ExportMenu.tsx +141 -0
  427. package/src/features/report-visualizer/components/Export/index.ts +2 -0
  428. package/src/features/report-visualizer/components/ReportExplorer/QuickFilters.tsx +152 -0
  429. package/src/features/report-visualizer/components/ReportExplorer/ReportCategoryFilter.tsx +54 -0
  430. package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerPanel.tsx +201 -0
  431. package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerRail.tsx +202 -0
  432. package/src/features/report-visualizer/components/ReportExplorer/ReportList.tsx +287 -0
  433. package/src/features/report-visualizer/components/ReportExplorer/ReportSearch.tsx +151 -0
  434. package/src/features/report-visualizer/components/ReportExplorer/ReportTagFilter.tsx +100 -0
  435. package/src/features/report-visualizer/components/ReportExplorer/RunReportButton.tsx +42 -0
  436. package/src/features/report-visualizer/components/ReportExplorer/RuntimeParameterForm.tsx +259 -0
  437. package/src/features/report-visualizer/components/ReportExplorer/index.ts +8 -0
  438. package/src/features/report-visualizer/components/ReportWorkspace/ReportLayer.tsx +33 -0
  439. package/src/features/report-visualizer/components/ReportWorkspace/ReportSummary.tsx +88 -0
  440. package/src/features/report-visualizer/components/ReportWorkspace/ReportViewTabs.tsx +72 -0
  441. package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspaceHeader.tsx +92 -0
  442. package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspacePanel.tsx +242 -0
  443. package/src/features/report-visualizer/components/ReportWorkspace/index.ts +5 -0
  444. package/src/features/report-visualizer/components/TableView/ColumnVisibilityMenu.tsx +206 -0
  445. package/src/features/report-visualizer/components/TableView/ReportTable.tsx +253 -0
  446. package/src/features/report-visualizer/components/TableView/index.ts +2 -0
  447. package/src/features/report-visualizer/hooks/index.ts +4 -0
  448. package/src/features/report-visualizer/hooks/useExplorerPreferences.ts +103 -0
  449. package/src/features/report-visualizer/hooks/useReportCapabilities.ts +112 -0
  450. package/src/features/report-visualizer/hooks/useReportParameters.ts +152 -0
  451. package/src/features/report-visualizer/hooks/useReports.ts +135 -0
  452. package/src/features/report-visualizer/index.ts +6 -0
  453. package/src/features/report-visualizer/report-visualizer.scss +593 -0
  454. package/src/features/report-visualizer/types/index.ts +1 -0
  455. package/src/features/report-visualizer/types/report-visualizer.types.ts +104 -0
  456. package/src/features/report-visualizer/utils/status-colors.ts +117 -0
  457. package/src/globals.d.ts +14 -0
  458. package/src/hooks/address-template.ts +86 -0
  459. package/src/hooks/concept.ts +112 -0
  460. package/src/hooks/indicator/index.ts +1 -0
  461. package/src/hooks/indicator/useIndicatorsByTheme.ts +41 -0
  462. package/src/hooks/openmrs-reference-data.ts +432 -0
  463. package/src/hooks/theme/index.ts +4 -0
  464. package/src/hooks/theme/useDataTheme.ts +61 -0
  465. package/src/hooks/theme/useDataThemes.ts +28 -0
  466. package/src/hooks/theme/useMambaTableMeta.ts +34 -0
  467. package/src/hooks/theme/useMambaTables.ts +95 -0
  468. package/src/hooks/use-report-builder-privileges.test.tsx +98 -0
  469. package/src/hooks/use-report-builder-privileges.ts +58 -0
  470. package/src/index.ts +29 -0
  471. package/src/resources/address-template/address-template.api.ts +180 -0
  472. package/src/resources/agegroup/agegroups.api.ts +58 -0
  473. package/src/resources/concept/concept.api.ts +116 -0
  474. package/src/resources/concepts/concept-mapper.ts +21 -0
  475. package/src/resources/concepts/concept-types.ts +31 -0
  476. package/src/resources/concepts/concepts.resource.ts +44 -0
  477. package/src/resources/concepts/useConceptSearch.ts +41 -0
  478. package/src/resources/dashboard/dashboard.api.ts +107 -0
  479. package/src/resources/etl-monitor/etl-monitor-test.api.ts +409 -0
  480. package/src/resources/etl-monitor/etl-monitor.api.ts +381 -0
  481. package/src/resources/etl-source/etl-source.api.ts +65 -0
  482. package/src/resources/etl-task/etl-task.api.ts +96 -0
  483. package/src/resources/etl-task/etl-task.types.ts +31 -0
  484. package/src/resources/indicator/indicators.api.ts +125 -0
  485. package/src/resources/legacy-reports/legacy-reports.api.ts +144 -0
  486. package/src/resources/linelist/linelist-reports.api.ts +616 -0
  487. package/src/resources/location/location.api.ts +135 -0
  488. package/src/resources/openmrs-api.ts +40 -0
  489. package/src/resources/patient-identifier-type/patient-identifier-type.api.ts +65 -0
  490. package/src/resources/person-attribute-type/person-attribute-type.api.ts +71 -0
  491. package/src/resources/preview/sql-preview.api.ts +54 -0
  492. package/src/resources/report/reports.api.ts +140 -0
  493. package/src/resources/report-category/report-category.api.ts +41 -0
  494. package/src/resources/report-import-export/import-export-api.ts +164 -0
  495. package/src/resources/report-library/report-library.api.ts +63 -0
  496. package/src/resources/report-section/report-sections.api.ts +73 -0
  497. package/src/resources/report-section/section-preview.api.ts +25 -0
  498. package/src/resources/theme/data-theme.api.ts +88 -0
  499. package/src/resources/theme/etl-schema.api.ts +101 -0
  500. package/src/resources/theme/etl-table-meta.api.ts +45 -0
  501. package/src/root.component.tsx +20 -0
  502. package/src/routes/report-builder.routes.tsx +160 -0
  503. package/src/routes/report-builder.scss +56 -0
  504. package/src/routes.json +28 -0
  505. package/src/sample-template.ts +121 -0
  506. package/src/template-utils.ts +110 -0
  507. package/src/types/carbon-react-inert.d.ts +13 -0
  508. package/src/types/condition-operators.ts +63 -0
  509. package/src/types/dashboard/dashboard.types.ts +137 -0
  510. package/src/types/dashboard/index.ts +4 -0
  511. package/src/types/etl/etl-types.ts +238 -0
  512. package/src/types/etl-monitor/etl-monitor-builder.types.ts +555 -0
  513. package/src/types/etl-monitor/etl-monitor-v2.types.ts +397 -0
  514. package/src/types/etl-monitor/etl-monitor.types.ts +188 -0
  515. package/src/types/etl-monitor/index.ts +6 -0
  516. package/src/types/linelist/compile-config.ts +455 -0
  517. package/src/types/linelist-types.ts +2059 -0
  518. package/src/types/theme/data-theme.types.ts +88 -0
  519. package/src/utils/api-error.utils.test.ts +151 -0
  520. package/src/utils/api-error.utils.ts +131 -0
  521. package/src/utils/config-enhancer.ts +62 -0
  522. package/src/utils/etl-browser/etl-browser.util.test.ts +145 -0
  523. package/src/utils/etl-browser/export-csv.util.ts +43 -0
  524. package/src/utils/etl-browser/group-tables.util.ts +84 -0
  525. package/src/utils/etl-browser/index.ts +4 -0
  526. package/src/utils/etl-browser/summarize-columns.util.ts +27 -0
  527. package/src/utils/etl-browser/table-sql.util.ts +20 -0
  528. package/src/utils/etl-monitor/config-v2-adapter.util.ts +460 -0
  529. package/src/utils/etl-monitor/endpoint-config-normalizer.util.ts +383 -0
  530. package/src/utils/etl-monitor/index.ts +10 -0
  531. package/src/utils/etl-monitor/jsonpath-resolver.util.ts +118 -0
  532. package/src/utils/etl-monitor/normalization-examples.ts +0 -0
  533. package/src/utils/etl-monitor/schema-inspector.util.ts +76 -0
  534. package/src/utils/etl-monitor/semantic-type-detector.util.ts +76 -0
  535. package/src/utils/etl-monitor/status-mapper.util.ts +105 -0
  536. package/src/utils/etl-monitor/value-formatters.util.ts +167 -0
  537. package/src/utils/etl-task/etl-task-card.util.test.ts +239 -0
  538. package/src/utils/etl-task/etl-task-card.util.ts +276 -0
  539. package/src/utils/etl-task/index.ts +1 -0
  540. package/src/utils/html-entities.utils.ts +34 -0
  541. package/src/utils/parameter-resolution.ts +218 -0
  542. package/src/views/etl-monitor-builder/add-monitor-modal.component.scss +0 -0
  543. package/src/views/etl-monitor-builder/add-monitor-modal.component.tsx +210 -0
  544. package/src/views/etl-monitor-builder/etl-monitor-builder.component.scss +157 -0
  545. package/src/views/etl-monitor-builder/etl-monitor-builder.component.tsx +252 -0
  546. package/src/views/etl-monitor-builder/etl-monitor-builder.store.ts +126 -0
  547. package/tools/scss-stub.js +12 -0
  548. package/tools/setup-tests.ts +1 -0
  549. package/translations/en.json +65 -0
  550. package/tsconfig.json +43 -0
  551. package/webpack.config.js +30 -0
@@ -0,0 +1,1137 @@
1
+ /**
2
+ * Query Configuration Panel - Redesigned to match mockup
3
+ *
4
+ * Accordion-style panel with:
5
+ * - Dataset
6
+ * - Population (with visual filter builder)
7
+ * - Columns
8
+ * - Filters
9
+ * - Sort
10
+ * - Parameters
11
+ * - Display & Export
12
+ */
13
+
14
+ import React, { useState, useCallback, useEffect } from 'react';
15
+ import {
16
+ Stack,
17
+ Button,
18
+ Select,
19
+ SelectItem,
20
+ TextInput,
21
+ TextArea,
22
+ Tag,
23
+ Toggle,
24
+ } from '@carbon/react';
25
+ import {
26
+ DataTable,
27
+ Filter,
28
+ Settings,
29
+ SortAscending,
30
+ List,
31
+ Export,
32
+ ChevronDown,
33
+ ChevronUp,
34
+ Add,
35
+ TrashCan,
36
+ Document,
37
+ Code,
38
+ } from '@carbon/react/icons';
39
+ import type {
40
+ LinelistReportDraft,
41
+ FilterFieldType,
42
+ FilterCondition,
43
+ PopulationMode,
44
+ LogicalOperator,
45
+ PopulationDefinition,
46
+ } from '../../../../types/linelist-types';
47
+ import type { IndicatorDto } from '../../../../resources/indicator/indicators.api';
48
+ import type { IndicatorOption } from '../../../indicators/types/composite-indicator.types';
49
+ import IndicatorSearchSelect from '../../../indicators/indicator-search-select.component';
50
+ import JsonPreview from './json-preview.component';
51
+ import ColumnCategorySelector from './column-category-selector.component';
52
+ import ParameterEditor from '../../definition/panels/parameter-panel.component';
53
+ import FilterMapEditor from './filtermap-editor.component';
54
+ import styles from './query-config-panel.scss';
55
+
56
+ type Props = {
57
+ draft: LinelistReportDraft;
58
+ onDraftChange: (updates: Partial<LinelistReportDraft>) => void;
59
+ availableFields?: Array<{ name: string; label: string; type: FilterFieldType }>; // From data source (for columns)
60
+ populationFields?: Array<{ name: string; label: string; type: FilterFieldType }>; // From theme (for population filters)
61
+ indicators?: IndicatorDto[]; // Available indicators
62
+ onRemoveColumn?: (columnId: string) => void; // Handler to remove a column
63
+ disabled?: boolean;
64
+ };
65
+
66
+ type IndicatorRule = {
67
+ id: string;
68
+ indicatorUuid: string;
69
+ name: string;
70
+ logicalOperator?: 'AND' | 'OR';
71
+ negate?: boolean;
72
+ };
73
+
74
+ /**
75
+ * Get operators for field type
76
+ */
77
+ function getOperatorsForFieldType(fieldType: FilterFieldType): string[] {
78
+ switch (fieldType) {
79
+ case 'TEXT':
80
+ return ['equals', 'not equals', 'contains', 'starts with', 'is blank'];
81
+ case 'NUMBER':
82
+ return ['equals', 'not equals', 'greater than', 'less than', 'between'];
83
+ case 'DATE':
84
+ return ['on', 'before', 'after', 'between'];
85
+ case 'CODED':
86
+ return ['is one of', 'is not one of'];
87
+ case 'BOOLEAN':
88
+ return ['is true', 'is false', 'not recorded'];
89
+ case 'LOCATION':
90
+ return ['equals', 'within hierarchy'];
91
+ default:
92
+ return ['equals', 'not equals'];
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Format operator label for display
98
+ */
99
+ function formatOperatorLabel(operator: string): string {
100
+ return operator
101
+ .split('_')
102
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
103
+ .join(' ');
104
+ }
105
+
106
+ /**
107
+ * Section state type
108
+ */
109
+ type SectionKey = 'dataset' | 'population' | 'columns' | 'filters' | 'sort' | 'parameters' | 'display' | 'json-preview';
110
+
111
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
112
+ export default function QueryConfigPanel({ draft, onDraftChange, availableFields = [], populationFields = [], indicators = [], onRemoveColumn, disabled = false }: Props) {
113
+ const [expandedSections, setExpandedSections] = useState<Set<SectionKey>>(new Set(['population']));
114
+ const [populationMode, setPopulationMode] = useState<PopulationMode>('SQL');
115
+
116
+ // Sync populationMode from the draft's build method when loading a report
117
+ useEffect(() => {
118
+ if (draft.population?.buildMethod === 'INDICATOR_BASED') {
119
+ setPopulationMode('INDICATOR');
120
+ } else if (draft.population?.buildMethod === 'VISUAL_FILTER') {
121
+ setPopulationMode('SQL');
122
+ } else {
123
+ setPopulationMode('SQL');
124
+ }
125
+ }, [draft.population?.buildMethod]);
126
+
127
+ // Drag and drop state for columns (for future use)
128
+ // const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
129
+ // const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
130
+
131
+ /**
132
+ * Toggle section expansion
133
+ */
134
+ const toggleSection = useCallback((key: SectionKey) => {
135
+ setExpandedSections((prev) => {
136
+ const next = new Set(prev);
137
+ if (next.has(key)) {
138
+ next.delete(key);
139
+ } else {
140
+ next.add(key);
141
+ }
142
+ return next;
143
+ });
144
+ }, []);
145
+
146
+ /**
147
+ * Add a condition to population filter
148
+ */
149
+ const addPopulationCondition = useCallback(() => {
150
+ const newCondition: FilterCondition = {
151
+ id: `cond-${Date.now()}`,
152
+ field: '',
153
+ fieldLabel: '',
154
+ fieldType: 'DATE',
155
+ operator: 'BETWEEN_DATES',
156
+ value: '',
157
+ };
158
+
159
+ // Ensure visualFilter and rootGroup exist - read from population.visualFilter with legacy fallback
160
+ const currentVisualFilter = draft.population?.visualFilter || draft.visualFilter;
161
+ const currentConditions = currentVisualFilter?.rootGroup?.conditions || [];
162
+ const updatedFilter = {
163
+ rootGroup: {
164
+ id: 'root',
165
+ logicalOperator: (currentVisualFilter?.rootGroup?.logicalOperator || 'AND') as LogicalOperator,
166
+ conditions: [...currentConditions, newCondition],
167
+ },
168
+ useVisualBuilder: true,
169
+ };
170
+
171
+ // Update both population.visualFilter (preferred) and legacy visualFilter for backward compatibility
172
+ onDraftChange({
173
+ population: {
174
+ ...draft.population,
175
+ visualFilter: updatedFilter,
176
+ },
177
+ visualFilter: updatedFilter, // Legacy - keep in sync for backward compatibility
178
+ });
179
+ }, [draft.population, draft.visualFilter, onDraftChange]);
180
+
181
+ /**
182
+ * Update a condition in population filter
183
+ */
184
+ const updatePopulationCondition = useCallback((conditionId: string, updates: Partial<FilterCondition>) => {
185
+ // Read from population.visualFilter with legacy fallback
186
+ const currentVisualFilter = draft.population?.visualFilter || draft.visualFilter;
187
+ const currentConditions = currentVisualFilter?.rootGroup?.conditions || [];
188
+ const updatedConditions = currentConditions.map((c) =>
189
+ c.id === conditionId ? { ...c, ...updates } : c
190
+ );
191
+
192
+ // If field changed, update fieldType and reset operator if needed
193
+ const updatedCondition = updatedConditions.find((c) => c.id === conditionId);
194
+ if (updatedCondition && updates.field) {
195
+ const fieldInfo = populationFields.find((f) => f.name === updates.field);
196
+ if (fieldInfo) {
197
+ updatedCondition.fieldType = fieldInfo.type;
198
+ updatedCondition.fieldLabel = fieldInfo.label;
199
+ // Reset operator if not compatible
200
+ const validOps = getOperatorsForFieldType(fieldInfo.type);
201
+ if (!validOps.includes(updatedCondition.operator.toLowerCase())) {
202
+ updatedCondition.operator = 'EQUALS';
203
+ }
204
+ }
205
+ }
206
+
207
+ const updatedFilter = {
208
+ rootGroup: {
209
+ id: currentVisualFilter?.rootGroup?.id || 'root',
210
+ logicalOperator: (currentVisualFilter?.rootGroup?.logicalOperator || 'AND') as LogicalOperator,
211
+ conditions: updatedConditions,
212
+ },
213
+ useVisualBuilder: true,
214
+ };
215
+
216
+ // Update both population.visualFilter (preferred) and legacy visualFilter for backward compatibility
217
+ onDraftChange({
218
+ population: {
219
+ ...draft.population,
220
+ visualFilter: updatedFilter,
221
+ },
222
+ visualFilter: updatedFilter, // Legacy - keep in sync
223
+ });
224
+ }, [draft.population, draft.visualFilter, onDraftChange, populationFields]);
225
+
226
+ /**
227
+ * Remove a condition from population filter
228
+ */
229
+ const removePopulationCondition = useCallback((conditionId: string) => {
230
+ // Read from population.visualFilter with legacy fallback
231
+ const currentVisualFilter = draft.population?.visualFilter || draft.visualFilter;
232
+ const currentConditions = currentVisualFilter?.rootGroup?.conditions || [];
233
+ const updatedConditions = currentConditions.filter(
234
+ (c) => c.id !== conditionId
235
+ );
236
+
237
+ const updatedFilter = {
238
+ rootGroup: {
239
+ id: currentVisualFilter?.rootGroup?.id || 'root',
240
+ logicalOperator: (currentVisualFilter?.rootGroup?.logicalOperator || 'AND') as LogicalOperator,
241
+ conditions: updatedConditions,
242
+ },
243
+ useVisualBuilder: true,
244
+ };
245
+
246
+ // Update both population.visualFilter (preferred) and legacy visualFilter for backward compatibility
247
+ onDraftChange({
248
+ population: {
249
+ ...draft.population,
250
+ visualFilter: updatedFilter,
251
+ },
252
+ visualFilter: updatedFilter, // Legacy - keep in sync
253
+ });
254
+ }, [draft.population, draft.visualFilter, onDraftChange]);
255
+
256
+ /**
257
+ * Handle population mode change
258
+ */
259
+ const handlePopulationModeChange = useCallback((mode: PopulationMode) => {
260
+ setPopulationMode(mode);
261
+
262
+ // Map PopulationMode to buildMethod
263
+ let buildMethod: PopulationDefinition['buildMethod'];
264
+ switch (mode) {
265
+ case 'INDICATOR':
266
+ buildMethod = 'INDICATOR_BASED';
267
+ break;
268
+ case 'HYBRID':
269
+ buildMethod = 'INDICATOR_BASED'; // HYBRID uses indicators too
270
+ break;
271
+ case 'SQL':
272
+ default:
273
+ buildMethod = 'SQL_BUILDER';
274
+ break;
275
+ }
276
+
277
+ // Initialize indicatorRules array if switching to INDICATOR mode
278
+ const currentRules = draft.population?.indicatorRules || draft.indicatorRules || [];
279
+ const indicatorRulesToUse = mode === 'INDICATOR' || mode === 'HYBRID'
280
+ ? (currentRules.length > 0 ? currentRules : [])
281
+ : draft.population?.indicatorRules || draft.indicatorRules || [];
282
+
283
+ // Update both population.buildMethod (preferred) and legacy fields
284
+ onDraftChange({
285
+ population: {
286
+ ...draft.population,
287
+ buildMethod,
288
+ indicatorRules: indicatorRulesToUse,
289
+ },
290
+ populationMode: mode, // Legacy - keep in sync
291
+ indicatorRules: indicatorRulesToUse, // Legacy - keep in sync
292
+ });
293
+ }, [draft.population, draft.indicatorRules, onDraftChange]);
294
+
295
+ /**
296
+ * Add an indicator rule
297
+ */
298
+ const addIndicatorRule = useCallback(() => {
299
+ const newRule: IndicatorRule = {
300
+ id: `rule-${Date.now()}`,
301
+ indicatorUuid: '',
302
+ name: '',
303
+ logicalOperator: 'AND',
304
+ negate: false,
305
+ };
306
+
307
+ const currentRules = draft.population?.indicatorRules || draft.indicatorRules || [];
308
+ const updatedRules = [...currentRules, newRule];
309
+ // Update both population.indicatorRules (preferred) and legacy indicatorRules
310
+ onDraftChange({
311
+ population: {
312
+ ...draft.population,
313
+ indicatorRules: updatedRules,
314
+ },
315
+ indicatorRules: updatedRules, // Legacy - keep in sync
316
+ });
317
+ }, [draft.population, draft.indicatorRules, onDraftChange]);
318
+
319
+ /**
320
+ * Update an indicator rule
321
+ */
322
+ const updateIndicatorRule = useCallback((ruleId: string, updates: Partial<IndicatorRule>) => {
323
+ const currentRules = draft.population?.indicatorRules || draft.indicatorRules || [];
324
+ const updatedRules = currentRules.map((rule) =>
325
+ rule.id === ruleId ? { ...rule, ...updates } : rule
326
+ );
327
+ // Update both population.indicatorRules (preferred) and legacy indicatorRules
328
+ onDraftChange({
329
+ population: {
330
+ ...draft.population,
331
+ indicatorRules: updatedRules,
332
+ },
333
+ indicatorRules: updatedRules, // Legacy - keep in sync
334
+ });
335
+ }, [draft.population, draft.indicatorRules, onDraftChange]);
336
+
337
+ /**
338
+ * Handle indicator selection from IndicatorSearchSelect
339
+ */
340
+ const handleIndicatorSelect = useCallback((ruleId: string, indicatorUuid: string, option: IndicatorOption | null) => {
341
+ const currentRules = draft.population?.indicatorRules || draft.indicatorRules || [];
342
+ const updatedRules = currentRules.map((rule) =>
343
+ rule.id === ruleId
344
+ ? { ...rule, indicatorUuid, name: option?.name || '' }
345
+ : rule
346
+ );
347
+ // Update both population.indicatorRules (preferred) and legacy indicatorRules
348
+ onDraftChange({
349
+ population: {
350
+ ...draft.population,
351
+ indicatorRules: updatedRules,
352
+ },
353
+ indicatorRules: updatedRules, // Legacy - keep in sync
354
+ });
355
+ }, [draft.population, draft.indicatorRules, onDraftChange]);
356
+
357
+ /**
358
+ * Remove an indicator rule
359
+ */
360
+ const removeIndicatorRule = useCallback((ruleId: string) => {
361
+ const currentRules = draft.population?.indicatorRules || draft.indicatorRules || [];
362
+ const updatedRules = currentRules.filter((rule) => rule.id !== ruleId);
363
+ // Update both population.indicatorRules (preferred) and legacy indicatorRules
364
+ onDraftChange({
365
+ population: {
366
+ ...draft.population,
367
+ indicatorRules: updatedRules,
368
+ },
369
+ indicatorRules: updatedRules, // Legacy - keep in sync
370
+ });
371
+ }, [draft.population, draft.indicatorRules, onDraftChange]);
372
+
373
+ // Read from population.visualFilter (preferred) with fallback to legacy visualFilter
374
+ const visualFilterRoot = draft.population?.visualFilter?.rootGroup || draft.visualFilter?.rootGroup;
375
+ const conditions = visualFilterRoot?.conditions || [];
376
+
377
+ /**
378
+ * Drag and drop handlers for columns (for future use)
379
+ */
380
+ // const handleDragStart = useCallback((index: number) => {
381
+ // setDraggedIndex(index);
382
+ // }, []);
383
+
384
+ // const handleDragOver = useCallback((e: React.DragEvent, index: number) => {
385
+ // e.preventDefault();
386
+ // if (draggedIndex === null || draggedIndex === index) return;
387
+ // setDragOverIndex(index);
388
+ // }, [draggedIndex]);
389
+
390
+ // const handleDragEnd = useCallback(() => {
391
+ // setDraggedIndex(null);
392
+ // setDragOverIndex(null);
393
+ // }, []);
394
+
395
+ // const handleDrop = useCallback((e: React.DragEvent, dropIndex: number) => {
396
+ // e.preventDefault();
397
+ // if (draggedIndex === null || draggedIndex === dropIndex) return;
398
+
399
+ // // Create new columns array with reordered items
400
+ // const newColumns = [...draft.columns];
401
+ // const [removed] = newColumns.splice(draggedIndex, 1);
402
+ // newColumns.splice(dropIndex, 0, removed);
403
+
404
+ // onDraftChange({ columns: newColumns });
405
+ // handleDragEnd();
406
+ // }, [draft.columns, draggedIndex, onDraftChange, handleDragEnd]);
407
+
408
+ return (
409
+ <div className={styles.container}>
410
+ {/* Dataset Section */}
411
+ <div className={styles.section}>
412
+ <button
413
+ className={styles.sectionHeader}
414
+ onClick={() => toggleSection('dataset')}
415
+ type="button"
416
+ >
417
+ <div className={styles.sectionHeaderLeft}>
418
+ <DataTable size={20} />
419
+ <span className={styles.sectionTitle}>Dataset</span>
420
+ </div>
421
+ <div className={styles.sectionHeaderRight}>
422
+ {expandedSections.has('dataset') ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
423
+ </div>
424
+ </button>
425
+ {expandedSections.has('dataset') && (
426
+ <div className={styles.sectionContent}>
427
+ <div className={styles.datasetInfo}>
428
+ <div className={styles.datasetRow}>
429
+ <span className={styles.datasetLabel}>Population sources:</span>
430
+ <span className={styles.datasetValue}>
431
+ {draft.populationSources && draft.populationSources.length > 0 ? (
432
+ <div className={styles.datasourceTags}>
433
+ {draft.populationSources
434
+ .filter(ps => ps.enabled)
435
+ .sort((a, b) => a.order - b.order)
436
+ .map(ps => (
437
+ <Tag key={ps.uuid} size="sm" type={
438
+ ps.joinType === 'JOIN' || ps.joinType === 'LEFT_JOIN' ? 'blue' :
439
+ ps.joinType === 'INTERSECT' ? 'green' :
440
+ ps.joinType === 'UNION' ? 'purple' : 'red'
441
+ }>
442
+ {ps.name} ({ps.joinType})
443
+ </Tag>
444
+ ))}
445
+ </div>
446
+ ) : 'Not selected'}
447
+ </span>
448
+ </div>
449
+ <div className={styles.datasetRow}>
450
+ <span className={styles.datasetLabel}>Data sources:</span>
451
+ <span className={styles.datasetValue}>
452
+ {draft.dataSources && draft.dataSources.length > 0 ? (
453
+ <div className={styles.datasourceTags}>
454
+ {draft.dataSources.map(ds => (
455
+ <Tag key={ds.uuid} size="sm" type={
456
+ ds.role === 'PRIMARY' ? 'green' :
457
+ ds.role === 'SECONDARY' ? 'blue' : 'purple'
458
+ }>
459
+ {ds.name} ({ds.role})
460
+ </Tag>
461
+ ))}
462
+ </div>
463
+ ) : 'Not selected'}
464
+ </span>
465
+ </div>
466
+ <div className={styles.datasetRow}>
467
+ <span className={styles.datasetLabel}>Row grain:</span>
468
+ <span className={styles.datasetValue}>{draft.rowGrain || 'Not set'}</span>
469
+ </div>
470
+ </div>
471
+ </div>
472
+ )}
473
+ </div>
474
+
475
+ {/* Population Section */}
476
+ <div className={styles.section}>
477
+ <button
478
+ className={styles.sectionHeader}
479
+ onClick={() => toggleSection('population')}
480
+ type="button"
481
+ >
482
+ <div className={styles.sectionHeaderLeft}>
483
+ <DataTable size={20} />
484
+ <span className={styles.sectionTitle}>Population</span>
485
+ <Tag size="sm" type="blue">
486
+ {populationMode === 'HYBRID'
487
+ ? ((draft.population?.indicatorRules || draft.indicatorRules)?.length || 0) + conditions.length
488
+ : populationMode === 'INDICATOR'
489
+ ? ((draft.population?.indicatorRules || draft.indicatorRules)?.length || 0)
490
+ : populationMode === 'SQL' && (draft.population?.sqlTemplate?.trim())
491
+ ? 'SQL'
492
+ : conditions.length}
493
+ </Tag>
494
+ </div>
495
+ <div className={styles.sectionHeaderRight}>
496
+ {expandedSections.has('population') ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
497
+ </div>
498
+ </button>
499
+ {expandedSections.has('population') && (
500
+ <div className={styles.sectionContent}>
501
+ {/* Population Mode Selector */}
502
+ <div className={styles.populationModeSelector}>
503
+ <span className={styles.modeLabel}>Define population using:</span>
504
+ <Select
505
+ id="population-mode"
506
+ size="sm"
507
+ value={populationMode}
508
+ onChange={(e: any) => handlePopulationModeChange(e.target.value as PopulationMode)}
509
+ disabled={disabled}
510
+ labelText=""
511
+ inline
512
+ >
513
+ <SelectItem value="SQL" text="SQL Query" />
514
+ <SelectItem value="INDICATOR" text="Indicators" />
515
+ <SelectItem value="HYBRID" text="Hybrid (SQL + Indicators)" />
516
+ </Select>
517
+ </div>
518
+
519
+ {populationMode === 'SQL' && (
520
+ <>
521
+ <div className={styles.populationHeader}>
522
+ <span className={styles.populationLabel}>All patients where (SQL):</span>
523
+ </div>
524
+
525
+ <Stack gap={3} className={styles.conditionsStack}>
526
+ {conditions.map((condition, index) => (
527
+ <div key={condition.id} className={styles.conditionRow}>
528
+ {/* Field dropdown */}
529
+ <Select
530
+ id={`field-${condition.id}`}
531
+ size="sm"
532
+ value={condition.field}
533
+ onChange={(e: any) => {
534
+ const fieldName = e.target.value;
535
+ const fieldInfo = populationFields.find((f) => f.name === fieldName);
536
+ updatePopulationCondition(condition.id, {
537
+ field: fieldName,
538
+ fieldLabel: fieldInfo?.label || fieldName,
539
+ fieldType: fieldInfo?.type || 'TEXT',
540
+ });
541
+ }}
542
+ disabled={disabled}
543
+ labelText=""
544
+ >
545
+ <SelectItem value="" text="Select field..." />
546
+ {populationFields.map((field) => (
547
+ <SelectItem key={field.name} value={field.name} text={field.label} />
548
+ ))}
549
+ </Select>
550
+
551
+ {/* Operator dropdown */}
552
+ <Select
553
+ id={`operator-${condition.id}`}
554
+ size="sm"
555
+ value={condition.operator}
556
+ onChange={(e: any) => updatePopulationCondition(condition.id, { operator: e.target.value })}
557
+ disabled={disabled || !condition.field}
558
+ labelText=""
559
+ >
560
+ {getOperatorsForFieldType(condition.fieldType).map((op) => (
561
+ <SelectItem
562
+ key={op.toUpperCase()}
563
+ value={op.toUpperCase()}
564
+ text={op.charAt(0).toUpperCase() + op.slice(1)}
565
+ />
566
+ ))}
567
+ </Select>
568
+
569
+ {/* Value input(s) */}
570
+ {condition.operator !== 'IS_BLANK' && condition.operator !== 'IS_TRUE' &&
571
+ condition.operator !== 'IS_FALSE' && condition.operator !== 'NOT_RECORDED' && (
572
+ <TextInput
573
+ id={`value-${condition.id}`}
574
+ size="sm"
575
+ placeholder={condition.fieldType === 'DATE' ? 'Start Date' : 'Value'}
576
+ value={Array.isArray(condition.value) ? condition.value[0] || '' : (condition.value || '')}
577
+ onChange={(e) =>
578
+ updatePopulationCondition(condition.id, { value: (e.target as HTMLInputElement).value })
579
+ }
580
+ disabled={disabled || !condition.field}
581
+ labelText=""
582
+ />
583
+ )}
584
+
585
+ {/* Second value for "between" operators */}
586
+ {(condition.operator === 'BETWEEN' || condition.operator === 'BETWEEN_DATES') && (
587
+ <TextInput
588
+ id={`value2-${condition.id}`}
589
+ size="sm"
590
+ placeholder={condition.fieldType === 'DATE' ? 'End Date' : 'To'}
591
+ value={condition.value2 || ''}
592
+ onChange={(e) =>
593
+ updatePopulationCondition(condition.id, { value2: (e.target as HTMLInputElement).value })
594
+ }
595
+ disabled={disabled || !condition.field}
596
+ labelText=""
597
+ />
598
+ )}
599
+
600
+ {/* "and" text between conditions */}
601
+ {index < conditions.length - 1 && (
602
+ <span className={styles.andText}>and</span>
603
+ )}
604
+
605
+ {/* Delete button */}
606
+ <Button
607
+ kind="ghost"
608
+ size="sm"
609
+ hasIconOnly
610
+ renderIcon={TrashCan}
611
+ onClick={() => removePopulationCondition(condition.id)}
612
+ disabled={disabled}
613
+ iconDescription="Remove condition"
614
+ />
615
+ </div>
616
+ ))}
617
+
618
+ {/* Add condition button */}
619
+ <Button
620
+ kind="ghost"
621
+ size="sm"
622
+ renderIcon={Add}
623
+ onClick={addPopulationCondition}
624
+ disabled={disabled}
625
+ >
626
+ Add condition
627
+ </Button>
628
+ </Stack>
629
+
630
+ {/* Raw SQL Editor */}
631
+ <div className={styles.sqlEditorSection}>
632
+ <div className={styles.populationHeader}>
633
+ <span className={styles.populationLabel}>Or edit raw SQL:</span>
634
+ <span className={styles.populationHint}>
635
+ Directly edit the SQL query (use :startDate and :endDate for date parameters)
636
+ </span>
637
+ </div>
638
+ <TextArea
639
+ id="population-sql"
640
+ className={styles.sqlEditor}
641
+ placeholder="SELECT DISTINCT patient_id AS client_id FROM your_table WHERE..."
642
+ value={draft.population?.sqlTemplate || ''}
643
+ onChange={(e: any) => {
644
+ onDraftChange({
645
+ population: {
646
+ ...draft.population,
647
+ sqlTemplate: e.target.value,
648
+ },
649
+ });
650
+ }}
651
+ disabled={disabled}
652
+ rows={6}
653
+ labelText=""
654
+ />
655
+ </div>
656
+ </>
657
+ )}
658
+
659
+ {populationMode === 'INDICATOR' && (
660
+ <>
661
+ <div className={styles.populationHeader}>
662
+ <span className={styles.populationLabel}>All patients matching these indicators:</span>
663
+ <span className={styles.populationHint}>
664
+ Use indicators with prebuilt query definitions for patient selection
665
+ </span>
666
+ </div>
667
+
668
+ <Stack gap={3} className={styles.conditionsStack}>
669
+ {((draft.population?.indicatorRules || draft.indicatorRules) || []).map((rule, index) => (
670
+ <div key={rule.id} className={styles.conditionRow}>
671
+ <Document size={16} className={styles.rowIcon} />
672
+
673
+ {/* Indicator search select */}
674
+ <div className={styles.indicatorSearch}>
675
+ <IndicatorSearchSelect
676
+ id={`indicator-${rule.id}`}
677
+ titleText=""
678
+ selectedId={rule.indicatorUuid}
679
+ disabled={disabled}
680
+ onChange={(indicatorUuid, option) => handleIndicatorSelect(rule.id, indicatorUuid, option)}
681
+ placeholder="Search indicators..."
682
+ />
683
+ </div>
684
+
685
+ {/* Logical operator */}
686
+ <Select
687
+ id={`logical-${rule.id}`}
688
+ size="sm"
689
+ value={rule.logicalOperator || 'AND'}
690
+ onChange={(e: any) => updateIndicatorRule(rule.id, { logicalOperator: e.target.value })}
691
+ disabled={disabled || !rule.indicatorUuid}
692
+ labelText=""
693
+ >
694
+ <SelectItem value="AND" text="AND" />
695
+ <SelectItem value="OR" text="OR" />
696
+ </Select>
697
+
698
+ {/* Negate toggle - only show when indicator is selected */}
699
+ {rule.indicatorUuid && (
700
+ <div className={styles.negateToggle}>
701
+ <Toggle
702
+ id={`negate-${rule.id}`}
703
+ labelA="Include"
704
+ labelB="Exclude"
705
+ toggled={rule.negate ?? false}
706
+ onToggle={(checked) => updateIndicatorRule(rule.id, { negate: checked })}
707
+ disabled={disabled}
708
+ />
709
+ </div>
710
+ )}
711
+
712
+ {/* "and" text between conditions */}
713
+ {index < ((draft.population?.indicatorRules || draft.indicatorRules)?.length || 0) - 1 && (
714
+ <span className={styles.andText}>and</span>
715
+ )}
716
+
717
+ {/* Delete button */}
718
+ <Button
719
+ kind="ghost"
720
+ size="sm"
721
+ hasIconOnly
722
+ renderIcon={TrashCan}
723
+ onClick={() => removeIndicatorRule(rule.id)}
724
+ disabled={disabled}
725
+ iconDescription="Remove indicator"
726
+ />
727
+ </div>
728
+ ))}
729
+
730
+ {/* Add indicator button */}
731
+ <Button
732
+ kind="ghost"
733
+ size="sm"
734
+ renderIcon={Add}
735
+ onClick={addIndicatorRule}
736
+ disabled={disabled}
737
+ >
738
+ Add indicator
739
+ </Button>
740
+ </Stack>
741
+ </>
742
+ )}
743
+
744
+ {populationMode === 'HYBRID' && (
745
+ <>
746
+ {/* Indicators Section in Hybrid Mode */}
747
+ <div className={styles.populationHeader}>
748
+ <span className={styles.populationLabel}>Indicators:</span>
749
+ <span className={styles.populationHint}>
750
+ Add indicators to combine with SQL
751
+ </span>
752
+ </div>
753
+
754
+ <Stack gap={3} className={styles.conditionsStack}>
755
+ {((draft.population?.indicatorRules || draft.indicatorRules) || []).map((rule, index) => (
756
+ <div key={rule.id} className={styles.conditionRow}>
757
+ <Document size={16} className={styles.rowIcon} />
758
+
759
+ {/* Indicator search select */}
760
+ <div className={styles.indicatorSearch}>
761
+ <IndicatorSearchSelect
762
+ id={`indicator-${rule.id}`}
763
+ titleText=""
764
+ selectedId={rule.indicatorUuid}
765
+ disabled={disabled}
766
+ onChange={(indicatorUuid, option) => handleIndicatorSelect(rule.id, indicatorUuid, option)}
767
+ placeholder="Search indicators..."
768
+ />
769
+ </div>
770
+
771
+ {/* Logical operator */}
772
+ <Select
773
+ id={`logical-${rule.id}`}
774
+ size="sm"
775
+ value={rule.logicalOperator || 'AND'}
776
+ onChange={(e: any) => updateIndicatorRule(rule.id, { logicalOperator: e.target.value })}
777
+ disabled={disabled || !rule.indicatorUuid}
778
+ labelText=""
779
+ >
780
+ <SelectItem value="AND" text="AND" />
781
+ <SelectItem value="OR" text="OR" />
782
+ </Select>
783
+
784
+ {/* Negate toggle - only show when indicator is selected */}
785
+ {rule.indicatorUuid && (
786
+ <div className={styles.negateToggle}>
787
+ <Toggle
788
+ id={`negate-${rule.id}`}
789
+ labelA="Include"
790
+ labelB="Exclude"
791
+ toggled={rule.negate ?? false}
792
+ onToggle={(checked) => updateIndicatorRule(rule.id, { negate: checked })}
793
+ disabled={disabled}
794
+ />
795
+ </div>
796
+ )}
797
+
798
+ {/* "and" text between conditions */}
799
+ {index < ((draft.population?.indicatorRules || draft.indicatorRules)?.length || 0) - 1 && (
800
+ <span className={styles.andText}>and</span>
801
+ )}
802
+
803
+ {/* Delete button */}
804
+ <Button
805
+ kind="ghost"
806
+ size="sm"
807
+ hasIconOnly
808
+ renderIcon={TrashCan}
809
+ onClick={() => removeIndicatorRule(rule.id)}
810
+ disabled={disabled}
811
+ iconDescription="Remove indicator"
812
+ />
813
+ </div>
814
+ ))}
815
+
816
+ {/* Add indicator button */}
817
+ <Button
818
+ kind="ghost"
819
+ size="sm"
820
+ renderIcon={Add}
821
+ onClick={addIndicatorRule}
822
+ disabled={disabled}
823
+ >
824
+ Add indicator
825
+ </Button>
826
+ </Stack>
827
+
828
+ {/* Divider between indicators and conditions */}
829
+ <div className={styles.hybridDivider}>
830
+ <span>AND</span>
831
+ </div>
832
+
833
+ {/* SQL Conditions Section in Hybrid Mode */}
834
+ <div className={styles.populationHeader}>
835
+ <span className={styles.populationLabel}>Additional SQL conditions:</span>
836
+ <span className={styles.populationHint}>
837
+ Filter the selected patients further with custom SQL
838
+ </span>
839
+ </div>
840
+
841
+ <Stack gap={3} className={styles.conditionsStack}>
842
+ {conditions.map((condition, index) => (
843
+ <div key={condition.id} className={styles.conditionRow}>
844
+ <Filter size={16} className={styles.rowIcon} />
845
+
846
+ {/* Field selector */}
847
+ <Select
848
+ id={`field-${condition.id}`}
849
+ size="sm"
850
+ value={condition.field}
851
+ onChange={(e: any) => updatePopulationCondition(condition.id, { field: e.target.value })}
852
+ disabled={disabled}
853
+ labelText=""
854
+ >
855
+ <SelectItem value="" text="Select field..." />
856
+ {populationFields.map((field) => (
857
+ <SelectItem key={field.name} value={field.name} text={field.label}>
858
+ {field.label}
859
+ </SelectItem>
860
+ ))}
861
+ </Select>
862
+
863
+ {/* Operator selector - shown when field is selected */}
864
+ {condition.field && (
865
+ <Select
866
+ id={`operator-${condition.id}`}
867
+ size="sm"
868
+ value={condition.operator}
869
+ onChange={(e: any) => updatePopulationCondition(condition.id, { operator: e.target.value })}
870
+ disabled={disabled}
871
+ labelText=""
872
+ >
873
+ {getOperatorsForFieldType(condition.fieldType).map((op) => (
874
+ <SelectItem key={op} value={op.toUpperCase()} text={formatOperatorLabel(op)}>
875
+ {formatOperatorLabel(op)}
876
+ </SelectItem>
877
+ ))}
878
+ </Select>
879
+ )}
880
+
881
+ {/* Value input - shown when operator requires a value */}
882
+ {condition.operator && ['IS_BLANK', 'IS_NOT_BLANK'].indexOf(condition.operator) === -1 && (
883
+ <TextInput
884
+ id={`value-${condition.id}`}
885
+ size="sm"
886
+ value={condition.value || ''}
887
+ onChange={(e: any) => updatePopulationCondition(condition.id, { value: e.target.value })}
888
+ disabled={disabled}
889
+ placeholder="Value"
890
+ labelText=""
891
+ />
892
+ )}
893
+
894
+ {/* Second value input for BETWEEN operators */}
895
+ {condition.operator === 'BETWEEN' && (
896
+ <TextInput
897
+ id={`value2-${condition.id}`}
898
+ size="sm"
899
+ value={condition.value2 || ''}
900
+ onChange={(e: any) => updatePopulationCondition(condition.id, { value2: e.target.value })}
901
+ disabled={disabled}
902
+ placeholder="To"
903
+ labelText=""
904
+ />
905
+ )}
906
+
907
+ {/* "and" text between conditions */}
908
+ {index < conditions.length - 1 && (
909
+ <span className={styles.andText}>and</span>
910
+ )}
911
+
912
+ {/* Delete button */}
913
+ <Button
914
+ kind="ghost"
915
+ size="sm"
916
+ hasIconOnly
917
+ renderIcon={TrashCan}
918
+ onClick={() => removePopulationCondition(condition.id)}
919
+ disabled={disabled}
920
+ iconDescription="Remove condition"
921
+ />
922
+ </div>
923
+ ))}
924
+
925
+ {/* Add condition button */}
926
+ <Button
927
+ kind="ghost"
928
+ size="sm"
929
+ renderIcon={Add}
930
+ onClick={addPopulationCondition}
931
+ disabled={disabled}
932
+ >
933
+ Add condition
934
+ </Button>
935
+ </Stack>
936
+
937
+ {/* Raw SQL Editor for Hybrid Mode */}
938
+ <div className={styles.sqlEditorSection}>
939
+ <div className={styles.populationHeader}>
940
+ <span className={styles.populationLabel}>Or edit raw SQL:</span>
941
+ <span className={styles.populationHint}>
942
+ Directly edit the SQL query (use :startDate and :endDate for date parameters)
943
+ </span>
944
+ </div>
945
+ <TextArea
946
+ id="population-sql-hybrid"
947
+ className={styles.sqlEditor}
948
+ placeholder="SELECT DISTINCT patient_id AS client_id FROM your_table WHERE..."
949
+ value={draft.population?.sqlTemplate || ''}
950
+ onChange={(e: any) => {
951
+ onDraftChange({
952
+ population: {
953
+ ...draft.population,
954
+ sqlTemplate: e.target.value,
955
+ },
956
+ });
957
+ }}
958
+ disabled={disabled}
959
+ rows={6}
960
+ labelText=""
961
+ />
962
+ </div>
963
+ </>
964
+ )}
965
+
966
+ {/* FilterMap Configuration */}
967
+ <div className={styles.filterMapSection}>
968
+ <FilterMapEditor
969
+ filterMap={draft.population.filterMap}
970
+ onChange={(filterMap) => onDraftChange({ population: { ...draft.population, filterMap } })}
971
+ disabled={disabled}
972
+ sqlQuery={draft.population.sqlTemplate}
973
+ />
974
+ </div>
975
+ </div>
976
+ )}
977
+ </div>
978
+
979
+ {/* Columns Section */}
980
+ <div className={styles.section}>
981
+ <button
982
+ className={styles.sectionHeader}
983
+ onClick={() => toggleSection('columns')}
984
+ type="button"
985
+ >
986
+ <div className={styles.sectionHeaderLeft}>
987
+ <List size={20} />
988
+ <span className={styles.sectionTitle}>Columns</span>
989
+ <Tag size="sm" type="gray">{draft.columns.length}</Tag>
990
+ </div>
991
+ <div className={styles.sectionHeaderRight}>
992
+ {expandedSections.has('columns') ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
993
+ </div>
994
+ </button>
995
+ {expandedSections.has('columns') && (
996
+ <div className={styles.sectionContent}>
997
+ <ColumnCategorySelector
998
+ columns={draft.columns}
999
+ onChange={(columns) => onDraftChange({ columns })}
1000
+ disabled={disabled}
1001
+ filterMap={draft.population.filterMap}
1002
+ />
1003
+ </div>
1004
+ )}
1005
+ </div>
1006
+
1007
+ {/* Filters Section */}
1008
+ <div className={styles.section}>
1009
+ <button
1010
+ className={styles.sectionHeader}
1011
+ onClick={() => toggleSection('filters')}
1012
+ type="button"
1013
+ >
1014
+ <div className={styles.sectionHeaderLeft}>
1015
+ <Filter size={20} />
1016
+ <span className={styles.sectionTitle}>Filters</span>
1017
+ <Tag size="sm" type="gray">0</Tag>
1018
+ </div>
1019
+ <div className={styles.sectionHeaderRight}>
1020
+ {expandedSections.has('filters') ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
1021
+ </div>
1022
+ </button>
1023
+ {expandedSections.has('filters') && (
1024
+ <div className={styles.sectionContent}>
1025
+ <div className={styles.placeholderContent}>
1026
+ <p>Runtime filters configuration will be shown here.</p>
1027
+ <p className={styles.placeholderSubtext}>Filters shown when running the report</p>
1028
+ </div>
1029
+ </div>
1030
+ )}
1031
+ </div>
1032
+
1033
+ {/* Sort Section */}
1034
+ <div className={styles.section}>
1035
+ <button
1036
+ className={styles.sectionHeader}
1037
+ onClick={() => toggleSection('sort')}
1038
+ type="button"
1039
+ >
1040
+ <div className={styles.sectionHeaderLeft}>
1041
+ <SortAscending size={20} />
1042
+ <span className={styles.sectionTitle}>Sort</span>
1043
+ <Tag size="sm" type="gray">{draft.sortConfig.length}</Tag>
1044
+ </div>
1045
+ <div className={styles.sectionHeaderRight}>
1046
+ {expandedSections.has('sort') ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
1047
+ </div>
1048
+ </button>
1049
+ {expandedSections.has('sort') && (
1050
+ <div className={styles.sectionContent}>
1051
+ <div className={styles.placeholderContent}>
1052
+ <p>Sort configuration will be shown here.</p>
1053
+ <p className={styles.placeholderSubtext}>
1054
+ {draft.sortConfig.length} sort rule{draft.sortConfig.length !== 1 ? 's' : ''} defined
1055
+ </p>
1056
+ </div>
1057
+ </div>
1058
+ )}
1059
+ </div>
1060
+
1061
+ {/* Parameters Section */}
1062
+ <div className={styles.section}>
1063
+ <button
1064
+ className={styles.sectionHeader}
1065
+ onClick={() => toggleSection('parameters')}
1066
+ type="button"
1067
+ >
1068
+ <div className={styles.sectionHeaderLeft}>
1069
+ <Settings size={20} />
1070
+ <span className={styles.sectionTitle}>Parameters</span>
1071
+ <Tag size="sm" type="gray">{draft.parameters.length}</Tag>
1072
+ </div>
1073
+ <div className={styles.sectionHeaderRight}>
1074
+ {expandedSections.has('parameters') ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
1075
+ </div>
1076
+ </button>
1077
+ {expandedSections.has('parameters') && (
1078
+ <div className={styles.sectionContent}>
1079
+ <ParameterEditor
1080
+ parameters={draft.parameters}
1081
+ onChange={(parameters) => onDraftChange({ parameters })}
1082
+ disabled={disabled}
1083
+ />
1084
+ </div>
1085
+ )}
1086
+ </div>
1087
+
1088
+ {/* Display & Export Section */}
1089
+ <div className={styles.section}>
1090
+ <button
1091
+ className={styles.sectionHeader}
1092
+ onClick={() => toggleSection('display')}
1093
+ type="button"
1094
+ >
1095
+ <div className={styles.sectionHeaderLeft}>
1096
+ <Export size={20} />
1097
+ <span className={styles.sectionTitle}>Display & Export</span>
1098
+ </div>
1099
+ <div className={styles.sectionHeaderRight}>
1100
+ {expandedSections.has('display') ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
1101
+ </div>
1102
+ </button>
1103
+ {expandedSections.has('display') && (
1104
+ <div className={styles.sectionContent}>
1105
+ <div className={styles.placeholderContent}>
1106
+ <p>Display and export settings will be shown here.</p>
1107
+ <p className={styles.placeholderSubtext}>Page size, export formats, etc.</p>
1108
+ </div>
1109
+ </div>
1110
+ )}
1111
+ </div>
1112
+
1113
+ {/* JSON Preview Section */}
1114
+ <div className={styles.section}>
1115
+ <button
1116
+ className={styles.sectionHeader}
1117
+ onClick={() => toggleSection('json-preview')}
1118
+ type="button"
1119
+ >
1120
+ <div className={styles.sectionHeaderLeft}>
1121
+ <Code size={20} />
1122
+ <span className={styles.sectionTitle}>JSON Preview</span>
1123
+ <Tag size="sm" type="teal">Live</Tag>
1124
+ </div>
1125
+ <div className={styles.sectionHeaderRight}>
1126
+ {expandedSections.has('json-preview') ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
1127
+ </div>
1128
+ </button>
1129
+ {expandedSections.has('json-preview') && (
1130
+ <div className={styles.sectionContent}>
1131
+ <JsonPreview draft={draft} indicators={indicators} />
1132
+ </div>
1133
+ )}
1134
+ </div>
1135
+ </div>
1136
+ );
1137
+ }