drizzle-cube 0.4.39 → 0.4.41
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.
- package/dist/cli/index.cjs +439 -0
- package/dist/client/charts/ChartLoader.d.ts +18 -3
- package/dist/client/charts/chartConfigRegistry.d.ts +11 -1
- package/dist/client/charts/chartPlugin.d.ts +74 -0
- package/dist/client/charts/lazyChartConfigRegistry.d.ts +10 -0
- package/dist/client/charts.js +13 -15
- package/dist/client/chunks/{DashboardEditModal-EmBxK-89.js → DashboardEditModal-D_7J62sG.js} +683 -685
- package/dist/client/chunks/DashboardEditModal-D_7J62sG.js.map +1 -0
- package/dist/client/chunks/{FieldSearchModal-BxQ5JhWz.js → FieldSearchModal-5Qz6vvTz.js} +15 -16
- package/dist/client/chunks/{FieldSearchModal-BxQ5JhWz.js.map → FieldSearchModal-5Qz6vvTz.js.map} +1 -1
- package/dist/client/chunks/{RetentionCombinedChart-D4Yf1TnQ.js → RetentionCombinedChart-B1hUYaXt.js} +2 -2
- package/dist/client/chunks/{RetentionCombinedChart-D4Yf1TnQ.js.map → RetentionCombinedChart-B1hUYaXt.js.map} +1 -1
- package/dist/client/chunks/{RetentionHeatmap-BoGY6mlZ.js → RetentionHeatmap-Dn2ocjVf.js} +2 -2
- package/dist/client/chunks/{RetentionHeatmap-BoGY6mlZ.js.map → RetentionHeatmap-Dn2ocjVf.js.map} +1 -1
- package/dist/client/chunks/{analysis-builder-D70S-LZy.js → analysis-builder-DPJJiHVx.js} +1506 -1564
- package/dist/client/chunks/analysis-builder-DPJJiHVx.js.map +1 -0
- package/dist/client/chunks/{analysis-builder-shared-BxHYfTzo.js → analysis-builder-shared-C-C-rOgu.js} +762 -778
- package/dist/client/chunks/analysis-builder-shared-C-C-rOgu.js.map +1 -0
- package/dist/client/chunks/{chart-bar-Bx4oKlqo.js → chart-bar-DVzmau1G.js} +1 -1
- package/dist/client/chunks/{chart-bar-Bx4oKlqo.js.map → chart-bar-DVzmau1G.js.map} +1 -1
- package/dist/client/chunks/{chart-box-plot-CVIi1aM5.js → chart-box-plot-CCmbHv1Y.js} +1 -1
- package/dist/client/chunks/{chart-box-plot-CVIi1aM5.js.map → chart-box-plot-CCmbHv1Y.js.map} +1 -1
- package/dist/client/chunks/{chart-bubble-DvyG15UB.js → chart-bubble-S6qSwWdK.js} +1 -1
- package/dist/client/chunks/{chart-bubble-DvyG15UB.js.map → chart-bubble-S6qSwWdK.js.map} +1 -1
- package/dist/client/chunks/{chart-candlestick-caHyxB9O.js → chart-candlestick-BNKbDruo.js} +1 -1
- package/dist/client/chunks/{chart-candlestick-caHyxB9O.js.map → chart-candlestick-BNKbDruo.js.map} +1 -1
- package/dist/client/chunks/{chart-config-activity-grid-USo7JrPh.js → chart-config-activity-grid-BSWS08cI.js} +1 -1
- package/dist/client/chunks/{chart-config-activity-grid-USo7JrPh.js.map → chart-config-activity-grid-BSWS08cI.js.map} +1 -1
- package/dist/client/chunks/{chart-config-area-D_ZufYzg.js → chart-config-area-DKwgcHp4.js} +1 -1
- package/dist/client/chunks/{chart-config-area-D_ZufYzg.js.map → chart-config-area-DKwgcHp4.js.map} +1 -1
- package/dist/client/chunks/{chart-config-bar-BCi2Wmd6.js → chart-config-bar-deTjEhap.js} +1 -1
- package/dist/client/chunks/{chart-config-bar-BCi2Wmd6.js.map → chart-config-bar-deTjEhap.js.map} +1 -1
- package/dist/client/chunks/{chart-config-box-plot-afKLzJSp.js → chart-config-box-plot-DU4iWk3V.js} +1 -1
- package/dist/client/chunks/{chart-config-box-plot-afKLzJSp.js.map → chart-config-box-plot-DU4iWk3V.js.map} +1 -1
- package/dist/client/chunks/{chart-config-bubble-CgbBjPv8.js → chart-config-bubble-B8FSHSW-.js} +1 -1
- package/dist/client/chunks/{chart-config-bubble-CgbBjPv8.js.map → chart-config-bubble-B8FSHSW-.js.map} +1 -1
- package/dist/client/chunks/{chart-config-candlestick-7boGjZ-A.js → chart-config-candlestick-BGfyWFft.js} +1 -1
- package/dist/client/chunks/{chart-config-candlestick-7boGjZ-A.js.map → chart-config-candlestick-BGfyWFft.js.map} +1 -1
- package/dist/client/chunks/{chart-config-data-table-Cl7sBasW.js → chart-config-data-table-DKRcGa8t.js} +1 -1
- package/dist/client/chunks/{chart-config-data-table-Cl7sBasW.js.map → chart-config-data-table-DKRcGa8t.js.map} +1 -1
- package/dist/client/chunks/{chart-config-funnel-CXPYQtTl.js → chart-config-funnel-Bt4iGFo_.js} +1 -1
- package/dist/client/chunks/{chart-config-funnel-CXPYQtTl.js.map → chart-config-funnel-Bt4iGFo_.js.map} +1 -1
- package/dist/client/chunks/{chart-config-gauge-DUNEUCvh.js → chart-config-gauge-Bk4Jjp3W.js} +1 -1
- package/dist/client/chunks/{chart-config-gauge-DUNEUCvh.js.map → chart-config-gauge-Bk4Jjp3W.js.map} +1 -1
- package/dist/client/chunks/{chart-config-heat-map-BFf1tO11.js → chart-config-heat-map-CkHsqkrY.js} +1 -1
- package/dist/client/chunks/{chart-config-heat-map-BFf1tO11.js.map → chart-config-heat-map-CkHsqkrY.js.map} +1 -1
- package/dist/client/chunks/{chart-config-kpi-delta-C5k2waIJ.js → chart-config-kpi-delta-CkUX98JV.js} +1 -1
- package/dist/client/chunks/{chart-config-kpi-delta-C5k2waIJ.js.map → chart-config-kpi-delta-CkUX98JV.js.map} +1 -1
- package/dist/client/chunks/{chart-config-kpi-number-DptOyhk0.js → chart-config-kpi-number-DcxyiUgs.js} +1 -1
- package/dist/client/chunks/{chart-config-kpi-number-DptOyhk0.js.map → chart-config-kpi-number-DcxyiUgs.js.map} +1 -1
- package/dist/client/chunks/{chart-config-kpi-text-D9DdVWqd.js → chart-config-kpi-text-DI4mj8CN.js} +1 -1
- package/dist/client/chunks/{chart-config-kpi-text-D9DdVWqd.js.map → chart-config-kpi-text-DI4mj8CN.js.map} +1 -1
- package/dist/client/chunks/{chart-config-line-B3NgLF7K.js → chart-config-line--j7-dLue.js} +1 -1
- package/dist/client/chunks/{chart-config-line-B3NgLF7K.js.map → chart-config-line--j7-dLue.js.map} +1 -1
- package/dist/client/chunks/{chart-config-markdown-tlfivQTt.js → chart-config-markdown-DUjvVjV4.js} +1 -1
- package/dist/client/chunks/{chart-config-markdown-tlfivQTt.js.map → chart-config-markdown-DUjvVjV4.js.map} +1 -1
- package/dist/client/chunks/{chart-config-measure-profile-D7XDwrU2.js → chart-config-measure-profile-B9FKBNGc.js} +1 -1
- package/dist/client/chunks/{chart-config-measure-profile-D7XDwrU2.js.map → chart-config-measure-profile-B9FKBNGc.js.map} +1 -1
- package/dist/client/chunks/{chart-config-pie-wY0B52PC.js → chart-config-pie-yU4jipl9.js} +1 -1
- package/dist/client/chunks/{chart-config-pie-wY0B52PC.js.map → chart-config-pie-yU4jipl9.js.map} +1 -1
- package/dist/client/chunks/{chart-config-radar-DRpJBy1M.js → chart-config-radar-R9Fkc8wL.js} +1 -1
- package/dist/client/chunks/{chart-config-radar-DRpJBy1M.js.map → chart-config-radar-R9Fkc8wL.js.map} +1 -1
- package/dist/client/chunks/{chart-config-radial-bar-DCUpXv9G.js → chart-config-radial-bar-DeoXfpIp.js} +1 -1
- package/dist/client/chunks/{chart-config-radial-bar-DCUpXv9G.js.map → chart-config-radial-bar-DeoXfpIp.js.map} +1 -1
- package/dist/client/chunks/{chart-config-sankey-CdOhlm4h.js → chart-config-sankey-CXEsxo6s.js} +1 -1
- package/dist/client/chunks/{chart-config-sankey-CdOhlm4h.js.map → chart-config-sankey-CXEsxo6s.js.map} +1 -1
- package/dist/client/chunks/{chart-config-scatter-B2su_x8f.js → chart-config-scatter-MVUFupub.js} +1 -1
- package/dist/client/chunks/{chart-config-scatter-B2su_x8f.js.map → chart-config-scatter-MVUFupub.js.map} +1 -1
- package/dist/client/chunks/{chart-config-sunburst-BPdjbk18.js → chart-config-sunburst-Z_gqIY5u.js} +1 -1
- package/dist/client/chunks/{chart-config-sunburst-BPdjbk18.js.map → chart-config-sunburst-Z_gqIY5u.js.map} +1 -1
- package/dist/client/chunks/{chart-config-tree-map-Cbsh2fe2.js → chart-config-tree-map-BD-xAeIy.js} +1 -1
- package/dist/client/chunks/{chart-config-tree-map-Cbsh2fe2.js.map → chart-config-tree-map-BD-xAeIy.js.map} +1 -1
- package/dist/client/chunks/{chart-config-waterfall-DGmuZfQF.js → chart-config-waterfall-CHwVkXZc.js} +1 -1
- package/dist/client/chunks/{chart-config-waterfall-DGmuZfQF.js.map → chart-config-waterfall-CHwVkXZc.js.map} +1 -1
- package/dist/client/chunks/{chart-data-table-CW_qZDpt.js → chart-data-table-D4s27-U3.js} +1452 -826
- package/dist/client/chunks/chart-data-table-D4s27-U3.js.map +1 -0
- package/dist/client/chunks/{chart-gauge-BLLJqeXo.js → chart-gauge-BFhc4i_f.js} +1 -1
- package/dist/client/chunks/{chart-gauge-BLLJqeXo.js.map → chart-gauge-BFhc4i_f.js.map} +1 -1
- package/dist/client/chunks/{chart-heat-map-f2fM2mDC.js → chart-heat-map-BOMQeUDL.js} +1 -1
- package/dist/client/chunks/{chart-heat-map-f2fM2mDC.js.map → chart-heat-map-BOMQeUDL.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-delta-BPexzOe7.js → chart-kpi-delta-DzGNnIcW.js} +4 -4
- package/dist/client/chunks/{chart-kpi-delta-BPexzOe7.js.map → chart-kpi-delta-DzGNnIcW.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-number-BBtGBtZL.js → chart-kpi-number-sHtgbE_f.js} +5 -5
- package/dist/client/chunks/{chart-kpi-number-BBtGBtZL.js.map → chart-kpi-number-sHtgbE_f.js.map} +1 -1
- package/dist/client/chunks/{chart-kpi-text-BqHhmJEB.js → chart-kpi-text-Bmk-GzVJ.js} +2 -2
- package/dist/client/chunks/{chart-kpi-text-BqHhmJEB.js.map → chart-kpi-text-Bmk-GzVJ.js.map} +1 -1
- package/dist/client/chunks/{chart-line-CPhQRMZ7.js → chart-line-Bkl5WQAw.js} +1 -1
- package/dist/client/chunks/{chart-line-CPhQRMZ7.js.map → chart-line-Bkl5WQAw.js.map} +1 -1
- package/dist/client/chunks/{chart-markdown-B2X4IwLO.js → chart-markdown-7MNetRtX.js} +1 -1
- package/dist/client/chunks/{chart-markdown-B2X4IwLO.js.map → chart-markdown-7MNetRtX.js.map} +1 -1
- package/dist/client/chunks/{chart-measure-profile-CVlqGslU.js → chart-measure-profile-B7h6vQo4.js} +1 -1
- package/dist/client/chunks/{chart-measure-profile-CVlqGslU.js.map → chart-measure-profile-B7h6vQo4.js.map} +1 -1
- package/dist/client/chunks/{chart-pie-DafSc9sE.js → chart-pie-Do2YnCxl.js} +1 -1
- package/dist/client/chunks/{chart-pie-DafSc9sE.js.map → chart-pie-Do2YnCxl.js.map} +1 -1
- package/dist/client/chunks/{chart-radar-Dz9F5k-B.js → chart-radar-C7gQkH70.js} +1 -1
- package/dist/client/chunks/{chart-radar-Dz9F5k-B.js.map → chart-radar-C7gQkH70.js.map} +1 -1
- package/dist/client/chunks/{chart-radial-bar-N3MNUL7o.js → chart-radial-bar-DHqCck3x.js} +1 -1
- package/dist/client/chunks/{chart-radial-bar-N3MNUL7o.js.map → chart-radial-bar-DHqCck3x.js.map} +1 -1
- package/dist/client/chunks/{chart-scatter-J2JNi88S.js → chart-scatter-YSHOUfXf.js} +1 -1
- package/dist/client/chunks/{chart-scatter-J2JNi88S.js.map → chart-scatter-YSHOUfXf.js.map} +1 -1
- package/dist/client/chunks/{chart-sunburst-D1NFQjqk.js → chart-sunburst-BGhJ4fui.js} +1 -1
- package/dist/client/chunks/{chart-sunburst-D1NFQjqk.js.map → chart-sunburst-BGhJ4fui.js.map} +1 -1
- package/dist/client/chunks/{chart-tree-map-CbYjko2s.js → chart-tree-map-BlhcXK1F.js} +1 -1
- package/dist/client/chunks/{chart-tree-map-CbYjko2s.js.map → chart-tree-map-BlhcXK1F.js.map} +1 -1
- package/dist/client/chunks/{chart-waterfall-Z65TGMUO.js → chart-waterfall-BWCAzlPq.js} +1 -1
- package/dist/client/chunks/{chart-waterfall-Z65TGMUO.js.map → chart-waterfall-BWCAzlPq.js.map} +1 -1
- package/dist/client/chunks/{charts-core-CJlGzwsW.js → charts-core-Cy3rHADX.js} +1 -1
- package/dist/client/chunks/{charts-core-CJlGzwsW.js.map → charts-core-Cy3rHADX.js.map} +1 -1
- package/dist/client/chunks/{core-DJrniqct.js → core-BdWfCZ3y.js} +1 -1
- package/dist/client/chunks/{core-DJrniqct.js.map → core-BdWfCZ3y.js.map} +1 -1
- package/dist/client/chunks/{dist-DDBeV_JI.js → dist-BWPE2m_X.js} +1 -1
- package/dist/client/chunks/{dist-DDBeV_JI.js.map → dist-BWPE2m_X.js.map} +1 -1
- package/dist/client/chunks/{javascript-BBwTSo6e.js → javascript-O1RIRkZr.js} +1 -1
- package/dist/client/chunks/{javascript-BBwTSo6e.js.map → javascript-O1RIRkZr.js.map} +1 -1
- package/dist/client/chunks/{json-BpTrLZSh.js → json-C5bX2tt1.js} +1 -1
- package/dist/client/chunks/{json-BpTrLZSh.js.map → json-C5bX2tt1.js.map} +1 -1
- package/dist/client/chunks/{retention-UEXlSdZ-.js → retention-YhT1Oohi.js} +1 -1
- package/dist/client/chunks/{retention-UEXlSdZ-.js.map → retention-YhT1Oohi.js.map} +1 -1
- package/dist/client/chunks/{schema-visualization-BFPl_eKV.js → schema-visualization-BJ8HrNqB.js} +12 -12
- package/dist/client/chunks/schema-visualization-BJ8HrNqB.js.map +1 -0
- package/dist/client/chunks/{sql-B0chxcEK.js → sql-D2qikO5q.js} +1 -1
- package/dist/client/chunks/{sql-B0chxcEK.js.map → sql-D2qikO5q.js.map} +1 -1
- package/dist/client/chunks/{syntaxHighlighting-BLl0ch4A.js → syntaxHighlighting-BYYWYmjr.js} +2 -2
- package/dist/client/chunks/{syntaxHighlighting-BLl0ch4A.js.map → syntaxHighlighting-BYYWYmjr.js.map} +1 -1
- package/dist/client/chunks/{useDebounce-C_wstEud.js → useDebounce-EWynD0lC.js} +12 -12
- package/dist/client/chunks/{useDebounce-C_wstEud.js.map → useDebounce-EWynD0lC.js.map} +1 -1
- package/dist/client/chunks/{useDirtyStateTracking-CgKZWkel.js → useDirtyStateTracking-KAjwj1Ht.js} +1 -1
- package/dist/client/chunks/{useDirtyStateTracking-CgKZWkel.js.map → useDirtyStateTracking-KAjwj1Ht.js.map} +1 -1
- package/dist/client/chunks/{useExplainAI-C9ytXRIC.js → useExplainAI-BBTJWQVu.js} +14 -14
- package/dist/client/chunks/{useExplainAI-C9ytXRIC.js.map → useExplainAI-BBTJWQVu.js.map} +1 -1
- package/dist/client/chunks/{useNotebookLayout-BFZ_33Kb.js → useNotebookLayout-DKkMenhj.js} +1 -1
- package/dist/client/chunks/{useNotebookLayout-BFZ_33Kb.js.map → useNotebookLayout-DKkMenhj.js.map} +1 -1
- package/dist/client/chunks/{utils-3FNmeZJR.js → utils-DMyRayr_.js} +2 -2
- package/dist/client/chunks/{utils-3FNmeZJR.js.map → utils-DMyRayr_.js.map} +1 -1
- package/dist/client/chunks/{vendor-BPRWulB7.js → vendor-iY25ogTA.js} +40 -40
- package/dist/client/chunks/{vendor-BPRWulB7.js.map → vendor-iY25ogTA.js.map} +1 -1
- package/dist/client/components.js +3 -3
- package/dist/client/hooks.js +5 -5
- package/dist/client/icons/registry.d.ts +6 -0
- package/dist/client/icons.js +2 -2
- package/dist/client/index.d.ts +3 -1
- package/dist/client/index.js +160 -161
- package/dist/client/index.js.map +1 -1
- package/dist/client/providers/CubeProvider.d.ts +4 -1
- package/dist/client/providers.js +2 -2
- package/dist/client/types.d.ts +2 -1
- package/dist/client/utils.js +6 -6
- package/dist/client-bundle-stats.html +1 -1
- package/package.json +6 -2
- package/dist/client/chunks/DashboardEditModal-EmBxK-89.js.map +0 -1
- package/dist/client/chunks/RetentionCombinedChart.config-DprbXd1N.js +0 -56
- package/dist/client/chunks/RetentionCombinedChart.config-DprbXd1N.js.map +0 -1
- package/dist/client/chunks/RetentionHeatmap.config-cbaNExVy.js +0 -25
- package/dist/client/chunks/RetentionHeatmap.config-cbaNExVy.js.map +0 -1
- package/dist/client/chunks/analysis-builder-D70S-LZy.js.map +0 -1
- package/dist/client/chunks/analysis-builder-shared-BxHYfTzo.js.map +0 -1
- package/dist/client/chunks/chart-data-table-CW_qZDpt.js.map +0 -1
- package/dist/client/chunks/charts-loader-gZjOqZwG.js +0 -259
- package/dist/client/chunks/charts-loader-gZjOqZwG.js.map +0 -1
- package/dist/client/chunks/lazyChartConfigRegistry-BjhxDaSf.js +0 -149
- package/dist/client/chunks/lazyChartConfigRegistry-BjhxDaSf.js.map +0 -1
- package/dist/client/chunks/schema-visualization-BFPl_eKV.js.map +0 -1
package/dist/client/chunks/{FieldSearchModal-BxQ5JhWz.js.map → FieldSearchModal-5Qz6vvTz.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FieldSearchModal-BxQ5JhWz.js","names":[],"sources":["../../../src/client/types/analysisConfig.ts","../../../src/client/utils/configMigration.ts","../../../src/client/utils/filterUtils.ts","../../../src/client/shared/components/CodeBlock.tsx","../../../src/client/utils/colorPalettes.ts","../../../src/client/components/Modal.tsx","../../../src/client/components/AnalysisBuilder/utils/idUtils.ts","../../../src/client/components/AnalysisBuilder/utils/fieldUtils.ts","../../../src/client/components/AnalysisBuilder/utils/recentFieldsUtils.ts","../../../src/client/adapters/funnelModeAdapter.ts","../../../src/client/adapters/flowModeAdapter.ts","../../../src/client/adapters/retentionModeAdapter.ts","../../../src/client/components/AnalysisBuilder/AnalysisDisplayConfigPanel.tsx","../../../src/client/components/ConfirmModal.tsx","../../../src/client/components/ColorPaletteSelector.tsx","../../../src/client/components/AnalysisBuilder/FieldSearchItem.tsx","../../../src/client/components/AnalysisBuilder/FieldDetailPanel.tsx","../../../src/client/components/AnalysisBuilder/FieldSearchModal.tsx"],"sourcesContent":["/**\n * AnalysisConfig - Versioned, mode-agnostic configuration format\n *\n * This is the canonical format for persisting analysis state to:\n * - localStorage (standalone AnalysisBuilder)\n * - Share URLs\n * - Dashboard portlets (via analysisConfig field)\n *\n * Key design principles:\n * - `query` field IS the executable query (no transformation needed)\n * - Per-mode chart config via `charts[analysisType]` map\n * - No UI state (activeQueryIndex, activeFunnelStepIndex) - those are transient\n */\n\nimport type {\n ChartType,\n ChartAxisConfig,\n ChartDisplayConfig,\n CubeQuery,\n MultiQueryConfig,\n} from '../types'\nimport type { ServerFunnelQuery } from './funnel'\nimport type { ServerFlowQuery } from './flow'\nimport type { ServerRetentionQuery } from './retention'\n\n// ============================================================================\n// Chart Configuration\n// ============================================================================\n\n/**\n * Chart configuration - shared across all analysis types\n */\nexport interface ChartConfig {\n chartType: ChartType\n chartConfig: ChartAxisConfig\n displayConfig: ChartDisplayConfig\n}\n\n// ============================================================================\n// Analysis Type\n// ============================================================================\n\n/**\n * Analysis type discriminator\n * Future modes: 'cohort'\n */\nexport type AnalysisType = 'query' | 'funnel' | 'flow' | 'retention'\n\n// ============================================================================\n// Base Configuration\n// ============================================================================\n\n/**\n * Base config - common to all analysis types\n */\ninterface AnalysisConfigBase {\n /** Version number for migration support */\n version: 1\n\n /** Which analysis mode this config represents */\n analysisType: AnalysisType\n\n /** Whether to show table or chart view */\n activeView: 'table' | 'chart'\n\n /**\n * Per-mode chart configuration map.\n * Each mode owns its own chart settings, allowing users to configure\n * different chart types for query mode vs funnel mode.\n */\n charts: {\n [K in AnalysisType]?: ChartConfig\n }\n}\n\n// ============================================================================\n// Query Mode Configuration\n// ============================================================================\n\n/**\n * Query mode config - for single queries and multi-query analysis\n *\n * The `query` field is the actual executable query:\n * - Single query: CubeQuery object\n * - Multi-query: MultiQueryConfig with queries array and merge strategy\n */\nexport interface QueryAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'query'\n\n /**\n * The executable query.\n * - CubeQuery: Single query with measures, dimensions, filters\n * - MultiQueryConfig: Multiple queries with merge strategy\n */\n query: CubeQuery | MultiQueryConfig\n}\n\n// ============================================================================\n// Funnel Mode Configuration\n// ============================================================================\n\n/**\n * Funnel mode config - for funnel analysis\n *\n * The `query` field is the ServerFunnelQuery which can be sent\n * directly to the server for execution.\n */\nexport interface FunnelAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'funnel'\n\n /**\n * Server funnel query - executable as-is.\n * Contains bindingKey, timeDimension, steps[], and options.\n */\n query: ServerFunnelQuery\n}\n\n// ============================================================================\n// Flow Mode Configuration\n// ============================================================================\n\n/**\n * Flow mode config - for bidirectional flow analysis\n *\n * The `query` field is the ServerFlowQuery which can be sent\n * directly to the server for execution.\n */\nexport interface FlowAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'flow'\n\n /**\n * Server flow query - executable as-is.\n * Contains bindingKey, timeDimension, eventDimension, startingStep, and depth config.\n */\n query: ServerFlowQuery\n}\n\n// ============================================================================\n// Retention Mode Configuration\n// ============================================================================\n\n/**\n * Retention mode config - for cohort-based retention analysis\n *\n * The `query` field is the ServerRetentionQuery which can be sent\n * directly to the server for execution.\n */\nexport interface RetentionAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'retention'\n\n /**\n * Server retention query - executable as-is.\n * Contains cohortTimeDimension, activityTimeDimension, bindingKey,\n * granularity settings, and period configuration.\n */\n query: ServerRetentionQuery\n}\n\n// ============================================================================\n// Union Type\n// ============================================================================\n\n/**\n * AnalysisConfig - union of all analysis mode configurations\n */\nexport type AnalysisConfig = QueryAnalysisConfig | FunnelAnalysisConfig | FlowAnalysisConfig | RetentionAnalysisConfig\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Check if config is for query mode\n */\nexport const isQueryConfig = (c: AnalysisConfig): c is QueryAnalysisConfig =>\n c.analysisType === 'query'\n\n/**\n * Check if config is for funnel mode\n */\nexport const isFunnelConfig = (c: AnalysisConfig): c is FunnelAnalysisConfig =>\n c.analysisType === 'funnel'\n\n/**\n * Check if config is for flow mode\n */\nexport const isFlowConfig = (c: AnalysisConfig): c is FlowAnalysisConfig =>\n c.analysisType === 'flow'\n\n/**\n * Check if config is for retention mode\n */\nexport const isRetentionConfig = (c: AnalysisConfig): c is RetentionAnalysisConfig =>\n c.analysisType === 'retention'\n\n/**\n * Check if a query config contains multiple queries\n */\nexport const isMultiQuery = (config: QueryAnalysisConfig): boolean =>\n 'queries' in config.query &&\n Array.isArray((config.query as MultiQueryConfig).queries)\n\n/**\n * Check if a query config contains a single query\n */\nexport const isSingleQuery = (config: QueryAnalysisConfig): boolean =>\n !isMultiQuery(config)\n\n/**\n * Type guard to validate if an unknown value is a valid AnalysisConfig\n */\nexport const isValidAnalysisConfig = (\n config: unknown\n): config is AnalysisConfig => {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n // Check version\n if (c.version !== 1) return false\n\n // Check analysisType\n if (c.analysisType !== 'query' && c.analysisType !== 'funnel' && c.analysisType !== 'flow' && c.analysisType !== 'retention') return false\n\n // Check query exists\n if (!c.query || typeof c.query !== 'object') return false\n\n // Check activeView\n if (c.activeView !== 'table' && c.activeView !== 'chart') return false\n\n return true\n}\n\n// ============================================================================\n// Default Factories\n// ============================================================================\n\n/**\n * Create a default empty query analysis config\n */\nexport const createDefaultQueryConfig = (): QueryAnalysisConfig => ({\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: {\n chartType: 'bar',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n measures: [],\n dimensions: [],\n },\n})\n\n/**\n * Create a default empty funnel analysis config\n */\nexport const createDefaultFunnelConfig = (): FunnelAnalysisConfig => ({\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n funnel: {\n bindingKey: '',\n timeDimension: '',\n steps: [],\n },\n },\n})\n\n/**\n * Create a default empty flow analysis config\n */\nexport const createDefaultFlowConfig = (): FlowAnalysisConfig => ({\n version: 1,\n analysisType: 'flow',\n activeView: 'chart',\n charts: {\n flow: {\n chartType: 'sankey',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n flow: {\n bindingKey: '',\n timeDimension: '',\n eventDimension: '',\n startingStep: {\n name: '',\n filter: undefined,\n },\n stepsBefore: 3,\n stepsAfter: 3,\n joinStrategy: 'auto',\n },\n },\n})\n\n/**\n * Create a default empty retention analysis config\n */\nexport const createDefaultRetentionConfig = (): RetentionAnalysisConfig => ({\n version: 1,\n analysisType: 'retention',\n activeView: 'chart',\n charts: {\n retention: {\n chartType: 'heatmap',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n retention: {\n timeDimension: '',\n bindingKey: '',\n dateRange: { start: '', end: '' },\n granularity: 'week',\n periods: 12,\n retentionType: 'classic',\n },\n },\n})\n\n/**\n * Create a default config for the given analysis type\n */\nexport const createDefaultConfig = (\n type: AnalysisType = 'query'\n): AnalysisConfig => {\n switch (type) {\n case 'funnel':\n return createDefaultFunnelConfig()\n case 'flow':\n return createDefaultFlowConfig()\n case 'retention':\n return createDefaultRetentionConfig()\n case 'query':\n default:\n return createDefaultQueryConfig()\n }\n}\n\n// ============================================================================\n// Workspace Format (localStorage persistence)\n// ============================================================================\n\n/**\n * AnalysisWorkspace - Multi-mode persistence format for localStorage\n *\n * Unlike AnalysisConfig (which represents a single analysis mode),\n * AnalysisWorkspace preserves state for ALL modes. This prevents state\n * loss when switching between query, funnel, flow, and retention modes.\n *\n * Usage:\n * - localStorage persistence → AnalysisWorkspace (preserves all modes)\n * - Share URLs → AnalysisConfig (shares one specific analysis)\n * - Dashboard portlets → AnalysisConfig (embeds one specific analysis)\n */\nexport interface AnalysisWorkspace {\n /** Version number for migration support */\n version: 1\n\n /** Currently active analysis type */\n activeType: AnalysisType\n\n /**\n * Per-mode configurations.\n * Each mode stores its complete AnalysisConfig independently.\n * Charts are duplicated per-mode but merged on load.\n */\n modes: {\n query?: QueryAnalysisConfig\n funnel?: FunnelAnalysisConfig\n flow?: FlowAnalysisConfig\n retention?: RetentionAnalysisConfig\n }\n}\n\n/**\n * Type guard to validate if an unknown value is a valid AnalysisWorkspace\n */\nexport const isValidAnalysisWorkspace = (\n data: unknown\n): data is AnalysisWorkspace => {\n if (!data || typeof data !== 'object') return false\n\n const w = data as Record<string, unknown>\n\n // Check version\n if (w.version !== 1) return false\n\n // Check activeType\n if (w.activeType !== 'query' && w.activeType !== 'funnel' && w.activeType !== 'flow' && w.activeType !== 'retention') return false\n\n // Check modes exists and is an object\n if (!w.modes || typeof w.modes !== 'object') return false\n\n return true\n}\n\n/**\n * Create a default empty workspace with all modes initialized\n */\nexport const createDefaultWorkspace = (): AnalysisWorkspace => ({\n version: 1,\n activeType: 'query',\n modes: {\n query: createDefaultQueryConfig(),\n funnel: createDefaultFunnelConfig(),\n flow: createDefaultFlowConfig(),\n retention: createDefaultRetentionConfig(),\n },\n})\n","/**\n * Config Migration Utilities\n *\n * Handles migration from legacy portlet/share formats to AnalysisConfig.\n * Used for backward compatibility when loading old saved configurations.\n */\n\nimport type {\n AnalysisConfig,\n QueryAnalysisConfig,\n FunnelAnalysisConfig,\n FlowAnalysisConfig,\n RetentionAnalysisConfig,\n ChartConfig,\n} from '../types/analysisConfig'\nimport { createDefaultQueryConfig, isValidAnalysisConfig } from '../types/analysisConfig'\nimport type {\n ChartType,\n ChartAxisConfig,\n ChartDisplayConfig,\n CubeQuery,\n MultiQueryConfig,\n FunnelBindingKey,\n QueryMergeStrategy,\n PortletConfig,\n} from '../types'\nimport type { ServerFunnelQuery, ServerFunnelStep } from '../types/funnel'\nimport type { ServerFlowQuery } from '../types/flow'\nimport { isServerRetentionQuery } from '../types/retention'\n\n// ============================================================================\n// Legacy Portlet Format\n// ============================================================================\n\n/**\n * Legacy portlet format (before AnalysisConfig)\n */\nexport interface LegacyPortlet {\n /** JSON string of CubeQuery or MultiQueryConfig */\n query: string\n chartType?: ChartType\n chartConfig?: ChartAxisConfig\n displayConfig?: ChartDisplayConfig\n analysisType?: 'query' | 'funnel'\n // Funnel-specific legacy fields\n funnelChartType?: ChartType\n funnelChartConfig?: ChartAxisConfig\n funnelDisplayConfig?: ChartDisplayConfig\n}\n\n/**\n * Legacy MultiQueryConfig with funnel merge strategy\n * This is a standalone type (not extending MultiQueryConfig) to handle old data\n * where mergeStrategy was 'funnel' before it was removed from QueryMergeStrategy.\n */\nexport interface LegacyFunnelMultiQuery {\n queries: CubeQuery[]\n mergeStrategy: 'funnel'\n mergeKeys?: string[]\n queryLabels?: string[]\n funnelBindingKey?: FunnelBindingKey | null\n stepTimeToConvert?: (string | null)[]\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Check if a query is a MultiQueryConfig\n */\nfunction isMultiQueryConfig(query: unknown): query is MultiQueryConfig {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'queries' in query &&\n Array.isArray((query as MultiQueryConfig).queries)\n )\n}\n\n/**\n * Check if a query is a legacy funnel multi-query\n * Uses type assertion since 'funnel' is no longer in QueryMergeStrategy\n */\nfunction isLegacyFunnelMultiQuery(\n query: unknown\n): query is LegacyFunnelMultiQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'queries' in query &&\n Array.isArray((query as { queries?: unknown[] }).queries) &&\n 'mergeStrategy' in query &&\n (query as { mergeStrategy?: string }).mergeStrategy === 'funnel'\n )\n}\n\n/**\n * Check if a query is a ServerFunnelQuery\n */\nfunction isServerFunnelQuery(query: unknown): query is ServerFunnelQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'funnel' in query &&\n typeof (query as ServerFunnelQuery).funnel === 'object'\n )\n}\n\n/**\n * Check if a query is a ServerFlowQuery\n */\nfunction isServerFlowQuery(query: unknown): query is ServerFlowQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'flow' in query &&\n typeof (query as ServerFlowQuery).flow === 'object'\n )\n}\n\n// ============================================================================\n// Migration Functions\n// ============================================================================\n\n/**\n * Extract chart config from legacy portlet fields\n */\nfunction extractChartConfig(\n portlet: LegacyPortlet,\n analysisType: 'query' | 'funnel' | 'flow' | 'retention'\n): ChartConfig {\n if (analysisType === 'funnel') {\n return {\n chartType: portlet.funnelChartType || portlet.chartType || 'funnel',\n chartConfig: portlet.funnelChartConfig || portlet.chartConfig || {},\n displayConfig: portlet.funnelDisplayConfig || portlet.displayConfig || {},\n }\n }\n\n if (analysisType === 'flow') {\n // Flow uses sankey chart by default\n return {\n chartType: portlet.chartType || 'sankey',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n }\n\n if (analysisType === 'retention') {\n // Retention uses retentionCombined chart by default\n return {\n chartType: portlet.chartType || 'retentionCombined',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n }\n\n return {\n chartType: portlet.chartType || 'bar',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n}\n\n/**\n * Migrate a legacy portlet to AnalysisConfig format\n *\n * Handles:\n * - Single CubeQuery → QueryAnalysisConfig\n * - MultiQueryConfig → QueryAnalysisConfig\n * - ServerFunnelQuery → FunnelAnalysisConfig\n * - ServerFlowQuery → FlowAnalysisConfig\n * - Legacy mergeStrategy:'funnel' → FunnelAnalysisConfig (via migrateLegacyFunnelMerge)\n *\n * @param portlet - Legacy portlet with query string\n * @returns AnalysisConfig in new format\n */\nexport function migrateLegacyPortlet(portlet: LegacyPortlet): AnalysisConfig {\n try {\n const query = JSON.parse(portlet.query)\n\n // Check if it's a ServerRetentionQuery\n if (isServerRetentionQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'retention')\n return {\n version: 1,\n analysisType: 'retention',\n activeView: 'chart',\n charts: {\n retention: chartConfig,\n },\n query,\n } as RetentionAnalysisConfig\n }\n\n // Check if it's a ServerFlowQuery\n if (isServerFlowQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'flow')\n return {\n version: 1,\n analysisType: 'flow',\n activeView: 'chart',\n charts: {\n flow: chartConfig,\n },\n query,\n } as FlowAnalysisConfig\n }\n\n // Check if it's already a ServerFunnelQuery\n if (isServerFunnelQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'funnel')\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query,\n } as FunnelAnalysisConfig\n }\n\n // Check if it's a legacy funnel multi-query\n if (isLegacyFunnelMultiQuery(query)) {\n return migrateLegacyFunnelMerge(query, portlet)\n }\n\n // Check explicit analysisType\n if (portlet.analysisType === 'funnel') {\n // Treat as funnel even if query isn't ServerFunnelQuery format\n // (This handles edge cases where analysisType was set but query wasn't converted)\n const chartConfig = extractChartConfig(portlet, 'funnel')\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query: isServerFunnelQuery(query)\n ? query\n : {\n funnel: {\n bindingKey: '',\n timeDimension: '',\n steps: [],\n },\n },\n } as FunnelAnalysisConfig\n }\n\n // Handle regular query or multi-query\n const chartConfig = extractChartConfig(portlet, 'query')\n\n // MultiQueryConfig (non-funnel, since isLegacyFunnelMultiQuery was checked earlier)\n if (isMultiQueryConfig(query)) {\n // Defensive: convert any lingering 'funnel' strategy to 'concat'\n const strategy = (query.mergeStrategy as string) === 'funnel' ? 'concat' : query.mergeStrategy\n return {\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: chartConfig,\n },\n query: {\n queries: query.queries,\n mergeStrategy: strategy as QueryMergeStrategy,\n mergeKeys: query.mergeKeys,\n queryLabels: query.queryLabels,\n },\n } as QueryAnalysisConfig\n }\n\n // Single CubeQuery\n return {\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: chartConfig,\n },\n query: query as CubeQuery,\n } as QueryAnalysisConfig\n } catch (error) {\n // If parsing fails, return default config\n console.warn('[configMigration] Failed to parse legacy portlet:', error)\n return createDefaultQueryConfig()\n }\n}\n\n/**\n * Migrate legacy mergeStrategy:'funnel' to FunnelAnalysisConfig\n *\n * This handles the old pattern where funnels were created using multi-query\n * with mergeStrategy: 'funnel'. Converts to the new dedicated funnel format.\n *\n * @param legacyQuery - MultiQueryConfig with mergeStrategy: 'funnel'\n * @param portlet - Legacy portlet for chart config\n * @returns FunnelAnalysisConfig in new format\n */\nexport function migrateLegacyFunnelMerge(\n legacyQuery: LegacyFunnelMultiQuery,\n portlet?: LegacyPortlet\n): FunnelAnalysisConfig {\n // Extract binding key\n let bindingKey: ServerFunnelQuery['funnel']['bindingKey'] = ''\n if (legacyQuery.funnelBindingKey?.dimension) {\n if (typeof legacyQuery.funnelBindingKey.dimension === 'string') {\n bindingKey = legacyQuery.funnelBindingKey.dimension\n } else if (Array.isArray(legacyQuery.funnelBindingKey.dimension)) {\n bindingKey = legacyQuery.funnelBindingKey.dimension.map((m) => ({\n cube: m.cube,\n dimension: m.dimension,\n }))\n }\n }\n\n // Extract time dimension from first query\n let timeDimension: string = ''\n if (\n legacyQuery.queries.length > 0 &&\n legacyQuery.queries[0].timeDimensions?.length\n ) {\n timeDimension = legacyQuery.queries[0].timeDimensions[0].dimension\n }\n\n // Convert queries to funnel steps\n const steps: ServerFunnelStep[] = legacyQuery.queries.map((query, index) => {\n const step: ServerFunnelStep = {\n name:\n legacyQuery.queryLabels?.[index] ||\n `Step ${index + 1}`,\n }\n\n // Convert filters\n if (query.filters && query.filters.length > 0) {\n step.filter =\n query.filters.length === 1 ? query.filters[0] : { and: query.filters }\n }\n\n // Add time to convert if specified\n if (\n legacyQuery.stepTimeToConvert &&\n legacyQuery.stepTimeToConvert[index]\n ) {\n step.timeToConvert = legacyQuery.stepTimeToConvert[index] as string\n }\n\n return step\n })\n\n // Build chart config\n const chartConfig: ChartConfig = portlet\n ? extractChartConfig(portlet, 'funnel')\n : {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: {},\n }\n\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query: {\n funnel: {\n bindingKey,\n timeDimension,\n steps,\n includeTimeMetrics: true,\n },\n },\n }\n}\n\n/**\n * Migrate any config to the latest version\n *\n * Handles:\n * - Already valid AnalysisConfig (returns as-is)\n * - Legacy portlet format (converts to AnalysisConfig)\n * - Unknown format (returns default config)\n *\n * @param config - Unknown config value\n * @returns Valid AnalysisConfig\n */\nexport function migrateConfig(config: unknown): AnalysisConfig {\n // Already valid?\n if (isValidAnalysisConfig(config)) {\n return config\n }\n\n // Check if it looks like a legacy portlet\n if (\n config &&\n typeof config === 'object' &&\n 'query' in config &&\n typeof (config as { query: unknown }).query === 'string'\n ) {\n return migrateLegacyPortlet(config as LegacyPortlet)\n }\n\n // Check if it's a raw query object (not wrapped in portlet)\n if (config && typeof config === 'object') {\n try {\n // Try to wrap it as a portlet and migrate\n const wrapped: LegacyPortlet = {\n query: JSON.stringify(config),\n }\n return migrateLegacyPortlet(wrapped)\n } catch {\n // Fall through to default\n }\n }\n\n // Return default config\n console.warn('[configMigration] Unknown config format, using defaults')\n return createDefaultQueryConfig()\n}\n\n/**\n * Check if a portlet has the new AnalysisConfig format\n */\nexport function hasAnalysisConfig(\n portlet: unknown\n): portlet is { analysisConfig: AnalysisConfig } {\n return (\n typeof portlet === 'object' &&\n portlet !== null &&\n 'analysisConfig' in portlet &&\n isValidAnalysisConfig((portlet as { analysisConfig: unknown }).analysisConfig)\n )\n}\n\n/**\n * Ensure a portlet has analysisConfig, migrating from legacy format if needed.\n *\n * This is the primary entry point for rendering portlets - it guarantees\n * that analysisConfig exists, either by using the existing one or by\n * converting legacy fields on-the-fly.\n *\n * @param portlet - PortletConfig which may or may not have analysisConfig\n * @returns PortletConfig with analysisConfig guaranteed to exist\n */\nexport function ensureAnalysisConfig(\n portlet: PortletConfig\n): PortletConfig & { analysisConfig: AnalysisConfig } {\n // If already has valid analysisConfig, return as-is\n if (hasAnalysisConfig(portlet)) {\n return portlet as PortletConfig & { analysisConfig: AnalysisConfig }\n }\n\n // Migrate from legacy fields\n // Note: 'flow' and 'retention' types don't have legacy format - filter to only query/funnel\n const legacyAnalysisType = portlet.analysisType === 'flow' || portlet.analysisType === 'retention' ? undefined : portlet.analysisType\n const analysisConfig = migrateLegacyPortlet({\n query: portlet.query ?? '{}',\n chartType: portlet.chartType,\n chartConfig: portlet.chartConfig,\n displayConfig: portlet.displayConfig,\n analysisType: legacyAnalysisType,\n funnelChartType: portlet.funnelChartType,\n funnelChartConfig: portlet.funnelChartConfig,\n funnelDisplayConfig: portlet.funnelDisplayConfig,\n })\n\n return { ...portlet, analysisConfig }\n}\n","/**\n * Filter utility functions for dashboard-level filtering\n *\n * NOTE: These are pure functions without internal caching.\n * Memoization should be handled at the component level using useMemo.\n */\n\nimport type { Filter, DashboardFilter, CubeMeta, GroupFilter, DashboardConfig, SimpleFilter } from '../types'\nimport { ensureAnalysisConfig } from './configMigration'\n\n/**\n * Check if a filter should be included in the query (has valid values or doesn't require values)\n * @param filter - The filter to check\n * @returns true if the filter should be included, false otherwise\n */\nexport function shouldIncludeFilter(filter: Filter): boolean {\n // Handle SimpleFilter\n if ('member' in filter && 'operator' in filter) {\n const simpleFilter = filter as SimpleFilter\n\n // Operators that don't require values\n const noValueOperators = ['set', 'notSet', 'isEmpty', 'isNotEmpty']\n if (noValueOperators.includes(simpleFilter.operator)) {\n return true\n }\n\n // For inDateRange, check if dateRange is provided as alternative to values\n if (simpleFilter.operator === 'inDateRange' && simpleFilter.dateRange) {\n return true\n }\n\n // For other operators, check if values exist and are non-empty\n return !!(simpleFilter.values && simpleFilter.values.length > 0)\n }\n\n // Handle GroupFilter - recursively check nested filters\n if ('type' in filter && 'filters' in filter) {\n const groupFilter = filter as GroupFilter\n // Include group filter if at least one nested filter is valid\n const validFilters = groupFilter.filters.filter(f => shouldIncludeFilter(f))\n return validFilters.length > 0\n }\n\n return false\n}\n\n/**\n * Get dashboard filters that should be applied to a portlet based on its mapping configuration\n *\n * @param dashboardFilters - All available dashboard filters\n * @param filterMapping - Array of filter IDs that apply to this portlet\n * @returns Array of filters that should be applied to the portlet\n */\nexport function getApplicableDashboardFilters(\n dashboardFilters: DashboardFilter[] | undefined,\n filterMapping: string[] | undefined\n): Filter[] {\n if (!dashboardFilters || !dashboardFilters.length) {\n return []\n }\n\n // If no mapping is specified, no dashboard filters apply\n if (!filterMapping || !filterMapping.length) {\n return []\n }\n\n // Compute filters that are in the mapping AND have valid values\n return dashboardFilters\n .filter(df => filterMapping.includes(df.id))\n .filter(df => shouldIncludeFilter(df.filter))\n .map(df => df.filter)\n}\n\n/**\n * Convert GroupFilter format to server format\n * GroupFilter: { type: 'and', filters: [...] }\n * Server format: { and: [...] } or { or: [...] }\n */\nfunction convertToServerFormat(filter: Filter): any {\n // Handle GroupFilter format\n if ('type' in filter && 'filters' in filter) {\n const groupFilter = filter as GroupFilter\n const convertedFilters = groupFilter.filters.map(convertToServerFormat)\n\n if (groupFilter.type === 'and') {\n return { and: convertedFilters }\n } else {\n return { or: convertedFilters }\n }\n }\n\n // Simple filter - return as-is\n return filter\n}\n\n/**\n * Filter format for merge operation:\n * - 'server': Returns {and: [...]} or {or: [...]} format (for API queries)\n * - 'client': Returns {type: 'and', filters: [...]} format (for UI components)\n */\nexport type FilterFormat = 'server' | 'client'\n\n/**\n * Merge dashboard filters with portlet filters using AND logic\n * Dashboard filters are combined with portlet filters so both sets of filters apply\n *\n * @param dashboardFilters - Filters from dashboard-level configuration\n * @param portletFilters - Filters from portlet query\n * @param format - Output format: 'server' for API queries, 'client' for UI (default: 'server')\n * @returns Merged filter array with AND logic in the specified format\n */\nexport function mergeDashboardAndPortletFilters(\n dashboardFilters: Filter[],\n portletFilters: Filter[] | undefined,\n format: FilterFormat = 'server'\n): Filter[] | undefined {\n // If no dashboard filters, return portlet filters as-is\n if (!dashboardFilters || dashboardFilters.length === 0) {\n return portletFilters\n }\n\n // If no portlet filters, return dashboard filters\n if (!portletFilters || portletFilters.length === 0) {\n return [...dashboardFilters]\n }\n\n // Both exist - need to merge with AND logic\n if (format === 'server') {\n // Server format: convert to {and: [...]} structure\n const allFilters = [...dashboardFilters, ...portletFilters].map(convertToServerFormat)\n return [{\n and: allFilters\n } as any]\n } else {\n // Client format: use {type: 'and', filters: [...]} structure\n const allFilters = [...dashboardFilters, ...portletFilters]\n return [{\n type: 'and',\n filters: allFilters\n } as GroupFilter]\n }\n}\n\n/**\n * @deprecated Use mergeDashboardAndPortletFilters(filters, portletFilters, 'client') instead\n */\nexport function mergeDashboardAndPortletFiltersClientFormat(\n dashboardFilters: Filter[],\n portletFilters: Filter[] | undefined\n): Filter[] | undefined {\n return mergeDashboardAndPortletFilters(dashboardFilters, portletFilters, 'client')\n}\n\n/**\n * Check if a filter field exists in the cube metadata\n * This helps identify filters that might not apply to a specific portlet's data\n * @param filter - The filter to validate\n * @param cubeMeta - Cube metadata to validate against\n * @returns true if the filter field exists in any cube's measures or dimensions\n */\nexport function validateFilterForCube(\n filter: Filter,\n cubeMeta: CubeMeta | null\n): boolean {\n if (!cubeMeta || !cubeMeta.cubes) {\n // If no metadata available, assume filter is valid (fail open)\n return true\n }\n\n // Extract member names from filter recursively\n const memberNames = extractMemberNamesFromFilter(filter)\n\n // Check if any of the member names exist in cube metadata\n return memberNames.some(memberName => {\n return cubeMeta.cubes.some(cube => {\n // Check measures\n const inMeasures = cube.measures?.some(m => m.name === memberName) ?? false\n // Check dimensions\n const inDimensions = cube.dimensions?.some(d => d.name === memberName) ?? false\n\n return inMeasures || inDimensions\n })\n })\n}\n\n/**\n * Extract all member names from a filter (handles nested group filters)\n * @param filter - The filter to extract members from\n * @returns Array of member names\n */\nfunction extractMemberNamesFromFilter(filter: Filter): string[] {\n if ('member' in filter) {\n // SimpleFilter\n return [filter.member]\n } else if ('type' in filter && 'filters' in filter) {\n // GroupFilter - recursively extract from nested filters\n return filter.filters.flatMap(f => extractMemberNamesFromFilter(f))\n }\n\n return []\n}\n\n/**\n * Validate that all dashboard filters in a portlet's mapping exist and are valid\n * @param dashboardFilters - All available dashboard filters\n * @param filterMapping - The portlet's filter mapping\n * @param cubeMeta - Cube metadata for validation\n * @returns Object with validation result and list of invalid filter IDs\n */\nexport function validatePortletFilterMapping(\n dashboardFilters: DashboardFilter[] | undefined,\n filterMapping: string[] | undefined,\n cubeMeta: CubeMeta | null\n): { isValid: boolean; invalidFilterIds: string[]; missingFilterIds: string[] } {\n if (!filterMapping || !filterMapping.length) {\n return { isValid: true, invalidFilterIds: [], missingFilterIds: [] }\n }\n\n if (!dashboardFilters || !dashboardFilters.length) {\n // Mapping references filters that don't exist\n return {\n isValid: false,\n invalidFilterIds: [],\n missingFilterIds: filterMapping\n }\n }\n\n const invalidFilterIds: string[] = []\n const missingFilterIds: string[] = []\n\n filterMapping.forEach(filterId => {\n const dashboardFilter = dashboardFilters.find(df => df.id === filterId)\n\n if (!dashboardFilter) {\n // Filter ID in mapping doesn't exist in dashboard filters\n missingFilterIds.push(filterId)\n } else {\n // Check if filter is valid for the cube metadata\n const isValid = validateFilterForCube(dashboardFilter.filter, cubeMeta)\n if (!isValid) {\n invalidFilterIds.push(filterId)\n }\n }\n })\n\n return {\n isValid: invalidFilterIds.length === 0 && missingFilterIds.length === 0,\n invalidFilterIds,\n missingFilterIds\n }\n}\n\n/**\n * Extract all unique measures, dimensions, and timeDimensions used across all portlets in a dashboard\n * This helps create a filtered schema view showing only fields relevant to the dashboard\n * @param dashboardConfig - Dashboard configuration\n * @returns Object with unique measures, dimensions, and timeDimensions\n */\nexport function extractDashboardFields(\n dashboardConfig: DashboardConfig\n): { measures: Set<string>; dimensions: Set<string>; timeDimensions: Set<string> } {\n const measures = new Set<string>()\n const dimensions = new Set<string>()\n const timeDimensions = new Set<string>()\n\n // Iterate through all portlets\n dashboardConfig.portlets.forEach(portlet => {\n try {\n // Get query from analysisConfig (migrating legacy format if needed)\n const normalizedPortlet = ensureAnalysisConfig(portlet)\n const query = normalizedPortlet.analysisConfig.query\n\n // Helper to extract fields from a CubeQuery\n const extractFromCubeQuery = (cubeQuery: any) => {\n if (cubeQuery.measures && Array.isArray(cubeQuery.measures)) {\n cubeQuery.measures.forEach((measure: string) => measures.add(measure))\n }\n if (cubeQuery.dimensions && Array.isArray(cubeQuery.dimensions)) {\n cubeQuery.dimensions.forEach((dimension: string) => dimensions.add(dimension))\n }\n if (cubeQuery.timeDimensions && Array.isArray(cubeQuery.timeDimensions)) {\n cubeQuery.timeDimensions.forEach((td: any) => {\n if (td.dimension) {\n timeDimensions.add(td.dimension)\n }\n })\n }\n if (cubeQuery.filters) {\n extractFieldsFromFilters(cubeQuery.filters).forEach(field => {\n dimensions.add(field)\n })\n }\n }\n\n // Handle different query types\n if ('funnel' in query) {\n // ServerFunnelQuery - extract time dimension from funnel config\n const funnelQuery = query as any\n if (funnelQuery.funnel?.timeDimension) {\n timeDimensions.add(funnelQuery.funnel.timeDimension)\n }\n // Could also extract binding key dimensions if needed\n } else if ('queries' in query) {\n // MultiQueryConfig - extract from all sub-queries\n const multiQuery = query as any\n multiQuery.queries.forEach((subQuery: any) => extractFromCubeQuery(subQuery))\n } else {\n // Single CubeQuery\n extractFromCubeQuery(query)\n }\n } catch (e) {\n // Skip portlets with invalid query\n console.warn('Failed to extract fields from portlet:', portlet.id, e)\n }\n })\n\n return { measures, dimensions, timeDimensions }\n}\n\n/**\n * Extract field names from filters recursively\n * @param filters - Filter array\n * @returns Array of unique field names\n */\nfunction extractFieldsFromFilters(filters: Filter[]): string[] {\n const fields: string[] = []\n\n filters.forEach(filter => {\n if ('member' in filter) {\n // SimpleFilter\n fields.push(filter.member)\n } else if ('type' in filter && 'filters' in filter) {\n // GroupFilter - recurse\n fields.push(...extractFieldsFromFilters(filter.filters))\n }\n })\n\n return [...new Set(fields)] // Return unique fields\n}\n\n/**\n * Time dimension type from CubeQuery\n */\ntype TimeDimension = {\n dimension: string\n granularity?: string\n dateRange?: string[] | string\n}\n\n/**\n * Helper to get date range from a SimpleFilter (backward compatible)\n * Reads from both dateRange and values for compatibility\n * Handles both:\n * - Preset ranges: [\"this quarter\"], [\"last 7 days\"] (single string value)\n * - Custom ranges: [\"2024-01-01\", \"2024-12-31\"] (two date values)\n */\nfunction getDateRangeFromFilter(filter: SimpleFilter): string[] | string | undefined {\n // Prefer dateRange for backward compatibility, fall back to values\n if (filter.dateRange) {\n return filter.dateRange\n }\n if (filter.values && filter.values.length > 0) {\n // Single value = preset like \"this quarter\", return as string\n // Multiple values = custom date range, return as array\n return filter.values.length === 1 ? filter.values[0] : filter.values\n }\n return undefined\n}\n\n/**\n * Apply universal time filters to a portlet's timeDimensions\n * Universal time filters apply their dateRange to ALL time dimensions in the portlet\n *\n * @param dashboardFilters - All dashboard filters\n * @param filterMapping - Filter IDs that apply to this portlet\n * @param portletTimeDimensions - The portlet's existing timeDimensions array\n * @returns Updated timeDimensions array with date ranges applied\n */\nexport function applyUniversalTimeFilters(\n dashboardFilters: DashboardFilter[] | undefined,\n filterMapping: string[] | undefined,\n portletTimeDimensions: TimeDimension[] | undefined\n): TimeDimension[] | undefined {\n // Return as-is if no time dimensions in portlet (skip silently)\n if (!portletTimeDimensions || portletTimeDimensions.length === 0) {\n return portletTimeDimensions\n }\n\n // If no mapping specified, no filters apply\n if (!filterMapping || filterMapping.length === 0) {\n return portletTimeDimensions\n }\n\n // Find applicable universal time filters that have valid date ranges\n const universalTimeFilters = dashboardFilters\n ?.filter(df => df.isUniversalTime && filterMapping.includes(df.id))\n ?.filter(df => {\n // Must be a SimpleFilter with a valid dateRange\n if (!('member' in df.filter)) return false\n const simpleFilter = df.filter as SimpleFilter\n const dateRange = getDateRangeFromFilter(simpleFilter)\n return dateRange !== undefined\n })\n\n if (!universalTimeFilters || universalTimeFilters.length === 0) {\n return portletTimeDimensions\n }\n\n // Use the first universal time filter's dateRange (typically only one)\n const timeFilter = universalTimeFilters[0]\n const simpleFilter = timeFilter.filter as SimpleFilter\n const dateRange = getDateRangeFromFilter(simpleFilter)\n\n // Apply dateRange to ALL time dimensions (dashboard wins - overrides portlet dateRange)\n return portletTimeDimensions.map(td => ({\n ...td,\n dateRange: dateRange\n }))\n}\n","/**\n * CodeBlock Component\n * Displays syntax-highlighted code with copy-to-clipboard functionality\n */\n\nimport React, { useState, useEffect, useRef } from 'react'\nimport { getIcon } from '../../icons'\nimport { getSyntaxHighlighter, loadSyntaxHighlighter } from '../../utils/syntaxHighlighting'\nimport './CodeBlock.css'\n\ninterface CodeBlockProps {\n code: string\n language: 'json' | 'sql'\n title?: string\n maxHeight?: string\n height?: string\n className?: string\n /** Additional content to render on the right side of the header (before Copy button) */\n headerRight?: React.ReactNode\n}\n\nexport const CodeBlock: React.FC<CodeBlockProps> = ({\n code,\n language,\n title,\n maxHeight = '16rem',\n height,\n className = '',\n headerRight\n}) => {\n const [copied, setCopied] = useState(false)\n const codeRef = useRef<HTMLElement>(null)\n const CopyIcon = getIcon('copy')\n const CheckIcon = getIcon('check')\n\n // Apply syntax highlighting\n useEffect(() => {\n if (!codeRef.current) return\n const element = codeRef.current\n let isActive = true\n\n element.textContent = code\n\n loadSyntaxHighlighter()\n .then(() => {\n if (!isActive) return\n const hljs = getSyntaxHighlighter()\n if (!hljs) return\n element.innerHTML = hljs.highlight(code, { language }).value\n })\n .catch(() => {\n if (isActive) {\n element.textContent = code\n }\n })\n\n return () => {\n isActive = false\n }\n }, [code, language])\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(code)\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n } catch {\n // Fallback for older browsers\n const textArea = document.createElement('textarea')\n textArea.value = code\n textArea.style.position = 'fixed'\n textArea.style.left = '-999999px'\n document.body.appendChild(textArea)\n textArea.select()\n document.execCommand('copy')\n document.body.removeChild(textArea)\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n }\n }\n\n return (\n <div className={`dc:relative ${className}`}>\n {/* Header with title, optional extra controls, and copy button */}\n <div className=\"dc:flex dc:items-center dc:justify-between dc:mb-2 dc:gap-2\">\n {title && (\n <h4 className=\"dc:text-sm dc:font-semibold text-dc-text\">{title}</h4>\n )}\n <div className=\"dc:flex dc:items-center dc:gap-2 dc:ml-auto\">\n {headerRight}\n <button\n onClick={handleCopy}\n className=\"dc:px-2 dc:py-1 dc:text-xs dc:rounded hover:bg-dc-surface-secondary dc:border border-dc-border dc:transition-colors dc:flex dc:items-center dc:gap-1.5\"\n title={copied ? 'Copied!' : 'Copy to clipboard'}\n >\n {copied ? (\n <>\n <CheckIcon className=\"dc:w-3.5 dc:h-3.5 text-dc-success\" />\n <span className=\"text-dc-success\">Copied</span>\n </>\n ) : (\n <>\n <CopyIcon className=\"dc:w-3.5 dc:h-3.5 text-dc-text-secondary\" />\n <span className=\"text-dc-text-secondary\">Copy</span>\n </>\n )}\n </button>\n </div>\n </div>\n\n {/* Code block with syntax highlighting */}\n <div\n className=\"bg-dc-surface-secondary dc:border border-dc-border dc:rounded dc:overflow-auto\"\n style={height ? { height, minHeight: height, maxHeight: height } : { maxHeight }}\n >\n <pre className=\"dc:p-3 dc:text-xs dc:m-0\">\n <code\n ref={codeRef}\n className={`hljs language-${language}`}\n >\n {code}\n </code>\n </pre>\n </div>\n </div>\n )\n}\n\nexport default CodeBlock\n","/**\n * Unified Color Palette System\n * Each palette contains coordinated series and gradient colors that work well together\n */\n\nexport interface ColorPalette {\n name: string\n label: string\n colors: string[] // For series-based charts (bar, line, pie, area, scatter, radar, etc.)\n gradient: string[] // For gradient-based charts (bubble, activity grid)\n}\n\n// Predefined color palettes with visually coordinated series and gradient colors\nexport const COLOR_PALETTES: ColorPalette[] = [\n {\n name: 'default',\n label: 'Default',\n colors: [\n '#3b82f6', // blue\n '#10b981', // green\n '#f59e0b', // yellow\n '#ef4444', // red\n '#8b5cf6', // purple\n '#f97316', // orange\n '#06b6d4', // cyan\n '#84cc16', // lime\n ],\n gradient: [\n '#fde725', // yellow (light - for low values)\n '#7ad151', // green\n '#22a884', // green-teal\n '#2a788e', // teal\n '#414487', // purple-blue\n '#440154', // dark purple (dark - for high values)\n ]\n },\n {\n name: 'ocean',\n label: 'Ocean',\n colors: [\n '#1e3a8a', // deep blue\n '#1e40af', // blue\n '#2563eb', // bright blue\n '#3b82f6', // light blue\n '#06b6d4', // cyan\n '#0891b2', // dark cyan\n '#0e7490', // teal\n '#0f766e', // dark teal\n ],\n gradient: [\n '#38bdf8', // cyan blue (light - for low values)\n '#0ea5e9', // light blue\n '#0284c7', // bright blue\n '#0369a1', // medium blue\n '#075985', // dark blue\n '#0c4a6e', // very dark blue (dark - for high values)\n ]\n },\n {\n name: 'sunset',\n label: 'Sunset',\n colors: [\n '#dc2626', // red\n '#ea580c', // orange-red\n '#f59e0b', // orange\n '#eab308', // yellow-orange\n '#d97706', // amber\n '#b45309', // dark amber\n '#92400e', // brown\n '#7c2d12', // dark brown\n ],\n gradient: [\n '#fbbf24', // light orange (light - for low values)\n '#f59e0b', // orange\n '#d97706', // amber\n '#b45309', // dark amber\n '#92400e', // brown\n '#7c2d12', // dark brown (dark - for high values)\n ]\n },\n {\n name: 'forest',\n label: 'Forest',\n colors: [\n '#166534', // dark green\n '#15803d', // green\n '#16a34a', // bright green\n '#22c55e', // light green\n '#4ade80', // lighter green\n '#65a30d', // lime green\n '#84cc16', // lime\n '#a3e635', // light lime\n ],\n gradient: [\n '#4ade80', // lighter green (light - for low values)\n '#22c55e', // light green\n '#16a34a', // bright green\n '#15803d', // green\n '#166534', // dark green\n '#14532d', // very dark green (dark - for high values)\n ]\n },\n {\n name: 'purple',\n label: 'Purple',\n colors: [\n '#581c87', // dark purple\n '#7c3aed', // purple\n '#8b5cf6', // bright purple\n '#a855f7', // light purple\n '#c084fc', // lighter purple\n '#e879f9', // magenta\n '#f0abfc', // light magenta\n '#fbbf24', // accent yellow\n ],\n gradient: [\n '#a855f7', // light purple (light - for low values)\n '#8b5cf6', // bright purple\n '#7c3aed', // purple\n '#6d28d9', // medium purple\n '#581c87', // dark purple\n '#4c1d95', // very dark purple (dark - for high values)\n ]\n },\n {\n name: 'monochrome',\n label: 'Monochrome',\n colors: [\n '#1f2937', // very dark gray\n '#374151', // dark gray\n '#4b5563', // medium gray\n '#6b7280', // gray\n '#9ca3af', // light gray\n '#d1d5db', // lighter gray\n '#e5e7eb', // very light gray\n '#f3f4f6', // almost white\n ],\n gradient: [\n '#9ca3af', // light gray (light - for low values)\n '#6b7280', // gray\n '#4b5563', // medium gray\n '#374151', // dark gray\n '#1f2937', // very dark gray\n '#111827', // black (dark - for high values)\n ]\n },\n {\n name: 'pastel',\n label: 'Pastel',\n colors: [\n '#93c5fd', // light blue\n '#86efac', // light green\n '#fde047', // light yellow\n '#fca5a5', // light red\n '#c4b5fd', // light purple\n '#fdba74', // light orange\n '#67e8f9', // light cyan\n '#bef264', // light lime\n ],\n gradient: [\n '#fed7aa', // very light orange (light - for low values)\n '#ddd6fe', // very light purple\n '#fecaca', // very light red\n '#fef08a', // very light yellow\n '#a7f3d0', // very light green\n '#bfdbfe', // very light blue (darker - for high values)\n ]\n },\n {\n name: 'vibrant',\n label: 'Vibrant',\n colors: [\n '#0000ff', // pure blue\n '#00ff00', // pure green\n '#ffff00', // pure yellow\n '#ff0000', // pure red\n '#ff00ff', // pure magenta\n '#ff8000', // pure orange\n '#00ffff', // pure cyan\n '#8000ff', // pure violet\n ],\n gradient: [\n '#ffff00', // yellow (light - for low values)\n '#80ff00', // lime\n '#00ff80', // green\n '#00ffff', // cyan\n '#0080ff', // blue\n '#4000ff', // blue-violet (dark - for high values)\n ]\n },\n {\n name: 'd3Category10',\n label: 'D3 Category 10',\n colors: [\n '#1f77b4', // blue\n '#ff7f0e', // orange\n '#2ca02c', // green\n '#d62728', // red\n '#9467bd', // purple\n '#8c564b', // brown\n '#e377c2', // pink\n '#7f7f7f', // gray\n '#bcbd22', // olive\n '#17becf', // cyan\n ],\n gradient: [\n '#9467bd', // purple (light - for low values)\n '#d62728', // red\n '#ff7f0e', // orange\n '#bcbd22', // olive\n '#2ca02c', // green\n '#1f77b4', // blue (dark - for high values)\n ]\n },\n {\n name: 'd3Tableau10',\n label: 'D3 Tableau 10',\n colors: [\n '#4e79a7', // blue\n '#f28e2c', // orange\n '#e15759', // red\n '#76b7b2', // teal\n '#59a14f', // green\n '#edc949', // yellow\n '#af7aa1', // purple\n '#ff9da7', // pink\n '#9c755f', // brown\n '#bab0ab', // gray\n ],\n gradient: [\n '#e15759', // red (light - for low values)\n '#f28e2c', // orange\n '#edc949', // yellow\n '#59a14f', // green\n '#76b7b2', // teal\n '#4e79a7', // blue (dark - for high values)\n ]\n },\n {\n name: 'colorBrewerSet1',\n label: 'ColorBrewer Set 1',\n colors: [\n '#e41a1c', // red\n '#377eb8', // blue\n '#4daf4a', // green\n '#984ea3', // purple\n '#ff7f00', // orange\n '#ffff33', // yellow\n '#a65628', // brown\n '#f781bf', // pink\n '#999999', // gray\n ],\n gradient: [\n '#984ea3', // purple (light - for low values)\n '#e41a1c', // red\n '#ff7f00', // orange\n '#ffff33', // yellow\n '#4daf4a', // green\n '#377eb8', // blue (dark - for high values)\n ]\n },\n {\n name: 'colorBrewerSet2',\n label: 'ColorBrewer Set 2',\n colors: [\n '#66c2a5', // teal\n '#fc8d62', // orange\n '#8da0cb', // blue\n '#e78ac3', // pink\n '#a6d854', // lime\n '#ffd92f', // yellow\n '#e5c494', // tan\n '#b3b3b3', // gray\n ],\n gradient: [\n '#e78ac3', // pink (light - for low values)\n '#fc8d62', // orange\n '#ffd92f', // yellow\n '#a6d854', // lime\n '#66c2a5', // teal\n '#8da0cb', // blue (dark - for high values)\n ]\n },\n {\n name: 'colorBrewerDark2',\n label: 'ColorBrewer Dark 2',\n colors: [\n '#1b9e77', // dark teal\n '#d95f02', // dark orange\n '#7570b3', // dark blue\n '#e7298a', // dark pink\n '#66a61e', // dark green\n '#e6ab02', // dark yellow\n '#a6761d', // dark brown\n '#666666', // dark gray\n ],\n gradient: [\n '#e7298a', // dark pink (light - for low values)\n '#d95f02', // dark orange\n '#e6ab02', // dark yellow\n '#66a61e', // dark green\n '#1b9e77', // dark teal\n '#7570b3', // dark blue (dark - for high values)\n ]\n },\n {\n name: 'colorBrewerPaired',\n label: 'ColorBrewer Paired',\n colors: [\n '#a6cee3', // light blue\n '#1f78b4', // blue\n '#b2df8a', // light green\n '#33a02c', // green\n '#fb9a99', // light red\n '#e31a1c', // red\n '#fdbf6f', // light orange\n '#ff7f00', // orange\n '#cab2d6', // light purple\n '#6a3d9a', // purple\n '#ffff99', // light yellow\n '#b15928', // brown\n ],\n gradient: [\n '#6a3d9a', // purple (light - for low values)\n '#e31a1c', // red\n '#ff7f00', // orange\n '#ffff99', // light yellow\n '#33a02c', // green\n '#1f78b4', // blue (dark - for high values)\n ]\n },\n {\n name: 'viridis',\n label: 'Viridis',\n colors: [\n '#440154', // dark purple\n '#482677', // purple\n '#3f4a8a', // blue-purple\n '#31678e', // blue\n '#26838f', // teal\n '#1f9d8a', // green-teal\n '#6cce5a', // green\n '#b6de2b', // yellow-green\n ],\n gradient: [\n '#b6de2b', // yellow-green (light - for low values)\n '#6cce5a', // green\n '#1f9d8a', // green-teal\n '#26838f', // teal\n '#31678e', // blue\n '#3f4a8a', // blue-purple\n '#482677', // purple\n '#440154', // dark purple (dark - for high values)\n ]\n },\n {\n name: 'plasma',\n label: 'Plasma',\n colors: [\n '#0c0786', // dark blue\n '#5c01a6', // purple\n '#900da4', // magenta\n '#bf3984', // pink\n '#e16462', // coral\n '#f99b45', // orange\n '#fcce25', // yellow\n '#f0f921', // bright yellow\n ],\n gradient: [\n '#f0f921', // bright yellow (light - for low values)\n '#fcce25', // yellow\n '#f99b45', // orange\n '#e16462', // coral\n '#bf3984', // pink\n '#900da4', // magenta\n '#5c01a6', // purple\n '#0c0786', // dark blue (dark - for high values)\n ]\n },\n {\n name: 'inferno',\n label: 'Inferno',\n colors: [\n '#000003', // black\n '#1f0c47', // dark blue\n '#550f6d', // purple\n '#88226a', // magenta\n '#a83655', // red\n '#cc4f39', // orange-red\n '#e6862a', // orange\n '#fec228', // yellow\n ],\n gradient: [\n '#fec228', // yellow (light - for low values)\n '#e6862a', // orange\n '#cc4f39', // orange-red\n '#a83655', // red\n '#88226a', // magenta\n '#550f6d', // purple\n '#1f0c47', // dark blue\n '#000003', // black (dark - for high values)\n ]\n },\n {\n name: 'magma',\n label: 'Magma',\n colors: [\n '#000003', // black\n '#140b34', // dark purple\n '#3b0f6f', // purple\n '#641a80', // magenta\n '#8b2981', // pink\n '#b63679', // coral\n '#de4968', // red\n '#fd9f6c', // orange\n ],\n gradient: [\n '#fd9f6c', // orange (light - for low values)\n '#de4968', // red\n '#b63679', // coral\n '#8b2981', // pink\n '#641a80', // magenta\n '#3b0f6f', // purple\n '#140b34', // dark purple\n '#000003', // black (dark - for high values)\n ]\n },\n {\n name: 'cividis',\n label: 'Cividis',\n colors: [\n '#00204c', // dark blue\n '#003f5c', // blue\n '#2c4b7a', // blue\n '#51576f', // blue-gray\n '#7f6874', // gray\n '#a8786e', // brown\n '#d2906d', // orange\n '#ffb570', // yellow\n ],\n gradient: [\n '#ffb570', // yellow (light - for low values)\n '#d2906d', // orange\n '#a8786e', // brown\n '#7f6874', // gray\n '#51576f', // blue-gray\n '#2c4b7a', // blue\n '#003f5c', // blue\n '#00204c', // dark blue (dark - for high values)\n ]\n },\n {\n name: 'turbo',\n label: 'Turbo',\n colors: [\n '#30123b', // purple\n '#4454c4', // blue\n '#1dd3c0', // cyan\n '#42f465', // green\n '#b2df22', // lime\n '#faba39', // yellow\n '#f66c19', // orange\n '#c42e02', // red\n ],\n gradient: [\n '#c42e02', // red (light - for low values)\n '#f66c19', // orange\n '#faba39', // yellow\n '#b2df22', // lime\n '#42f465', // green\n '#1dd3c0', // cyan\n '#4454c4', // blue\n '#30123b', // purple (dark - for high values)\n ]\n },\n {\n name: 'warm',\n label: 'Warm',\n colors: [\n '#8b0000', // dark red\n '#b22222', // red\n '#cd5c5c', // light red\n '#ff6347', // tomato\n '#ff8c00', // dark orange\n '#ffa500', // orange\n '#ffd700', // gold\n '#ffff00', // yellow\n ],\n gradient: [\n '#ffd700', // gold (light - for low values)\n '#ffa500', // orange\n '#ff8c00', // dark orange\n '#ff6347', // tomato\n '#b22222', // red\n '#8b0000', // dark red (dark - for high values)\n ]\n },\n {\n name: 'cool',\n label: 'Cool',\n colors: [\n '#000080', // navy\n '#0000ff', // blue\n '#4169e1', // royal blue\n '#00bfff', // deep sky blue\n '#00ffff', // cyan\n '#40e0d0', // turquoise\n '#20b2aa', // light sea green\n '#008b8b', // dark cyan\n ],\n gradient: [\n '#40e0d0', // turquoise (light - for low values)\n '#00ffff', // cyan\n '#00bfff', // deep sky blue\n '#4169e1', // royal blue\n '#0000ff', // blue\n '#000080', // navy (dark - for high values)\n ]\n },\n {\n name: 'earth',\n label: 'Earth',\n colors: [\n '#8b4513', // saddle brown\n '#a0522d', // sienna\n '#cd853f', // peru\n '#daa520', // goldenrod\n '#d2691e', // chocolate\n '#bc8f8f', // rosy brown\n '#f4a460', // sandy brown\n '#deb887', // burlywood\n ],\n gradient: [\n '#f4a460', // sandy brown (light - for low values)\n '#d2691e', // chocolate\n '#daa520', // goldenrod\n '#cd853f', // peru\n '#a0522d', // sienna\n '#8b4513', // saddle brown (dark - for high values)\n ]\n },\n {\n name: 'autumn',\n label: 'Autumn',\n colors: [\n '#8b0000', // dark red\n '#a0522d', // sienna\n '#cd853f', // peru\n '#daa520', // goldenrod\n '#ff8c00', // dark orange\n '#ff4500', // orange red\n '#dc143c', // crimson\n '#b22222', // fire brick\n ],\n gradient: [\n '#ff4500', // orange red (light - for low values)\n '#ff8c00', // dark orange\n '#daa520', // goldenrod\n '#cd853f', // peru\n '#a0522d', // sienna\n '#8b0000', // dark red (dark - for high values)\n ]\n },\n {\n name: 'spring',\n label: 'Spring',\n colors: [\n '#32cd32', // lime green\n '#98fb98', // pale green\n '#90ee90', // light green\n '#ffb6c1', // light pink\n '#ffc0cb', // pink\n '#ffffe0', // light yellow\n '#f0fff0', // honeydew\n '#e0ffff', // light cyan\n ],\n gradient: [\n '#e0ffff', // light cyan (light - for low values)\n '#ffc0cb', // pink\n '#ffb6c1', // light pink\n '#ffffe0', // light yellow\n '#98fb98', // pale green\n '#32cd32', // lime green (dark - for high values)\n ]\n },\n {\n name: 'winter',\n label: 'Winter',\n colors: [\n '#191970', // midnight blue\n '#4682b4', // steel blue\n '#87ceeb', // sky blue\n '#b0e0e6', // powder blue\n '#e0ffff', // light cyan\n '#f0f8ff', // alice blue\n '#c0c0c0', // silver\n '#708090', // slate gray\n ],\n gradient: [\n '#f0f8ff', // alice blue (light - for low values)\n '#e0ffff', // light cyan\n '#b0e0e6', // powder blue\n '#87ceeb', // sky blue\n '#4682b4', // steel blue\n '#191970', // midnight blue (dark - for high values)\n ]\n },\n {\n name: 'neon',\n label: 'Neon',\n colors: [\n '#ff0080', // neon pink\n '#00ff80', // neon green\n '#8000ff', // neon purple\n '#ff8000', // neon orange\n '#0080ff', // neon blue\n '#80ff00', // neon lime\n '#ff0040', // neon red\n '#40ff00', // bright green\n ],\n gradient: [\n '#ff0080', // neon pink (light - for low values)\n '#ff8000', // neon orange\n '#80ff00', // neon lime\n '#00ff80', // neon green\n '#0080ff', // neon blue\n '#8000ff', // neon purple (dark - for high values)\n ]\n },\n {\n name: 'retro',\n label: 'Retro',\n colors: [\n '#ff69b4', // hot pink\n '#ffd700', // gold\n '#32cd32', // lime green\n '#00ced1', // dark turquoise\n '#ff6347', // tomato\n '#9370db', // medium purple\n '#ffa500', // orange\n '#20b2aa', // light sea green\n ],\n gradient: [\n '#00ced1', // dark turquoise (light - for low values)\n '#32cd32', // lime green\n '#ffd700', // gold\n '#ff6347', // tomato\n '#ff69b4', // hot pink\n '#9370db', // medium purple (dark - for high values)\n ]\n },\n {\n name: 'corporate',\n label: 'Corporate',\n colors: [\n '#003366', // dark blue\n '#0066cc', // blue\n '#336699', // steel blue\n '#6699cc', // light blue\n '#4d4d4d', // dark gray\n '#808080', // gray\n '#b3b3b3', // light gray\n '#cccccc', // very light gray\n ],\n gradient: [\n '#b3b3b3', // light gray (light - for low values)\n '#808080', // gray\n '#6699cc', // light blue\n '#336699', // steel blue\n '#0066cc', // blue\n '#003366', // dark blue (dark - for high values)\n ]\n },\n {\n name: 'material',\n label: 'Material Design',\n colors: [\n '#f44336', // red\n '#e91e63', // pink\n '#9c27b0', // purple\n '#673ab7', // deep purple\n '#3f51b5', // indigo\n '#2196f3', // blue\n '#03a9f4', // light blue\n '#00bcd4', // cyan\n ],\n gradient: [\n '#e91e63', // pink (light - for low values)\n '#03a9f4', // light blue\n '#00bcd4', // cyan\n '#2196f3', // blue\n '#3f51b5', // indigo\n '#673ab7', // deep purple (dark - for high values)\n ]\n }\n]\n\n/**\n * Get a color palette by name, with fallback to default\n */\nexport function getColorPalette(paletteName?: string): ColorPalette {\n if (!paletteName) {\n return COLOR_PALETTES[0] // default palette\n }\n \n const palette = COLOR_PALETTES.find(p => p.name === paletteName)\n return palette || COLOR_PALETTES[0] // fallback to default\n}\n\n/**\n * Get just the series colors for a palette\n */\nexport function getSeriesColors(paletteName?: string): string[] {\n return getColorPalette(paletteName).colors\n}\n\n/**\n * Get just the gradient colors for a palette\n */\nexport function getGradientColors(paletteName?: string): string[] {\n return getColorPalette(paletteName).gradient\n}\n\n/**\n * Chart types that use series colors (discrete categories)\n */\nexport const SERIES_CHART_TYPES = [\n 'bar', 'line', 'area', 'pie', 'scatter', 'radar', 'radialBar', 'treeMap'\n] as const\n\n/**\n * Chart types that use gradient colors (continuous values)\n */\nexport const GRADIENT_CHART_TYPES = [\n 'bubble', 'activityGrid'\n] as const\n\n/**\n * Determine if a chart type uses gradient colors\n */\nexport function usesGradientColors(chartType: string): boolean {\n return GRADIENT_CHART_TYPES.includes(chartType as any)\n}","import React, { useEffect, useCallback } from 'react'\n\nexport interface ModalProps {\n isOpen: boolean\n onClose: () => void\n title?: string\n size?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'full' | 'fullscreen' | 'fullscreen-mobile'\n closeOnBackdropClick?: boolean\n closeOnEscape?: boolean\n showCloseButton?: boolean\n className?: string\n children: React.ReactNode\n footer?: React.ReactNode\n noPadding?: boolean\n}\n\nconst Modal: React.FC<ModalProps> = ({\n isOpen,\n onClose,\n title,\n size = 'md',\n closeOnBackdropClick = true,\n closeOnEscape = true,\n showCloseButton = true,\n children,\n footer,\n noPadding = false\n}) => {\n // Handle ESC key press\n const handleEscapeKey = useCallback((event: KeyboardEvent) => {\n if (event.key === 'Escape' && closeOnEscape) {\n onClose()\n }\n }, [closeOnEscape, onClose])\n\n\n // Manage ESC key listener and body scroll\n useEffect(() => {\n if (isOpen) {\n // Add ESC key listener\n if (closeOnEscape) {\n document.addEventListener('keydown', handleEscapeKey)\n }\n\n // Prevent body scroll when modal is open\n document.body.style.overflow = 'hidden'\n } else {\n // Restore body scroll\n document.body.style.overflow = 'unset'\n }\n\n // Cleanup\n return () => {\n document.removeEventListener('keydown', handleEscapeKey)\n document.body.style.overflow = 'unset'\n }\n }, [isOpen, closeOnEscape, handleEscapeKey])\n\n\n if (!isOpen) return null\n\n const getSizeClasses = () => {\n switch (size) {\n case 'sm':\n return 'dc:max-w-md'\n case 'md':\n return 'dc:max-w-lg'\n case 'lg':\n return 'dc:max-w-2xl'\n case 'xl':\n return 'dc:max-w-6xl'\n case 'xxl':\n return 'dc:max-w-[1400px]' // Good for retina/mac displays\n case 'full':\n return 'dc:max-w-7xl'\n case 'fullscreen':\n return 'dc:w-[90vw] dc:h-[90vh] dc:max-w-none'\n case 'fullscreen-mobile':\n return 'dc:w-full dc:h-full dc:md:w-[min(90vw,1400px)] dc:md:h-[90vh]'\n default:\n return 'dc:max-w-lg'\n }\n }\n\n return (\n <div\n className={`dc:fixed dc:inset-0 dc:z-50 dc:backdrop-blur-md ${size === 'fullscreen-mobile' ? 'dc:flex dc:md:flex dc:md:items-center dc:md:justify-center' : 'dc:flex dc:items-center dc:justify-center'}`}\n style={{ backgroundColor: 'var(--dc-overlay)' }}\n onClick={closeOnBackdropClick ? onClose : undefined}\n >\n <div\n className={`dc:relative bg-dc-surface dc:border border-dc-border ${size === 'fullscreen-mobile' ? 'dc:rounded-none dc:md:rounded-lg' : 'dc:rounded-lg'} ${size === 'fullscreen' || size === 'fullscreen-mobile' ? '' : 'dc:mx-4'} ${getSizeClasses()} ${size === 'fullscreen' || size === 'fullscreen-mobile' ? '' : 'dc:max-h-[90vh]'} dc:flex dc:flex-col`}\n style={{ boxShadow: 'var(--dc-shadow-2xl)' }}\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? 'modal-title' : undefined}\n >\n {/* Header */}\n {(title || showCloseButton) && (\n <div className=\"dc:flex dc:items-center dc:justify-between dc:px-6 dc:py-4 dc:border-b border-dc-border\">\n {title && (\n <h2 id=\"modal-title\" className=\"dc:text-xl dc:font-semibold text-dc-text\">\n {title}\n </h2>\n )}\n {showCloseButton && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-dc-text-muted hover:text-dc-text-secondary dc:transition-colors dc:p-2 dc:-mr-2\"\n aria-label=\"Close modal\"\n >\n <svg width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n )}\n </div>\n )}\n\n {/* Content */}\n <div className={`dc:flex-1 dc:overflow-y-auto ${noPadding ? '' : 'dc:px-6 dc:py-4'}`}>\n {children}\n </div>\n\n {/* Footer */}\n {footer && (\n <div className=\"dc:flex dc:items-center dc:justify-end dc:space-x-3 dc:px-6 dc:py-4 dc:border-t border-dc-border bg-dc-surface-secondary\">\n {React.Children.toArray(footer)}\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default Modal","/**\n * ID Generation Utilities for AnalysisBuilder\n */\n\n/**\n * Generate a unique ID for items\n */\nexport function generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`\n}\n\n/**\n * Generate letter label for metrics (A, B, C, ..., AA, AB, ...)\n */\nexport function generateMetricLabel(index: number): string {\n let label = ''\n let n = index\n do {\n label = String.fromCharCode(65 + (n % 26)) + label\n n = Math.floor(n / 26) - 1\n } while (n >= 0)\n return label\n}\n","/**\n * Field Metadata Utilities for AnalysisBuilder\n *\n * Functions for working with field metadata from the schema.\n */\n\nimport type { FieldOption, FieldType } from '../types'\nimport type { MetaResponse, MetaField } from '../../../shared/types'\n\n/**\n * Get cube name from a field name (e.g., \"Employees.count\" -> \"Employees\")\n */\nexport function getCubeNameFromField(fieldName: string): string {\n return fieldName.split('.')[0]\n}\n\n/**\n * Get field short name from full name (e.g., \"Employees.count\" -> \"count\")\n */\nexport function getFieldShortName(fieldName: string): string {\n const parts = fieldName.split('.')\n return parts.length > 1 ? parts.slice(1).join('.') : fieldName\n}\n\n/**\n * Find field metadata from schema\n */\nexport function findFieldInSchema(\n fieldName: string,\n schema: MetaResponse | null\n): { field: MetaField; cubeName: string; fieldType: FieldType } | null {\n if (!schema) return null\n\n for (const cube of schema.cubes) {\n // Check measures\n const measure = cube.measures.find((m) => m.name === fieldName)\n if (measure) {\n return { field: measure, cubeName: cube.name, fieldType: 'measure' }\n }\n\n // Check dimensions\n const dimension = cube.dimensions.find((d) => d.name === fieldName)\n if (dimension) {\n return {\n field: dimension,\n cubeName: cube.name,\n fieldType: dimension.type === 'time' ? 'timeDimension' : 'dimension'\n }\n }\n }\n\n return null\n}\n\n/**\n * Get display title for a field\n */\nexport function getFieldTitle(fieldName: string, schema: MetaResponse | null): string {\n const found = findFieldInSchema(fieldName, schema)\n if (found) {\n return found.field.title || found.field.shortTitle || fieldName\n }\n return fieldName\n}\n\n/**\n * Determine field type from metadata\n */\nexport function getFieldType(field: MetaField): FieldType {\n if (field.type === 'time') return 'timeDimension'\n // Measures typically have aggregation types\n if (['count', 'countDistinct', 'sum', 'avg', 'min', 'max', 'runningTotal', 'countDistinctApprox'].includes(field.type)) {\n return 'measure'\n }\n return 'dimension'\n}\n\n/**\n * Convert schema to flat list of field options\n */\nexport function schemaToFieldOptions(\n schema: MetaResponse | null,\n mode: 'metrics' | 'breakdown' | 'filter' | 'dimensionFilter'\n): FieldOption[] {\n if (!schema) return []\n\n const options: FieldOption[] = []\n\n for (const cube of schema.cubes) {\n if (mode === 'metrics') {\n // Add measures only\n for (const measure of cube.measures) {\n options.push({\n name: measure.name,\n title: measure.title || measure.shortTitle || measure.name,\n shortTitle: measure.shortTitle || measure.title || measure.name,\n type: measure.type,\n description: measure.description,\n cubeName: cube.name,\n fieldType: 'measure'\n })\n }\n } else if (mode === 'breakdown' || mode === 'dimensionFilter') {\n // Add dimensions only (both regular and time)\n // 'dimensionFilter' is used for funnel step filters where measures don't work\n for (const dimension of cube.dimensions) {\n const isTime = dimension.type === 'time'\n options.push({\n name: dimension.name,\n title: dimension.title || dimension.shortTitle || dimension.name,\n shortTitle: dimension.shortTitle || dimension.title || dimension.name,\n type: dimension.type,\n description: dimension.description,\n cubeName: cube.name,\n fieldType: isTime ? 'timeDimension' : 'dimension'\n })\n }\n } else {\n // 'filter' mode - add BOTH measures AND dimensions\n // Add measures first\n for (const measure of cube.measures) {\n options.push({\n name: measure.name,\n title: measure.title || measure.shortTitle || measure.name,\n shortTitle: measure.shortTitle || measure.title || measure.name,\n type: measure.type,\n description: measure.description,\n cubeName: cube.name,\n fieldType: 'measure'\n })\n }\n // Add dimensions (both regular and time)\n for (const dimension of cube.dimensions) {\n const isTime = dimension.type === 'time'\n options.push({\n name: dimension.name,\n title: dimension.title || dimension.shortTitle || dimension.name,\n shortTitle: dimension.shortTitle || dimension.title || dimension.name,\n type: dimension.type,\n description: dimension.description,\n cubeName: cube.name,\n fieldType: isTime ? 'timeDimension' : 'dimension'\n })\n }\n }\n }\n\n return options\n}\n\n/**\n * Filter field options by search term\n */\nexport function filterFieldOptions(\n options: FieldOption[],\n searchTerm: string,\n selectedCube?: string | null\n): FieldOption[] {\n let filtered = options\n\n // Filter by cube if selected\n if (selectedCube && selectedCube !== 'all') {\n filtered = filtered.filter((opt) => opt.cubeName === selectedCube)\n }\n\n // Filter by search term\n if (searchTerm.trim()) {\n const term = searchTerm.toLowerCase()\n filtered = filtered.filter(\n (opt) =>\n opt.name.toLowerCase().includes(term) ||\n opt.title.toLowerCase().includes(term) ||\n (opt.description?.toLowerCase().includes(term) ?? false)\n )\n }\n\n return filtered\n}\n\n/**\n * Group field options by cube\n */\nexport function groupFieldsByCube(options: FieldOption[]): Map<string, FieldOption[]> {\n const grouped = new Map<string, FieldOption[]>()\n\n for (const option of options) {\n const existing = grouped.get(option.cubeName) || []\n existing.push(option)\n grouped.set(option.cubeName, existing)\n }\n\n return grouped\n}\n\n/**\n * Get list of cube names from schema\n */\nexport function getCubeNames(schema: MetaResponse | null): string[] {\n if (!schema) return []\n return schema.cubes.map((cube) => cube.name)\n}\n\n/**\n * Get cube title by name\n */\nexport function getCubeTitle(cubeName: string, schema: MetaResponse | null): string {\n if (!schema) return cubeName\n const cube = schema.cubes.find((c) => c.name === cubeName)\n return cube?.title || cubeName\n}\n\n/**\n * Get all cubes reachable from a source cube via join relationships\n * Includes the source cube itself plus all cubes it has joins to\n *\n * @param sourceCube - Name of the cube to find related cubes for\n * @param schema - Full schema with all cubes\n * @returns Set of cube names that are reachable from the source\n */\nexport function getRelatedCubeNames(\n sourceCube: string,\n schema: MetaResponse | null\n): Set<string> {\n const related = new Set<string>()\n\n if (!schema) return related\n\n // Always include the source cube\n related.add(sourceCube)\n\n // Find the source cube and get its relationships\n const cube = schema.cubes.find((c) => c.name === sourceCube)\n if (!cube || !cube.relationships) return related\n\n // Add all directly related cubes\n for (const rel of cube.relationships) {\n related.add(rel.targetCube)\n }\n\n return related\n}\n\n/**\n * Filter schema to include only cubes reachable from a source cube\n * This is used for funnel step filters where cross-cube filtering is supported\n *\n * @param sourceCube - Name of the cube to find related cubes for\n * @param schema - Full schema with all cubes\n * @returns Filtered schema containing only reachable cubes\n */\nexport function getRelatedCubesSchema(\n sourceCube: string,\n schema: MetaResponse | null\n): MetaResponse | null {\n if (!schema) return null\n\n const relatedNames = getRelatedCubeNames(sourceCube, schema)\n\n return {\n cubes: schema.cubes\n .filter((c) => relatedNames.has(c.name))\n .map((c) => ({\n ...c,\n description: c.description || '',\n })),\n }\n}\n","/**\n * Recent Fields Storage Utilities for AnalysisBuilder\n *\n * Functions for tracking and retrieving recently used fields.\n */\n\nimport type { FieldOption, RecentFieldsStorage } from '../types'\nimport type { MetaResponse } from '../../../shared/types'\nimport { schemaToFieldOptions } from './fieldUtils'\n\nconst RECENT_FIELDS_KEY = 'drizzle-cube-recent-fields'\nconst MAX_RECENT_FIELDS = 10\n\n/**\n * Get recent fields from localStorage\n */\nexport function getRecentFields(): RecentFieldsStorage {\n try {\n const stored = localStorage.getItem(RECENT_FIELDS_KEY)\n if (stored) {\n return JSON.parse(stored)\n }\n } catch {\n // Ignore errors\n }\n return { metrics: [], breakdowns: [] }\n}\n\n/**\n * Add a field to recent fields\n */\nexport function addRecentField(fieldName: string, mode: 'metrics' | 'breakdowns'): void {\n try {\n const recent = getRecentFields()\n const list = recent[mode]\n\n // Remove if already exists\n const filtered = list.filter((f) => f !== fieldName)\n\n // Add to front\n filtered.unshift(fieldName)\n\n // Limit size\n recent[mode] = filtered.slice(0, MAX_RECENT_FIELDS)\n\n localStorage.setItem(RECENT_FIELDS_KEY, JSON.stringify(recent))\n } catch {\n // Ignore errors\n }\n}\n\n/**\n * Get recent field options from schema\n */\nexport function getRecentFieldOptions(\n schema: MetaResponse | null,\n mode: 'metrics' | 'breakdown' | 'filter' | 'dimensionFilter',\n recentFieldNames: string[]\n): FieldOption[] {\n if (!schema || recentFieldNames.length === 0) return []\n\n const allOptions = schemaToFieldOptions(schema, mode)\n const recentOptions: FieldOption[] = []\n\n for (const fieldName of recentFieldNames) {\n const option = allOptions.find((opt) => opt.name === fieldName)\n if (option) {\n recentOptions.push(option)\n }\n }\n\n return recentOptions\n}\n","/**\n * Funnel Mode Adapter\n *\n * Handles conversion between UI state and AnalysisConfig for funnel mode.\n * Converts funnelSteps UI state to/from ServerFunnelQuery format.\n */\n\nimport type { ModeAdapter, ValidationResult } from './modeAdapter'\nimport { generateId } from '../components/AnalysisBuilder/utils'\nimport type {\n AnalysisConfig,\n FunnelAnalysisConfig,\n AnalysisType,\n ChartConfig,\n} from '../types/analysisConfig'\nimport type { FunnelStepState, FunnelBindingKey, Filter } from '../types'\nimport type { ServerFunnelQuery, ServerFunnelStep } from '../types/funnel'\n\n// ============================================================================\n// Funnel Slice State Type\n// ============================================================================\n\n/**\n * The shape of funnel mode state in the store.\n * This is what the adapter's load() returns and save() receives.\n */\nexport interface FunnelSliceState {\n /** The cube all funnel steps use (single-cube mode) */\n funnelCube: string | null\n /** Funnel step definitions */\n funnelSteps: FunnelStepState[]\n /** Currently selected step index */\n activeFunnelStepIndex: number\n /** Time dimension for temporal ordering */\n funnelTimeDimension: string | null\n /** Binding key that links entities across steps */\n funnelBindingKey: FunnelBindingKey | null\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Convert FunnelSliceState to ServerFunnelQuery\n */\nfunction stateToServerQuery(state: FunnelSliceState): ServerFunnelQuery {\n // Convert binding key to server format\n let bindingKey: ServerFunnelQuery['funnel']['bindingKey'] = ''\n if (state.funnelBindingKey) {\n if (typeof state.funnelBindingKey.dimension === 'string') {\n bindingKey = state.funnelBindingKey.dimension\n } else if (Array.isArray(state.funnelBindingKey.dimension)) {\n bindingKey = state.funnelBindingKey.dimension.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n }))\n }\n }\n\n // Convert time dimension to server format\n let timeDimension: ServerFunnelQuery['funnel']['timeDimension'] =\n state.funnelTimeDimension || ''\n\n // Convert steps to server format\n const steps: ServerFunnelStep[] = state.funnelSteps.map((step) => {\n const serverStep: ServerFunnelStep = {\n name: step.name,\n }\n\n // Only include cube if different from default (multi-cube support)\n if (step.cube && step.cube !== state.funnelCube) {\n serverStep.cube = step.cube\n }\n\n // Include filters if present\n if (step.filters && step.filters.length > 0) {\n // Convert to server filter format\n serverStep.filter =\n step.filters.length === 1\n ? step.filters[0]\n : { and: step.filters }\n }\n\n // Include timeToConvert if present\n if (step.timeToConvert) {\n serverStep.timeToConvert = step.timeToConvert\n }\n\n return serverStep\n })\n\n return {\n funnel: {\n bindingKey,\n timeDimension,\n steps,\n includeTimeMetrics: true,\n },\n }\n}\n\n/**\n * Convert ServerFunnelQuery to FunnelSliceState\n */\nfunction serverQueryToState(query: ServerFunnelQuery): FunnelSliceState {\n const { funnel } = query\n\n // Extract cube from first step or binding key\n let funnelCube: string | null = null\n if (funnel.steps.length > 0 && funnel.steps[0].cube) {\n funnelCube = funnel.steps[0].cube\n } else if (typeof funnel.bindingKey === 'string') {\n // Extract cube from binding key (e.g., \"Events.userId\" -> \"Events\")\n const parts = funnel.bindingKey.split('.')\n if (parts.length > 0) {\n funnelCube = parts[0]\n }\n }\n\n // Convert binding key to client format\n let funnelBindingKey: FunnelBindingKey | null = null\n if (funnel.bindingKey) {\n if (typeof funnel.bindingKey === 'string') {\n funnelBindingKey = { dimension: funnel.bindingKey }\n } else if (Array.isArray(funnel.bindingKey)) {\n funnelBindingKey = {\n dimension: funnel.bindingKey.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n })),\n }\n }\n }\n\n // Convert time dimension\n let funnelTimeDimension: string | null = null\n if (funnel.timeDimension) {\n if (typeof funnel.timeDimension === 'string') {\n funnelTimeDimension = funnel.timeDimension\n } else if (Array.isArray(funnel.timeDimension) && funnel.timeDimension.length > 0) {\n funnelTimeDimension = `${funnel.timeDimension[0].cube}.${funnel.timeDimension[0].dimension}`\n }\n }\n\n // Convert steps\n const funnelSteps: FunnelStepState[] = funnel.steps.map((step) => {\n // Extract filters\n let filters: Filter[] = []\n if (step.filter) {\n if (Array.isArray(step.filter)) {\n // Already an array of filters\n filters = step.filter as Filter[]\n } else if (\n typeof step.filter === 'object' &&\n 'and' in (step.filter as { and?: unknown })\n ) {\n // { and: [...] } format\n filters = (step.filter as { and: Filter[] }).and\n } else {\n // Single filter object - wrap in array\n filters = [step.filter as Filter]\n }\n }\n\n return {\n id: generateId(),\n name: step.name,\n cube: step.cube || funnelCube || '',\n filters,\n timeToConvert: step.timeToConvert,\n }\n })\n\n return {\n funnelCube,\n funnelSteps,\n activeFunnelStepIndex: 0,\n funnelTimeDimension,\n funnelBindingKey,\n }\n}\n\n/**\n * Check if a config is a valid funnel config\n */\nfunction isValidFunnelConfig(config: unknown): config is FunnelAnalysisConfig {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n if (c.version !== 1) return false\n if (c.analysisType !== 'funnel') return false\n if (!c.query || typeof c.query !== 'object') return false\n\n const query = c.query as Record<string, unknown>\n if (!query.funnel || typeof query.funnel !== 'object') return false\n\n return true\n}\n\n// ============================================================================\n// Funnel Mode Adapter\n// ============================================================================\n\nexport const funnelModeAdapter: ModeAdapter<FunnelSliceState> = {\n type: 'funnel',\n\n createInitial(): FunnelSliceState {\n return {\n funnelCube: null,\n funnelSteps: [],\n activeFunnelStepIndex: 0,\n funnelTimeDimension: null,\n funnelBindingKey: null,\n }\n },\n\n extractState(storeState: Record<string, unknown>): FunnelSliceState {\n return {\n funnelCube: storeState.funnelCube as string | null,\n funnelSteps: storeState.funnelSteps as FunnelStepState[],\n activeFunnelStepIndex: storeState.activeFunnelStepIndex as number,\n funnelTimeDimension: storeState.funnelTimeDimension as string | null,\n funnelBindingKey: storeState.funnelBindingKey as FunnelBindingKey | null,\n }\n },\n\n canLoad(config: unknown): config is AnalysisConfig {\n return isValidFunnelConfig(config)\n },\n\n load(config: AnalysisConfig): FunnelSliceState {\n // Type guard - ensure it's a funnel config\n if (config.analysisType !== 'funnel') {\n throw new Error(\n `Cannot load ${config.analysisType} config with funnel adapter`\n )\n }\n\n const funnelConfig = config as FunnelAnalysisConfig\n return serverQueryToState(funnelConfig.query)\n },\n\n save(\n state: FunnelSliceState,\n charts: Partial<Record<AnalysisType, ChartConfig>>,\n activeView: 'table' | 'chart'\n ): FunnelAnalysisConfig {\n return {\n version: 1,\n analysisType: 'funnel',\n activeView,\n charts: {\n funnel: charts.funnel || this.getDefaultChartConfig(),\n },\n query: stateToServerQuery(state),\n }\n },\n\n validate(state: FunnelSliceState): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Must have at least 2 steps for a funnel\n if (state.funnelSteps.length < 2) {\n errors.push('A funnel requires at least 2 steps')\n }\n\n // Must have a binding key\n if (!state.funnelBindingKey?.dimension) {\n errors.push('A binding key is required to link funnel steps')\n }\n\n // Must have a time dimension\n if (!state.funnelTimeDimension) {\n errors.push('A time dimension is required for funnel ordering')\n }\n\n // Check each step\n state.funnelSteps.forEach((step, index) => {\n if (!step.name || step.name.trim() === '') {\n warnings.push(`Step ${index + 1} has no name`)\n }\n\n // Warn if step has no distinguishing filter\n if (step.filters.length === 0) {\n warnings.push(\n `Step ${index + 1} \"${step.name}\" has no filter - all events will match`\n )\n }\n })\n\n // Check for duplicate step names\n const names = state.funnelSteps.map((s) => s.name.toLowerCase())\n const duplicates = names.filter(\n (name, index) => names.indexOf(name) !== index\n )\n if (duplicates.length > 0) {\n warnings.push(`Duplicate step names: ${[...new Set(duplicates)].join(', ')}`)\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n }\n },\n\n clear(state: FunnelSliceState): FunnelSliceState {\n // Keep cube selection but clear steps\n return {\n ...this.createInitial(),\n funnelCube: state.funnelCube,\n }\n },\n\n getDefaultChartConfig(): ChartConfig {\n return {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: { showLegend: true, showGrid: true, showTooltip: true },\n }\n },\n}\n","/**\n * Flow Mode Adapter\n *\n * Handles conversion between UI state and AnalysisConfig for flow mode.\n * Converts FlowSliceState UI state to/from ServerFlowQuery format.\n */\n\nimport type { ModeAdapter, ValidationResult } from './modeAdapter'\nimport type {\n AnalysisConfig,\n FlowAnalysisConfig,\n AnalysisType,\n ChartConfig,\n} from '../types/analysisConfig'\nimport type { Filter, FunnelBindingKey } from '../types'\nimport type {\n FlowSliceState,\n ServerFlowQuery,\n FlowStartingStep,\n} from '../types/flow'\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Convert FlowSliceState to ServerFlowQuery\n */\nfunction stateToServerQuery(state: FlowSliceState): ServerFlowQuery {\n // Convert binding key to server format\n let bindingKey: ServerFlowQuery['flow']['bindingKey'] = ''\n if (state.flowBindingKey) {\n if (typeof state.flowBindingKey.dimension === 'string') {\n bindingKey = state.flowBindingKey.dimension\n } else if (Array.isArray(state.flowBindingKey.dimension)) {\n bindingKey = state.flowBindingKey.dimension.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n }))\n }\n }\n\n // Convert time dimension to server format\n const timeDimension: ServerFlowQuery['flow']['timeDimension'] =\n state.flowTimeDimension || ''\n\n // Convert starting step to server format\n // Server accepts Filter | Filter[] for multiple filters\n const startingStep: ServerFlowQuery['flow']['startingStep'] = {\n name: state.startingStep.name || 'Starting Step',\n filter:\n state.startingStep.filters.length === 1\n ? state.startingStep.filters[0]\n : state.startingStep.filters.length > 1\n ? state.startingStep.filters\n : undefined,\n }\n\n return {\n flow: {\n bindingKey,\n timeDimension,\n startingStep,\n stepsBefore: state.stepsBefore,\n stepsAfter: state.stepsAfter,\n eventDimension: state.eventDimension || '',\n joinStrategy: state.joinStrategy,\n },\n }\n}\n\n/**\n * Convert ServerFlowQuery to FlowSliceState\n */\nfunction serverQueryToState(query: ServerFlowQuery): FlowSliceState {\n const { flow } = query\n\n // Extract cube from binding key or event dimension\n let flowCube: string | null = null\n if (typeof flow.bindingKey === 'string') {\n const parts = flow.bindingKey.split('.')\n if (parts.length > 0) {\n flowCube = parts[0]\n }\n } else if (Array.isArray(flow.bindingKey) && flow.bindingKey.length > 0) {\n flowCube = flow.bindingKey[0].cube\n }\n\n // Convert binding key to client format\n let flowBindingKey: FunnelBindingKey | null = null\n if (flow.bindingKey) {\n if (typeof flow.bindingKey === 'string') {\n flowBindingKey = { dimension: flow.bindingKey }\n } else if (Array.isArray(flow.bindingKey)) {\n flowBindingKey = {\n dimension: flow.bindingKey.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n })),\n }\n }\n }\n\n // Convert time dimension\n let flowTimeDimension: string | null = null\n if (flow.timeDimension) {\n if (typeof flow.timeDimension === 'string') {\n flowTimeDimension = flow.timeDimension\n } else if (Array.isArray(flow.timeDimension) && flow.timeDimension.length > 0) {\n flowTimeDimension = `${flow.timeDimension[0].cube}.${flow.timeDimension[0].dimension}`\n }\n }\n\n // Convert starting step filters\n let startingStepFilters: Filter[] = []\n if (flow.startingStep.filter) {\n if (Array.isArray(flow.startingStep.filter)) {\n startingStepFilters = flow.startingStep.filter\n } else {\n startingStepFilters = [flow.startingStep.filter]\n }\n }\n\n return {\n flowCube,\n flowBindingKey,\n flowTimeDimension,\n startingStep: {\n name: flow.startingStep.name || '',\n filters: startingStepFilters,\n },\n stepsBefore: flow.stepsBefore || 3,\n stepsAfter: flow.stepsAfter || 3,\n eventDimension: flow.eventDimension || null,\n joinStrategy: flow.joinStrategy || 'auto',\n }\n}\n\n/**\n * Check if a config is a valid flow config\n */\nfunction isValidFlowConfig(config: unknown): config is FlowAnalysisConfig {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n if (c.version !== 1) return false\n if (c.analysisType !== 'flow') return false\n if (!c.query || typeof c.query !== 'object') return false\n\n const query = c.query as Record<string, unknown>\n if (!query.flow || typeof query.flow !== 'object') return false\n\n return true\n}\n\n// ============================================================================\n// Flow Mode Adapter\n// ============================================================================\n\nexport const flowModeAdapter: ModeAdapter<FlowSliceState> = {\n type: 'flow',\n\n createInitial(): FlowSliceState {\n return {\n flowCube: null,\n flowBindingKey: null,\n flowTimeDimension: null,\n startingStep: {\n name: '',\n filters: [],\n },\n stepsBefore: 3,\n stepsAfter: 3,\n eventDimension: null,\n joinStrategy: 'auto',\n }\n },\n\n extractState(storeState: Record<string, unknown>): FlowSliceState {\n return {\n flowCube: storeState.flowCube as string | null,\n flowBindingKey: storeState.flowBindingKey as FunnelBindingKey | null,\n flowTimeDimension: storeState.flowTimeDimension as string | null,\n startingStep: storeState.startingStep as FlowStartingStep,\n stepsBefore: storeState.stepsBefore as number,\n stepsAfter: storeState.stepsAfter as number,\n eventDimension: storeState.eventDimension as string | null,\n joinStrategy: (storeState.joinStrategy as 'auto' | 'lateral' | 'window') || 'auto',\n }\n },\n\n canLoad(config: unknown): config is AnalysisConfig {\n return isValidFlowConfig(config)\n },\n\n load(config: AnalysisConfig): FlowSliceState {\n // Type guard - ensure it's a flow config\n if (config.analysisType !== 'flow') {\n throw new Error(\n `Cannot load ${config.analysisType} config with flow adapter`\n )\n }\n\n const flowConfig = config as FlowAnalysisConfig\n return serverQueryToState(flowConfig.query)\n },\n\n save(\n state: FlowSliceState,\n charts: Partial<Record<AnalysisType, ChartConfig>>,\n activeView: 'table' | 'chart'\n ): FlowAnalysisConfig {\n return {\n version: 1,\n analysisType: 'flow',\n activeView,\n charts: {\n flow: charts.flow || this.getDefaultChartConfig(),\n },\n query: stateToServerQuery(state),\n }\n },\n\n validate(state: FlowSliceState): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Must have a cube selected\n if (!state.flowCube) {\n errors.push('Select an event stream cube for flow analysis')\n }\n\n // Must have a binding key\n if (!state.flowBindingKey?.dimension) {\n errors.push('A binding key is required to link events to entities')\n }\n\n // Must have a time dimension\n if (!state.flowTimeDimension) {\n errors.push('A time dimension is required for event ordering')\n }\n\n // Must have an event dimension\n if (!state.eventDimension) {\n errors.push('An event dimension is required to categorize events')\n }\n\n // Must have starting step filters\n if (state.startingStep.filters.length === 0) {\n errors.push('The starting step must have at least one filter to identify the anchor event')\n }\n\n // Validate depth bounds\n if (state.stepsBefore < 0 || state.stepsBefore > 5) {\n errors.push(`Steps before must be between 0 and 5`)\n }\n if (state.stepsAfter < 0 || state.stepsAfter > 5) {\n errors.push(`Steps after must be between 0 and 5`)\n }\n\n if (\n state.joinStrategy &&\n !['auto', 'lateral', 'window'].includes(state.joinStrategy)\n ) {\n errors.push('Join strategy must be auto, lateral, or window')\n }\n\n // Warnings\n if (!state.startingStep.name) {\n warnings.push('Starting step has no name - using default')\n }\n\n // Performance warnings for high depth\n if (state.stepsBefore >= 4 || state.stepsAfter >= 4) {\n warnings.push('High step depth (4-5) may impact query performance on large datasets')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n }\n },\n\n clear(state: FlowSliceState): FlowSliceState {\n // Keep cube selection but clear other settings\n return {\n ...this.createInitial(),\n flowCube: state.flowCube,\n }\n },\n\n getDefaultChartConfig(): ChartConfig {\n return {\n chartType: 'sankey',\n chartConfig: {},\n displayConfig: {\n showLegend: true,\n showGrid: false,\n showTooltip: true,\n },\n }\n },\n}\n","/**\n * Retention Mode Adapter\n *\n * Handles conversion between UI state and AnalysisConfig for retention mode.\n * Converts RetentionSliceState UI state to/from ServerRetentionQuery format.\n *\n * Simplified Mixpanel-style format (Phase 5):\n * - Single cube for all analysis\n * - Single timestamp dimension\n * - Single cohort with breakdown support\n * - Granularity = viewing periods\n */\n\nimport type { ModeAdapter, ValidationResult } from './modeAdapter'\nimport type {\n AnalysisConfig,\n RetentionAnalysisConfig,\n AnalysisType,\n ChartConfig,\n} from '../types/analysisConfig'\nimport type { Filter } from '../types'\nimport type { FunnelBindingKey } from '../types/funnel'\nimport type {\n ServerRetentionQuery,\n RetentionSliceState,\n RetentionGranularity,\n RetentionType,\n DateRange,\n RetentionBreakdownItem,\n} from '../types/retention'\nimport { defaultRetentionSliceState, getDateRangeFromPreset, DEFAULT_DATE_RANGE_PRESET } from '../types/retention'\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Convert RetentionSliceState to ServerRetentionQuery\n * Uses the new simplified format from Phase 1\n */\nfunction stateToServerQuery(state: RetentionSliceState): ServerRetentionQuery {\n // Convert binding key to server format\n let bindingKey: ServerRetentionQuery['retention']['bindingKey'] = ''\n if (state.retentionBindingKey) {\n if (typeof state.retentionBindingKey.dimension === 'string') {\n bindingKey = state.retentionBindingKey.dimension\n } else if (Array.isArray(state.retentionBindingKey.dimension)) {\n bindingKey = state.retentionBindingKey.dimension.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n }))\n }\n }\n\n // Build the server query with new simplified format\n const query: ServerRetentionQuery = {\n retention: {\n timeDimension: state.retentionTimeDimension || '',\n bindingKey,\n dateRange: state.retentionDateRange,\n granularity: state.retentionViewGranularity,\n periods: state.retentionPeriods,\n retentionType: state.retentionType,\n },\n }\n\n // Add cohort filters if present\n if (state.retentionCohortFilters.length > 0) {\n query.retention.cohortFilters =\n state.retentionCohortFilters.length === 1\n ? state.retentionCohortFilters[0]\n : state.retentionCohortFilters\n }\n\n // Add activity filters if present\n if (state.retentionActivityFilters.length > 0) {\n query.retention.activityFilters =\n state.retentionActivityFilters.length === 1\n ? state.retentionActivityFilters[0]\n : state.retentionActivityFilters\n }\n\n // Add breakdown dimensions if present\n if (state.retentionBreakdowns && state.retentionBreakdowns.length > 0) {\n query.retention.breakdownDimensions = state.retentionBreakdowns.map((b) => b.field)\n }\n\n return query\n}\n\n/**\n * Convert ServerRetentionQuery to RetentionSliceState\n * Uses the new simplified format from Phase 1\n */\nfunction serverQueryToState(query: ServerRetentionQuery): RetentionSliceState {\n const { retention } = query\n\n // Extract cube from time dimension\n let retentionCube: string | null = null\n if (typeof retention.timeDimension === 'string') {\n const parts = retention.timeDimension.split('.')\n if (parts.length > 0) {\n retentionCube = parts[0]\n }\n } else if (retention.timeDimension?.cube) {\n retentionCube = retention.timeDimension.cube\n }\n\n // Convert binding key to client format\n let retentionBindingKey: FunnelBindingKey | null = null\n if (retention.bindingKey) {\n if (typeof retention.bindingKey === 'string') {\n retentionBindingKey = { dimension: retention.bindingKey }\n } else if (Array.isArray(retention.bindingKey)) {\n retentionBindingKey = {\n dimension: retention.bindingKey.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n })),\n }\n }\n }\n\n // Convert time dimension\n let retentionTimeDimension: string | null = null\n if (retention.timeDimension) {\n if (typeof retention.timeDimension === 'string') {\n retentionTimeDimension = retention.timeDimension\n } else {\n retentionTimeDimension = `${retention.timeDimension.cube}.${retention.timeDimension.dimension}`\n }\n }\n\n // Convert filters\n let retentionCohortFilters: Filter[] = []\n if (retention.cohortFilters) {\n if (Array.isArray(retention.cohortFilters)) {\n retentionCohortFilters = retention.cohortFilters as Filter[]\n } else {\n retentionCohortFilters = [retention.cohortFilters as Filter]\n }\n }\n\n let retentionActivityFilters: Filter[] = []\n if (retention.activityFilters) {\n if (Array.isArray(retention.activityFilters)) {\n retentionActivityFilters = retention.activityFilters as Filter[]\n } else {\n retentionActivityFilters = [retention.activityFilters as Filter]\n }\n }\n\n // Convert breakdown dimensions\n let retentionBreakdowns: RetentionBreakdownItem[] = []\n if (retention.breakdownDimensions && Array.isArray(retention.breakdownDimensions)) {\n retentionBreakdowns = retention.breakdownDimensions.map((field) => ({\n field,\n label: field.split('.').pop() || field,\n }))\n }\n\n // Extract or default the date range\n const retentionDateRange: DateRange = retention.dateRange || getDateRangeFromPreset(DEFAULT_DATE_RANGE_PRESET)\n\n return {\n retentionCube,\n retentionBindingKey,\n retentionTimeDimension,\n retentionDateRange,\n retentionViewGranularity: retention.granularity as RetentionGranularity,\n retentionPeriods: retention.periods,\n retentionType: retention.retentionType as RetentionType,\n retentionCohortFilters,\n retentionActivityFilters,\n retentionBreakdowns,\n }\n}\n\n/**\n * Check if a config is a valid retention config\n */\nfunction isValidRetentionConfig(config: unknown): config is RetentionAnalysisConfig {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n if (c.version !== 1) return false\n if (c.analysisType !== 'retention') return false\n if (!c.query || typeof c.query !== 'object') return false\n\n const query = c.query as Record<string, unknown>\n if (!query.retention || typeof query.retention !== 'object') return false\n\n return true\n}\n\n// ============================================================================\n// Retention Mode Adapter\n// ============================================================================\n\nexport const retentionModeAdapter: ModeAdapter<RetentionSliceState> = {\n type: 'retention',\n\n createInitial(): RetentionSliceState {\n return { ...defaultRetentionSliceState }\n },\n\n extractState(storeState: Record<string, unknown>): RetentionSliceState {\n return {\n retentionCube: storeState.retentionCube as string | null,\n retentionBindingKey: storeState.retentionBindingKey as FunnelBindingKey | null,\n retentionTimeDimension: storeState.retentionTimeDimension as string | null,\n retentionDateRange: (storeState.retentionDateRange as DateRange) || getDateRangeFromPreset(DEFAULT_DATE_RANGE_PRESET),\n retentionViewGranularity: (storeState.retentionViewGranularity as RetentionGranularity) || 'week',\n retentionPeriods: (storeState.retentionPeriods as number) || 12,\n retentionType: (storeState.retentionType as RetentionType) || 'classic',\n retentionCohortFilters: (storeState.retentionCohortFilters as Filter[]) || [],\n retentionActivityFilters: (storeState.retentionActivityFilters as Filter[]) || [],\n retentionBreakdowns: (storeState.retentionBreakdowns as RetentionBreakdownItem[]) || [],\n }\n },\n\n canLoad(config: unknown): config is AnalysisConfig {\n return isValidRetentionConfig(config)\n },\n\n load(config: AnalysisConfig): RetentionSliceState {\n // Type guard - ensure it's a retention config\n if (config.analysisType !== 'retention') {\n throw new Error(\n `Cannot load ${config.analysisType} config with retention adapter`\n )\n }\n\n const retentionConfig = config as RetentionAnalysisConfig\n return serverQueryToState(retentionConfig.query)\n },\n\n save(\n state: RetentionSliceState,\n charts: Partial<Record<AnalysisType, ChartConfig>>,\n activeView: 'table' | 'chart'\n ): RetentionAnalysisConfig {\n return {\n version: 1,\n analysisType: 'retention',\n activeView,\n charts: {\n retention: charts.retention || this.getDefaultChartConfig(),\n },\n query: stateToServerQuery(state),\n }\n },\n\n validate(state: RetentionSliceState): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Must have a cube selected\n if (!state.retentionCube) {\n errors.push('Select a cube for retention analysis')\n }\n\n // Must have a time dimension\n if (!state.retentionTimeDimension) {\n errors.push('Select a timestamp dimension for the analysis')\n }\n\n // Must have a binding key\n if (!state.retentionBindingKey?.dimension) {\n errors.push('Select a user identifier (binding key) to track retention')\n }\n\n // Date range is required\n if (!state.retentionDateRange?.start || !state.retentionDateRange?.end) {\n errors.push('Date range is required for retention analysis')\n } else {\n // Validate date format\n const startDate = new Date(state.retentionDateRange.start)\n const endDate = new Date(state.retentionDateRange.end)\n if (isNaN(startDate.getTime())) {\n errors.push('Invalid start date format')\n }\n if (isNaN(endDate.getTime())) {\n errors.push('Invalid end date format')\n }\n if (startDate > endDate) {\n errors.push('Start date must be before or equal to end date')\n }\n }\n\n // Periods must be valid\n if (state.retentionPeriods < 1) {\n errors.push('At least 1 retention period is required')\n }\n if (state.retentionPeriods > 52) {\n warnings.push('More than 52 periods may impact performance')\n }\n\n // Check time dimension format\n if (state.retentionTimeDimension) {\n const parts = state.retentionTimeDimension.split('.')\n if (parts.length < 2) {\n warnings.push('Time dimension should be in format \"Cube.dimension\"')\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n }\n },\n\n clear(state: RetentionSliceState): RetentionSliceState {\n // Keep cube selection and date range but clear other configuration\n return {\n ...this.createInitial(),\n retentionCube: state.retentionCube,\n retentionDateRange: state.retentionDateRange,\n }\n },\n\n getDefaultChartConfig(): ChartConfig {\n return {\n chartType: 'retentionCombined',\n chartConfig: {\n // RetentionCombinedChart auto-configures from the retention data structure\n // No explicit axis mapping needed\n },\n displayConfig: {\n showLegend: true,\n showTooltip: true,\n showGrid: true,\n retentionDisplayMode: 'combined',\n },\n }\n },\n}\n","/**\n * AnalysisDisplayConfigPanel Component\n *\n * A panel for configuring chart display options (legend, grid, tooltip, etc.)\n * Extracted from AnalysisChartConfigPanel to be shown in its own tab.\n */\n\nimport { useState, useCallback, useEffect } from 'react'\nimport SectionHeading from './SectionHeading'\nimport { useChartConfig } from '../../charts/lazyChartConfigRegistry'\nimport type { ChartType, ChartDisplayConfig, ColorPalette, AxisFormatConfig } from '../../types'\nimport { AxisFormatControls } from '../charts/AxisFormatControls'\n\ninterface AnalysisDisplayConfigPanelProps {\n chartType: ChartType\n displayConfig: ChartDisplayConfig\n colorPalette?: ColorPalette\n onDisplayConfigChange: (config: ChartDisplayConfig) => void\n /** Keys to exclude from displayOptionsConfig rendering (e.g., ['content'] when content is managed elsewhere) */\n excludeKeys?: string[]\n}\n\n/**\n * StringArrayInput - A textarea that edits an array of strings\n * Uses local state while editing and only updates on blur\n */\nfunction StringArrayInput({\n label,\n value,\n onChange,\n placeholder,\n description,\n}: {\n label: string\n value: string[]\n onChange: (value: string[]) => void\n placeholder?: string\n description?: string\n}) {\n // Local state for textarea editing\n const [localText, setLocalText] = useState(() => value.join('\\n'))\n\n // Sync local state when external value changes (e.g., from undo/redo or load)\n useEffect(() => {\n const externalText = value.join('\\n')\n setLocalText(externalText)\n }, [value])\n\n const handleBlur = useCallback(() => {\n // Convert text to array, filtering empty strings\n const arrayValue = localText\n .split('\\n')\n .map(s => s.trim())\n .filter(s => s.length > 0)\n onChange(arrayValue)\n }, [localText, onChange])\n\n return (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{label}</label>\n <textarea\n value={localText}\n onChange={(e) => setLocalText(e.target.value)}\n onBlur={handleBlur}\n placeholder={placeholder}\n rows={4}\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text dc:resize-y\"\n />\n {description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{description}</p>\n )}\n </div>\n )\n}\n\nexport default function AnalysisDisplayConfigPanel({\n chartType,\n displayConfig,\n colorPalette,\n onDisplayConfigChange,\n excludeKeys,\n}: AnalysisDisplayConfigPanelProps) {\n // Get configuration for current chart type\n const { config: chartTypeConfig, loaded: chartConfigLoaded } = useChartConfig(chartType)\n\n if (!chartConfigLoaded) {\n return (\n <div className=\"dc:text-center text-dc-text-muted dc:text-sm dc:py-4\">\n Loading display options...\n </div>\n )\n }\n\n // Check if we have any display options to show\n const hasDisplayOptions =\n (chartTypeConfig.displayOptions && chartTypeConfig.displayOptions.length > 0) ||\n (chartTypeConfig.displayOptionsConfig && chartTypeConfig.displayOptionsConfig.length > 0)\n\n if (!hasDisplayOptions) {\n return (\n <div className=\"dc:text-center text-dc-text-muted dc:text-sm dc:py-4\">\n <p>No display options available for this chart type.</p>\n </div>\n )\n }\n\n return (\n <div className=\"dc:space-y-6\">\n <div>\n <SectionHeading className=\"dc:mb-2\">Display Options</SectionHeading>\n <div className=\"dc:space-y-2\">\n {/* Backward compatibility: Simple boolean display options */}\n {chartTypeConfig.displayOptions?.includes('showLegend') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.showLegend ?? true}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n showLegend: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Show Legend</span>\n </label>\n )}\n\n {chartTypeConfig.displayOptions?.includes('showGrid') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.showGrid ?? true}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n showGrid: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Show Grid</span>\n </label>\n )}\n\n {chartTypeConfig.displayOptions?.includes('showTooltip') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.showTooltip ?? true}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n showTooltip: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Show Tooltip</span>\n </label>\n )}\n\n {chartTypeConfig.displayOptions?.includes('stacked') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.stacked ?? false}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n stacked: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Stacked</span>\n </label>\n )}\n\n {chartTypeConfig.displayOptions?.includes('hideHeader') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.hideHeader ?? false}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n hideHeader: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Hide Header</span>\n </label>\n )}\n\n {/* New structured display options */}\n {chartTypeConfig.displayOptionsConfig?.filter(option => !excludeKeys?.includes(option.key)).map((option) => (\n <div key={option.key} className={`dc:space-y-1 ${option.type === 'axisFormat' ? 'dc:mt-6 dc:pt-2' : ''}`}>\n {option.type === 'boolean' && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={\n (displayConfig[option.key as keyof ChartDisplayConfig] as boolean) ??\n option.defaultValue ??\n false\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">{option.label}</span>\n </label>\n )}\n\n {option.type === 'string' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">\n {option.label}\n {option.key === 'content' && (\n <span className=\"dc:text-xs text-dc-text-muted dc:ml-1\">\n (only headers, lists and links)\n </span>\n )}\n </label>\n {option.key === 'content' ? (\n <textarea\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n ''\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n placeholder={option.placeholder}\n rows={8}\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent dc:font-mono dc:resize-y bg-dc-surface text-dc-text\"\n />\n ) : (\n <input\n type=\"text\"\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n ''\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n placeholder={option.placeholder}\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text\"\n />\n )}\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'paletteColor' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <div className=\"dc:flex dc:flex-wrap dc:gap-2\">\n {colorPalette?.colors.map((color, index) => {\n const isSelected =\n ((displayConfig[option.key as keyof ChartDisplayConfig] as number) ??\n option.defaultValue ??\n 0) === index\n return (\n <button\n key={index}\n type=\"button\"\n onClick={() =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: index\n })\n }\n className={`dc:w-8 dc:h-8 dc:rounded dc:border-2 dc:transition-all dc:duration-200 dc:hover:scale-110 focus:outline-hidden dc:focus:ring-2 focus:ring-dc-accent dc:focus:ring-offset-1 ${\n isSelected\n ? 'dc:ring-2 dc:ring-offset-1 dc:scale-110'\n : 'hover:border-dc-text-muted'\n }`}\n style={{\n backgroundColor: color,\n borderColor: isSelected ? 'var(--dc-primary)' : 'var(--dc-border)'\n }}\n title={`Color ${index + 1}: ${color}`}\n />\n )\n }) || [\n // Fallback if no palette available\n <button\n key={0}\n type=\"button\"\n onClick={() =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: 0\n })\n }\n className=\"dc:w-8 dc:h-8 dc:rounded-sm dc:border-2 dc:ring-2 dc:ring-offset-1\"\n style={{\n backgroundColor: '#8884d8',\n borderColor: 'var(--dc-primary)',\n boxShadow: '0 0 0 2px var(--dc-primary)'\n }}\n title=\"Default Color\"\n />\n ]}\n </div>\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'number' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <input\n type=\"number\"\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as number) ??\n option.defaultValue ??\n 0\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value === '' ? undefined : Number(e.target.value)\n })\n }\n placeholder={option.placeholder}\n min={option.min}\n max={option.max}\n step={option.step}\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text\"\n />\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'select' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <select\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n ''\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text\"\n >\n {option.options?.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'color' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <div className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"color\"\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n '#8884d8'\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n className=\"dc:w-12 dc:h-8 dc:border border-dc-border dc:rounded-sm dc:cursor-pointer\"\n />\n <input\n type=\"text\"\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n '#8884d8'\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n placeholder={option.placeholder || '#8884d8'}\n className=\"dc:flex-1 dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text\"\n />\n </div>\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'axisFormat' && (\n <AxisFormatControls\n axisLabel={option.label}\n value={(displayConfig[option.key as keyof ChartDisplayConfig] as AxisFormatConfig) || {}}\n onChange={(config) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: Object.keys(config).length > 0 ? config : undefined\n })\n }\n />\n )}\n\n {option.type === 'stringArray' && (\n <StringArrayInput\n label={option.label}\n value={(displayConfig[option.key as keyof ChartDisplayConfig] as string[]) ?? []}\n onChange={(arrayValue) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: arrayValue.length > 0 ? arrayValue : undefined\n })\n }\n placeholder={option.placeholder}\n description={option.description}\n />\n )}\n\n {option.type === 'buttonGroup' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <div className=\"dc:flex dc:border border-dc-border dc:rounded-sm dc:overflow-hidden\">\n {option.options?.map((opt) => {\n const isSelected = (displayConfig[option.key as keyof ChartDisplayConfig] ?? option.defaultValue) === opt.value\n return (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: opt.value\n })\n }\n className={`dc:flex-1 dc:px-3 dc:py-1.5 dc:text-sm dc:font-medium dc:transition-colors ${\n isSelected\n ? 'bg-dc-primary text-white'\n : 'bg-dc-surface text-dc-text hover:bg-dc-border'\n }`}\n >\n {opt.label}\n </button>\n )\n })}\n </div>\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n )\n}\n","import React from 'react'\nimport Modal from './Modal'\n\nexport interface ConfirmModalProps {\n isOpen: boolean\n onClose: () => void\n onConfirm: () => void | Promise<void>\n title?: string\n message: React.ReactNode\n confirmText?: string\n cancelText?: string\n confirmVariant?: 'danger' | 'primary' | 'warning'\n isLoading?: boolean\n}\n\n/**\n * A reusable confirmation modal component.\n *\n * Usage:\n * ```tsx\n * <ConfirmModal\n * isOpen={showConfirm}\n * onClose={() => setShowConfirm(false)}\n * onConfirm={handleDelete}\n * title=\"Delete Portlet\"\n * message=\"Are you sure you want to delete this portlet? This action cannot be undone.\"\n * confirmText=\"Delete\"\n * confirmVariant=\"danger\"\n * />\n * ```\n */\nconst ConfirmModal: React.FC<ConfirmModalProps> = ({\n isOpen,\n onClose,\n onConfirm,\n title = 'Confirm',\n message,\n confirmText = 'Confirm',\n cancelText = 'Cancel',\n confirmVariant = 'primary',\n isLoading = false,\n}) => {\n const handleConfirm = async () => {\n await onConfirm()\n onClose()\n }\n\n const getConfirmButtonClasses = () => {\n const baseClasses = 'dc:px-4 dc:py-2 dc:text-sm dc:font-medium dc:rounded-md dc:transition-colors dc:focus:outline-none dc:focus:ring-2 dc:focus:ring-offset-2 dc:disabled:opacity-50 dc:disabled:cursor-not-allowed'\n\n switch (confirmVariant) {\n case 'danger':\n return `${baseClasses} bg-dc-danger dc:text-white dc:hover:bg-dc-danger/90 focus:ring-dc-danger`\n case 'warning':\n return `${baseClasses} bg-dc-warning dc:text-white dc:hover:bg-dc-warning/90 focus:ring-dc-warning`\n case 'primary':\n default:\n return `${baseClasses} bg-dc-primary dc:text-white dc:hover:bg-dc-primary/90 focus:ring-dc-primary`\n }\n }\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n size=\"sm\"\n closeOnBackdropClick={!isLoading}\n closeOnEscape={!isLoading}\n footer={\n <>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={isLoading}\n className=\"dc:px-4 dc:py-2 dc:text-sm dc:font-medium text-dc-text-secondary bg-dc-surface dc:border border-dc-border dc:rounded-md hover:bg-dc-surface-hover dc:transition-colors dc:focus:outline-none dc:focus:ring-2 dc:focus:ring-offset-2 focus:ring-dc-primary dc:disabled:opacity-50 dc:disabled:cursor-not-allowed\"\n >\n {cancelText}\n </button>\n <button\n type=\"button\"\n onClick={handleConfirm}\n disabled={isLoading}\n className={getConfirmButtonClasses()}\n >\n {isLoading ? (\n <span className=\"dc:flex dc:items-center dc:gap-2\">\n <svg className=\"dc:animate-spin dc:h-4 dc:w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"dc:opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path className=\"dc:opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n Processing...\n </span>\n ) : (\n confirmText\n )}\n </button>\n </>\n }\n >\n <div className=\"text-dc-text-secondary\">\n {message}\n </div>\n </Modal>\n )\n}\n\nexport default ConfirmModal\n","/**\n * Color Palette Selector Component\n * Allows users to select from predefined color palettes for their dashboard\n */\n\nimport { useState, useRef, useEffect } from 'react'\nimport { COLOR_PALETTES, getColorPalette } from '../utils/colorPalettes'\nimport { getIcon } from '../icons'\n\nconst ChevronDownIcon = getIcon('chevronDown')\n\ninterface ColorPaletteSelectorProps {\n currentPalette?: string\n onPaletteChange: (paletteName: string) => void\n className?: string\n}\n\nexport default function ColorPaletteSelector({\n currentPalette = 'default',\n onPaletteChange,\n className = ''\n}: ColorPaletteSelectorProps) {\n const [isOpen, setIsOpen] = useState(false)\n const dropdownRef = useRef<HTMLDivElement>(null)\n \n const currentPaletteObj = getColorPalette(currentPalette)\n\n // Close dropdown when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false)\n }\n }\n\n if (isOpen) {\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }\n }, [isOpen])\n\n const handlePaletteSelect = (paletteName: string) => {\n onPaletteChange(paletteName)\n setIsOpen(false)\n }\n\n return (\n <div className={`dc:relative ${className}`} ref={dropdownRef}>\n {/* Trigger Button */}\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n className=\"dc:inline-flex dc:items-center dc:gap-2 dc:px-3 dc:py-1.5 bg-dc-surface dc:border border-dc-border dc:rounded-md shadow-xs dc:text-sm dc:font-medium text-dc-text-secondary hover:bg-dc-surface-hover focus:outline-hidden dc:focus:ring-2 dc:focus:ring-offset-2 focus:ring-dc-accent\"\n >\n {/* Current Palette Preview - Hidden on mobile */}\n <div className=\"dc:hidden dc:md:flex dc:items-center dc:gap-1.5\">\n <div className=\"dc:flex dc:gap-0.5\">\n {currentPaletteObj.colors.slice(0, 4).map((color, index) => (\n <div\n key={index}\n className=\"dc:w-3 dc:h-3 rounded-xs dc:border border-dc-border\"\n style={{ backgroundColor: color }}\n title={`Series Color ${index + 1}`}\n />\n ))}\n </div>\n <span className=\"dc:text-xs text-dc-text-secondary\">|</span>\n <div className=\"dc:flex dc:gap-0.5\">\n {currentPaletteObj.gradient.slice(0, 3).map((color, index) => (\n <div\n key={index}\n className=\"dc:w-2 dc:h-3 dc:border-r dc:first:rounded-l-sm dc:last:rounded-r-sm dc:last:border-r-0\"\n style={{\n backgroundColor: color,\n borderColor: 'var(--dc-border)'\n }}\n title={`Gradient Color ${index + 1}`}\n />\n ))}\n </div>\n </div>\n <span>{currentPaletteObj.label}</span>\n <ChevronDownIcon \n className={`dc:w-4 dc:h-4 dc:transition-transform ${isOpen ? 'dc:rotate-180' : ''}`} \n />\n </button>\n\n {/* Dropdown Menu - Responsive width */}\n {isOpen && (\n <div className=\"dc:absolute dc:top-full dc:left-0 dc:mt-1 dc:w-72 dc:md:w-80 dc:lg:w-96 bg-dc-surface dc:border border-dc-border dc:rounded-md dc:shadow-lg dc:z-50 dc:max-h-80 dc:overflow-y-auto\">\n <div className=\"dc:py-1\">\n {COLOR_PALETTES.slice().sort((a, b) => a.label.localeCompare(b.label)).map((palette) => (\n <button\n key={palette.name}\n type=\"button\"\n onClick={() => handlePaletteSelect(palette.name)}\n className={`dc:w-full dc:px-3 dc:py-2 dc:text-left dc:text-sm hover:bg-dc-surface-hover focus:outline-hidden focus:bg-dc-surface-hover ${\n palette.name === currentPalette ? 'bg-dc-surface-secondary' : 'text-dc-text-secondary'\n }`}\n style={palette.name === currentPalette ? { color: 'var(--dc-primary)' } : undefined}\n >\n <div className=\"dc:flex dc:items-center dc:gap-3\">\n {/* Palette Preview - Hidden on mobile */}\n <div className=\"dc:hidden dc:md:flex dc:items-center dc:gap-2\">\n {/* Series Colors */}\n <div className=\"dc:flex dc:gap-0.5\">\n {palette.colors.slice(0, 6).map((color, index) => (\n <div\n key={`series-${index}`}\n className=\"dc:w-3 dc:h-3 rounded-xs dc:border border-dc-border\"\n style={{ backgroundColor: color }}\n />\n ))}\n </div>\n\n {/* Separator */}\n <div className=\"dc:w-px dc:h-4 bg-dc-border\" />\n \n {/* Gradient Colors */}\n <div className=\"dc:flex\">\n {palette.gradient.map((color, index) => (\n <div\n key={`gradient-${index}`}\n className=\"dc:w-2 dc:h-4 dc:first:rounded-l-sm dc:last:rounded-r-sm\"\n style={{ backgroundColor: color }}\n />\n ))}\n </div>\n </div>\n \n {/* Palette Name */}\n <span className=\"dc:font-medium\">{palette.label}</span>\n \n {/* Current Indicator */}\n {palette.name === currentPalette && (\n <div className=\"dc:ml-auto\">\n <div className=\"dc:w-2 dc:h-2 dc:rounded-full\" style={{ backgroundColor: 'var(--dc-primary)' }}></div>\n </div>\n )}\n </div>\n </button>\n ))}\n </div>\n </div>\n )}\n </div>\n )\n}","/**\n * FieldSearchItem Component\n *\n * A single field item in the search results list.\n * Shows field icon, title, type badge, and selection state.\n */\n\nimport { memo } from 'react'\nimport { getIcon, getMeasureTypeIcon, getFieldTypeIcon } from '../../icons'\nimport type { FieldSearchItemProps } from './types'\n\nconst CheckIcon = getIcon('check')\n\nfunction FieldSearchItem({\n field,\n isSelected,\n isFocused,\n onClick,\n onMouseEnter,\n ...props\n}: FieldSearchItemProps & { 'data-field-index'?: number }) {\n // Get appropriate icon based on field type\n const getFieldIcon = () => {\n if (field.fieldType === 'measure') {\n const Icon = getMeasureTypeIcon(field.type)\n return Icon ? <Icon className=\"dc:w-4 dc:h-4\" /> : null\n } else if (field.fieldType === 'timeDimension') {\n const Icon = getFieldTypeIcon('time')\n return Icon ? <Icon className=\"dc:w-4 dc:h-4\" /> : null\n } else {\n const Icon = getFieldTypeIcon('dimension')\n return Icon ? <Icon className=\"dc:w-4 dc:h-4\" /> : null\n }\n }\n\n // Get badge color based on field type\n const getBadgeStyle = () => {\n if (field.fieldType === 'measure') {\n return 'bg-dc-measure text-dc-measure-text'\n } else if (field.fieldType === 'timeDimension') {\n return 'bg-dc-time-dimension text-dc-time-dimension-text'\n } else {\n return 'bg-dc-dimension text-dc-dimension-text'\n }\n }\n\n // Get short type label\n const getTypeLabel = () => {\n if (field.fieldType === 'measure') {\n return field.type.charAt(0).toUpperCase() + field.type.slice(1)\n } else if (field.fieldType === 'timeDimension') {\n return 'Time'\n } else {\n return 'Dim'\n }\n }\n\n return (\n <button\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n className={`dc:w-full dc:text-left dc:px-3 dc:py-2 dc:rounded-lg dc:flex dc:items-center dc:gap-3 dc:transition-colors dc:group ${\n isFocused\n ? 'bg-dc-primary/10 dc:ring-1 ring-dc-primary'\n : isSelected\n ? 'bg-dc-success/10'\n : 'hover:bg-dc-surface-hover'\n }`}\n {...props}\n >\n {/* Icon */}\n <span\n className={`dc:shrink-0 dc:w-8 dc:h-8 dc:flex dc:items-center dc:justify-center dc:rounded-md ${\n field.fieldType === 'measure'\n ? 'bg-dc-measure text-dc-measure-text'\n : field.fieldType === 'timeDimension'\n ? 'bg-dc-time-dimension text-dc-time-dimension-text'\n : 'bg-dc-dimension text-dc-dimension-text'\n }`}\n >\n {getFieldIcon()}\n </span>\n\n {/* Title and name */}\n <div className=\"dc:flex-1 dc:min-w-0\">\n <div className=\"dc:text-sm dc:font-medium text-dc-text dc:truncate\">\n {field.title}\n </div>\n <div className=\"dc:text-xs text-dc-text-muted dc:truncate\">{field.name}</div>\n </div>\n\n {/* Type badge */}\n <span\n className={`dc:shrink-0 dc:px-2 dc:py-0.5 dc:rounded dc:text-xs dc:font-medium ${getBadgeStyle()}`}\n >\n {getTypeLabel()}\n </span>\n\n {/* Selection indicator */}\n {isSelected && (\n <span className=\"dc:shrink-0 dc:w-5 dc:h-5 dc:flex dc:items-center dc:justify-center dc:rounded-full bg-dc-success text-white\">\n <CheckIcon className=\"dc:w-3 dc:h-3\" />\n </span>\n )}\n </button>\n )\n}\n\nexport default memo(FieldSearchItem)\n","/**\n * FieldDetailPanel Component\n *\n * Shows detailed information about the currently focused/hovered field.\n * Displays: icon, title, description, type, cube name, and technical name.\n */\n\nimport { memo } from 'react'\nimport { getMeasureTypeIcon, getFieldTypeIcon } from '../../icons'\nimport type { FieldDetailPanelProps } from './types'\n\nfunction FieldDetailPanel({ field }: FieldDetailPanelProps) {\n if (!field) {\n return (\n <div className=\"dc:p-6 dc:text-center text-dc-text-muted\">\n <p className=\"dc:text-sm\">Hover over a field to see details</p>\n </div>\n )\n }\n\n // Get appropriate icon based on field type\n const getFieldIcon = () => {\n if (field.fieldType === 'measure') {\n const Icon = getMeasureTypeIcon(field.type)\n return Icon ? <Icon className=\"dc:w-6 dc:h-6\" /> : null\n } else if (field.fieldType === 'timeDimension') {\n const Icon = getFieldTypeIcon('time')\n return Icon ? <Icon className=\"dc:w-6 dc:h-6\" /> : null\n } else {\n const Icon = getFieldTypeIcon('dimension')\n return Icon ? <Icon className=\"dc:w-6 dc:h-6\" /> : null\n }\n }\n\n // Get icon background color - use field type specific colors for consistency\n const getIconBgStyle = () => {\n if (field.fieldType === 'measure') {\n return 'bg-dc-measure text-dc-measure-text'\n } else if (field.fieldType === 'timeDimension') {\n return 'bg-dc-time-dimension text-dc-time-dimension-text'\n } else {\n return 'bg-dc-dimension text-dc-dimension-text'\n }\n }\n\n // Get type display name\n const getTypeDisplay = () => {\n if (field.fieldType === 'measure') {\n const typeMap: Record<string, string> = {\n count: 'Count',\n countDistinct: 'Count Distinct',\n countDistinctApprox: 'Count Distinct (Approx)',\n sum: 'Sum',\n avg: 'Average',\n min: 'Minimum',\n max: 'Maximum',\n runningTotal: 'Running Total',\n number: 'Number'\n }\n return typeMap[field.type] || field.type\n } else if (field.fieldType === 'timeDimension') {\n return 'Time Dimension'\n } else {\n const typeMap: Record<string, string> = {\n string: 'Text',\n number: 'Number',\n boolean: 'Boolean',\n geo: 'Geographic'\n }\n return typeMap[field.type] || 'Dimension'\n }\n }\n\n return (\n <div className=\"dc:p-4\">\n {/* Header with icon and title */}\n <div className=\"dc:flex dc:items-start dc:gap-3 dc:mb-4\">\n <span\n className={`dc:shrink-0 dc:w-12 dc:h-12 dc:flex dc:items-center dc:justify-center dc:rounded-lg ${getIconBgStyle()}`}\n >\n {getFieldIcon()}\n </span>\n <div className=\"dc:flex-1 dc:min-w-0\">\n <h3 className=\"dc:text-base dc:font-semibold text-dc-text dc:leading-tight\">\n {field.title}\n </h3>\n <p className=\"dc:text-xs text-dc-text-muted dc:mt-0.5 dc:truncate\">\n {field.name}\n </p>\n </div>\n </div>\n\n {/* Description */}\n {field.description && (\n <div className=\"dc:mb-4\">\n <p className=\"dc:text-sm text-dc-text-secondary dc:leading-relaxed\">\n {field.description}\n </p>\n </div>\n )}\n\n {/* Metadata */}\n <div className=\"dc:space-y-3 dc:pt-4 dc:border-t border-dc-border\">\n <div className=\"dc:flex dc:items-center dc:justify-between\">\n <span className=\"dc:text-xs text-dc-text-muted\">Type</span>\n <span className=\"dc:text-sm text-dc-text dc:font-medium\">{getTypeDisplay()}</span>\n </div>\n <div className=\"dc:flex dc:items-center dc:justify-between\">\n <span className=\"dc:text-xs text-dc-text-muted\">Cube</span>\n <span className=\"dc:text-sm text-dc-text dc:font-medium\">{field.cubeName}</span>\n </div>\n <div className=\"dc:flex dc:items-center dc:justify-between\">\n <span className=\"dc:text-xs text-dc-text-muted\">Category</span>\n <span\n className={`dc:text-xs dc:px-2 dc:py-0.5 dc:rounded dc:font-medium ${\n field.fieldType === 'measure'\n ? 'bg-dc-measure text-dc-measure-text'\n : field.fieldType === 'timeDimension'\n ? 'bg-dc-time-dimension text-dc-time-dimension-text'\n : 'bg-dc-dimension text-dc-dimension-text'\n }`}\n >\n {field.fieldType === 'measure'\n ? 'Measure'\n : field.fieldType === 'timeDimension'\n ? 'Time Dimension'\n : 'Dimension'}\n </span>\n </div>\n </div>\n\n {/* Usage hint */}\n <div className=\"dc:mt-6 dc:p-3 bg-dc-surface dc:rounded-lg\">\n <p className=\"dc:text-xs text-dc-text-muted\">\n Press <kbd className=\"dc:px-1 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">Enter</kbd> or click to add this field to your query.\n </p>\n </div>\n </div>\n )\n}\n\nexport default memo(FieldDetailPanel)\n","/**\n * FieldSearchModal Component\n *\n * A full-screen search modal for selecting cube fields (measures/dimensions).\n * Features:\n * - Real-time search filtering\n * - Cube-based category filtering\n * - Three-column layout: Categories | Results | Details\n * - Keyboard navigation support\n * - Recent fields tracking\n */\n\nimport { useState, useMemo, useCallback, useEffect, useRef, KeyboardEvent } from 'react'\nimport { getIcon } from '../../icons'\nimport type { FieldSearchModalProps, FieldOption } from './types'\nimport type { MetaField } from '../../shared/types'\nimport {\n schemaToFieldOptions,\n filterFieldOptions,\n groupFieldsByCube,\n getCubeNames,\n getCubeTitle,\n getRecentFields,\n addRecentField,\n getRecentFieldOptions\n} from './utils'\nimport FieldSearchItem from './FieldSearchItem'\nimport FieldDetailPanel from './FieldDetailPanel'\n\nconst SearchIcon = getIcon('search')\nconst CloseIcon = getIcon('close')\n\nexport default function FieldSearchModal({\n isOpen,\n onClose,\n onSelect,\n mode,\n schema,\n selectedFields,\n recentFields: externalRecentFields\n}: FieldSearchModalProps) {\n // State\n const [searchTerm, setSearchTerm] = useState('')\n const [selectedCube, setSelectedCube] = useState<string | null>(null)\n const [focusedField, setFocusedField] = useState<FieldOption | null>(null)\n const [focusedIndex, setFocusedIndex] = useState(-1)\n const [lastSelectedIndex, setLastSelectedIndex] = useState<number | null>(null)\n\n // Refs\n const searchInputRef = useRef<HTMLInputElement>(null)\n const resultsContainerRef = useRef<HTMLDivElement>(null)\n\n // Get recent fields from localStorage or props\n const recentFieldNames = useMemo(() => {\n if (externalRecentFields) return externalRecentFields\n const stored = getRecentFields()\n return mode === 'metrics' ? stored.metrics : stored.breakdowns\n }, [externalRecentFields, mode])\n\n // Map mode to field options mode\n const fieldOptionsMode = mode\n\n // Get all field options for current mode\n const allFieldOptions = useMemo(() => {\n return schemaToFieldOptions(schema, fieldOptionsMode)\n }, [schema, fieldOptionsMode])\n\n // Get cube names for category filter\n const cubeNames = useMemo(() => {\n return getCubeNames(schema)\n }, [schema])\n\n // Filter fields by search and cube\n const filteredFields = useMemo(() => {\n return filterFieldOptions(allFieldOptions, searchTerm, selectedCube)\n }, [allFieldOptions, searchTerm, selectedCube])\n\n // Group filtered fields by cube\n const groupedFields = useMemo(() => {\n return groupFieldsByCube(filteredFields)\n }, [filteredFields])\n\n // Get recent field options (only when not searching)\n const recentOptions = useMemo(() => {\n if (searchTerm.trim()) return []\n return getRecentFieldOptions(schema, fieldOptionsMode, recentFieldNames).filter(\n (f) => !selectedCube || f.cubeName === selectedCube\n )\n }, [schema, fieldOptionsMode, recentFieldNames, searchTerm, selectedCube])\n\n // Flat list of visible fields for keyboard navigation\n const flatFieldsList = useMemo(() => {\n const list: FieldOption[] = [...recentOptions]\n groupedFields.forEach((fields) => {\n list.push(...fields)\n })\n return list\n }, [recentOptions, groupedFields])\n\n // Focus search input when modal opens\n useEffect(() => {\n if (isOpen && searchInputRef.current) {\n searchInputRef.current.focus()\n }\n }, [isOpen])\n\n // Reset state when modal closes\n useEffect(() => {\n if (!isOpen) {\n setSearchTerm('')\n setSelectedCube(null)\n setFocusedField(null)\n setFocusedIndex(-1)\n setLastSelectedIndex(null)\n }\n }, [isOpen])\n\n // Handle single field selection\n const selectSingleField = useCallback(\n (field: FieldOption, keepOpen: boolean = false) => {\n // Add to recent fields\n addRecentField(field.name, mode === 'metrics' ? 'metrics' : 'breakdowns')\n\n // Create MetaField object for callback\n const metaField: MetaField = {\n name: field.name,\n title: field.title,\n shortTitle: field.shortTitle,\n type: field.type,\n description: field.description\n }\n\n onSelect(metaField, field.fieldType, field.cubeName, keepOpen)\n },\n [mode, onSelect]\n )\n\n // Handle field selection with shift-click support for range selection\n const handleSelectField = useCallback(\n (field: FieldOption, fieldIndex: number, shiftKey: boolean = false) => {\n // Shift-click for range selection - keep modal open\n if (shiftKey && lastSelectedIndex !== null && lastSelectedIndex !== fieldIndex) {\n const startIndex = Math.min(lastSelectedIndex, fieldIndex)\n const endIndex = Math.max(lastSelectedIndex, fieldIndex)\n\n // Select all fields in the range, keep modal open for all\n for (let i = startIndex; i <= endIndex; i++) {\n const rangeField = flatFieldsList[i]\n if (rangeField && !selectedFields.includes(rangeField.name)) {\n selectSingleField(rangeField, true) // Keep modal open\n }\n }\n } else if (shiftKey) {\n // Shift-click on single item - select but keep modal open\n selectSingleField(field, true)\n } else {\n // Normal single selection - close modal after\n selectSingleField(field, false)\n }\n\n // Update last selected index for next shift-click\n setLastSelectedIndex(fieldIndex)\n },\n [flatFieldsList, lastSelectedIndex, selectSingleField, selectedFields]\n )\n\n // Keyboard navigation\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (flatFieldsList.length === 0) return\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n setFocusedIndex((prev) => {\n const next = Math.min(prev + 1, flatFieldsList.length - 1)\n setFocusedField(flatFieldsList[next])\n return next\n })\n break\n\n case 'ArrowUp':\n e.preventDefault()\n setFocusedIndex((prev) => {\n const next = Math.max(prev - 1, 0)\n setFocusedField(flatFieldsList[next])\n return next\n })\n break\n\n case 'Enter':\n e.preventDefault()\n if (focusedIndex >= 0 && flatFieldsList[focusedIndex]) {\n handleSelectField(flatFieldsList[focusedIndex], focusedIndex, e.shiftKey)\n }\n break\n\n case 'Escape':\n e.preventDefault()\n onClose()\n break\n }\n },\n [flatFieldsList, focusedIndex, handleSelectField, onClose]\n )\n\n // Scroll focused item into view\n useEffect(() => {\n if (focusedIndex >= 0 && resultsContainerRef.current) {\n const focusedElement = resultsContainerRef.current.querySelector(\n `[data-field-index=\"${focusedIndex}\"]`\n )\n if (focusedElement) {\n focusedElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' })\n }\n }\n }, [focusedIndex])\n\n if (!isOpen) return null\n\n const searchPlaceholder =\n mode === 'metrics' ? 'Search metrics...' : mode === 'filter' ? 'Search fields to filter...' : 'Search dimensions...'\n\n const modalTitle = mode === 'metrics' ? 'Select a Metric' : mode === 'filter' ? 'Select a Field to Filter' : 'Select a Dimension'\n const focusedFieldId = focusedIndex >= 0 && flatFieldsList[focusedIndex]\n ? `field-option-${flatFieldsList[focusedIndex].name.replace(/\\./g, '-')}`\n : undefined\n\n return (\n <div\n className=\"dc:fixed dc:inset-0 dc:z-50 dc:flex dc:items-center dc:justify-center\"\n style={{ backgroundColor: 'var(--dc-overlay)' }}\n onClick={onClose}\n role=\"presentation\"\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={modalTitle}\n className=\"bg-dc-surface dc:shadow-xl dc:w-full dc:h-full dc:md:rounded-lg dc:md:w-[900px] dc:md:max-w-[900px] dc:md:h-[80vh] dc:md:max-h-[700px] dc:flex dc:flex-col dc:overflow-hidden\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={handleKeyDown}\n >\n {/* Header with Search */}\n <div className=\"dc:shrink-0 dc:border-b border-dc-border\">\n <div className=\"dc:flex dc:items-center dc:px-4 dc:py-3 dc:gap-3\">\n <SearchIcon className=\"dc:w-5 dc:h-5 text-dc-text-muted\" aria-hidden={true} />\n <input\n ref={searchInputRef}\n type=\"text\"\n value={searchTerm}\n onChange={(e) => {\n setSearchTerm(e.target.value)\n setFocusedIndex(-1)\n }}\n placeholder={searchPlaceholder}\n className=\"dc:flex-1 bg-transparent dc:border-none dc:outline-none text-dc-text placeholder-dc-text-muted dc:text-lg\"\n aria-label={searchPlaceholder}\n aria-controls=\"field-search-results\"\n aria-activedescendant={focusedFieldId}\n role=\"combobox\"\n aria-expanded=\"true\"\n aria-autocomplete=\"list\"\n />\n <button\n onClick={onClose}\n className=\"dc:p-1 text-dc-text-secondary hover:text-dc-text dc:rounded\"\n aria-label=\"Close dialog\"\n >\n <CloseIcon className=\"dc:w-5 dc:h-5\" aria-hidden={true} />\n </button>\n </div>\n {/* Mobile cube filter - shown only on mobile */}\n {cubeNames.length > 1 && (\n <div className=\"dc:md:hidden dc:px-4 dc:pb-3\">\n <select\n value={selectedCube || ''}\n onChange={(e) => setSelectedCube(e.target.value || null)}\n className=\"dc:w-full dc:px-3 dc:py-2 bg-dc-surface dc:border border-dc-border dc:rounded-lg dc:text-sm text-dc-text dc:focus:outline-none dc:focus:ring-1 focus:ring-dc-primary\"\n aria-label=\"Filter by cube\"\n >\n <option value=\"\">All Cubes</option>\n {cubeNames.map((cubeName) => (\n <option key={cubeName} value={cubeName}>\n {getCubeTitle(cubeName, schema)}\n </option>\n ))}\n </select>\n </div>\n )}\n </div>\n\n {/* Three Column Layout - Single column on mobile */}\n <div className=\"dc:flex-1 dc:flex dc:overflow-hidden\">\n {/* Left Column - Categories (hidden on mobile) */}\n <nav\n className=\"dc:hidden dc:md:block dc:w-48 dc:shrink-0 dc:border-r border-dc-border dc:overflow-y-auto bg-dc-surface-secondary\"\n aria-label=\"Filter by cube\"\n >\n <div className=\"dc:p-2\" role=\"group\" aria-label=\"Cube categories\">\n <button\n onClick={() => setSelectedCube(null)}\n className={`dc:w-full dc:text-left dc:px-3 dc:py-2 dc:rounded-md dc:text-sm dc:transition-colors ${\n selectedCube === null\n ? 'bg-dc-primary/10 text-dc-primary dc:font-medium'\n : 'text-dc-text hover:bg-dc-surface-hover'\n }`}\n aria-pressed={selectedCube === null}\n >\n All\n </button>\n {cubeNames.map((cubeName) => (\n <button\n key={cubeName}\n onClick={() => setSelectedCube(cubeName)}\n className={`dc:w-full dc:text-left dc:px-3 dc:py-2 dc:rounded-md dc:text-sm dc:transition-colors dc:truncate ${\n selectedCube === cubeName\n ? 'bg-dc-primary/10 text-dc-primary dc:font-medium'\n : 'text-dc-text hover:bg-dc-surface-hover'\n }`}\n title={getCubeTitle(cubeName, schema)}\n aria-pressed={selectedCube === cubeName}\n >\n {getCubeTitle(cubeName, schema)}\n </button>\n ))}\n </div>\n </nav>\n\n {/* Middle Column - Results */}\n <div\n id=\"field-search-results\"\n ref={resultsContainerRef}\n className=\"dc:flex-1 dc:overflow-y-auto dc:p-4\"\n role=\"listbox\"\n aria-label=\"Available fields\"\n >\n {filteredFields.length === 0 && recentOptions.length === 0 ? (\n <div className=\"dc:text-center dc:py-12 text-dc-text-muted\">\n <p className=\"dc:text-lg dc:mb-2\">No fields found</p>\n <p className=\"dc:text-sm\">\n {searchTerm\n ? `No ${mode === 'metrics' ? 'metrics' : 'dimensions'} match \"${searchTerm}\"`\n : `No ${mode === 'metrics' ? 'metrics' : 'dimensions'} available`}\n </p>\n </div>\n ) : (\n <div className=\"dc:space-y-6\">\n {/* Recent Fields */}\n {recentOptions.length > 0 && (\n <div>\n <h3 className=\"dc:text-xs dc:font-semibold text-dc-text-muted dc:uppercase dc:tracking-wider dc:mb-2\">\n Recents\n </h3>\n <div className=\"dc:space-y-1\">\n {recentOptions.map((field, idx) => (\n <FieldSearchItem\n key={`recent-${field.name}`}\n field={field}\n isSelected={selectedFields.includes(field.name)}\n isFocused={focusedIndex === idx}\n onClick={(e) => handleSelectField(field, idx, e.shiftKey)}\n onMouseEnter={() => {\n setFocusedField(field)\n setFocusedIndex(idx)\n }}\n data-field-index={idx}\n />\n ))}\n </div>\n </div>\n )}\n\n {/* Grouped by Cube */}\n {Array.from(groupedFields.entries()).map(([cubeName, fields]) => (\n <div key={cubeName}>\n <h3 className=\"dc:text-xs dc:font-semibold text-dc-text-muted dc:uppercase dc:tracking-wider dc:mb-2\">\n {getCubeTitle(cubeName, schema)}\n </h3>\n <div className=\"dc:space-y-1\">\n {fields.map((field) => {\n const fieldIndex =\n recentOptions.length +\n Array.from(groupedFields.entries())\n .slice(\n 0,\n Array.from(groupedFields.keys()).indexOf(cubeName)\n )\n .reduce((sum, [, f]) => sum + f.length, 0) +\n fields.indexOf(field)\n\n return (\n <FieldSearchItem\n key={field.name}\n field={field}\n isSelected={selectedFields.includes(field.name)}\n isFocused={focusedIndex === fieldIndex}\n onClick={(e) => handleSelectField(field, fieldIndex, e.shiftKey)}\n onMouseEnter={() => {\n setFocusedField(field)\n setFocusedIndex(fieldIndex)\n }}\n data-field-index={fieldIndex}\n />\n )\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Right Column - Field Details (hidden on mobile) */}\n <div className=\"dc:hidden dc:md:block dc:w-72 dc:shrink-0 dc:border-l border-dc-border bg-dc-surface-secondary dc:overflow-y-auto\">\n <FieldDetailPanel field={focusedField} />\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"dc:shrink-0 dc:border-t border-dc-border dc:px-4 dc:py-3 dc:flex dc:items-center dc:justify-between dc:text-sm text-dc-text-muted\">\n <div>\n <span className=\"text-dc-text-secondary\">{filteredFields.length}</span>{' '}\n {mode === 'metrics' ? 'metrics' : mode === 'filter' ? 'fields' : 'dimensions'} available\n </div>\n {/* Keyboard shortcuts - hidden on mobile */}\n <div className=\"dc:hidden dc:md:flex dc:items-center dc:gap-4\">\n <span>\n <kbd className=\"dc:px-1.5 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">↑↓</kbd> Navigate\n </span>\n <span>\n <kbd className=\"dc:px-1.5 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">Enter</kbd> Select\n </span>\n <span>\n <kbd className=\"dc:px-1.5 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">Shift</kbd>+Click Multi-select\n </span>\n <span>\n <kbd className=\"dc:px-1.5 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">Esc</kbd> Close\n </span>\n </div>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACF;AAKD,SAAgB,EAAgB,GAAoC;AAMlE,QALK,KAIW,EAAe,MAAK,MAAK,EAAE,SAAS,EAAY,IAHvD,EAAe;;;;AC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"}
|
|
1
|
+
{"version":3,"file":"FieldSearchModal-5Qz6vvTz.js","names":[],"sources":["../../../src/client/types/analysisConfig.ts","../../../src/client/utils/configMigration.ts","../../../src/client/utils/filterUtils.ts","../../../src/client/shared/components/CodeBlock.tsx","../../../src/client/utils/colorPalettes.ts","../../../src/client/components/Modal.tsx","../../../src/client/components/AnalysisBuilder/utils/idUtils.ts","../../../src/client/components/AnalysisBuilder/utils/fieldUtils.ts","../../../src/client/components/AnalysisBuilder/utils/recentFieldsUtils.ts","../../../src/client/adapters/funnelModeAdapter.ts","../../../src/client/adapters/flowModeAdapter.ts","../../../src/client/adapters/retentionModeAdapter.ts","../../../src/client/components/AnalysisBuilder/AnalysisDisplayConfigPanel.tsx","../../../src/client/components/ConfirmModal.tsx","../../../src/client/components/ColorPaletteSelector.tsx","../../../src/client/components/AnalysisBuilder/FieldSearchItem.tsx","../../../src/client/components/AnalysisBuilder/FieldDetailPanel.tsx","../../../src/client/components/AnalysisBuilder/FieldSearchModal.tsx"],"sourcesContent":["/**\n * AnalysisConfig - Versioned, mode-agnostic configuration format\n *\n * This is the canonical format for persisting analysis state to:\n * - localStorage (standalone AnalysisBuilder)\n * - Share URLs\n * - Dashboard portlets (via analysisConfig field)\n *\n * Key design principles:\n * - `query` field IS the executable query (no transformation needed)\n * - Per-mode chart config via `charts[analysisType]` map\n * - No UI state (activeQueryIndex, activeFunnelStepIndex) - those are transient\n */\n\nimport type {\n ChartType,\n ChartAxisConfig,\n ChartDisplayConfig,\n CubeQuery,\n MultiQueryConfig,\n} from '../types'\nimport type { ServerFunnelQuery } from './funnel'\nimport type { ServerFlowQuery } from './flow'\nimport type { ServerRetentionQuery } from './retention'\n\n// ============================================================================\n// Chart Configuration\n// ============================================================================\n\n/**\n * Chart configuration - shared across all analysis types\n */\nexport interface ChartConfig {\n chartType: ChartType\n chartConfig: ChartAxisConfig\n displayConfig: ChartDisplayConfig\n}\n\n// ============================================================================\n// Analysis Type\n// ============================================================================\n\n/**\n * Analysis type discriminator\n * Future modes: 'cohort'\n */\nexport type AnalysisType = 'query' | 'funnel' | 'flow' | 'retention'\n\n// ============================================================================\n// Base Configuration\n// ============================================================================\n\n/**\n * Base config - common to all analysis types\n */\ninterface AnalysisConfigBase {\n /** Version number for migration support */\n version: 1\n\n /** Which analysis mode this config represents */\n analysisType: AnalysisType\n\n /** Whether to show table or chart view */\n activeView: 'table' | 'chart'\n\n /**\n * Per-mode chart configuration map.\n * Each mode owns its own chart settings, allowing users to configure\n * different chart types for query mode vs funnel mode.\n */\n charts: {\n [K in AnalysisType]?: ChartConfig\n }\n}\n\n// ============================================================================\n// Query Mode Configuration\n// ============================================================================\n\n/**\n * Query mode config - for single queries and multi-query analysis\n *\n * The `query` field is the actual executable query:\n * - Single query: CubeQuery object\n * - Multi-query: MultiQueryConfig with queries array and merge strategy\n */\nexport interface QueryAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'query'\n\n /**\n * The executable query.\n * - CubeQuery: Single query with measures, dimensions, filters\n * - MultiQueryConfig: Multiple queries with merge strategy\n */\n query: CubeQuery | MultiQueryConfig\n}\n\n// ============================================================================\n// Funnel Mode Configuration\n// ============================================================================\n\n/**\n * Funnel mode config - for funnel analysis\n *\n * The `query` field is the ServerFunnelQuery which can be sent\n * directly to the server for execution.\n */\nexport interface FunnelAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'funnel'\n\n /**\n * Server funnel query - executable as-is.\n * Contains bindingKey, timeDimension, steps[], and options.\n */\n query: ServerFunnelQuery\n}\n\n// ============================================================================\n// Flow Mode Configuration\n// ============================================================================\n\n/**\n * Flow mode config - for bidirectional flow analysis\n *\n * The `query` field is the ServerFlowQuery which can be sent\n * directly to the server for execution.\n */\nexport interface FlowAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'flow'\n\n /**\n * Server flow query - executable as-is.\n * Contains bindingKey, timeDimension, eventDimension, startingStep, and depth config.\n */\n query: ServerFlowQuery\n}\n\n// ============================================================================\n// Retention Mode Configuration\n// ============================================================================\n\n/**\n * Retention mode config - for cohort-based retention analysis\n *\n * The `query` field is the ServerRetentionQuery which can be sent\n * directly to the server for execution.\n */\nexport interface RetentionAnalysisConfig extends AnalysisConfigBase {\n analysisType: 'retention'\n\n /**\n * Server retention query - executable as-is.\n * Contains cohortTimeDimension, activityTimeDimension, bindingKey,\n * granularity settings, and period configuration.\n */\n query: ServerRetentionQuery\n}\n\n// ============================================================================\n// Union Type\n// ============================================================================\n\n/**\n * AnalysisConfig - union of all analysis mode configurations\n */\nexport type AnalysisConfig = QueryAnalysisConfig | FunnelAnalysisConfig | FlowAnalysisConfig | RetentionAnalysisConfig\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Check if config is for query mode\n */\nexport const isQueryConfig = (c: AnalysisConfig): c is QueryAnalysisConfig =>\n c.analysisType === 'query'\n\n/**\n * Check if config is for funnel mode\n */\nexport const isFunnelConfig = (c: AnalysisConfig): c is FunnelAnalysisConfig =>\n c.analysisType === 'funnel'\n\n/**\n * Check if config is for flow mode\n */\nexport const isFlowConfig = (c: AnalysisConfig): c is FlowAnalysisConfig =>\n c.analysisType === 'flow'\n\n/**\n * Check if config is for retention mode\n */\nexport const isRetentionConfig = (c: AnalysisConfig): c is RetentionAnalysisConfig =>\n c.analysisType === 'retention'\n\n/**\n * Check if a query config contains multiple queries\n */\nexport const isMultiQuery = (config: QueryAnalysisConfig): boolean =>\n 'queries' in config.query &&\n Array.isArray((config.query as MultiQueryConfig).queries)\n\n/**\n * Check if a query config contains a single query\n */\nexport const isSingleQuery = (config: QueryAnalysisConfig): boolean =>\n !isMultiQuery(config)\n\n/**\n * Type guard to validate if an unknown value is a valid AnalysisConfig\n */\nexport const isValidAnalysisConfig = (\n config: unknown\n): config is AnalysisConfig => {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n // Check version\n if (c.version !== 1) return false\n\n // Check analysisType\n if (c.analysisType !== 'query' && c.analysisType !== 'funnel' && c.analysisType !== 'flow' && c.analysisType !== 'retention') return false\n\n // Check query exists\n if (!c.query || typeof c.query !== 'object') return false\n\n // Check activeView\n if (c.activeView !== 'table' && c.activeView !== 'chart') return false\n\n return true\n}\n\n// ============================================================================\n// Default Factories\n// ============================================================================\n\n/**\n * Create a default empty query analysis config\n */\nexport const createDefaultQueryConfig = (): QueryAnalysisConfig => ({\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: {\n chartType: 'bar',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n measures: [],\n dimensions: [],\n },\n})\n\n/**\n * Create a default empty funnel analysis config\n */\nexport const createDefaultFunnelConfig = (): FunnelAnalysisConfig => ({\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n funnel: {\n bindingKey: '',\n timeDimension: '',\n steps: [],\n },\n },\n})\n\n/**\n * Create a default empty flow analysis config\n */\nexport const createDefaultFlowConfig = (): FlowAnalysisConfig => ({\n version: 1,\n analysisType: 'flow',\n activeView: 'chart',\n charts: {\n flow: {\n chartType: 'sankey',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n flow: {\n bindingKey: '',\n timeDimension: '',\n eventDimension: '',\n startingStep: {\n name: '',\n filter: undefined,\n },\n stepsBefore: 3,\n stepsAfter: 3,\n joinStrategy: 'auto',\n },\n },\n})\n\n/**\n * Create a default empty retention analysis config\n */\nexport const createDefaultRetentionConfig = (): RetentionAnalysisConfig => ({\n version: 1,\n analysisType: 'retention',\n activeView: 'chart',\n charts: {\n retention: {\n chartType: 'heatmap',\n chartConfig: {},\n displayConfig: {},\n },\n },\n query: {\n retention: {\n timeDimension: '',\n bindingKey: '',\n dateRange: { start: '', end: '' },\n granularity: 'week',\n periods: 12,\n retentionType: 'classic',\n },\n },\n})\n\n/**\n * Create a default config for the given analysis type\n */\nexport const createDefaultConfig = (\n type: AnalysisType = 'query'\n): AnalysisConfig => {\n switch (type) {\n case 'funnel':\n return createDefaultFunnelConfig()\n case 'flow':\n return createDefaultFlowConfig()\n case 'retention':\n return createDefaultRetentionConfig()\n case 'query':\n default:\n return createDefaultQueryConfig()\n }\n}\n\n// ============================================================================\n// Workspace Format (localStorage persistence)\n// ============================================================================\n\n/**\n * AnalysisWorkspace - Multi-mode persistence format for localStorage\n *\n * Unlike AnalysisConfig (which represents a single analysis mode),\n * AnalysisWorkspace preserves state for ALL modes. This prevents state\n * loss when switching between query, funnel, flow, and retention modes.\n *\n * Usage:\n * - localStorage persistence → AnalysisWorkspace (preserves all modes)\n * - Share URLs → AnalysisConfig (shares one specific analysis)\n * - Dashboard portlets → AnalysisConfig (embeds one specific analysis)\n */\nexport interface AnalysisWorkspace {\n /** Version number for migration support */\n version: 1\n\n /** Currently active analysis type */\n activeType: AnalysisType\n\n /**\n * Per-mode configurations.\n * Each mode stores its complete AnalysisConfig independently.\n * Charts are duplicated per-mode but merged on load.\n */\n modes: {\n query?: QueryAnalysisConfig\n funnel?: FunnelAnalysisConfig\n flow?: FlowAnalysisConfig\n retention?: RetentionAnalysisConfig\n }\n}\n\n/**\n * Type guard to validate if an unknown value is a valid AnalysisWorkspace\n */\nexport const isValidAnalysisWorkspace = (\n data: unknown\n): data is AnalysisWorkspace => {\n if (!data || typeof data !== 'object') return false\n\n const w = data as Record<string, unknown>\n\n // Check version\n if (w.version !== 1) return false\n\n // Check activeType\n if (w.activeType !== 'query' && w.activeType !== 'funnel' && w.activeType !== 'flow' && w.activeType !== 'retention') return false\n\n // Check modes exists and is an object\n if (!w.modes || typeof w.modes !== 'object') return false\n\n return true\n}\n\n/**\n * Create a default empty workspace with all modes initialized\n */\nexport const createDefaultWorkspace = (): AnalysisWorkspace => ({\n version: 1,\n activeType: 'query',\n modes: {\n query: createDefaultQueryConfig(),\n funnel: createDefaultFunnelConfig(),\n flow: createDefaultFlowConfig(),\n retention: createDefaultRetentionConfig(),\n },\n})\n","/**\n * Config Migration Utilities\n *\n * Handles migration from legacy portlet/share formats to AnalysisConfig.\n * Used for backward compatibility when loading old saved configurations.\n */\n\nimport type {\n AnalysisConfig,\n QueryAnalysisConfig,\n FunnelAnalysisConfig,\n FlowAnalysisConfig,\n RetentionAnalysisConfig,\n ChartConfig,\n} from '../types/analysisConfig'\nimport { createDefaultQueryConfig, isValidAnalysisConfig } from '../types/analysisConfig'\nimport type {\n ChartType,\n ChartAxisConfig,\n ChartDisplayConfig,\n CubeQuery,\n MultiQueryConfig,\n FunnelBindingKey,\n QueryMergeStrategy,\n PortletConfig,\n} from '../types'\nimport type { ServerFunnelQuery, ServerFunnelStep } from '../types/funnel'\nimport type { ServerFlowQuery } from '../types/flow'\nimport { isServerRetentionQuery } from '../types/retention'\n\n// ============================================================================\n// Legacy Portlet Format\n// ============================================================================\n\n/**\n * Legacy portlet format (before AnalysisConfig)\n */\nexport interface LegacyPortlet {\n /** JSON string of CubeQuery or MultiQueryConfig */\n query: string\n chartType?: ChartType\n chartConfig?: ChartAxisConfig\n displayConfig?: ChartDisplayConfig\n analysisType?: 'query' | 'funnel'\n // Funnel-specific legacy fields\n funnelChartType?: ChartType\n funnelChartConfig?: ChartAxisConfig\n funnelDisplayConfig?: ChartDisplayConfig\n}\n\n/**\n * Legacy MultiQueryConfig with funnel merge strategy\n * This is a standalone type (not extending MultiQueryConfig) to handle old data\n * where mergeStrategy was 'funnel' before it was removed from QueryMergeStrategy.\n */\nexport interface LegacyFunnelMultiQuery {\n queries: CubeQuery[]\n mergeStrategy: 'funnel'\n mergeKeys?: string[]\n queryLabels?: string[]\n funnelBindingKey?: FunnelBindingKey | null\n stepTimeToConvert?: (string | null)[]\n}\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n/**\n * Check if a query is a MultiQueryConfig\n */\nfunction isMultiQueryConfig(query: unknown): query is MultiQueryConfig {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'queries' in query &&\n Array.isArray((query as MultiQueryConfig).queries)\n )\n}\n\n/**\n * Check if a query is a legacy funnel multi-query\n * Uses type assertion since 'funnel' is no longer in QueryMergeStrategy\n */\nfunction isLegacyFunnelMultiQuery(\n query: unknown\n): query is LegacyFunnelMultiQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'queries' in query &&\n Array.isArray((query as { queries?: unknown[] }).queries) &&\n 'mergeStrategy' in query &&\n (query as { mergeStrategy?: string }).mergeStrategy === 'funnel'\n )\n}\n\n/**\n * Check if a query is a ServerFunnelQuery\n */\nfunction isServerFunnelQuery(query: unknown): query is ServerFunnelQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'funnel' in query &&\n typeof (query as ServerFunnelQuery).funnel === 'object'\n )\n}\n\n/**\n * Check if a query is a ServerFlowQuery\n */\nfunction isServerFlowQuery(query: unknown): query is ServerFlowQuery {\n return (\n typeof query === 'object' &&\n query !== null &&\n 'flow' in query &&\n typeof (query as ServerFlowQuery).flow === 'object'\n )\n}\n\n// ============================================================================\n// Migration Functions\n// ============================================================================\n\n/**\n * Extract chart config from legacy portlet fields\n */\nfunction extractChartConfig(\n portlet: LegacyPortlet,\n analysisType: 'query' | 'funnel' | 'flow' | 'retention'\n): ChartConfig {\n if (analysisType === 'funnel') {\n return {\n chartType: portlet.funnelChartType || portlet.chartType || 'funnel',\n chartConfig: portlet.funnelChartConfig || portlet.chartConfig || {},\n displayConfig: portlet.funnelDisplayConfig || portlet.displayConfig || {},\n }\n }\n\n if (analysisType === 'flow') {\n // Flow uses sankey chart by default\n return {\n chartType: portlet.chartType || 'sankey',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n }\n\n if (analysisType === 'retention') {\n // Retention uses retentionCombined chart by default\n return {\n chartType: portlet.chartType || 'retentionCombined',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n }\n\n return {\n chartType: portlet.chartType || 'bar',\n chartConfig: portlet.chartConfig || {},\n displayConfig: portlet.displayConfig || {},\n }\n}\n\n/**\n * Migrate a legacy portlet to AnalysisConfig format\n *\n * Handles:\n * - Single CubeQuery → QueryAnalysisConfig\n * - MultiQueryConfig → QueryAnalysisConfig\n * - ServerFunnelQuery → FunnelAnalysisConfig\n * - ServerFlowQuery → FlowAnalysisConfig\n * - Legacy mergeStrategy:'funnel' → FunnelAnalysisConfig (via migrateLegacyFunnelMerge)\n *\n * @param portlet - Legacy portlet with query string\n * @returns AnalysisConfig in new format\n */\nexport function migrateLegacyPortlet(portlet: LegacyPortlet): AnalysisConfig {\n try {\n const query = JSON.parse(portlet.query)\n\n // Check if it's a ServerRetentionQuery\n if (isServerRetentionQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'retention')\n return {\n version: 1,\n analysisType: 'retention',\n activeView: 'chart',\n charts: {\n retention: chartConfig,\n },\n query,\n } as RetentionAnalysisConfig\n }\n\n // Check if it's a ServerFlowQuery\n if (isServerFlowQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'flow')\n return {\n version: 1,\n analysisType: 'flow',\n activeView: 'chart',\n charts: {\n flow: chartConfig,\n },\n query,\n } as FlowAnalysisConfig\n }\n\n // Check if it's already a ServerFunnelQuery\n if (isServerFunnelQuery(query)) {\n const chartConfig = extractChartConfig(portlet, 'funnel')\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query,\n } as FunnelAnalysisConfig\n }\n\n // Check if it's a legacy funnel multi-query\n if (isLegacyFunnelMultiQuery(query)) {\n return migrateLegacyFunnelMerge(query, portlet)\n }\n\n // Check explicit analysisType\n if (portlet.analysisType === 'funnel') {\n // Treat as funnel even if query isn't ServerFunnelQuery format\n // (This handles edge cases where analysisType was set but query wasn't converted)\n const chartConfig = extractChartConfig(portlet, 'funnel')\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query: isServerFunnelQuery(query)\n ? query\n : {\n funnel: {\n bindingKey: '',\n timeDimension: '',\n steps: [],\n },\n },\n } as FunnelAnalysisConfig\n }\n\n // Handle regular query or multi-query\n const chartConfig = extractChartConfig(portlet, 'query')\n\n // MultiQueryConfig (non-funnel, since isLegacyFunnelMultiQuery was checked earlier)\n if (isMultiQueryConfig(query)) {\n // Defensive: convert any lingering 'funnel' strategy to 'concat'\n const strategy = (query.mergeStrategy as string) === 'funnel' ? 'concat' : query.mergeStrategy\n return {\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: chartConfig,\n },\n query: {\n queries: query.queries,\n mergeStrategy: strategy as QueryMergeStrategy,\n mergeKeys: query.mergeKeys,\n queryLabels: query.queryLabels,\n },\n } as QueryAnalysisConfig\n }\n\n // Single CubeQuery\n return {\n version: 1,\n analysisType: 'query',\n activeView: 'chart',\n charts: {\n query: chartConfig,\n },\n query: query as CubeQuery,\n } as QueryAnalysisConfig\n } catch (error) {\n // If parsing fails, return default config\n console.warn('[configMigration] Failed to parse legacy portlet:', error)\n return createDefaultQueryConfig()\n }\n}\n\n/**\n * Migrate legacy mergeStrategy:'funnel' to FunnelAnalysisConfig\n *\n * This handles the old pattern where funnels were created using multi-query\n * with mergeStrategy: 'funnel'. Converts to the new dedicated funnel format.\n *\n * @param legacyQuery - MultiQueryConfig with mergeStrategy: 'funnel'\n * @param portlet - Legacy portlet for chart config\n * @returns FunnelAnalysisConfig in new format\n */\nexport function migrateLegacyFunnelMerge(\n legacyQuery: LegacyFunnelMultiQuery,\n portlet?: LegacyPortlet\n): FunnelAnalysisConfig {\n // Extract binding key\n let bindingKey: ServerFunnelQuery['funnel']['bindingKey'] = ''\n if (legacyQuery.funnelBindingKey?.dimension) {\n if (typeof legacyQuery.funnelBindingKey.dimension === 'string') {\n bindingKey = legacyQuery.funnelBindingKey.dimension\n } else if (Array.isArray(legacyQuery.funnelBindingKey.dimension)) {\n bindingKey = legacyQuery.funnelBindingKey.dimension.map((m) => ({\n cube: m.cube,\n dimension: m.dimension,\n }))\n }\n }\n\n // Extract time dimension from first query\n let timeDimension: string = ''\n if (\n legacyQuery.queries.length > 0 &&\n legacyQuery.queries[0].timeDimensions?.length\n ) {\n timeDimension = legacyQuery.queries[0].timeDimensions[0].dimension\n }\n\n // Convert queries to funnel steps\n const steps: ServerFunnelStep[] = legacyQuery.queries.map((query, index) => {\n const step: ServerFunnelStep = {\n name:\n legacyQuery.queryLabels?.[index] ||\n `Step ${index + 1}`,\n }\n\n // Convert filters\n if (query.filters && query.filters.length > 0) {\n step.filter =\n query.filters.length === 1 ? query.filters[0] : { and: query.filters }\n }\n\n // Add time to convert if specified\n if (\n legacyQuery.stepTimeToConvert &&\n legacyQuery.stepTimeToConvert[index]\n ) {\n step.timeToConvert = legacyQuery.stepTimeToConvert[index] as string\n }\n\n return step\n })\n\n // Build chart config\n const chartConfig: ChartConfig = portlet\n ? extractChartConfig(portlet, 'funnel')\n : {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: {},\n }\n\n return {\n version: 1,\n analysisType: 'funnel',\n activeView: 'chart',\n charts: {\n funnel: chartConfig,\n },\n query: {\n funnel: {\n bindingKey,\n timeDimension,\n steps,\n includeTimeMetrics: true,\n },\n },\n }\n}\n\n/**\n * Migrate any config to the latest version\n *\n * Handles:\n * - Already valid AnalysisConfig (returns as-is)\n * - Legacy portlet format (converts to AnalysisConfig)\n * - Unknown format (returns default config)\n *\n * @param config - Unknown config value\n * @returns Valid AnalysisConfig\n */\nexport function migrateConfig(config: unknown): AnalysisConfig {\n // Already valid?\n if (isValidAnalysisConfig(config)) {\n return config\n }\n\n // Check if it looks like a legacy portlet\n if (\n config &&\n typeof config === 'object' &&\n 'query' in config &&\n typeof (config as { query: unknown }).query === 'string'\n ) {\n return migrateLegacyPortlet(config as LegacyPortlet)\n }\n\n // Check if it's a raw query object (not wrapped in portlet)\n if (config && typeof config === 'object') {\n try {\n // Try to wrap it as a portlet and migrate\n const wrapped: LegacyPortlet = {\n query: JSON.stringify(config),\n }\n return migrateLegacyPortlet(wrapped)\n } catch {\n // Fall through to default\n }\n }\n\n // Return default config\n console.warn('[configMigration] Unknown config format, using defaults')\n return createDefaultQueryConfig()\n}\n\n/**\n * Check if a portlet has the new AnalysisConfig format\n */\nexport function hasAnalysisConfig(\n portlet: unknown\n): portlet is { analysisConfig: AnalysisConfig } {\n return (\n typeof portlet === 'object' &&\n portlet !== null &&\n 'analysisConfig' in portlet &&\n isValidAnalysisConfig((portlet as { analysisConfig: unknown }).analysisConfig)\n )\n}\n\n/**\n * Ensure a portlet has analysisConfig, migrating from legacy format if needed.\n *\n * This is the primary entry point for rendering portlets - it guarantees\n * that analysisConfig exists, either by using the existing one or by\n * converting legacy fields on-the-fly.\n *\n * @param portlet - PortletConfig which may or may not have analysisConfig\n * @returns PortletConfig with analysisConfig guaranteed to exist\n */\nexport function ensureAnalysisConfig(\n portlet: PortletConfig\n): PortletConfig & { analysisConfig: AnalysisConfig } {\n // If already has valid analysisConfig, return as-is\n if (hasAnalysisConfig(portlet)) {\n return portlet as PortletConfig & { analysisConfig: AnalysisConfig }\n }\n\n // Migrate from legacy fields\n // Note: 'flow' and 'retention' types don't have legacy format - filter to only query/funnel\n const legacyAnalysisType = portlet.analysisType === 'flow' || portlet.analysisType === 'retention' ? undefined : portlet.analysisType\n const analysisConfig = migrateLegacyPortlet({\n query: portlet.query ?? '{}',\n chartType: portlet.chartType,\n chartConfig: portlet.chartConfig,\n displayConfig: portlet.displayConfig,\n analysisType: legacyAnalysisType,\n funnelChartType: portlet.funnelChartType,\n funnelChartConfig: portlet.funnelChartConfig,\n funnelDisplayConfig: portlet.funnelDisplayConfig,\n })\n\n return { ...portlet, analysisConfig }\n}\n","/**\n * Filter utility functions for dashboard-level filtering\n *\n * NOTE: These are pure functions without internal caching.\n * Memoization should be handled at the component level using useMemo.\n */\n\nimport type { Filter, DashboardFilter, CubeMeta, GroupFilter, DashboardConfig, SimpleFilter } from '../types'\nimport { ensureAnalysisConfig } from './configMigration'\n\n/**\n * Check if a filter should be included in the query (has valid values or doesn't require values)\n * @param filter - The filter to check\n * @returns true if the filter should be included, false otherwise\n */\nexport function shouldIncludeFilter(filter: Filter): boolean {\n // Handle SimpleFilter\n if ('member' in filter && 'operator' in filter) {\n const simpleFilter = filter as SimpleFilter\n\n // Operators that don't require values\n const noValueOperators = ['set', 'notSet', 'isEmpty', 'isNotEmpty']\n if (noValueOperators.includes(simpleFilter.operator)) {\n return true\n }\n\n // For inDateRange, check if dateRange is provided as alternative to values\n if (simpleFilter.operator === 'inDateRange' && simpleFilter.dateRange) {\n return true\n }\n\n // For other operators, check if values exist and are non-empty\n return !!(simpleFilter.values && simpleFilter.values.length > 0)\n }\n\n // Handle GroupFilter - recursively check nested filters\n if ('type' in filter && 'filters' in filter) {\n const groupFilter = filter as GroupFilter\n // Include group filter if at least one nested filter is valid\n const validFilters = groupFilter.filters.filter(f => shouldIncludeFilter(f))\n return validFilters.length > 0\n }\n\n return false\n}\n\n/**\n * Get dashboard filters that should be applied to a portlet based on its mapping configuration\n *\n * @param dashboardFilters - All available dashboard filters\n * @param filterMapping - Array of filter IDs that apply to this portlet\n * @returns Array of filters that should be applied to the portlet\n */\nexport function getApplicableDashboardFilters(\n dashboardFilters: DashboardFilter[] | undefined,\n filterMapping: string[] | undefined\n): Filter[] {\n if (!dashboardFilters || !dashboardFilters.length) {\n return []\n }\n\n // If no mapping is specified, no dashboard filters apply\n if (!filterMapping || !filterMapping.length) {\n return []\n }\n\n // Compute filters that are in the mapping AND have valid values\n return dashboardFilters\n .filter(df => filterMapping.includes(df.id))\n .filter(df => shouldIncludeFilter(df.filter))\n .map(df => df.filter)\n}\n\n/**\n * Convert GroupFilter format to server format\n * GroupFilter: { type: 'and', filters: [...] }\n * Server format: { and: [...] } or { or: [...] }\n */\nfunction convertToServerFormat(filter: Filter): any {\n // Handle GroupFilter format\n if ('type' in filter && 'filters' in filter) {\n const groupFilter = filter as GroupFilter\n const convertedFilters = groupFilter.filters.map(convertToServerFormat)\n\n if (groupFilter.type === 'and') {\n return { and: convertedFilters }\n } else {\n return { or: convertedFilters }\n }\n }\n\n // Simple filter - return as-is\n return filter\n}\n\n/**\n * Filter format for merge operation:\n * - 'server': Returns {and: [...]} or {or: [...]} format (for API queries)\n * - 'client': Returns {type: 'and', filters: [...]} format (for UI components)\n */\nexport type FilterFormat = 'server' | 'client'\n\n/**\n * Merge dashboard filters with portlet filters using AND logic\n * Dashboard filters are combined with portlet filters so both sets of filters apply\n *\n * @param dashboardFilters - Filters from dashboard-level configuration\n * @param portletFilters - Filters from portlet query\n * @param format - Output format: 'server' for API queries, 'client' for UI (default: 'server')\n * @returns Merged filter array with AND logic in the specified format\n */\nexport function mergeDashboardAndPortletFilters(\n dashboardFilters: Filter[],\n portletFilters: Filter[] | undefined,\n format: FilterFormat = 'server'\n): Filter[] | undefined {\n // If no dashboard filters, return portlet filters as-is\n if (!dashboardFilters || dashboardFilters.length === 0) {\n return portletFilters\n }\n\n // If no portlet filters, return dashboard filters\n if (!portletFilters || portletFilters.length === 0) {\n return [...dashboardFilters]\n }\n\n // Both exist - need to merge with AND logic\n if (format === 'server') {\n // Server format: convert to {and: [...]} structure\n const allFilters = [...dashboardFilters, ...portletFilters].map(convertToServerFormat)\n return [{\n and: allFilters\n } as any]\n } else {\n // Client format: use {type: 'and', filters: [...]} structure\n const allFilters = [...dashboardFilters, ...portletFilters]\n return [{\n type: 'and',\n filters: allFilters\n } as GroupFilter]\n }\n}\n\n/**\n * @deprecated Use mergeDashboardAndPortletFilters(filters, portletFilters, 'client') instead\n */\nexport function mergeDashboardAndPortletFiltersClientFormat(\n dashboardFilters: Filter[],\n portletFilters: Filter[] | undefined\n): Filter[] | undefined {\n return mergeDashboardAndPortletFilters(dashboardFilters, portletFilters, 'client')\n}\n\n/**\n * Check if a filter field exists in the cube metadata\n * This helps identify filters that might not apply to a specific portlet's data\n * @param filter - The filter to validate\n * @param cubeMeta - Cube metadata to validate against\n * @returns true if the filter field exists in any cube's measures or dimensions\n */\nexport function validateFilterForCube(\n filter: Filter,\n cubeMeta: CubeMeta | null\n): boolean {\n if (!cubeMeta || !cubeMeta.cubes) {\n // If no metadata available, assume filter is valid (fail open)\n return true\n }\n\n // Extract member names from filter recursively\n const memberNames = extractMemberNamesFromFilter(filter)\n\n // Check if any of the member names exist in cube metadata\n return memberNames.some(memberName => {\n return cubeMeta.cubes.some(cube => {\n // Check measures\n const inMeasures = cube.measures?.some(m => m.name === memberName) ?? false\n // Check dimensions\n const inDimensions = cube.dimensions?.some(d => d.name === memberName) ?? false\n\n return inMeasures || inDimensions\n })\n })\n}\n\n/**\n * Extract all member names from a filter (handles nested group filters)\n * @param filter - The filter to extract members from\n * @returns Array of member names\n */\nfunction extractMemberNamesFromFilter(filter: Filter): string[] {\n if ('member' in filter) {\n // SimpleFilter\n return [filter.member]\n } else if ('type' in filter && 'filters' in filter) {\n // GroupFilter - recursively extract from nested filters\n return filter.filters.flatMap(f => extractMemberNamesFromFilter(f))\n }\n\n return []\n}\n\n/**\n * Validate that all dashboard filters in a portlet's mapping exist and are valid\n * @param dashboardFilters - All available dashboard filters\n * @param filterMapping - The portlet's filter mapping\n * @param cubeMeta - Cube metadata for validation\n * @returns Object with validation result and list of invalid filter IDs\n */\nexport function validatePortletFilterMapping(\n dashboardFilters: DashboardFilter[] | undefined,\n filterMapping: string[] | undefined,\n cubeMeta: CubeMeta | null\n): { isValid: boolean; invalidFilterIds: string[]; missingFilterIds: string[] } {\n if (!filterMapping || !filterMapping.length) {\n return { isValid: true, invalidFilterIds: [], missingFilterIds: [] }\n }\n\n if (!dashboardFilters || !dashboardFilters.length) {\n // Mapping references filters that don't exist\n return {\n isValid: false,\n invalidFilterIds: [],\n missingFilterIds: filterMapping\n }\n }\n\n const invalidFilterIds: string[] = []\n const missingFilterIds: string[] = []\n\n filterMapping.forEach(filterId => {\n const dashboardFilter = dashboardFilters.find(df => df.id === filterId)\n\n if (!dashboardFilter) {\n // Filter ID in mapping doesn't exist in dashboard filters\n missingFilterIds.push(filterId)\n } else {\n // Check if filter is valid for the cube metadata\n const isValid = validateFilterForCube(dashboardFilter.filter, cubeMeta)\n if (!isValid) {\n invalidFilterIds.push(filterId)\n }\n }\n })\n\n return {\n isValid: invalidFilterIds.length === 0 && missingFilterIds.length === 0,\n invalidFilterIds,\n missingFilterIds\n }\n}\n\n/**\n * Extract all unique measures, dimensions, and timeDimensions used across all portlets in a dashboard\n * This helps create a filtered schema view showing only fields relevant to the dashboard\n * @param dashboardConfig - Dashboard configuration\n * @returns Object with unique measures, dimensions, and timeDimensions\n */\nexport function extractDashboardFields(\n dashboardConfig: DashboardConfig\n): { measures: Set<string>; dimensions: Set<string>; timeDimensions: Set<string> } {\n const measures = new Set<string>()\n const dimensions = new Set<string>()\n const timeDimensions = new Set<string>()\n\n // Iterate through all portlets\n dashboardConfig.portlets.forEach(portlet => {\n try {\n // Get query from analysisConfig (migrating legacy format if needed)\n const normalizedPortlet = ensureAnalysisConfig(portlet)\n const query = normalizedPortlet.analysisConfig.query\n\n // Helper to extract fields from a CubeQuery\n const extractFromCubeQuery = (cubeQuery: any) => {\n if (cubeQuery.measures && Array.isArray(cubeQuery.measures)) {\n cubeQuery.measures.forEach((measure: string) => measures.add(measure))\n }\n if (cubeQuery.dimensions && Array.isArray(cubeQuery.dimensions)) {\n cubeQuery.dimensions.forEach((dimension: string) => dimensions.add(dimension))\n }\n if (cubeQuery.timeDimensions && Array.isArray(cubeQuery.timeDimensions)) {\n cubeQuery.timeDimensions.forEach((td: any) => {\n if (td.dimension) {\n timeDimensions.add(td.dimension)\n }\n })\n }\n if (cubeQuery.filters) {\n extractFieldsFromFilters(cubeQuery.filters).forEach(field => {\n dimensions.add(field)\n })\n }\n }\n\n // Handle different query types\n if ('funnel' in query) {\n // ServerFunnelQuery - extract time dimension from funnel config\n const funnelQuery = query as any\n if (funnelQuery.funnel?.timeDimension) {\n timeDimensions.add(funnelQuery.funnel.timeDimension)\n }\n // Could also extract binding key dimensions if needed\n } else if ('queries' in query) {\n // MultiQueryConfig - extract from all sub-queries\n const multiQuery = query as any\n multiQuery.queries.forEach((subQuery: any) => extractFromCubeQuery(subQuery))\n } else {\n // Single CubeQuery\n extractFromCubeQuery(query)\n }\n } catch (e) {\n // Skip portlets with invalid query\n console.warn('Failed to extract fields from portlet:', portlet.id, e)\n }\n })\n\n return { measures, dimensions, timeDimensions }\n}\n\n/**\n * Extract field names from filters recursively\n * @param filters - Filter array\n * @returns Array of unique field names\n */\nfunction extractFieldsFromFilters(filters: Filter[]): string[] {\n const fields: string[] = []\n\n filters.forEach(filter => {\n if ('member' in filter) {\n // SimpleFilter\n fields.push(filter.member)\n } else if ('type' in filter && 'filters' in filter) {\n // GroupFilter - recurse\n fields.push(...extractFieldsFromFilters(filter.filters))\n }\n })\n\n return [...new Set(fields)] // Return unique fields\n}\n\n/**\n * Time dimension type from CubeQuery\n */\ntype TimeDimension = {\n dimension: string\n granularity?: string\n dateRange?: string[] | string\n}\n\n/**\n * Helper to get date range from a SimpleFilter (backward compatible)\n * Reads from both dateRange and values for compatibility\n * Handles both:\n * - Preset ranges: [\"this quarter\"], [\"last 7 days\"] (single string value)\n * - Custom ranges: [\"2024-01-01\", \"2024-12-31\"] (two date values)\n */\nfunction getDateRangeFromFilter(filter: SimpleFilter): string[] | string | undefined {\n // Prefer dateRange for backward compatibility, fall back to values\n if (filter.dateRange) {\n return filter.dateRange\n }\n if (filter.values && filter.values.length > 0) {\n // Single value = preset like \"this quarter\", return as string\n // Multiple values = custom date range, return as array\n return filter.values.length === 1 ? filter.values[0] : filter.values\n }\n return undefined\n}\n\n/**\n * Apply universal time filters to a portlet's timeDimensions\n * Universal time filters apply their dateRange to ALL time dimensions in the portlet\n *\n * @param dashboardFilters - All dashboard filters\n * @param filterMapping - Filter IDs that apply to this portlet\n * @param portletTimeDimensions - The portlet's existing timeDimensions array\n * @returns Updated timeDimensions array with date ranges applied\n */\nexport function applyUniversalTimeFilters(\n dashboardFilters: DashboardFilter[] | undefined,\n filterMapping: string[] | undefined,\n portletTimeDimensions: TimeDimension[] | undefined\n): TimeDimension[] | undefined {\n // Return as-is if no time dimensions in portlet (skip silently)\n if (!portletTimeDimensions || portletTimeDimensions.length === 0) {\n return portletTimeDimensions\n }\n\n // If no mapping specified, no filters apply\n if (!filterMapping || filterMapping.length === 0) {\n return portletTimeDimensions\n }\n\n // Find applicable universal time filters that have valid date ranges\n const universalTimeFilters = dashboardFilters\n ?.filter(df => df.isUniversalTime && filterMapping.includes(df.id))\n ?.filter(df => {\n // Must be a SimpleFilter with a valid dateRange\n if (!('member' in df.filter)) return false\n const simpleFilter = df.filter as SimpleFilter\n const dateRange = getDateRangeFromFilter(simpleFilter)\n return dateRange !== undefined\n })\n\n if (!universalTimeFilters || universalTimeFilters.length === 0) {\n return portletTimeDimensions\n }\n\n // Use the first universal time filter's dateRange (typically only one)\n const timeFilter = universalTimeFilters[0]\n const simpleFilter = timeFilter.filter as SimpleFilter\n const dateRange = getDateRangeFromFilter(simpleFilter)\n\n // Apply dateRange to ALL time dimensions (dashboard wins - overrides portlet dateRange)\n return portletTimeDimensions.map(td => ({\n ...td,\n dateRange: dateRange\n }))\n}\n","/**\n * CodeBlock Component\n * Displays syntax-highlighted code with copy-to-clipboard functionality\n */\n\nimport React, { useState, useEffect, useRef } from 'react'\nimport { getIcon } from '../../icons'\nimport { getSyntaxHighlighter, loadSyntaxHighlighter } from '../../utils/syntaxHighlighting'\nimport './CodeBlock.css'\n\ninterface CodeBlockProps {\n code: string\n language: 'json' | 'sql'\n title?: string\n maxHeight?: string\n height?: string\n className?: string\n /** Additional content to render on the right side of the header (before Copy button) */\n headerRight?: React.ReactNode\n}\n\nexport const CodeBlock: React.FC<CodeBlockProps> = ({\n code,\n language,\n title,\n maxHeight = '16rem',\n height,\n className = '',\n headerRight\n}) => {\n const [copied, setCopied] = useState(false)\n const codeRef = useRef<HTMLElement>(null)\n const CopyIcon = getIcon('copy')\n const CheckIcon = getIcon('check')\n\n // Apply syntax highlighting\n useEffect(() => {\n if (!codeRef.current) return\n const element = codeRef.current\n let isActive = true\n\n element.textContent = code\n\n loadSyntaxHighlighter()\n .then(() => {\n if (!isActive) return\n const hljs = getSyntaxHighlighter()\n if (!hljs) return\n element.innerHTML = hljs.highlight(code, { language }).value\n })\n .catch(() => {\n if (isActive) {\n element.textContent = code\n }\n })\n\n return () => {\n isActive = false\n }\n }, [code, language])\n\n const handleCopy = async () => {\n try {\n await navigator.clipboard.writeText(code)\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n } catch {\n // Fallback for older browsers\n const textArea = document.createElement('textarea')\n textArea.value = code\n textArea.style.position = 'fixed'\n textArea.style.left = '-999999px'\n document.body.appendChild(textArea)\n textArea.select()\n document.execCommand('copy')\n document.body.removeChild(textArea)\n setCopied(true)\n setTimeout(() => setCopied(false), 2000)\n }\n }\n\n return (\n <div className={`dc:relative ${className}`}>\n {/* Header with title, optional extra controls, and copy button */}\n <div className=\"dc:flex dc:items-center dc:justify-between dc:mb-2 dc:gap-2\">\n {title && (\n <h4 className=\"dc:text-sm dc:font-semibold text-dc-text\">{title}</h4>\n )}\n <div className=\"dc:flex dc:items-center dc:gap-2 dc:ml-auto\">\n {headerRight}\n <button\n onClick={handleCopy}\n className=\"dc:px-2 dc:py-1 dc:text-xs dc:rounded hover:bg-dc-surface-secondary dc:border border-dc-border dc:transition-colors dc:flex dc:items-center dc:gap-1.5\"\n title={copied ? 'Copied!' : 'Copy to clipboard'}\n >\n {copied ? (\n <>\n <CheckIcon className=\"dc:w-3.5 dc:h-3.5 text-dc-success\" />\n <span className=\"text-dc-success\">Copied</span>\n </>\n ) : (\n <>\n <CopyIcon className=\"dc:w-3.5 dc:h-3.5 text-dc-text-secondary\" />\n <span className=\"text-dc-text-secondary\">Copy</span>\n </>\n )}\n </button>\n </div>\n </div>\n\n {/* Code block with syntax highlighting */}\n <div\n className=\"bg-dc-surface-secondary dc:border border-dc-border dc:rounded dc:overflow-auto\"\n style={height ? { height, minHeight: height, maxHeight: height } : { maxHeight }}\n >\n <pre className=\"dc:p-3 dc:text-xs dc:m-0\">\n <code\n ref={codeRef}\n className={`hljs language-${language}`}\n >\n {code}\n </code>\n </pre>\n </div>\n </div>\n )\n}\n\nexport default CodeBlock\n","/**\n * Unified Color Palette System\n * Each palette contains coordinated series and gradient colors that work well together\n */\n\nexport interface ColorPalette {\n name: string\n label: string\n colors: string[] // For series-based charts (bar, line, pie, area, scatter, radar, etc.)\n gradient: string[] // For gradient-based charts (bubble, activity grid)\n}\n\n// Predefined color palettes with visually coordinated series and gradient colors\nexport const COLOR_PALETTES: ColorPalette[] = [\n {\n name: 'default',\n label: 'Default',\n colors: [\n '#3b82f6', // blue\n '#10b981', // green\n '#f59e0b', // yellow\n '#ef4444', // red\n '#8b5cf6', // purple\n '#f97316', // orange\n '#06b6d4', // cyan\n '#84cc16', // lime\n ],\n gradient: [\n '#fde725', // yellow (light - for low values)\n '#7ad151', // green\n '#22a884', // green-teal\n '#2a788e', // teal\n '#414487', // purple-blue\n '#440154', // dark purple (dark - for high values)\n ]\n },\n {\n name: 'ocean',\n label: 'Ocean',\n colors: [\n '#1e3a8a', // deep blue\n '#1e40af', // blue\n '#2563eb', // bright blue\n '#3b82f6', // light blue\n '#06b6d4', // cyan\n '#0891b2', // dark cyan\n '#0e7490', // teal\n '#0f766e', // dark teal\n ],\n gradient: [\n '#38bdf8', // cyan blue (light - for low values)\n '#0ea5e9', // light blue\n '#0284c7', // bright blue\n '#0369a1', // medium blue\n '#075985', // dark blue\n '#0c4a6e', // very dark blue (dark - for high values)\n ]\n },\n {\n name: 'sunset',\n label: 'Sunset',\n colors: [\n '#dc2626', // red\n '#ea580c', // orange-red\n '#f59e0b', // orange\n '#eab308', // yellow-orange\n '#d97706', // amber\n '#b45309', // dark amber\n '#92400e', // brown\n '#7c2d12', // dark brown\n ],\n gradient: [\n '#fbbf24', // light orange (light - for low values)\n '#f59e0b', // orange\n '#d97706', // amber\n '#b45309', // dark amber\n '#92400e', // brown\n '#7c2d12', // dark brown (dark - for high values)\n ]\n },\n {\n name: 'forest',\n label: 'Forest',\n colors: [\n '#166534', // dark green\n '#15803d', // green\n '#16a34a', // bright green\n '#22c55e', // light green\n '#4ade80', // lighter green\n '#65a30d', // lime green\n '#84cc16', // lime\n '#a3e635', // light lime\n ],\n gradient: [\n '#4ade80', // lighter green (light - for low values)\n '#22c55e', // light green\n '#16a34a', // bright green\n '#15803d', // green\n '#166534', // dark green\n '#14532d', // very dark green (dark - for high values)\n ]\n },\n {\n name: 'purple',\n label: 'Purple',\n colors: [\n '#581c87', // dark purple\n '#7c3aed', // purple\n '#8b5cf6', // bright purple\n '#a855f7', // light purple\n '#c084fc', // lighter purple\n '#e879f9', // magenta\n '#f0abfc', // light magenta\n '#fbbf24', // accent yellow\n ],\n gradient: [\n '#a855f7', // light purple (light - for low values)\n '#8b5cf6', // bright purple\n '#7c3aed', // purple\n '#6d28d9', // medium purple\n '#581c87', // dark purple\n '#4c1d95', // very dark purple (dark - for high values)\n ]\n },\n {\n name: 'monochrome',\n label: 'Monochrome',\n colors: [\n '#1f2937', // very dark gray\n '#374151', // dark gray\n '#4b5563', // medium gray\n '#6b7280', // gray\n '#9ca3af', // light gray\n '#d1d5db', // lighter gray\n '#e5e7eb', // very light gray\n '#f3f4f6', // almost white\n ],\n gradient: [\n '#9ca3af', // light gray (light - for low values)\n '#6b7280', // gray\n '#4b5563', // medium gray\n '#374151', // dark gray\n '#1f2937', // very dark gray\n '#111827', // black (dark - for high values)\n ]\n },\n {\n name: 'pastel',\n label: 'Pastel',\n colors: [\n '#93c5fd', // light blue\n '#86efac', // light green\n '#fde047', // light yellow\n '#fca5a5', // light red\n '#c4b5fd', // light purple\n '#fdba74', // light orange\n '#67e8f9', // light cyan\n '#bef264', // light lime\n ],\n gradient: [\n '#fed7aa', // very light orange (light - for low values)\n '#ddd6fe', // very light purple\n '#fecaca', // very light red\n '#fef08a', // very light yellow\n '#a7f3d0', // very light green\n '#bfdbfe', // very light blue (darker - for high values)\n ]\n },\n {\n name: 'vibrant',\n label: 'Vibrant',\n colors: [\n '#0000ff', // pure blue\n '#00ff00', // pure green\n '#ffff00', // pure yellow\n '#ff0000', // pure red\n '#ff00ff', // pure magenta\n '#ff8000', // pure orange\n '#00ffff', // pure cyan\n '#8000ff', // pure violet\n ],\n gradient: [\n '#ffff00', // yellow (light - for low values)\n '#80ff00', // lime\n '#00ff80', // green\n '#00ffff', // cyan\n '#0080ff', // blue\n '#4000ff', // blue-violet (dark - for high values)\n ]\n },\n {\n name: 'd3Category10',\n label: 'D3 Category 10',\n colors: [\n '#1f77b4', // blue\n '#ff7f0e', // orange\n '#2ca02c', // green\n '#d62728', // red\n '#9467bd', // purple\n '#8c564b', // brown\n '#e377c2', // pink\n '#7f7f7f', // gray\n '#bcbd22', // olive\n '#17becf', // cyan\n ],\n gradient: [\n '#9467bd', // purple (light - for low values)\n '#d62728', // red\n '#ff7f0e', // orange\n '#bcbd22', // olive\n '#2ca02c', // green\n '#1f77b4', // blue (dark - for high values)\n ]\n },\n {\n name: 'd3Tableau10',\n label: 'D3 Tableau 10',\n colors: [\n '#4e79a7', // blue\n '#f28e2c', // orange\n '#e15759', // red\n '#76b7b2', // teal\n '#59a14f', // green\n '#edc949', // yellow\n '#af7aa1', // purple\n '#ff9da7', // pink\n '#9c755f', // brown\n '#bab0ab', // gray\n ],\n gradient: [\n '#e15759', // red (light - for low values)\n '#f28e2c', // orange\n '#edc949', // yellow\n '#59a14f', // green\n '#76b7b2', // teal\n '#4e79a7', // blue (dark - for high values)\n ]\n },\n {\n name: 'colorBrewerSet1',\n label: 'ColorBrewer Set 1',\n colors: [\n '#e41a1c', // red\n '#377eb8', // blue\n '#4daf4a', // green\n '#984ea3', // purple\n '#ff7f00', // orange\n '#ffff33', // yellow\n '#a65628', // brown\n '#f781bf', // pink\n '#999999', // gray\n ],\n gradient: [\n '#984ea3', // purple (light - for low values)\n '#e41a1c', // red\n '#ff7f00', // orange\n '#ffff33', // yellow\n '#4daf4a', // green\n '#377eb8', // blue (dark - for high values)\n ]\n },\n {\n name: 'colorBrewerSet2',\n label: 'ColorBrewer Set 2',\n colors: [\n '#66c2a5', // teal\n '#fc8d62', // orange\n '#8da0cb', // blue\n '#e78ac3', // pink\n '#a6d854', // lime\n '#ffd92f', // yellow\n '#e5c494', // tan\n '#b3b3b3', // gray\n ],\n gradient: [\n '#e78ac3', // pink (light - for low values)\n '#fc8d62', // orange\n '#ffd92f', // yellow\n '#a6d854', // lime\n '#66c2a5', // teal\n '#8da0cb', // blue (dark - for high values)\n ]\n },\n {\n name: 'colorBrewerDark2',\n label: 'ColorBrewer Dark 2',\n colors: [\n '#1b9e77', // dark teal\n '#d95f02', // dark orange\n '#7570b3', // dark blue\n '#e7298a', // dark pink\n '#66a61e', // dark green\n '#e6ab02', // dark yellow\n '#a6761d', // dark brown\n '#666666', // dark gray\n ],\n gradient: [\n '#e7298a', // dark pink (light - for low values)\n '#d95f02', // dark orange\n '#e6ab02', // dark yellow\n '#66a61e', // dark green\n '#1b9e77', // dark teal\n '#7570b3', // dark blue (dark - for high values)\n ]\n },\n {\n name: 'colorBrewerPaired',\n label: 'ColorBrewer Paired',\n colors: [\n '#a6cee3', // light blue\n '#1f78b4', // blue\n '#b2df8a', // light green\n '#33a02c', // green\n '#fb9a99', // light red\n '#e31a1c', // red\n '#fdbf6f', // light orange\n '#ff7f00', // orange\n '#cab2d6', // light purple\n '#6a3d9a', // purple\n '#ffff99', // light yellow\n '#b15928', // brown\n ],\n gradient: [\n '#6a3d9a', // purple (light - for low values)\n '#e31a1c', // red\n '#ff7f00', // orange\n '#ffff99', // light yellow\n '#33a02c', // green\n '#1f78b4', // blue (dark - for high values)\n ]\n },\n {\n name: 'viridis',\n label: 'Viridis',\n colors: [\n '#440154', // dark purple\n '#482677', // purple\n '#3f4a8a', // blue-purple\n '#31678e', // blue\n '#26838f', // teal\n '#1f9d8a', // green-teal\n '#6cce5a', // green\n '#b6de2b', // yellow-green\n ],\n gradient: [\n '#b6de2b', // yellow-green (light - for low values)\n '#6cce5a', // green\n '#1f9d8a', // green-teal\n '#26838f', // teal\n '#31678e', // blue\n '#3f4a8a', // blue-purple\n '#482677', // purple\n '#440154', // dark purple (dark - for high values)\n ]\n },\n {\n name: 'plasma',\n label: 'Plasma',\n colors: [\n '#0c0786', // dark blue\n '#5c01a6', // purple\n '#900da4', // magenta\n '#bf3984', // pink\n '#e16462', // coral\n '#f99b45', // orange\n '#fcce25', // yellow\n '#f0f921', // bright yellow\n ],\n gradient: [\n '#f0f921', // bright yellow (light - for low values)\n '#fcce25', // yellow\n '#f99b45', // orange\n '#e16462', // coral\n '#bf3984', // pink\n '#900da4', // magenta\n '#5c01a6', // purple\n '#0c0786', // dark blue (dark - for high values)\n ]\n },\n {\n name: 'inferno',\n label: 'Inferno',\n colors: [\n '#000003', // black\n '#1f0c47', // dark blue\n '#550f6d', // purple\n '#88226a', // magenta\n '#a83655', // red\n '#cc4f39', // orange-red\n '#e6862a', // orange\n '#fec228', // yellow\n ],\n gradient: [\n '#fec228', // yellow (light - for low values)\n '#e6862a', // orange\n '#cc4f39', // orange-red\n '#a83655', // red\n '#88226a', // magenta\n '#550f6d', // purple\n '#1f0c47', // dark blue\n '#000003', // black (dark - for high values)\n ]\n },\n {\n name: 'magma',\n label: 'Magma',\n colors: [\n '#000003', // black\n '#140b34', // dark purple\n '#3b0f6f', // purple\n '#641a80', // magenta\n '#8b2981', // pink\n '#b63679', // coral\n '#de4968', // red\n '#fd9f6c', // orange\n ],\n gradient: [\n '#fd9f6c', // orange (light - for low values)\n '#de4968', // red\n '#b63679', // coral\n '#8b2981', // pink\n '#641a80', // magenta\n '#3b0f6f', // purple\n '#140b34', // dark purple\n '#000003', // black (dark - for high values)\n ]\n },\n {\n name: 'cividis',\n label: 'Cividis',\n colors: [\n '#00204c', // dark blue\n '#003f5c', // blue\n '#2c4b7a', // blue\n '#51576f', // blue-gray\n '#7f6874', // gray\n '#a8786e', // brown\n '#d2906d', // orange\n '#ffb570', // yellow\n ],\n gradient: [\n '#ffb570', // yellow (light - for low values)\n '#d2906d', // orange\n '#a8786e', // brown\n '#7f6874', // gray\n '#51576f', // blue-gray\n '#2c4b7a', // blue\n '#003f5c', // blue\n '#00204c', // dark blue (dark - for high values)\n ]\n },\n {\n name: 'turbo',\n label: 'Turbo',\n colors: [\n '#30123b', // purple\n '#4454c4', // blue\n '#1dd3c0', // cyan\n '#42f465', // green\n '#b2df22', // lime\n '#faba39', // yellow\n '#f66c19', // orange\n '#c42e02', // red\n ],\n gradient: [\n '#c42e02', // red (light - for low values)\n '#f66c19', // orange\n '#faba39', // yellow\n '#b2df22', // lime\n '#42f465', // green\n '#1dd3c0', // cyan\n '#4454c4', // blue\n '#30123b', // purple (dark - for high values)\n ]\n },\n {\n name: 'warm',\n label: 'Warm',\n colors: [\n '#8b0000', // dark red\n '#b22222', // red\n '#cd5c5c', // light red\n '#ff6347', // tomato\n '#ff8c00', // dark orange\n '#ffa500', // orange\n '#ffd700', // gold\n '#ffff00', // yellow\n ],\n gradient: [\n '#ffd700', // gold (light - for low values)\n '#ffa500', // orange\n '#ff8c00', // dark orange\n '#ff6347', // tomato\n '#b22222', // red\n '#8b0000', // dark red (dark - for high values)\n ]\n },\n {\n name: 'cool',\n label: 'Cool',\n colors: [\n '#000080', // navy\n '#0000ff', // blue\n '#4169e1', // royal blue\n '#00bfff', // deep sky blue\n '#00ffff', // cyan\n '#40e0d0', // turquoise\n '#20b2aa', // light sea green\n '#008b8b', // dark cyan\n ],\n gradient: [\n '#40e0d0', // turquoise (light - for low values)\n '#00ffff', // cyan\n '#00bfff', // deep sky blue\n '#4169e1', // royal blue\n '#0000ff', // blue\n '#000080', // navy (dark - for high values)\n ]\n },\n {\n name: 'earth',\n label: 'Earth',\n colors: [\n '#8b4513', // saddle brown\n '#a0522d', // sienna\n '#cd853f', // peru\n '#daa520', // goldenrod\n '#d2691e', // chocolate\n '#bc8f8f', // rosy brown\n '#f4a460', // sandy brown\n '#deb887', // burlywood\n ],\n gradient: [\n '#f4a460', // sandy brown (light - for low values)\n '#d2691e', // chocolate\n '#daa520', // goldenrod\n '#cd853f', // peru\n '#a0522d', // sienna\n '#8b4513', // saddle brown (dark - for high values)\n ]\n },\n {\n name: 'autumn',\n label: 'Autumn',\n colors: [\n '#8b0000', // dark red\n '#a0522d', // sienna\n '#cd853f', // peru\n '#daa520', // goldenrod\n '#ff8c00', // dark orange\n '#ff4500', // orange red\n '#dc143c', // crimson\n '#b22222', // fire brick\n ],\n gradient: [\n '#ff4500', // orange red (light - for low values)\n '#ff8c00', // dark orange\n '#daa520', // goldenrod\n '#cd853f', // peru\n '#a0522d', // sienna\n '#8b0000', // dark red (dark - for high values)\n ]\n },\n {\n name: 'spring',\n label: 'Spring',\n colors: [\n '#32cd32', // lime green\n '#98fb98', // pale green\n '#90ee90', // light green\n '#ffb6c1', // light pink\n '#ffc0cb', // pink\n '#ffffe0', // light yellow\n '#f0fff0', // honeydew\n '#e0ffff', // light cyan\n ],\n gradient: [\n '#e0ffff', // light cyan (light - for low values)\n '#ffc0cb', // pink\n '#ffb6c1', // light pink\n '#ffffe0', // light yellow\n '#98fb98', // pale green\n '#32cd32', // lime green (dark - for high values)\n ]\n },\n {\n name: 'winter',\n label: 'Winter',\n colors: [\n '#191970', // midnight blue\n '#4682b4', // steel blue\n '#87ceeb', // sky blue\n '#b0e0e6', // powder blue\n '#e0ffff', // light cyan\n '#f0f8ff', // alice blue\n '#c0c0c0', // silver\n '#708090', // slate gray\n ],\n gradient: [\n '#f0f8ff', // alice blue (light - for low values)\n '#e0ffff', // light cyan\n '#b0e0e6', // powder blue\n '#87ceeb', // sky blue\n '#4682b4', // steel blue\n '#191970', // midnight blue (dark - for high values)\n ]\n },\n {\n name: 'neon',\n label: 'Neon',\n colors: [\n '#ff0080', // neon pink\n '#00ff80', // neon green\n '#8000ff', // neon purple\n '#ff8000', // neon orange\n '#0080ff', // neon blue\n '#80ff00', // neon lime\n '#ff0040', // neon red\n '#40ff00', // bright green\n ],\n gradient: [\n '#ff0080', // neon pink (light - for low values)\n '#ff8000', // neon orange\n '#80ff00', // neon lime\n '#00ff80', // neon green\n '#0080ff', // neon blue\n '#8000ff', // neon purple (dark - for high values)\n ]\n },\n {\n name: 'retro',\n label: 'Retro',\n colors: [\n '#ff69b4', // hot pink\n '#ffd700', // gold\n '#32cd32', // lime green\n '#00ced1', // dark turquoise\n '#ff6347', // tomato\n '#9370db', // medium purple\n '#ffa500', // orange\n '#20b2aa', // light sea green\n ],\n gradient: [\n '#00ced1', // dark turquoise (light - for low values)\n '#32cd32', // lime green\n '#ffd700', // gold\n '#ff6347', // tomato\n '#ff69b4', // hot pink\n '#9370db', // medium purple (dark - for high values)\n ]\n },\n {\n name: 'corporate',\n label: 'Corporate',\n colors: [\n '#003366', // dark blue\n '#0066cc', // blue\n '#336699', // steel blue\n '#6699cc', // light blue\n '#4d4d4d', // dark gray\n '#808080', // gray\n '#b3b3b3', // light gray\n '#cccccc', // very light gray\n ],\n gradient: [\n '#b3b3b3', // light gray (light - for low values)\n '#808080', // gray\n '#6699cc', // light blue\n '#336699', // steel blue\n '#0066cc', // blue\n '#003366', // dark blue (dark - for high values)\n ]\n },\n {\n name: 'material',\n label: 'Material Design',\n colors: [\n '#f44336', // red\n '#e91e63', // pink\n '#9c27b0', // purple\n '#673ab7', // deep purple\n '#3f51b5', // indigo\n '#2196f3', // blue\n '#03a9f4', // light blue\n '#00bcd4', // cyan\n ],\n gradient: [\n '#e91e63', // pink (light - for low values)\n '#03a9f4', // light blue\n '#00bcd4', // cyan\n '#2196f3', // blue\n '#3f51b5', // indigo\n '#673ab7', // deep purple (dark - for high values)\n ]\n }\n]\n\n/**\n * Get a color palette by name, with fallback to default\n */\nexport function getColorPalette(paletteName?: string): ColorPalette {\n if (!paletteName) {\n return COLOR_PALETTES[0] // default palette\n }\n \n const palette = COLOR_PALETTES.find(p => p.name === paletteName)\n return palette || COLOR_PALETTES[0] // fallback to default\n}\n\n/**\n * Get just the series colors for a palette\n */\nexport function getSeriesColors(paletteName?: string): string[] {\n return getColorPalette(paletteName).colors\n}\n\n/**\n * Get just the gradient colors for a palette\n */\nexport function getGradientColors(paletteName?: string): string[] {\n return getColorPalette(paletteName).gradient\n}\n\n/**\n * Chart types that use series colors (discrete categories)\n */\nexport const SERIES_CHART_TYPES = [\n 'bar', 'line', 'area', 'pie', 'scatter', 'radar', 'radialBar', 'treeMap'\n] as const\n\n/**\n * Chart types that use gradient colors (continuous values)\n */\nexport const GRADIENT_CHART_TYPES = [\n 'bubble', 'activityGrid'\n] as const\n\n/**\n * Determine if a chart type uses gradient colors\n */\nexport function usesGradientColors(chartType: string): boolean {\n return GRADIENT_CHART_TYPES.includes(chartType as any)\n}","import React, { useEffect, useCallback } from 'react'\n\nexport interface ModalProps {\n isOpen: boolean\n onClose: () => void\n title?: string\n size?: 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'full' | 'fullscreen' | 'fullscreen-mobile'\n closeOnBackdropClick?: boolean\n closeOnEscape?: boolean\n showCloseButton?: boolean\n className?: string\n children: React.ReactNode\n footer?: React.ReactNode\n noPadding?: boolean\n}\n\nconst Modal: React.FC<ModalProps> = ({\n isOpen,\n onClose,\n title,\n size = 'md',\n closeOnBackdropClick = true,\n closeOnEscape = true,\n showCloseButton = true,\n children,\n footer,\n noPadding = false\n}) => {\n // Handle ESC key press\n const handleEscapeKey = useCallback((event: KeyboardEvent) => {\n if (event.key === 'Escape' && closeOnEscape) {\n onClose()\n }\n }, [closeOnEscape, onClose])\n\n\n // Manage ESC key listener and body scroll\n useEffect(() => {\n if (isOpen) {\n // Add ESC key listener\n if (closeOnEscape) {\n document.addEventListener('keydown', handleEscapeKey)\n }\n\n // Prevent body scroll when modal is open\n document.body.style.overflow = 'hidden'\n } else {\n // Restore body scroll\n document.body.style.overflow = 'unset'\n }\n\n // Cleanup\n return () => {\n document.removeEventListener('keydown', handleEscapeKey)\n document.body.style.overflow = 'unset'\n }\n }, [isOpen, closeOnEscape, handleEscapeKey])\n\n\n if (!isOpen) return null\n\n const getSizeClasses = () => {\n switch (size) {\n case 'sm':\n return 'dc:max-w-md'\n case 'md':\n return 'dc:max-w-lg'\n case 'lg':\n return 'dc:max-w-2xl'\n case 'xl':\n return 'dc:max-w-6xl'\n case 'xxl':\n return 'dc:max-w-[1400px]' // Good for retina/mac displays\n case 'full':\n return 'dc:max-w-7xl'\n case 'fullscreen':\n return 'dc:w-[90vw] dc:h-[90vh] dc:max-w-none'\n case 'fullscreen-mobile':\n return 'dc:w-full dc:h-full dc:md:w-[min(90vw,1400px)] dc:md:h-[90vh]'\n default:\n return 'dc:max-w-lg'\n }\n }\n\n return (\n <div\n className={`dc:fixed dc:inset-0 dc:z-50 dc:backdrop-blur-md ${size === 'fullscreen-mobile' ? 'dc:flex dc:md:flex dc:md:items-center dc:md:justify-center' : 'dc:flex dc:items-center dc:justify-center'}`}\n style={{ backgroundColor: 'var(--dc-overlay)' }}\n onClick={closeOnBackdropClick ? onClose : undefined}\n >\n <div\n className={`dc:relative bg-dc-surface dc:border border-dc-border ${size === 'fullscreen-mobile' ? 'dc:rounded-none dc:md:rounded-lg' : 'dc:rounded-lg'} ${size === 'fullscreen' || size === 'fullscreen-mobile' ? '' : 'dc:mx-4'} ${getSizeClasses()} ${size === 'fullscreen' || size === 'fullscreen-mobile' ? '' : 'dc:max-h-[90vh]'} dc:flex dc:flex-col`}\n style={{ boxShadow: 'var(--dc-shadow-2xl)' }}\n onClick={(e) => e.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? 'modal-title' : undefined}\n >\n {/* Header */}\n {(title || showCloseButton) && (\n <div className=\"dc:flex dc:items-center dc:justify-between dc:px-6 dc:py-4 dc:border-b border-dc-border\">\n {title && (\n <h2 id=\"modal-title\" className=\"dc:text-xl dc:font-semibold text-dc-text\">\n {title}\n </h2>\n )}\n {showCloseButton && (\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-dc-text-muted hover:text-dc-text-secondary dc:transition-colors dc:p-2 dc:-mr-2\"\n aria-label=\"Close modal\"\n >\n <svg width=\"24\" height=\"24\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n )}\n </div>\n )}\n\n {/* Content */}\n <div className={`dc:flex-1 dc:overflow-y-auto ${noPadding ? '' : 'dc:px-6 dc:py-4'}`}>\n {children}\n </div>\n\n {/* Footer */}\n {footer && (\n <div className=\"dc:flex dc:items-center dc:justify-end dc:space-x-3 dc:px-6 dc:py-4 dc:border-t border-dc-border bg-dc-surface-secondary\">\n {React.Children.toArray(footer)}\n </div>\n )}\n </div>\n </div>\n )\n}\n\nexport default Modal","/**\n * ID Generation Utilities for AnalysisBuilder\n */\n\n/**\n * Generate a unique ID for items\n */\nexport function generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`\n}\n\n/**\n * Generate letter label for metrics (A, B, C, ..., AA, AB, ...)\n */\nexport function generateMetricLabel(index: number): string {\n let label = ''\n let n = index\n do {\n label = String.fromCharCode(65 + (n % 26)) + label\n n = Math.floor(n / 26) - 1\n } while (n >= 0)\n return label\n}\n","/**\n * Field Metadata Utilities for AnalysisBuilder\n *\n * Functions for working with field metadata from the schema.\n */\n\nimport type { FieldOption, FieldType } from '../types'\nimport type { MetaResponse, MetaField } from '../../../shared/types'\n\n/**\n * Get cube name from a field name (e.g., \"Employees.count\" -> \"Employees\")\n */\nexport function getCubeNameFromField(fieldName: string): string {\n return fieldName.split('.')[0]\n}\n\n/**\n * Get field short name from full name (e.g., \"Employees.count\" -> \"count\")\n */\nexport function getFieldShortName(fieldName: string): string {\n const parts = fieldName.split('.')\n return parts.length > 1 ? parts.slice(1).join('.') : fieldName\n}\n\n/**\n * Find field metadata from schema\n */\nexport function findFieldInSchema(\n fieldName: string,\n schema: MetaResponse | null\n): { field: MetaField; cubeName: string; fieldType: FieldType } | null {\n if (!schema) return null\n\n for (const cube of schema.cubes) {\n // Check measures\n const measure = cube.measures.find((m) => m.name === fieldName)\n if (measure) {\n return { field: measure, cubeName: cube.name, fieldType: 'measure' }\n }\n\n // Check dimensions\n const dimension = cube.dimensions.find((d) => d.name === fieldName)\n if (dimension) {\n return {\n field: dimension,\n cubeName: cube.name,\n fieldType: dimension.type === 'time' ? 'timeDimension' : 'dimension'\n }\n }\n }\n\n return null\n}\n\n/**\n * Get display title for a field\n */\nexport function getFieldTitle(fieldName: string, schema: MetaResponse | null): string {\n const found = findFieldInSchema(fieldName, schema)\n if (found) {\n return found.field.title || found.field.shortTitle || fieldName\n }\n return fieldName\n}\n\n/**\n * Determine field type from metadata\n */\nexport function getFieldType(field: MetaField): FieldType {\n if (field.type === 'time') return 'timeDimension'\n // Measures typically have aggregation types\n if (['count', 'countDistinct', 'sum', 'avg', 'min', 'max', 'runningTotal', 'countDistinctApprox'].includes(field.type)) {\n return 'measure'\n }\n return 'dimension'\n}\n\n/**\n * Convert schema to flat list of field options\n */\nexport function schemaToFieldOptions(\n schema: MetaResponse | null,\n mode: 'metrics' | 'breakdown' | 'filter' | 'dimensionFilter'\n): FieldOption[] {\n if (!schema) return []\n\n const options: FieldOption[] = []\n\n for (const cube of schema.cubes) {\n if (mode === 'metrics') {\n // Add measures only\n for (const measure of cube.measures) {\n options.push({\n name: measure.name,\n title: measure.title || measure.shortTitle || measure.name,\n shortTitle: measure.shortTitle || measure.title || measure.name,\n type: measure.type,\n description: measure.description,\n cubeName: cube.name,\n fieldType: 'measure'\n })\n }\n } else if (mode === 'breakdown' || mode === 'dimensionFilter') {\n // Add dimensions only (both regular and time)\n // 'dimensionFilter' is used for funnel step filters where measures don't work\n for (const dimension of cube.dimensions) {\n const isTime = dimension.type === 'time'\n options.push({\n name: dimension.name,\n title: dimension.title || dimension.shortTitle || dimension.name,\n shortTitle: dimension.shortTitle || dimension.title || dimension.name,\n type: dimension.type,\n description: dimension.description,\n cubeName: cube.name,\n fieldType: isTime ? 'timeDimension' : 'dimension'\n })\n }\n } else {\n // 'filter' mode - add BOTH measures AND dimensions\n // Add measures first\n for (const measure of cube.measures) {\n options.push({\n name: measure.name,\n title: measure.title || measure.shortTitle || measure.name,\n shortTitle: measure.shortTitle || measure.title || measure.name,\n type: measure.type,\n description: measure.description,\n cubeName: cube.name,\n fieldType: 'measure'\n })\n }\n // Add dimensions (both regular and time)\n for (const dimension of cube.dimensions) {\n const isTime = dimension.type === 'time'\n options.push({\n name: dimension.name,\n title: dimension.title || dimension.shortTitle || dimension.name,\n shortTitle: dimension.shortTitle || dimension.title || dimension.name,\n type: dimension.type,\n description: dimension.description,\n cubeName: cube.name,\n fieldType: isTime ? 'timeDimension' : 'dimension'\n })\n }\n }\n }\n\n return options\n}\n\n/**\n * Filter field options by search term\n */\nexport function filterFieldOptions(\n options: FieldOption[],\n searchTerm: string,\n selectedCube?: string | null\n): FieldOption[] {\n let filtered = options\n\n // Filter by cube if selected\n if (selectedCube && selectedCube !== 'all') {\n filtered = filtered.filter((opt) => opt.cubeName === selectedCube)\n }\n\n // Filter by search term\n if (searchTerm.trim()) {\n const term = searchTerm.toLowerCase()\n filtered = filtered.filter(\n (opt) =>\n opt.name.toLowerCase().includes(term) ||\n opt.title.toLowerCase().includes(term) ||\n (opt.description?.toLowerCase().includes(term) ?? false)\n )\n }\n\n return filtered\n}\n\n/**\n * Group field options by cube\n */\nexport function groupFieldsByCube(options: FieldOption[]): Map<string, FieldOption[]> {\n const grouped = new Map<string, FieldOption[]>()\n\n for (const option of options) {\n const existing = grouped.get(option.cubeName) || []\n existing.push(option)\n grouped.set(option.cubeName, existing)\n }\n\n return grouped\n}\n\n/**\n * Get list of cube names from schema\n */\nexport function getCubeNames(schema: MetaResponse | null): string[] {\n if (!schema) return []\n return schema.cubes.map((cube) => cube.name)\n}\n\n/**\n * Get cube title by name\n */\nexport function getCubeTitle(cubeName: string, schema: MetaResponse | null): string {\n if (!schema) return cubeName\n const cube = schema.cubes.find((c) => c.name === cubeName)\n return cube?.title || cubeName\n}\n\n/**\n * Get all cubes reachable from a source cube via join relationships\n * Includes the source cube itself plus all cubes it has joins to\n *\n * @param sourceCube - Name of the cube to find related cubes for\n * @param schema - Full schema with all cubes\n * @returns Set of cube names that are reachable from the source\n */\nexport function getRelatedCubeNames(\n sourceCube: string,\n schema: MetaResponse | null\n): Set<string> {\n const related = new Set<string>()\n\n if (!schema) return related\n\n // Always include the source cube\n related.add(sourceCube)\n\n // Find the source cube and get its relationships\n const cube = schema.cubes.find((c) => c.name === sourceCube)\n if (!cube || !cube.relationships) return related\n\n // Add all directly related cubes\n for (const rel of cube.relationships) {\n related.add(rel.targetCube)\n }\n\n return related\n}\n\n/**\n * Filter schema to include only cubes reachable from a source cube\n * This is used for funnel step filters where cross-cube filtering is supported\n *\n * @param sourceCube - Name of the cube to find related cubes for\n * @param schema - Full schema with all cubes\n * @returns Filtered schema containing only reachable cubes\n */\nexport function getRelatedCubesSchema(\n sourceCube: string,\n schema: MetaResponse | null\n): MetaResponse | null {\n if (!schema) return null\n\n const relatedNames = getRelatedCubeNames(sourceCube, schema)\n\n return {\n cubes: schema.cubes\n .filter((c) => relatedNames.has(c.name))\n .map((c) => ({\n ...c,\n description: c.description || '',\n })),\n }\n}\n","/**\n * Recent Fields Storage Utilities for AnalysisBuilder\n *\n * Functions for tracking and retrieving recently used fields.\n */\n\nimport type { FieldOption, RecentFieldsStorage } from '../types'\nimport type { MetaResponse } from '../../../shared/types'\nimport { schemaToFieldOptions } from './fieldUtils'\n\nconst RECENT_FIELDS_KEY = 'drizzle-cube-recent-fields'\nconst MAX_RECENT_FIELDS = 10\n\n/**\n * Get recent fields from localStorage\n */\nexport function getRecentFields(): RecentFieldsStorage {\n try {\n const stored = localStorage.getItem(RECENT_FIELDS_KEY)\n if (stored) {\n return JSON.parse(stored)\n }\n } catch {\n // Ignore errors\n }\n return { metrics: [], breakdowns: [] }\n}\n\n/**\n * Add a field to recent fields\n */\nexport function addRecentField(fieldName: string, mode: 'metrics' | 'breakdowns'): void {\n try {\n const recent = getRecentFields()\n const list = recent[mode]\n\n // Remove if already exists\n const filtered = list.filter((f) => f !== fieldName)\n\n // Add to front\n filtered.unshift(fieldName)\n\n // Limit size\n recent[mode] = filtered.slice(0, MAX_RECENT_FIELDS)\n\n localStorage.setItem(RECENT_FIELDS_KEY, JSON.stringify(recent))\n } catch {\n // Ignore errors\n }\n}\n\n/**\n * Get recent field options from schema\n */\nexport function getRecentFieldOptions(\n schema: MetaResponse | null,\n mode: 'metrics' | 'breakdown' | 'filter' | 'dimensionFilter',\n recentFieldNames: string[]\n): FieldOption[] {\n if (!schema || recentFieldNames.length === 0) return []\n\n const allOptions = schemaToFieldOptions(schema, mode)\n const recentOptions: FieldOption[] = []\n\n for (const fieldName of recentFieldNames) {\n const option = allOptions.find((opt) => opt.name === fieldName)\n if (option) {\n recentOptions.push(option)\n }\n }\n\n return recentOptions\n}\n","/**\n * Funnel Mode Adapter\n *\n * Handles conversion between UI state and AnalysisConfig for funnel mode.\n * Converts funnelSteps UI state to/from ServerFunnelQuery format.\n */\n\nimport type { ModeAdapter, ValidationResult } from './modeAdapter'\nimport { generateId } from '../components/AnalysisBuilder/utils'\nimport type {\n AnalysisConfig,\n FunnelAnalysisConfig,\n AnalysisType,\n ChartConfig,\n} from '../types/analysisConfig'\nimport type { FunnelStepState, FunnelBindingKey, Filter } from '../types'\nimport type { ServerFunnelQuery, ServerFunnelStep } from '../types/funnel'\n\n// ============================================================================\n// Funnel Slice State Type\n// ============================================================================\n\n/**\n * The shape of funnel mode state in the store.\n * This is what the adapter's load() returns and save() receives.\n */\nexport interface FunnelSliceState {\n /** The cube all funnel steps use (single-cube mode) */\n funnelCube: string | null\n /** Funnel step definitions */\n funnelSteps: FunnelStepState[]\n /** Currently selected step index */\n activeFunnelStepIndex: number\n /** Time dimension for temporal ordering */\n funnelTimeDimension: string | null\n /** Binding key that links entities across steps */\n funnelBindingKey: FunnelBindingKey | null\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Convert FunnelSliceState to ServerFunnelQuery\n */\nfunction stateToServerQuery(state: FunnelSliceState): ServerFunnelQuery {\n // Convert binding key to server format\n let bindingKey: ServerFunnelQuery['funnel']['bindingKey'] = ''\n if (state.funnelBindingKey) {\n if (typeof state.funnelBindingKey.dimension === 'string') {\n bindingKey = state.funnelBindingKey.dimension\n } else if (Array.isArray(state.funnelBindingKey.dimension)) {\n bindingKey = state.funnelBindingKey.dimension.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n }))\n }\n }\n\n // Convert time dimension to server format\n let timeDimension: ServerFunnelQuery['funnel']['timeDimension'] =\n state.funnelTimeDimension || ''\n\n // Convert steps to server format\n const steps: ServerFunnelStep[] = state.funnelSteps.map((step) => {\n const serverStep: ServerFunnelStep = {\n name: step.name,\n }\n\n // Only include cube if different from default (multi-cube support)\n if (step.cube && step.cube !== state.funnelCube) {\n serverStep.cube = step.cube\n }\n\n // Include filters if present\n if (step.filters && step.filters.length > 0) {\n // Convert to server filter format\n serverStep.filter =\n step.filters.length === 1\n ? step.filters[0]\n : { and: step.filters }\n }\n\n // Include timeToConvert if present\n if (step.timeToConvert) {\n serverStep.timeToConvert = step.timeToConvert\n }\n\n return serverStep\n })\n\n return {\n funnel: {\n bindingKey,\n timeDimension,\n steps,\n includeTimeMetrics: true,\n },\n }\n}\n\n/**\n * Convert ServerFunnelQuery to FunnelSliceState\n */\nfunction serverQueryToState(query: ServerFunnelQuery): FunnelSliceState {\n const { funnel } = query\n\n // Extract cube from first step or binding key\n let funnelCube: string | null = null\n if (funnel.steps.length > 0 && funnel.steps[0].cube) {\n funnelCube = funnel.steps[0].cube\n } else if (typeof funnel.bindingKey === 'string') {\n // Extract cube from binding key (e.g., \"Events.userId\" -> \"Events\")\n const parts = funnel.bindingKey.split('.')\n if (parts.length > 0) {\n funnelCube = parts[0]\n }\n }\n\n // Convert binding key to client format\n let funnelBindingKey: FunnelBindingKey | null = null\n if (funnel.bindingKey) {\n if (typeof funnel.bindingKey === 'string') {\n funnelBindingKey = { dimension: funnel.bindingKey }\n } else if (Array.isArray(funnel.bindingKey)) {\n funnelBindingKey = {\n dimension: funnel.bindingKey.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n })),\n }\n }\n }\n\n // Convert time dimension\n let funnelTimeDimension: string | null = null\n if (funnel.timeDimension) {\n if (typeof funnel.timeDimension === 'string') {\n funnelTimeDimension = funnel.timeDimension\n } else if (Array.isArray(funnel.timeDimension) && funnel.timeDimension.length > 0) {\n funnelTimeDimension = `${funnel.timeDimension[0].cube}.${funnel.timeDimension[0].dimension}`\n }\n }\n\n // Convert steps\n const funnelSteps: FunnelStepState[] = funnel.steps.map((step) => {\n // Extract filters\n let filters: Filter[] = []\n if (step.filter) {\n if (Array.isArray(step.filter)) {\n // Already an array of filters\n filters = step.filter as Filter[]\n } else if (\n typeof step.filter === 'object' &&\n 'and' in (step.filter as { and?: unknown })\n ) {\n // { and: [...] } format\n filters = (step.filter as { and: Filter[] }).and\n } else {\n // Single filter object - wrap in array\n filters = [step.filter as Filter]\n }\n }\n\n return {\n id: generateId(),\n name: step.name,\n cube: step.cube || funnelCube || '',\n filters,\n timeToConvert: step.timeToConvert,\n }\n })\n\n return {\n funnelCube,\n funnelSteps,\n activeFunnelStepIndex: 0,\n funnelTimeDimension,\n funnelBindingKey,\n }\n}\n\n/**\n * Check if a config is a valid funnel config\n */\nfunction isValidFunnelConfig(config: unknown): config is FunnelAnalysisConfig {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n if (c.version !== 1) return false\n if (c.analysisType !== 'funnel') return false\n if (!c.query || typeof c.query !== 'object') return false\n\n const query = c.query as Record<string, unknown>\n if (!query.funnel || typeof query.funnel !== 'object') return false\n\n return true\n}\n\n// ============================================================================\n// Funnel Mode Adapter\n// ============================================================================\n\nexport const funnelModeAdapter: ModeAdapter<FunnelSliceState> = {\n type: 'funnel',\n\n createInitial(): FunnelSliceState {\n return {\n funnelCube: null,\n funnelSteps: [],\n activeFunnelStepIndex: 0,\n funnelTimeDimension: null,\n funnelBindingKey: null,\n }\n },\n\n extractState(storeState: Record<string, unknown>): FunnelSliceState {\n return {\n funnelCube: storeState.funnelCube as string | null,\n funnelSteps: storeState.funnelSteps as FunnelStepState[],\n activeFunnelStepIndex: storeState.activeFunnelStepIndex as number,\n funnelTimeDimension: storeState.funnelTimeDimension as string | null,\n funnelBindingKey: storeState.funnelBindingKey as FunnelBindingKey | null,\n }\n },\n\n canLoad(config: unknown): config is AnalysisConfig {\n return isValidFunnelConfig(config)\n },\n\n load(config: AnalysisConfig): FunnelSliceState {\n // Type guard - ensure it's a funnel config\n if (config.analysisType !== 'funnel') {\n throw new Error(\n `Cannot load ${config.analysisType} config with funnel adapter`\n )\n }\n\n const funnelConfig = config as FunnelAnalysisConfig\n return serverQueryToState(funnelConfig.query)\n },\n\n save(\n state: FunnelSliceState,\n charts: Partial<Record<AnalysisType, ChartConfig>>,\n activeView: 'table' | 'chart'\n ): FunnelAnalysisConfig {\n return {\n version: 1,\n analysisType: 'funnel',\n activeView,\n charts: {\n funnel: charts.funnel || this.getDefaultChartConfig(),\n },\n query: stateToServerQuery(state),\n }\n },\n\n validate(state: FunnelSliceState): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Must have at least 2 steps for a funnel\n if (state.funnelSteps.length < 2) {\n errors.push('A funnel requires at least 2 steps')\n }\n\n // Must have a binding key\n if (!state.funnelBindingKey?.dimension) {\n errors.push('A binding key is required to link funnel steps')\n }\n\n // Must have a time dimension\n if (!state.funnelTimeDimension) {\n errors.push('A time dimension is required for funnel ordering')\n }\n\n // Check each step\n state.funnelSteps.forEach((step, index) => {\n if (!step.name || step.name.trim() === '') {\n warnings.push(`Step ${index + 1} has no name`)\n }\n\n // Warn if step has no distinguishing filter\n if (step.filters.length === 0) {\n warnings.push(\n `Step ${index + 1} \"${step.name}\" has no filter - all events will match`\n )\n }\n })\n\n // Check for duplicate step names\n const names = state.funnelSteps.map((s) => s.name.toLowerCase())\n const duplicates = names.filter(\n (name, index) => names.indexOf(name) !== index\n )\n if (duplicates.length > 0) {\n warnings.push(`Duplicate step names: ${[...new Set(duplicates)].join(', ')}`)\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n }\n },\n\n clear(state: FunnelSliceState): FunnelSliceState {\n // Keep cube selection but clear steps\n return {\n ...this.createInitial(),\n funnelCube: state.funnelCube,\n }\n },\n\n getDefaultChartConfig(): ChartConfig {\n return {\n chartType: 'funnel',\n chartConfig: {},\n displayConfig: { showLegend: true, showGrid: true, showTooltip: true },\n }\n },\n}\n","/**\n * Flow Mode Adapter\n *\n * Handles conversion between UI state and AnalysisConfig for flow mode.\n * Converts FlowSliceState UI state to/from ServerFlowQuery format.\n */\n\nimport type { ModeAdapter, ValidationResult } from './modeAdapter'\nimport type {\n AnalysisConfig,\n FlowAnalysisConfig,\n AnalysisType,\n ChartConfig,\n} from '../types/analysisConfig'\nimport type { Filter, FunnelBindingKey } from '../types'\nimport type {\n FlowSliceState,\n ServerFlowQuery,\n FlowStartingStep,\n} from '../types/flow'\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Convert FlowSliceState to ServerFlowQuery\n */\nfunction stateToServerQuery(state: FlowSliceState): ServerFlowQuery {\n // Convert binding key to server format\n let bindingKey: ServerFlowQuery['flow']['bindingKey'] = ''\n if (state.flowBindingKey) {\n if (typeof state.flowBindingKey.dimension === 'string') {\n bindingKey = state.flowBindingKey.dimension\n } else if (Array.isArray(state.flowBindingKey.dimension)) {\n bindingKey = state.flowBindingKey.dimension.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n }))\n }\n }\n\n // Convert time dimension to server format\n const timeDimension: ServerFlowQuery['flow']['timeDimension'] =\n state.flowTimeDimension || ''\n\n // Convert starting step to server format\n // Server accepts Filter | Filter[] for multiple filters\n const startingStep: ServerFlowQuery['flow']['startingStep'] = {\n name: state.startingStep.name || 'Starting Step',\n filter:\n state.startingStep.filters.length === 1\n ? state.startingStep.filters[0]\n : state.startingStep.filters.length > 1\n ? state.startingStep.filters\n : undefined,\n }\n\n return {\n flow: {\n bindingKey,\n timeDimension,\n startingStep,\n stepsBefore: state.stepsBefore,\n stepsAfter: state.stepsAfter,\n eventDimension: state.eventDimension || '',\n joinStrategy: state.joinStrategy,\n },\n }\n}\n\n/**\n * Convert ServerFlowQuery to FlowSliceState\n */\nfunction serverQueryToState(query: ServerFlowQuery): FlowSliceState {\n const { flow } = query\n\n // Extract cube from binding key or event dimension\n let flowCube: string | null = null\n if (typeof flow.bindingKey === 'string') {\n const parts = flow.bindingKey.split('.')\n if (parts.length > 0) {\n flowCube = parts[0]\n }\n } else if (Array.isArray(flow.bindingKey) && flow.bindingKey.length > 0) {\n flowCube = flow.bindingKey[0].cube\n }\n\n // Convert binding key to client format\n let flowBindingKey: FunnelBindingKey | null = null\n if (flow.bindingKey) {\n if (typeof flow.bindingKey === 'string') {\n flowBindingKey = { dimension: flow.bindingKey }\n } else if (Array.isArray(flow.bindingKey)) {\n flowBindingKey = {\n dimension: flow.bindingKey.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n })),\n }\n }\n }\n\n // Convert time dimension\n let flowTimeDimension: string | null = null\n if (flow.timeDimension) {\n if (typeof flow.timeDimension === 'string') {\n flowTimeDimension = flow.timeDimension\n } else if (Array.isArray(flow.timeDimension) && flow.timeDimension.length > 0) {\n flowTimeDimension = `${flow.timeDimension[0].cube}.${flow.timeDimension[0].dimension}`\n }\n }\n\n // Convert starting step filters\n let startingStepFilters: Filter[] = []\n if (flow.startingStep.filter) {\n if (Array.isArray(flow.startingStep.filter)) {\n startingStepFilters = flow.startingStep.filter\n } else {\n startingStepFilters = [flow.startingStep.filter]\n }\n }\n\n return {\n flowCube,\n flowBindingKey,\n flowTimeDimension,\n startingStep: {\n name: flow.startingStep.name || '',\n filters: startingStepFilters,\n },\n stepsBefore: flow.stepsBefore || 3,\n stepsAfter: flow.stepsAfter || 3,\n eventDimension: flow.eventDimension || null,\n joinStrategy: flow.joinStrategy || 'auto',\n }\n}\n\n/**\n * Check if a config is a valid flow config\n */\nfunction isValidFlowConfig(config: unknown): config is FlowAnalysisConfig {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n if (c.version !== 1) return false\n if (c.analysisType !== 'flow') return false\n if (!c.query || typeof c.query !== 'object') return false\n\n const query = c.query as Record<string, unknown>\n if (!query.flow || typeof query.flow !== 'object') return false\n\n return true\n}\n\n// ============================================================================\n// Flow Mode Adapter\n// ============================================================================\n\nexport const flowModeAdapter: ModeAdapter<FlowSliceState> = {\n type: 'flow',\n\n createInitial(): FlowSliceState {\n return {\n flowCube: null,\n flowBindingKey: null,\n flowTimeDimension: null,\n startingStep: {\n name: '',\n filters: [],\n },\n stepsBefore: 3,\n stepsAfter: 3,\n eventDimension: null,\n joinStrategy: 'auto',\n }\n },\n\n extractState(storeState: Record<string, unknown>): FlowSliceState {\n return {\n flowCube: storeState.flowCube as string | null,\n flowBindingKey: storeState.flowBindingKey as FunnelBindingKey | null,\n flowTimeDimension: storeState.flowTimeDimension as string | null,\n startingStep: storeState.startingStep as FlowStartingStep,\n stepsBefore: storeState.stepsBefore as number,\n stepsAfter: storeState.stepsAfter as number,\n eventDimension: storeState.eventDimension as string | null,\n joinStrategy: (storeState.joinStrategy as 'auto' | 'lateral' | 'window') || 'auto',\n }\n },\n\n canLoad(config: unknown): config is AnalysisConfig {\n return isValidFlowConfig(config)\n },\n\n load(config: AnalysisConfig): FlowSliceState {\n // Type guard - ensure it's a flow config\n if (config.analysisType !== 'flow') {\n throw new Error(\n `Cannot load ${config.analysisType} config with flow adapter`\n )\n }\n\n const flowConfig = config as FlowAnalysisConfig\n return serverQueryToState(flowConfig.query)\n },\n\n save(\n state: FlowSliceState,\n charts: Partial<Record<AnalysisType, ChartConfig>>,\n activeView: 'table' | 'chart'\n ): FlowAnalysisConfig {\n return {\n version: 1,\n analysisType: 'flow',\n activeView,\n charts: {\n flow: charts.flow || this.getDefaultChartConfig(),\n },\n query: stateToServerQuery(state),\n }\n },\n\n validate(state: FlowSliceState): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Must have a cube selected\n if (!state.flowCube) {\n errors.push('Select an event stream cube for flow analysis')\n }\n\n // Must have a binding key\n if (!state.flowBindingKey?.dimension) {\n errors.push('A binding key is required to link events to entities')\n }\n\n // Must have a time dimension\n if (!state.flowTimeDimension) {\n errors.push('A time dimension is required for event ordering')\n }\n\n // Must have an event dimension\n if (!state.eventDimension) {\n errors.push('An event dimension is required to categorize events')\n }\n\n // Must have starting step filters\n if (state.startingStep.filters.length === 0) {\n errors.push('The starting step must have at least one filter to identify the anchor event')\n }\n\n // Validate depth bounds\n if (state.stepsBefore < 0 || state.stepsBefore > 5) {\n errors.push(`Steps before must be between 0 and 5`)\n }\n if (state.stepsAfter < 0 || state.stepsAfter > 5) {\n errors.push(`Steps after must be between 0 and 5`)\n }\n\n if (\n state.joinStrategy &&\n !['auto', 'lateral', 'window'].includes(state.joinStrategy)\n ) {\n errors.push('Join strategy must be auto, lateral, or window')\n }\n\n // Warnings\n if (!state.startingStep.name) {\n warnings.push('Starting step has no name - using default')\n }\n\n // Performance warnings for high depth\n if (state.stepsBefore >= 4 || state.stepsAfter >= 4) {\n warnings.push('High step depth (4-5) may impact query performance on large datasets')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n }\n },\n\n clear(state: FlowSliceState): FlowSliceState {\n // Keep cube selection but clear other settings\n return {\n ...this.createInitial(),\n flowCube: state.flowCube,\n }\n },\n\n getDefaultChartConfig(): ChartConfig {\n return {\n chartType: 'sankey',\n chartConfig: {},\n displayConfig: {\n showLegend: true,\n showGrid: false,\n showTooltip: true,\n },\n }\n },\n}\n","/**\n * Retention Mode Adapter\n *\n * Handles conversion between UI state and AnalysisConfig for retention mode.\n * Converts RetentionSliceState UI state to/from ServerRetentionQuery format.\n *\n * Simplified Mixpanel-style format (Phase 5):\n * - Single cube for all analysis\n * - Single timestamp dimension\n * - Single cohort with breakdown support\n * - Granularity = viewing periods\n */\n\nimport type { ModeAdapter, ValidationResult } from './modeAdapter'\nimport type {\n AnalysisConfig,\n RetentionAnalysisConfig,\n AnalysisType,\n ChartConfig,\n} from '../types/analysisConfig'\nimport type { Filter } from '../types'\nimport type { FunnelBindingKey } from '../types/funnel'\nimport type {\n ServerRetentionQuery,\n RetentionSliceState,\n RetentionGranularity,\n RetentionType,\n DateRange,\n RetentionBreakdownItem,\n} from '../types/retention'\nimport { defaultRetentionSliceState, getDateRangeFromPreset, DEFAULT_DATE_RANGE_PRESET } from '../types/retention'\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Convert RetentionSliceState to ServerRetentionQuery\n * Uses the new simplified format from Phase 1\n */\nfunction stateToServerQuery(state: RetentionSliceState): ServerRetentionQuery {\n // Convert binding key to server format\n let bindingKey: ServerRetentionQuery['retention']['bindingKey'] = ''\n if (state.retentionBindingKey) {\n if (typeof state.retentionBindingKey.dimension === 'string') {\n bindingKey = state.retentionBindingKey.dimension\n } else if (Array.isArray(state.retentionBindingKey.dimension)) {\n bindingKey = state.retentionBindingKey.dimension.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n }))\n }\n }\n\n // Build the server query with new simplified format\n const query: ServerRetentionQuery = {\n retention: {\n timeDimension: state.retentionTimeDimension || '',\n bindingKey,\n dateRange: state.retentionDateRange,\n granularity: state.retentionViewGranularity,\n periods: state.retentionPeriods,\n retentionType: state.retentionType,\n },\n }\n\n // Add cohort filters if present\n if (state.retentionCohortFilters.length > 0) {\n query.retention.cohortFilters =\n state.retentionCohortFilters.length === 1\n ? state.retentionCohortFilters[0]\n : state.retentionCohortFilters\n }\n\n // Add activity filters if present\n if (state.retentionActivityFilters.length > 0) {\n query.retention.activityFilters =\n state.retentionActivityFilters.length === 1\n ? state.retentionActivityFilters[0]\n : state.retentionActivityFilters\n }\n\n // Add breakdown dimensions if present\n if (state.retentionBreakdowns && state.retentionBreakdowns.length > 0) {\n query.retention.breakdownDimensions = state.retentionBreakdowns.map((b) => b.field)\n }\n\n return query\n}\n\n/**\n * Convert ServerRetentionQuery to RetentionSliceState\n * Uses the new simplified format from Phase 1\n */\nfunction serverQueryToState(query: ServerRetentionQuery): RetentionSliceState {\n const { retention } = query\n\n // Extract cube from time dimension\n let retentionCube: string | null = null\n if (typeof retention.timeDimension === 'string') {\n const parts = retention.timeDimension.split('.')\n if (parts.length > 0) {\n retentionCube = parts[0]\n }\n } else if (retention.timeDimension?.cube) {\n retentionCube = retention.timeDimension.cube\n }\n\n // Convert binding key to client format\n let retentionBindingKey: FunnelBindingKey | null = null\n if (retention.bindingKey) {\n if (typeof retention.bindingKey === 'string') {\n retentionBindingKey = { dimension: retention.bindingKey }\n } else if (Array.isArray(retention.bindingKey)) {\n retentionBindingKey = {\n dimension: retention.bindingKey.map((mapping) => ({\n cube: mapping.cube,\n dimension: mapping.dimension,\n })),\n }\n }\n }\n\n // Convert time dimension\n let retentionTimeDimension: string | null = null\n if (retention.timeDimension) {\n if (typeof retention.timeDimension === 'string') {\n retentionTimeDimension = retention.timeDimension\n } else {\n retentionTimeDimension = `${retention.timeDimension.cube}.${retention.timeDimension.dimension}`\n }\n }\n\n // Convert filters\n let retentionCohortFilters: Filter[] = []\n if (retention.cohortFilters) {\n if (Array.isArray(retention.cohortFilters)) {\n retentionCohortFilters = retention.cohortFilters as Filter[]\n } else {\n retentionCohortFilters = [retention.cohortFilters as Filter]\n }\n }\n\n let retentionActivityFilters: Filter[] = []\n if (retention.activityFilters) {\n if (Array.isArray(retention.activityFilters)) {\n retentionActivityFilters = retention.activityFilters as Filter[]\n } else {\n retentionActivityFilters = [retention.activityFilters as Filter]\n }\n }\n\n // Convert breakdown dimensions\n let retentionBreakdowns: RetentionBreakdownItem[] = []\n if (retention.breakdownDimensions && Array.isArray(retention.breakdownDimensions)) {\n retentionBreakdowns = retention.breakdownDimensions.map((field) => ({\n field,\n label: field.split('.').pop() || field,\n }))\n }\n\n // Extract or default the date range\n const retentionDateRange: DateRange = retention.dateRange || getDateRangeFromPreset(DEFAULT_DATE_RANGE_PRESET)\n\n return {\n retentionCube,\n retentionBindingKey,\n retentionTimeDimension,\n retentionDateRange,\n retentionViewGranularity: retention.granularity as RetentionGranularity,\n retentionPeriods: retention.periods,\n retentionType: retention.retentionType as RetentionType,\n retentionCohortFilters,\n retentionActivityFilters,\n retentionBreakdowns,\n }\n}\n\n/**\n * Check if a config is a valid retention config\n */\nfunction isValidRetentionConfig(config: unknown): config is RetentionAnalysisConfig {\n if (!config || typeof config !== 'object') return false\n\n const c = config as Record<string, unknown>\n\n if (c.version !== 1) return false\n if (c.analysisType !== 'retention') return false\n if (!c.query || typeof c.query !== 'object') return false\n\n const query = c.query as Record<string, unknown>\n if (!query.retention || typeof query.retention !== 'object') return false\n\n return true\n}\n\n// ============================================================================\n// Retention Mode Adapter\n// ============================================================================\n\nexport const retentionModeAdapter: ModeAdapter<RetentionSliceState> = {\n type: 'retention',\n\n createInitial(): RetentionSliceState {\n return { ...defaultRetentionSliceState }\n },\n\n extractState(storeState: Record<string, unknown>): RetentionSliceState {\n return {\n retentionCube: storeState.retentionCube as string | null,\n retentionBindingKey: storeState.retentionBindingKey as FunnelBindingKey | null,\n retentionTimeDimension: storeState.retentionTimeDimension as string | null,\n retentionDateRange: (storeState.retentionDateRange as DateRange) || getDateRangeFromPreset(DEFAULT_DATE_RANGE_PRESET),\n retentionViewGranularity: (storeState.retentionViewGranularity as RetentionGranularity) || 'week',\n retentionPeriods: (storeState.retentionPeriods as number) || 12,\n retentionType: (storeState.retentionType as RetentionType) || 'classic',\n retentionCohortFilters: (storeState.retentionCohortFilters as Filter[]) || [],\n retentionActivityFilters: (storeState.retentionActivityFilters as Filter[]) || [],\n retentionBreakdowns: (storeState.retentionBreakdowns as RetentionBreakdownItem[]) || [],\n }\n },\n\n canLoad(config: unknown): config is AnalysisConfig {\n return isValidRetentionConfig(config)\n },\n\n load(config: AnalysisConfig): RetentionSliceState {\n // Type guard - ensure it's a retention config\n if (config.analysisType !== 'retention') {\n throw new Error(\n `Cannot load ${config.analysisType} config with retention adapter`\n )\n }\n\n const retentionConfig = config as RetentionAnalysisConfig\n return serverQueryToState(retentionConfig.query)\n },\n\n save(\n state: RetentionSliceState,\n charts: Partial<Record<AnalysisType, ChartConfig>>,\n activeView: 'table' | 'chart'\n ): RetentionAnalysisConfig {\n return {\n version: 1,\n analysisType: 'retention',\n activeView,\n charts: {\n retention: charts.retention || this.getDefaultChartConfig(),\n },\n query: stateToServerQuery(state),\n }\n },\n\n validate(state: RetentionSliceState): ValidationResult {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Must have a cube selected\n if (!state.retentionCube) {\n errors.push('Select a cube for retention analysis')\n }\n\n // Must have a time dimension\n if (!state.retentionTimeDimension) {\n errors.push('Select a timestamp dimension for the analysis')\n }\n\n // Must have a binding key\n if (!state.retentionBindingKey?.dimension) {\n errors.push('Select a user identifier (binding key) to track retention')\n }\n\n // Date range is required\n if (!state.retentionDateRange?.start || !state.retentionDateRange?.end) {\n errors.push('Date range is required for retention analysis')\n } else {\n // Validate date format\n const startDate = new Date(state.retentionDateRange.start)\n const endDate = new Date(state.retentionDateRange.end)\n if (isNaN(startDate.getTime())) {\n errors.push('Invalid start date format')\n }\n if (isNaN(endDate.getTime())) {\n errors.push('Invalid end date format')\n }\n if (startDate > endDate) {\n errors.push('Start date must be before or equal to end date')\n }\n }\n\n // Periods must be valid\n if (state.retentionPeriods < 1) {\n errors.push('At least 1 retention period is required')\n }\n if (state.retentionPeriods > 52) {\n warnings.push('More than 52 periods may impact performance')\n }\n\n // Check time dimension format\n if (state.retentionTimeDimension) {\n const parts = state.retentionTimeDimension.split('.')\n if (parts.length < 2) {\n warnings.push('Time dimension should be in format \"Cube.dimension\"')\n }\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n warnings,\n }\n },\n\n clear(state: RetentionSliceState): RetentionSliceState {\n // Keep cube selection and date range but clear other configuration\n return {\n ...this.createInitial(),\n retentionCube: state.retentionCube,\n retentionDateRange: state.retentionDateRange,\n }\n },\n\n getDefaultChartConfig(): ChartConfig {\n return {\n chartType: 'retentionCombined',\n chartConfig: {\n // RetentionCombinedChart auto-configures from the retention data structure\n // No explicit axis mapping needed\n },\n displayConfig: {\n showLegend: true,\n showTooltip: true,\n showGrid: true,\n retentionDisplayMode: 'combined',\n },\n }\n },\n}\n","/**\n * AnalysisDisplayConfigPanel Component\n *\n * A panel for configuring chart display options (legend, grid, tooltip, etc.)\n * Extracted from AnalysisChartConfigPanel to be shown in its own tab.\n */\n\nimport { useState, useCallback, useEffect } from 'react'\nimport SectionHeading from './SectionHeading'\nimport { useChartConfig } from '../../charts/lazyChartConfigRegistry'\nimport type { ChartType, ChartDisplayConfig, ColorPalette, AxisFormatConfig } from '../../types'\nimport { AxisFormatControls } from '../charts/AxisFormatControls'\n\ninterface AnalysisDisplayConfigPanelProps {\n chartType: ChartType\n displayConfig: ChartDisplayConfig\n colorPalette?: ColorPalette\n onDisplayConfigChange: (config: ChartDisplayConfig) => void\n /** Keys to exclude from displayOptionsConfig rendering (e.g., ['content'] when content is managed elsewhere) */\n excludeKeys?: string[]\n}\n\n/**\n * StringArrayInput - A textarea that edits an array of strings\n * Uses local state while editing and only updates on blur\n */\nfunction StringArrayInput({\n label,\n value,\n onChange,\n placeholder,\n description,\n}: {\n label: string\n value: string[]\n onChange: (value: string[]) => void\n placeholder?: string\n description?: string\n}) {\n // Local state for textarea editing\n const [localText, setLocalText] = useState(() => value.join('\\n'))\n\n // Sync local state when external value changes (e.g., from undo/redo or load)\n useEffect(() => {\n const externalText = value.join('\\n')\n setLocalText(externalText)\n }, [value])\n\n const handleBlur = useCallback(() => {\n // Convert text to array, filtering empty strings\n const arrayValue = localText\n .split('\\n')\n .map(s => s.trim())\n .filter(s => s.length > 0)\n onChange(arrayValue)\n }, [localText, onChange])\n\n return (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{label}</label>\n <textarea\n value={localText}\n onChange={(e) => setLocalText(e.target.value)}\n onBlur={handleBlur}\n placeholder={placeholder}\n rows={4}\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text dc:resize-y\"\n />\n {description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{description}</p>\n )}\n </div>\n )\n}\n\nexport default function AnalysisDisplayConfigPanel({\n chartType,\n displayConfig,\n colorPalette,\n onDisplayConfigChange,\n excludeKeys,\n}: AnalysisDisplayConfigPanelProps) {\n // Get configuration for current chart type\n const { config: chartTypeConfig, loaded: chartConfigLoaded } = useChartConfig(chartType)\n\n if (!chartConfigLoaded) {\n return (\n <div className=\"dc:text-center text-dc-text-muted dc:text-sm dc:py-4\">\n Loading display options...\n </div>\n )\n }\n\n // Check if we have any display options to show\n const hasDisplayOptions =\n (chartTypeConfig.displayOptions && chartTypeConfig.displayOptions.length > 0) ||\n (chartTypeConfig.displayOptionsConfig && chartTypeConfig.displayOptionsConfig.length > 0)\n\n if (!hasDisplayOptions) {\n return (\n <div className=\"dc:text-center text-dc-text-muted dc:text-sm dc:py-4\">\n <p>No display options available for this chart type.</p>\n </div>\n )\n }\n\n return (\n <div className=\"dc:space-y-6\">\n <div>\n <SectionHeading className=\"dc:mb-2\">Display Options</SectionHeading>\n <div className=\"dc:space-y-2\">\n {/* Backward compatibility: Simple boolean display options */}\n {chartTypeConfig.displayOptions?.includes('showLegend') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.showLegend ?? true}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n showLegend: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Show Legend</span>\n </label>\n )}\n\n {chartTypeConfig.displayOptions?.includes('showGrid') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.showGrid ?? true}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n showGrid: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Show Grid</span>\n </label>\n )}\n\n {chartTypeConfig.displayOptions?.includes('showTooltip') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.showTooltip ?? true}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n showTooltip: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Show Tooltip</span>\n </label>\n )}\n\n {chartTypeConfig.displayOptions?.includes('stacked') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.stacked ?? false}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n stacked: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Stacked</span>\n </label>\n )}\n\n {chartTypeConfig.displayOptions?.includes('hideHeader') && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={displayConfig.hideHeader ?? false}\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n hideHeader: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">Hide Header</span>\n </label>\n )}\n\n {/* New structured display options */}\n {chartTypeConfig.displayOptionsConfig?.filter(option => !excludeKeys?.includes(option.key)).map((option) => (\n <div key={option.key} className={`dc:space-y-1 ${option.type === 'axisFormat' ? 'dc:mt-6 dc:pt-2' : ''}`}>\n {option.type === 'boolean' && (\n <label className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"checkbox\"\n checked={\n (displayConfig[option.key as keyof ChartDisplayConfig] as boolean) ??\n option.defaultValue ??\n false\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.checked\n })\n }\n className=\"dc:rounded border-dc-border focus:ring-dc-accent\"\n style={{ color: 'var(--dc-primary)' }}\n />\n <span className=\"dc:text-sm text-dc-text\">{option.label}</span>\n </label>\n )}\n\n {option.type === 'string' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">\n {option.label}\n {option.key === 'content' && (\n <span className=\"dc:text-xs text-dc-text-muted dc:ml-1\">\n (only headers, lists and links)\n </span>\n )}\n </label>\n {option.key === 'content' ? (\n <textarea\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n ''\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n placeholder={option.placeholder}\n rows={8}\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent dc:font-mono dc:resize-y bg-dc-surface text-dc-text\"\n />\n ) : (\n <input\n type=\"text\"\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n ''\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n placeholder={option.placeholder}\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text\"\n />\n )}\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'paletteColor' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <div className=\"dc:flex dc:flex-wrap dc:gap-2\">\n {colorPalette?.colors.map((color, index) => {\n const isSelected =\n ((displayConfig[option.key as keyof ChartDisplayConfig] as number) ??\n option.defaultValue ??\n 0) === index\n return (\n <button\n key={index}\n type=\"button\"\n onClick={() =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: index\n })\n }\n className={`dc:w-8 dc:h-8 dc:rounded dc:border-2 dc:transition-all dc:duration-200 dc:hover:scale-110 focus:outline-hidden dc:focus:ring-2 focus:ring-dc-accent dc:focus:ring-offset-1 ${\n isSelected\n ? 'dc:ring-2 dc:ring-offset-1 dc:scale-110'\n : 'hover:border-dc-text-muted'\n }`}\n style={{\n backgroundColor: color,\n borderColor: isSelected ? 'var(--dc-primary)' : 'var(--dc-border)'\n }}\n title={`Color ${index + 1}: ${color}`}\n />\n )\n }) || [\n // Fallback if no palette available\n <button\n key={0}\n type=\"button\"\n onClick={() =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: 0\n })\n }\n className=\"dc:w-8 dc:h-8 dc:rounded-sm dc:border-2 dc:ring-2 dc:ring-offset-1\"\n style={{\n backgroundColor: '#8884d8',\n borderColor: 'var(--dc-primary)',\n boxShadow: '0 0 0 2px var(--dc-primary)'\n }}\n title=\"Default Color\"\n />\n ]}\n </div>\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'number' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <input\n type=\"number\"\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as number) ??\n option.defaultValue ??\n 0\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value === '' ? undefined : Number(e.target.value)\n })\n }\n placeholder={option.placeholder}\n min={option.min}\n max={option.max}\n step={option.step}\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text\"\n />\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'select' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <select\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n ''\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n className=\"dc:w-full dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text\"\n >\n {option.options?.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'color' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <div className=\"dc:flex dc:items-center dc:space-x-2\">\n <input\n type=\"color\"\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n '#8884d8'\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n className=\"dc:w-12 dc:h-8 dc:border border-dc-border dc:rounded-sm dc:cursor-pointer\"\n />\n <input\n type=\"text\"\n value={\n (displayConfig[option.key as keyof ChartDisplayConfig] as string) ??\n option.defaultValue ??\n '#8884d8'\n }\n onChange={(e) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: e.target.value\n })\n }\n placeholder={option.placeholder || '#8884d8'}\n className=\"dc:flex-1 dc:px-2 dc:py-1 dc:text-sm dc:border border-dc-border dc:rounded-sm focus:ring-dc-accent focus:border-dc-accent bg-dc-surface text-dc-text\"\n />\n </div>\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n\n {option.type === 'axisFormat' && (\n <AxisFormatControls\n axisLabel={option.label}\n value={(displayConfig[option.key as keyof ChartDisplayConfig] as AxisFormatConfig) || {}}\n onChange={(config) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: Object.keys(config).length > 0 ? config : undefined\n })\n }\n />\n )}\n\n {option.type === 'stringArray' && (\n <StringArrayInput\n label={option.label}\n value={(displayConfig[option.key as keyof ChartDisplayConfig] as string[]) ?? []}\n onChange={(arrayValue) =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: arrayValue.length > 0 ? arrayValue : undefined\n })\n }\n placeholder={option.placeholder}\n description={option.description}\n />\n )}\n\n {option.type === 'buttonGroup' && (\n <div className=\"dc:space-y-1\">\n <label className=\"dc:text-sm text-dc-text-secondary\">{option.label}</label>\n <div className=\"dc:flex dc:border border-dc-border dc:rounded-sm dc:overflow-hidden\">\n {option.options?.map((opt) => {\n const isSelected = (displayConfig[option.key as keyof ChartDisplayConfig] ?? option.defaultValue) === opt.value\n return (\n <button\n key={opt.value}\n type=\"button\"\n onClick={() =>\n onDisplayConfigChange({\n ...displayConfig,\n [option.key]: opt.value\n })\n }\n className={`dc:flex-1 dc:px-3 dc:py-1.5 dc:text-sm dc:font-medium dc:transition-colors ${\n isSelected\n ? 'bg-dc-primary text-white'\n : 'bg-dc-surface text-dc-text hover:bg-dc-border'\n }`}\n >\n {opt.label}\n </button>\n )\n })}\n </div>\n {option.description && (\n <p className=\"dc:text-xs text-dc-text-muted\">{option.description}</p>\n )}\n </div>\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n )\n}\n","import React from 'react'\nimport Modal from './Modal'\n\nexport interface ConfirmModalProps {\n isOpen: boolean\n onClose: () => void\n onConfirm: () => void | Promise<void>\n title?: string\n message: React.ReactNode\n confirmText?: string\n cancelText?: string\n confirmVariant?: 'danger' | 'primary' | 'warning'\n isLoading?: boolean\n}\n\n/**\n * A reusable confirmation modal component.\n *\n * Usage:\n * ```tsx\n * <ConfirmModal\n * isOpen={showConfirm}\n * onClose={() => setShowConfirm(false)}\n * onConfirm={handleDelete}\n * title=\"Delete Portlet\"\n * message=\"Are you sure you want to delete this portlet? This action cannot be undone.\"\n * confirmText=\"Delete\"\n * confirmVariant=\"danger\"\n * />\n * ```\n */\nconst ConfirmModal: React.FC<ConfirmModalProps> = ({\n isOpen,\n onClose,\n onConfirm,\n title = 'Confirm',\n message,\n confirmText = 'Confirm',\n cancelText = 'Cancel',\n confirmVariant = 'primary',\n isLoading = false,\n}) => {\n const handleConfirm = async () => {\n await onConfirm()\n onClose()\n }\n\n const getConfirmButtonClasses = () => {\n const baseClasses = 'dc:px-4 dc:py-2 dc:text-sm dc:font-medium dc:rounded-md dc:transition-colors dc:focus:outline-none dc:focus:ring-2 dc:focus:ring-offset-2 dc:disabled:opacity-50 dc:disabled:cursor-not-allowed'\n\n switch (confirmVariant) {\n case 'danger':\n return `${baseClasses} bg-dc-danger dc:text-white dc:hover:bg-dc-danger/90 focus:ring-dc-danger`\n case 'warning':\n return `${baseClasses} bg-dc-warning dc:text-white dc:hover:bg-dc-warning/90 focus:ring-dc-warning`\n case 'primary':\n default:\n return `${baseClasses} bg-dc-primary dc:text-white dc:hover:bg-dc-primary/90 focus:ring-dc-primary`\n }\n }\n\n return (\n <Modal\n isOpen={isOpen}\n onClose={onClose}\n title={title}\n size=\"sm\"\n closeOnBackdropClick={!isLoading}\n closeOnEscape={!isLoading}\n footer={\n <>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={isLoading}\n className=\"dc:px-4 dc:py-2 dc:text-sm dc:font-medium text-dc-text-secondary bg-dc-surface dc:border border-dc-border dc:rounded-md hover:bg-dc-surface-hover dc:transition-colors dc:focus:outline-none dc:focus:ring-2 dc:focus:ring-offset-2 focus:ring-dc-primary dc:disabled:opacity-50 dc:disabled:cursor-not-allowed\"\n >\n {cancelText}\n </button>\n <button\n type=\"button\"\n onClick={handleConfirm}\n disabled={isLoading}\n className={getConfirmButtonClasses()}\n >\n {isLoading ? (\n <span className=\"dc:flex dc:items-center dc:gap-2\">\n <svg className=\"dc:animate-spin dc:h-4 dc:w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\n <circle className=\"dc:opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\"></circle>\n <path className=\"dc:opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n </svg>\n Processing...\n </span>\n ) : (\n confirmText\n )}\n </button>\n </>\n }\n >\n <div className=\"text-dc-text-secondary\">\n {message}\n </div>\n </Modal>\n )\n}\n\nexport default ConfirmModal\n","/**\n * Color Palette Selector Component\n * Allows users to select from predefined color palettes for their dashboard\n */\n\nimport { useState, useRef, useEffect } from 'react'\nimport { COLOR_PALETTES, getColorPalette } from '../utils/colorPalettes'\nimport { getIcon } from '../icons'\n\nconst ChevronDownIcon = getIcon('chevronDown')\n\ninterface ColorPaletteSelectorProps {\n currentPalette?: string\n onPaletteChange: (paletteName: string) => void\n className?: string\n}\n\nexport default function ColorPaletteSelector({\n currentPalette = 'default',\n onPaletteChange,\n className = ''\n}: ColorPaletteSelectorProps) {\n const [isOpen, setIsOpen] = useState(false)\n const dropdownRef = useRef<HTMLDivElement>(null)\n \n const currentPaletteObj = getColorPalette(currentPalette)\n\n // Close dropdown when clicking outside\n useEffect(() => {\n function handleClickOutside(event: MouseEvent) {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false)\n }\n }\n\n if (isOpen) {\n document.addEventListener('mousedown', handleClickOutside)\n return () => document.removeEventListener('mousedown', handleClickOutside)\n }\n }, [isOpen])\n\n const handlePaletteSelect = (paletteName: string) => {\n onPaletteChange(paletteName)\n setIsOpen(false)\n }\n\n return (\n <div className={`dc:relative ${className}`} ref={dropdownRef}>\n {/* Trigger Button */}\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n className=\"dc:inline-flex dc:items-center dc:gap-2 dc:px-3 dc:py-1.5 bg-dc-surface dc:border border-dc-border dc:rounded-md shadow-xs dc:text-sm dc:font-medium text-dc-text-secondary hover:bg-dc-surface-hover focus:outline-hidden dc:focus:ring-2 dc:focus:ring-offset-2 focus:ring-dc-accent\"\n >\n {/* Current Palette Preview - Hidden on mobile */}\n <div className=\"dc:hidden dc:md:flex dc:items-center dc:gap-1.5\">\n <div className=\"dc:flex dc:gap-0.5\">\n {currentPaletteObj.colors.slice(0, 4).map((color, index) => (\n <div\n key={index}\n className=\"dc:w-3 dc:h-3 rounded-xs dc:border border-dc-border\"\n style={{ backgroundColor: color }}\n title={`Series Color ${index + 1}`}\n />\n ))}\n </div>\n <span className=\"dc:text-xs text-dc-text-secondary\">|</span>\n <div className=\"dc:flex dc:gap-0.5\">\n {currentPaletteObj.gradient.slice(0, 3).map((color, index) => (\n <div\n key={index}\n className=\"dc:w-2 dc:h-3 dc:border-r dc:first:rounded-l-sm dc:last:rounded-r-sm dc:last:border-r-0\"\n style={{\n backgroundColor: color,\n borderColor: 'var(--dc-border)'\n }}\n title={`Gradient Color ${index + 1}`}\n />\n ))}\n </div>\n </div>\n <span>{currentPaletteObj.label}</span>\n <ChevronDownIcon \n className={`dc:w-4 dc:h-4 dc:transition-transform ${isOpen ? 'dc:rotate-180' : ''}`} \n />\n </button>\n\n {/* Dropdown Menu - Responsive width */}\n {isOpen && (\n <div className=\"dc:absolute dc:top-full dc:left-0 dc:mt-1 dc:w-72 dc:md:w-80 dc:lg:w-96 bg-dc-surface dc:border border-dc-border dc:rounded-md dc:shadow-lg dc:z-50 dc:max-h-80 dc:overflow-y-auto\">\n <div className=\"dc:py-1\">\n {COLOR_PALETTES.slice().sort((a, b) => a.label.localeCompare(b.label)).map((palette) => (\n <button\n key={palette.name}\n type=\"button\"\n onClick={() => handlePaletteSelect(palette.name)}\n className={`dc:w-full dc:px-3 dc:py-2 dc:text-left dc:text-sm hover:bg-dc-surface-hover focus:outline-hidden focus:bg-dc-surface-hover ${\n palette.name === currentPalette ? 'bg-dc-surface-secondary' : 'text-dc-text-secondary'\n }`}\n style={palette.name === currentPalette ? { color: 'var(--dc-primary)' } : undefined}\n >\n <div className=\"dc:flex dc:items-center dc:gap-3\">\n {/* Palette Preview - Hidden on mobile */}\n <div className=\"dc:hidden dc:md:flex dc:items-center dc:gap-2\">\n {/* Series Colors */}\n <div className=\"dc:flex dc:gap-0.5\">\n {palette.colors.slice(0, 6).map((color, index) => (\n <div\n key={`series-${index}`}\n className=\"dc:w-3 dc:h-3 rounded-xs dc:border border-dc-border\"\n style={{ backgroundColor: color }}\n />\n ))}\n </div>\n\n {/* Separator */}\n <div className=\"dc:w-px dc:h-4 bg-dc-border\" />\n \n {/* Gradient Colors */}\n <div className=\"dc:flex\">\n {palette.gradient.map((color, index) => (\n <div\n key={`gradient-${index}`}\n className=\"dc:w-2 dc:h-4 dc:first:rounded-l-sm dc:last:rounded-r-sm\"\n style={{ backgroundColor: color }}\n />\n ))}\n </div>\n </div>\n \n {/* Palette Name */}\n <span className=\"dc:font-medium\">{palette.label}</span>\n \n {/* Current Indicator */}\n {palette.name === currentPalette && (\n <div className=\"dc:ml-auto\">\n <div className=\"dc:w-2 dc:h-2 dc:rounded-full\" style={{ backgroundColor: 'var(--dc-primary)' }}></div>\n </div>\n )}\n </div>\n </button>\n ))}\n </div>\n </div>\n )}\n </div>\n )\n}","/**\n * FieldSearchItem Component\n *\n * A single field item in the search results list.\n * Shows field icon, title, type badge, and selection state.\n */\n\nimport { memo } from 'react'\nimport { getIcon, getMeasureTypeIcon, getFieldTypeIcon } from '../../icons'\nimport type { FieldSearchItemProps } from './types'\n\nconst CheckIcon = getIcon('check')\n\nfunction FieldSearchItem({\n field,\n isSelected,\n isFocused,\n onClick,\n onMouseEnter,\n ...props\n}: FieldSearchItemProps & { 'data-field-index'?: number }) {\n // Get appropriate icon based on field type\n const getFieldIcon = () => {\n if (field.fieldType === 'measure') {\n const Icon = getMeasureTypeIcon(field.type)\n return Icon ? <Icon className=\"dc:w-4 dc:h-4\" /> : null\n } else if (field.fieldType === 'timeDimension') {\n const Icon = getFieldTypeIcon('time')\n return Icon ? <Icon className=\"dc:w-4 dc:h-4\" /> : null\n } else {\n const Icon = getFieldTypeIcon('dimension')\n return Icon ? <Icon className=\"dc:w-4 dc:h-4\" /> : null\n }\n }\n\n // Get badge color based on field type\n const getBadgeStyle = () => {\n if (field.fieldType === 'measure') {\n return 'bg-dc-measure text-dc-measure-text'\n } else if (field.fieldType === 'timeDimension') {\n return 'bg-dc-time-dimension text-dc-time-dimension-text'\n } else {\n return 'bg-dc-dimension text-dc-dimension-text'\n }\n }\n\n // Get short type label\n const getTypeLabel = () => {\n if (field.fieldType === 'measure') {\n return field.type.charAt(0).toUpperCase() + field.type.slice(1)\n } else if (field.fieldType === 'timeDimension') {\n return 'Time'\n } else {\n return 'Dim'\n }\n }\n\n return (\n <button\n onClick={onClick}\n onMouseEnter={onMouseEnter}\n className={`dc:w-full dc:text-left dc:px-3 dc:py-2 dc:rounded-lg dc:flex dc:items-center dc:gap-3 dc:transition-colors dc:group ${\n isFocused\n ? 'bg-dc-primary/10 dc:ring-1 ring-dc-primary'\n : isSelected\n ? 'bg-dc-success/10'\n : 'hover:bg-dc-surface-hover'\n }`}\n {...props}\n >\n {/* Icon */}\n <span\n className={`dc:shrink-0 dc:w-8 dc:h-8 dc:flex dc:items-center dc:justify-center dc:rounded-md ${\n field.fieldType === 'measure'\n ? 'bg-dc-measure text-dc-measure-text'\n : field.fieldType === 'timeDimension'\n ? 'bg-dc-time-dimension text-dc-time-dimension-text'\n : 'bg-dc-dimension text-dc-dimension-text'\n }`}\n >\n {getFieldIcon()}\n </span>\n\n {/* Title and name */}\n <div className=\"dc:flex-1 dc:min-w-0\">\n <div className=\"dc:text-sm dc:font-medium text-dc-text dc:truncate\">\n {field.title}\n </div>\n <div className=\"dc:text-xs text-dc-text-muted dc:truncate\">{field.name}</div>\n </div>\n\n {/* Type badge */}\n <span\n className={`dc:shrink-0 dc:px-2 dc:py-0.5 dc:rounded dc:text-xs dc:font-medium ${getBadgeStyle()}`}\n >\n {getTypeLabel()}\n </span>\n\n {/* Selection indicator */}\n {isSelected && (\n <span className=\"dc:shrink-0 dc:w-5 dc:h-5 dc:flex dc:items-center dc:justify-center dc:rounded-full bg-dc-success text-white\">\n <CheckIcon className=\"dc:w-3 dc:h-3\" />\n </span>\n )}\n </button>\n )\n}\n\nexport default memo(FieldSearchItem)\n","/**\n * FieldDetailPanel Component\n *\n * Shows detailed information about the currently focused/hovered field.\n * Displays: icon, title, description, type, cube name, and technical name.\n */\n\nimport { memo } from 'react'\nimport { getMeasureTypeIcon, getFieldTypeIcon } from '../../icons'\nimport type { FieldDetailPanelProps } from './types'\n\nfunction FieldDetailPanel({ field }: FieldDetailPanelProps) {\n if (!field) {\n return (\n <div className=\"dc:p-6 dc:text-center text-dc-text-muted\">\n <p className=\"dc:text-sm\">Hover over a field to see details</p>\n </div>\n )\n }\n\n // Get appropriate icon based on field type\n const getFieldIcon = () => {\n if (field.fieldType === 'measure') {\n const Icon = getMeasureTypeIcon(field.type)\n return Icon ? <Icon className=\"dc:w-6 dc:h-6\" /> : null\n } else if (field.fieldType === 'timeDimension') {\n const Icon = getFieldTypeIcon('time')\n return Icon ? <Icon className=\"dc:w-6 dc:h-6\" /> : null\n } else {\n const Icon = getFieldTypeIcon('dimension')\n return Icon ? <Icon className=\"dc:w-6 dc:h-6\" /> : null\n }\n }\n\n // Get icon background color - use field type specific colors for consistency\n const getIconBgStyle = () => {\n if (field.fieldType === 'measure') {\n return 'bg-dc-measure text-dc-measure-text'\n } else if (field.fieldType === 'timeDimension') {\n return 'bg-dc-time-dimension text-dc-time-dimension-text'\n } else {\n return 'bg-dc-dimension text-dc-dimension-text'\n }\n }\n\n // Get type display name\n const getTypeDisplay = () => {\n if (field.fieldType === 'measure') {\n const typeMap: Record<string, string> = {\n count: 'Count',\n countDistinct: 'Count Distinct',\n countDistinctApprox: 'Count Distinct (Approx)',\n sum: 'Sum',\n avg: 'Average',\n min: 'Minimum',\n max: 'Maximum',\n runningTotal: 'Running Total',\n number: 'Number'\n }\n return typeMap[field.type] || field.type\n } else if (field.fieldType === 'timeDimension') {\n return 'Time Dimension'\n } else {\n const typeMap: Record<string, string> = {\n string: 'Text',\n number: 'Number',\n boolean: 'Boolean',\n geo: 'Geographic'\n }\n return typeMap[field.type] || 'Dimension'\n }\n }\n\n return (\n <div className=\"dc:p-4\">\n {/* Header with icon and title */}\n <div className=\"dc:flex dc:items-start dc:gap-3 dc:mb-4\">\n <span\n className={`dc:shrink-0 dc:w-12 dc:h-12 dc:flex dc:items-center dc:justify-center dc:rounded-lg ${getIconBgStyle()}`}\n >\n {getFieldIcon()}\n </span>\n <div className=\"dc:flex-1 dc:min-w-0\">\n <h3 className=\"dc:text-base dc:font-semibold text-dc-text dc:leading-tight\">\n {field.title}\n </h3>\n <p className=\"dc:text-xs text-dc-text-muted dc:mt-0.5 dc:truncate\">\n {field.name}\n </p>\n </div>\n </div>\n\n {/* Description */}\n {field.description && (\n <div className=\"dc:mb-4\">\n <p className=\"dc:text-sm text-dc-text-secondary dc:leading-relaxed\">\n {field.description}\n </p>\n </div>\n )}\n\n {/* Metadata */}\n <div className=\"dc:space-y-3 dc:pt-4 dc:border-t border-dc-border\">\n <div className=\"dc:flex dc:items-center dc:justify-between\">\n <span className=\"dc:text-xs text-dc-text-muted\">Type</span>\n <span className=\"dc:text-sm text-dc-text dc:font-medium\">{getTypeDisplay()}</span>\n </div>\n <div className=\"dc:flex dc:items-center dc:justify-between\">\n <span className=\"dc:text-xs text-dc-text-muted\">Cube</span>\n <span className=\"dc:text-sm text-dc-text dc:font-medium\">{field.cubeName}</span>\n </div>\n <div className=\"dc:flex dc:items-center dc:justify-between\">\n <span className=\"dc:text-xs text-dc-text-muted\">Category</span>\n <span\n className={`dc:text-xs dc:px-2 dc:py-0.5 dc:rounded dc:font-medium ${\n field.fieldType === 'measure'\n ? 'bg-dc-measure text-dc-measure-text'\n : field.fieldType === 'timeDimension'\n ? 'bg-dc-time-dimension text-dc-time-dimension-text'\n : 'bg-dc-dimension text-dc-dimension-text'\n }`}\n >\n {field.fieldType === 'measure'\n ? 'Measure'\n : field.fieldType === 'timeDimension'\n ? 'Time Dimension'\n : 'Dimension'}\n </span>\n </div>\n </div>\n\n {/* Usage hint */}\n <div className=\"dc:mt-6 dc:p-3 bg-dc-surface dc:rounded-lg\">\n <p className=\"dc:text-xs text-dc-text-muted\">\n Press <kbd className=\"dc:px-1 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">Enter</kbd> or click to add this field to your query.\n </p>\n </div>\n </div>\n )\n}\n\nexport default memo(FieldDetailPanel)\n","/**\n * FieldSearchModal Component\n *\n * A full-screen search modal for selecting cube fields (measures/dimensions).\n * Features:\n * - Real-time search filtering\n * - Cube-based category filtering\n * - Three-column layout: Categories | Results | Details\n * - Keyboard navigation support\n * - Recent fields tracking\n */\n\nimport { useState, useMemo, useCallback, useEffect, useRef, KeyboardEvent } from 'react'\nimport { getIcon } from '../../icons'\nimport type { FieldSearchModalProps, FieldOption } from './types'\nimport type { MetaField } from '../../shared/types'\nimport {\n schemaToFieldOptions,\n filterFieldOptions,\n groupFieldsByCube,\n getCubeNames,\n getCubeTitle,\n getRecentFields,\n addRecentField,\n getRecentFieldOptions\n} from './utils'\nimport FieldSearchItem from './FieldSearchItem'\nimport FieldDetailPanel from './FieldDetailPanel'\n\nconst SearchIcon = getIcon('search')\nconst CloseIcon = getIcon('close')\n\nexport default function FieldSearchModal({\n isOpen,\n onClose,\n onSelect,\n mode,\n schema,\n selectedFields,\n recentFields: externalRecentFields\n}: FieldSearchModalProps) {\n // State\n const [searchTerm, setSearchTerm] = useState('')\n const [selectedCube, setSelectedCube] = useState<string | null>(null)\n const [focusedField, setFocusedField] = useState<FieldOption | null>(null)\n const [focusedIndex, setFocusedIndex] = useState(-1)\n const [lastSelectedIndex, setLastSelectedIndex] = useState<number | null>(null)\n\n // Refs\n const searchInputRef = useRef<HTMLInputElement>(null)\n const resultsContainerRef = useRef<HTMLDivElement>(null)\n\n // Get recent fields from localStorage or props\n const recentFieldNames = useMemo(() => {\n if (externalRecentFields) return externalRecentFields\n const stored = getRecentFields()\n return mode === 'metrics' ? stored.metrics : stored.breakdowns\n }, [externalRecentFields, mode])\n\n // Map mode to field options mode\n const fieldOptionsMode = mode\n\n // Get all field options for current mode\n const allFieldOptions = useMemo(() => {\n return schemaToFieldOptions(schema, fieldOptionsMode)\n }, [schema, fieldOptionsMode])\n\n // Get cube names for category filter\n const cubeNames = useMemo(() => {\n return getCubeNames(schema)\n }, [schema])\n\n // Filter fields by search and cube\n const filteredFields = useMemo(() => {\n return filterFieldOptions(allFieldOptions, searchTerm, selectedCube)\n }, [allFieldOptions, searchTerm, selectedCube])\n\n // Group filtered fields by cube\n const groupedFields = useMemo(() => {\n return groupFieldsByCube(filteredFields)\n }, [filteredFields])\n\n // Get recent field options (only when not searching)\n const recentOptions = useMemo(() => {\n if (searchTerm.trim()) return []\n return getRecentFieldOptions(schema, fieldOptionsMode, recentFieldNames).filter(\n (f) => !selectedCube || f.cubeName === selectedCube\n )\n }, [schema, fieldOptionsMode, recentFieldNames, searchTerm, selectedCube])\n\n // Flat list of visible fields for keyboard navigation\n const flatFieldsList = useMemo(() => {\n const list: FieldOption[] = [...recentOptions]\n groupedFields.forEach((fields) => {\n list.push(...fields)\n })\n return list\n }, [recentOptions, groupedFields])\n\n // Focus search input when modal opens\n useEffect(() => {\n if (isOpen && searchInputRef.current) {\n searchInputRef.current.focus()\n }\n }, [isOpen])\n\n // Reset state when modal closes\n useEffect(() => {\n if (!isOpen) {\n setSearchTerm('')\n setSelectedCube(null)\n setFocusedField(null)\n setFocusedIndex(-1)\n setLastSelectedIndex(null)\n }\n }, [isOpen])\n\n // Handle single field selection\n const selectSingleField = useCallback(\n (field: FieldOption, keepOpen: boolean = false) => {\n // Add to recent fields\n addRecentField(field.name, mode === 'metrics' ? 'metrics' : 'breakdowns')\n\n // Create MetaField object for callback\n const metaField: MetaField = {\n name: field.name,\n title: field.title,\n shortTitle: field.shortTitle,\n type: field.type,\n description: field.description\n }\n\n onSelect(metaField, field.fieldType, field.cubeName, keepOpen)\n },\n [mode, onSelect]\n )\n\n // Handle field selection with shift-click support for range selection\n const handleSelectField = useCallback(\n (field: FieldOption, fieldIndex: number, shiftKey: boolean = false) => {\n // Shift-click for range selection - keep modal open\n if (shiftKey && lastSelectedIndex !== null && lastSelectedIndex !== fieldIndex) {\n const startIndex = Math.min(lastSelectedIndex, fieldIndex)\n const endIndex = Math.max(lastSelectedIndex, fieldIndex)\n\n // Select all fields in the range, keep modal open for all\n for (let i = startIndex; i <= endIndex; i++) {\n const rangeField = flatFieldsList[i]\n if (rangeField && !selectedFields.includes(rangeField.name)) {\n selectSingleField(rangeField, true) // Keep modal open\n }\n }\n } else if (shiftKey) {\n // Shift-click on single item - select but keep modal open\n selectSingleField(field, true)\n } else {\n // Normal single selection - close modal after\n selectSingleField(field, false)\n }\n\n // Update last selected index for next shift-click\n setLastSelectedIndex(fieldIndex)\n },\n [flatFieldsList, lastSelectedIndex, selectSingleField, selectedFields]\n )\n\n // Keyboard navigation\n const handleKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (flatFieldsList.length === 0) return\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n setFocusedIndex((prev) => {\n const next = Math.min(prev + 1, flatFieldsList.length - 1)\n setFocusedField(flatFieldsList[next])\n return next\n })\n break\n\n case 'ArrowUp':\n e.preventDefault()\n setFocusedIndex((prev) => {\n const next = Math.max(prev - 1, 0)\n setFocusedField(flatFieldsList[next])\n return next\n })\n break\n\n case 'Enter':\n e.preventDefault()\n if (focusedIndex >= 0 && flatFieldsList[focusedIndex]) {\n handleSelectField(flatFieldsList[focusedIndex], focusedIndex, e.shiftKey)\n }\n break\n\n case 'Escape':\n e.preventDefault()\n onClose()\n break\n }\n },\n [flatFieldsList, focusedIndex, handleSelectField, onClose]\n )\n\n // Scroll focused item into view\n useEffect(() => {\n if (focusedIndex >= 0 && resultsContainerRef.current) {\n const focusedElement = resultsContainerRef.current.querySelector(\n `[data-field-index=\"${focusedIndex}\"]`\n )\n if (focusedElement) {\n focusedElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' })\n }\n }\n }, [focusedIndex])\n\n if (!isOpen) return null\n\n const searchPlaceholder =\n mode === 'metrics' ? 'Search metrics...' : mode === 'filter' ? 'Search fields to filter...' : 'Search dimensions...'\n\n const modalTitle = mode === 'metrics' ? 'Select a Metric' : mode === 'filter' ? 'Select a Field to Filter' : 'Select a Dimension'\n const focusedFieldId = focusedIndex >= 0 && flatFieldsList[focusedIndex]\n ? `field-option-${flatFieldsList[focusedIndex].name.replace(/\\./g, '-')}`\n : undefined\n\n return (\n <div\n className=\"dc:fixed dc:inset-0 dc:z-50 dc:flex dc:items-center dc:justify-center\"\n style={{ backgroundColor: 'var(--dc-overlay)' }}\n onClick={onClose}\n role=\"presentation\"\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={modalTitle}\n className=\"bg-dc-surface dc:shadow-xl dc:w-full dc:h-full dc:md:rounded-lg dc:md:w-[900px] dc:md:max-w-[900px] dc:md:h-[80vh] dc:md:max-h-[700px] dc:flex dc:flex-col dc:overflow-hidden\"\n onClick={(e) => e.stopPropagation()}\n onKeyDown={handleKeyDown}\n >\n {/* Header with Search */}\n <div className=\"dc:shrink-0 dc:border-b border-dc-border\">\n <div className=\"dc:flex dc:items-center dc:px-4 dc:py-3 dc:gap-3\">\n <SearchIcon className=\"dc:w-5 dc:h-5 text-dc-text-muted\" aria-hidden={true} />\n <input\n ref={searchInputRef}\n type=\"text\"\n value={searchTerm}\n onChange={(e) => {\n setSearchTerm(e.target.value)\n setFocusedIndex(-1)\n }}\n placeholder={searchPlaceholder}\n className=\"dc:flex-1 bg-transparent dc:border-none dc:outline-none text-dc-text placeholder-dc-text-muted dc:text-lg\"\n aria-label={searchPlaceholder}\n aria-controls=\"field-search-results\"\n aria-activedescendant={focusedFieldId}\n role=\"combobox\"\n aria-expanded=\"true\"\n aria-autocomplete=\"list\"\n />\n <button\n onClick={onClose}\n className=\"dc:p-1 text-dc-text-secondary hover:text-dc-text dc:rounded\"\n aria-label=\"Close dialog\"\n >\n <CloseIcon className=\"dc:w-5 dc:h-5\" aria-hidden={true} />\n </button>\n </div>\n {/* Mobile cube filter - shown only on mobile */}\n {cubeNames.length > 1 && (\n <div className=\"dc:md:hidden dc:px-4 dc:pb-3\">\n <select\n value={selectedCube || ''}\n onChange={(e) => setSelectedCube(e.target.value || null)}\n className=\"dc:w-full dc:px-3 dc:py-2 bg-dc-surface dc:border border-dc-border dc:rounded-lg dc:text-sm text-dc-text dc:focus:outline-none dc:focus:ring-1 focus:ring-dc-primary\"\n aria-label=\"Filter by cube\"\n >\n <option value=\"\">All Cubes</option>\n {cubeNames.map((cubeName) => (\n <option key={cubeName} value={cubeName}>\n {getCubeTitle(cubeName, schema)}\n </option>\n ))}\n </select>\n </div>\n )}\n </div>\n\n {/* Three Column Layout - Single column on mobile */}\n <div className=\"dc:flex-1 dc:flex dc:overflow-hidden\">\n {/* Left Column - Categories (hidden on mobile) */}\n <nav\n className=\"dc:hidden dc:md:block dc:w-48 dc:shrink-0 dc:border-r border-dc-border dc:overflow-y-auto bg-dc-surface-secondary\"\n aria-label=\"Filter by cube\"\n >\n <div className=\"dc:p-2\" role=\"group\" aria-label=\"Cube categories\">\n <button\n onClick={() => setSelectedCube(null)}\n className={`dc:w-full dc:text-left dc:px-3 dc:py-2 dc:rounded-md dc:text-sm dc:transition-colors ${\n selectedCube === null\n ? 'bg-dc-primary/10 text-dc-primary dc:font-medium'\n : 'text-dc-text hover:bg-dc-surface-hover'\n }`}\n aria-pressed={selectedCube === null}\n >\n All\n </button>\n {cubeNames.map((cubeName) => (\n <button\n key={cubeName}\n onClick={() => setSelectedCube(cubeName)}\n className={`dc:w-full dc:text-left dc:px-3 dc:py-2 dc:rounded-md dc:text-sm dc:transition-colors dc:truncate ${\n selectedCube === cubeName\n ? 'bg-dc-primary/10 text-dc-primary dc:font-medium'\n : 'text-dc-text hover:bg-dc-surface-hover'\n }`}\n title={getCubeTitle(cubeName, schema)}\n aria-pressed={selectedCube === cubeName}\n >\n {getCubeTitle(cubeName, schema)}\n </button>\n ))}\n </div>\n </nav>\n\n {/* Middle Column - Results */}\n <div\n id=\"field-search-results\"\n ref={resultsContainerRef}\n className=\"dc:flex-1 dc:overflow-y-auto dc:p-4\"\n role=\"listbox\"\n aria-label=\"Available fields\"\n >\n {filteredFields.length === 0 && recentOptions.length === 0 ? (\n <div className=\"dc:text-center dc:py-12 text-dc-text-muted\">\n <p className=\"dc:text-lg dc:mb-2\">No fields found</p>\n <p className=\"dc:text-sm\">\n {searchTerm\n ? `No ${mode === 'metrics' ? 'metrics' : 'dimensions'} match \"${searchTerm}\"`\n : `No ${mode === 'metrics' ? 'metrics' : 'dimensions'} available`}\n </p>\n </div>\n ) : (\n <div className=\"dc:space-y-6\">\n {/* Recent Fields */}\n {recentOptions.length > 0 && (\n <div>\n <h3 className=\"dc:text-xs dc:font-semibold text-dc-text-muted dc:uppercase dc:tracking-wider dc:mb-2\">\n Recents\n </h3>\n <div className=\"dc:space-y-1\">\n {recentOptions.map((field, idx) => (\n <FieldSearchItem\n key={`recent-${field.name}`}\n field={field}\n isSelected={selectedFields.includes(field.name)}\n isFocused={focusedIndex === idx}\n onClick={(e) => handleSelectField(field, idx, e.shiftKey)}\n onMouseEnter={() => {\n setFocusedField(field)\n setFocusedIndex(idx)\n }}\n data-field-index={idx}\n />\n ))}\n </div>\n </div>\n )}\n\n {/* Grouped by Cube */}\n {Array.from(groupedFields.entries()).map(([cubeName, fields]) => (\n <div key={cubeName}>\n <h3 className=\"dc:text-xs dc:font-semibold text-dc-text-muted dc:uppercase dc:tracking-wider dc:mb-2\">\n {getCubeTitle(cubeName, schema)}\n </h3>\n <div className=\"dc:space-y-1\">\n {fields.map((field) => {\n const fieldIndex =\n recentOptions.length +\n Array.from(groupedFields.entries())\n .slice(\n 0,\n Array.from(groupedFields.keys()).indexOf(cubeName)\n )\n .reduce((sum, [, f]) => sum + f.length, 0) +\n fields.indexOf(field)\n\n return (\n <FieldSearchItem\n key={field.name}\n field={field}\n isSelected={selectedFields.includes(field.name)}\n isFocused={focusedIndex === fieldIndex}\n onClick={(e) => handleSelectField(field, fieldIndex, e.shiftKey)}\n onMouseEnter={() => {\n setFocusedField(field)\n setFocusedIndex(fieldIndex)\n }}\n data-field-index={fieldIndex}\n />\n )\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Right Column - Field Details (hidden on mobile) */}\n <div className=\"dc:hidden dc:md:block dc:w-72 dc:shrink-0 dc:border-l border-dc-border bg-dc-surface-secondary dc:overflow-y-auto\">\n <FieldDetailPanel field={focusedField} />\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"dc:shrink-0 dc:border-t border-dc-border dc:px-4 dc:py-3 dc:flex dc:items-center dc:justify-between dc:text-sm text-dc-text-muted\">\n <div>\n <span className=\"text-dc-text-secondary\">{filteredFields.length}</span>{' '}\n {mode === 'metrics' ? 'metrics' : mode === 'filter' ? 'fields' : 'dimensions'} available\n </div>\n {/* Keyboard shortcuts - hidden on mobile */}\n <div className=\"dc:hidden dc:md:flex dc:items-center dc:gap-4\">\n <span>\n <kbd className=\"dc:px-1.5 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">↑↓</kbd> Navigate\n </span>\n <span>\n <kbd className=\"dc:px-1.5 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">Enter</kbd> Select\n </span>\n <span>\n <kbd className=\"dc:px-1.5 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">Shift</kbd>+Click Multi-select\n </span>\n <span>\n <kbd className=\"dc:px-1.5 dc:py-0.5 bg-dc-surface-tertiary dc:rounded dc:text-xs\">Esc</kbd> Close\n </span>\n </div>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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ACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACD;EACE,MAAM;EACN,OAAO;EACP,QAAQ;GACN;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD;EACD,UAAU;GACR;GACA;GACA;GACA;GACA;GACA;GACD;EACF;CACF;AAKD,SAAgB,EAAgB,GAAoC;AAMlE,QALK,KAIW,EAAe,MAAK,MAAK,EAAE,SAAS,EAAY,IAHvD,EAAe;;;;AC9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"}
|