@burdenoff/microfe-bigconsole 2026.1004.2 → 2026.1004.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/bigconsole/BigConsoleRoutes.d.ts +1 -1
  2. package/dist/bigconsole/BigConsoleRoutes.d.ts.map +1 -1
  3. package/dist/bigconsole/BigConsoleRoutes.js +15 -1
  4. package/dist/bigconsole/BigConsoleRoutes.js.map +1 -1
  5. package/dist/bigconsole/components/DrilldownModal.d.ts.map +1 -1
  6. package/dist/bigconsole/components/DrilldownModal.js +54 -53
  7. package/dist/bigconsole/components/DrilldownModal.js.map +1 -1
  8. package/dist/bigconsole/components/builder/inspector/DataBindingSection.d.ts +3 -1
  9. package/dist/bigconsole/components/builder/inspector/DataBindingSection.d.ts.map +1 -1
  10. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js +40 -39
  11. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js.map +1 -1
  12. package/dist/bigconsole/components/dashboard/DashboardCanvas.d.ts.map +1 -1
  13. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +184 -177
  14. package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
  15. package/dist/bigconsole/components/dashboard/DashboardGrid.d.ts.map +1 -1
  16. package/dist/bigconsole/components/dashboard/DashboardGrid.js +40 -22
  17. package/dist/bigconsole/components/dashboard/DashboardGrid.js.map +1 -1
  18. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.d.ts.map +1 -1
  19. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +31 -25
  20. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
  21. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.d.ts +1 -1
  22. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.d.ts.map +1 -1
  23. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js +115 -80
  24. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js.map +1 -1
  25. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.d.ts.map +1 -1
  26. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js +177 -146
  27. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js.map +1 -1
  28. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.d.ts +1 -1
  29. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.d.ts.map +1 -1
  30. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +375 -325
  31. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
  32. package/dist/bigconsole/components/datasink/CacheTTLSelector.d.ts.map +1 -1
  33. package/dist/bigconsole/components/datasink/CacheTTLSelector.js +7 -1
  34. package/dist/bigconsole/components/datasink/CacheTTLSelector.js.map +1 -1
  35. package/dist/bigconsole/components/parser/ParserSelector.d.ts +3 -1
  36. package/dist/bigconsole/components/parser/ParserSelector.d.ts.map +1 -1
  37. package/dist/bigconsole/components/parser/ParserSelector.js +94 -94
  38. package/dist/bigconsole/components/parser/ParserSelector.js.map +1 -1
  39. package/dist/bigconsole/components/pipeline/PipelineCard.d.ts.map +1 -1
  40. package/dist/bigconsole/components/pipeline/PipelineCard.js +111 -89
  41. package/dist/bigconsole/components/pipeline/PipelineCard.js.map +1 -1
  42. package/dist/bigconsole/components/pipeline/PipelineScopePicker.d.ts +8 -0
  43. package/dist/bigconsole/components/pipeline/PipelineScopePicker.d.ts.map +1 -0
  44. package/dist/bigconsole/components/pipeline/PipelineScopePicker.js +52 -0
  45. package/dist/bigconsole/components/pipeline/PipelineScopePicker.js.map +1 -0
  46. package/dist/bigconsole/components/widgets/PropertiesPanel.d.ts.map +1 -1
  47. package/dist/bigconsole/components/widgets/PropertiesPanel.js +257 -221
  48. package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
  49. package/dist/bigconsole/components/widgets/WidgetHeader.d.ts +1 -0
  50. package/dist/bigconsole/components/widgets/WidgetHeader.d.ts.map +1 -1
  51. package/dist/bigconsole/components/widgets/WidgetHeader.js +29 -29
  52. package/dist/bigconsole/components/widgets/WidgetHeader.js.map +1 -1
  53. package/dist/bigconsole/components/widgets/WidgetWrapper.d.ts +2 -0
  54. package/dist/bigconsole/components/widgets/WidgetWrapper.d.ts.map +1 -1
  55. package/dist/bigconsole/components/widgets/WidgetWrapper.js +247 -239
  56. package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
  57. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.d.ts.map +1 -1
  58. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js +6 -4
  59. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js.map +1 -1
  60. package/dist/bigconsole/components/widgets/states/EmptyState.d.ts +1 -0
  61. package/dist/bigconsole/components/widgets/states/EmptyState.d.ts.map +1 -1
  62. package/dist/bigconsole/components/widgets/states/EmptyState.js +4 -4
  63. package/dist/bigconsole/components/widgets/states/EmptyState.js.map +1 -1
  64. package/dist/bigconsole/components/workflow/WorkflowDesignPreview.d.ts +7 -0
  65. package/dist/bigconsole/components/workflow/WorkflowDesignPreview.d.ts.map +1 -0
  66. package/dist/bigconsole/components/workflow/WorkflowDesignPreview.js +94 -0
  67. package/dist/bigconsole/components/workflow/WorkflowDesignPreview.js.map +1 -0
  68. package/dist/bigconsole/components/workflow/WorkflowSinkLinkForm.d.ts +7 -0
  69. package/dist/bigconsole/components/workflow/WorkflowSinkLinkForm.d.ts.map +1 -0
  70. package/dist/bigconsole/components/workflow/WorkflowSinkLinkForm.js +101 -0
  71. package/dist/bigconsole/components/workflow/WorkflowSinkLinkForm.js.map +1 -0
  72. package/dist/bigconsole/hooks/useBigConsoleSettings.d.ts +16 -0
  73. package/dist/bigconsole/hooks/useBigConsoleSettings.d.ts.map +1 -0
  74. package/dist/bigconsole/hooks/useBigConsoleSettings.js +57 -0
  75. package/dist/bigconsole/hooks/useBigConsoleSettings.js.map +1 -0
  76. package/dist/bigconsole/hooks/useDashboardOperations.d.ts.map +1 -1
  77. package/dist/bigconsole/hooks/useDashboardOperations.js +102 -79
  78. package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
  79. package/dist/bigconsole/hooks/useExportDashboard.d.ts +3 -1
  80. package/dist/bigconsole/hooks/useExportDashboard.d.ts.map +1 -1
  81. package/dist/bigconsole/hooks/useExportDashboard.js +23 -13
  82. package/dist/bigconsole/hooks/useExportDashboard.js.map +1 -1
  83. package/dist/bigconsole/hooks/useFilterUrlSync.d.ts +18 -0
  84. package/dist/bigconsole/hooks/useFilterUrlSync.d.ts.map +1 -1
  85. package/dist/bigconsole/hooks/useFilterUrlSync.js +87 -57
  86. package/dist/bigconsole/hooks/useFilterUrlSync.js.map +1 -1
  87. package/dist/bigconsole/hooks/useGlobalFilters.d.ts.map +1 -1
  88. package/dist/bigconsole/hooks/useGlobalFilters.js +39 -29
  89. package/dist/bigconsole/hooks/useGlobalFilters.js.map +1 -1
  90. package/dist/bigconsole/hooks/usePageOperations.d.ts.map +1 -1
  91. package/dist/bigconsole/hooks/usePageOperations.js +1 -0
  92. package/dist/bigconsole/hooks/usePageOperations.js.map +1 -1
  93. package/dist/bigconsole/hooks/usePipelineOperations.d.ts.map +1 -1
  94. package/dist/bigconsole/hooks/usePipelineOperations.js +114 -112
  95. package/dist/bigconsole/hooks/usePipelineOperations.js.map +1 -1
  96. package/dist/bigconsole/hooks/useShareRecipients.d.ts +22 -0
  97. package/dist/bigconsole/hooks/useShareRecipients.d.ts.map +1 -0
  98. package/dist/bigconsole/hooks/useShareRecipients.js +198 -0
  99. package/dist/bigconsole/hooks/useShareRecipients.js.map +1 -0
  100. package/dist/bigconsole/pages/DashboardBuilderPage.d.ts.map +1 -1
  101. package/dist/bigconsole/pages/DashboardBuilderPage.js +198 -193
  102. package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
  103. package/dist/bigconsole/pages/DashboardViewPage.d.ts.map +1 -1
  104. package/dist/bigconsole/pages/DashboardViewPage.js +171 -167
  105. package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
  106. package/dist/bigconsole/pages/DashboardsPage.d.ts.map +1 -1
  107. package/dist/bigconsole/pages/DashboardsPage.js +313 -266
  108. package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
  109. package/dist/bigconsole/pages/DataSinkBuilderPage.d.ts.map +1 -1
  110. package/dist/bigconsole/pages/DataSinkBuilderPage.js +84 -79
  111. package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
  112. package/dist/bigconsole/pages/DataSinkViewPage.d.ts.map +1 -1
  113. package/dist/bigconsole/pages/DataSinkViewPage.js +267 -257
  114. package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
  115. package/dist/bigconsole/pages/InsightsPage.d.ts.map +1 -1
  116. package/dist/bigconsole/pages/InsightsPage.js +465 -402
  117. package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
  118. package/dist/bigconsole/pages/PipelineBuilderPage.d.ts.map +1 -1
  119. package/dist/bigconsole/pages/PipelineBuilderPage.js +169 -157
  120. package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
  121. package/dist/bigconsole/pages/PipelineViewPage.d.ts.map +1 -1
  122. package/dist/bigconsole/pages/PipelineViewPage.js +370 -230
  123. package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
  124. package/dist/bigconsole/pages/PipelinesPage.d.ts.map +1 -1
  125. package/dist/bigconsole/pages/PipelinesPage.js +181 -151
  126. package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
  127. package/dist/bigconsole/pages/SettingsPage.d.ts.map +1 -1
  128. package/dist/bigconsole/pages/SettingsPage.js +188 -208
  129. package/dist/bigconsole/pages/SettingsPage.js.map +1 -1
  130. package/dist/bigconsole/pages/WidgetDemoPage.d.ts.map +1 -1
  131. package/dist/bigconsole/pages/WidgetDemoPage.js +134 -117
  132. package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
  133. package/dist/bigconsole/pages/WorkflowViewPage.d.ts.map +1 -1
  134. package/dist/bigconsole/pages/WorkflowViewPage.js +315 -267
  135. package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
  136. package/dist/bigconsole/pages/WorkflowsPage.d.ts.map +1 -1
  137. package/dist/bigconsole/pages/WorkflowsPage.js +4 -3
  138. package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
  139. package/dist/bigconsole/types/index.d.ts +4 -0
  140. package/dist/bigconsole/types/index.d.ts.map +1 -1
  141. package/dist/generated/wspace-operations.d.ts +148 -0
  142. package/dist/generated/wspace-operations.d.ts.map +1 -1
  143. package/dist/generated/wspace-operations.js +172 -133
  144. package/dist/generated/wspace-operations.js.map +1 -1
  145. package/dist/generated/wspace-types.d.ts +21 -0
  146. package/dist/generated/wspace-types.d.ts.map +1 -1
  147. package/dist/generated/wspace-types.js.map +1 -1
  148. package/dist/microfe-bigconsole.css +1 -1
  149. package/dist/translations/en.d.ts.map +1 -1
  150. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardCanvas.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardCanvas.tsx"],"sourcesContent":["/**\n * DashboardCanvas Component\n *\n * Main canvas for dashboard with grid layout and widget rendering.\n * Phase 2: Added GlobalFiltersBar and PageTabsManager integration.\n * Phase 3: Added WidgetPalette sidebar for drag-and-drop widget creation.\n */\n\nimport { type FC, memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus } from 'lucide-react';\nimport type { Layout } from 'react-grid-layout';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { DashboardVisibility, Widget, WidgetType } from '../../types';\nimport { BIGCONSOLE_PERMISSIONS } from '../../../constants/permissions';\nimport { useDashboardStore, useWidgetStore } from '../../store';\nimport { useDashboardOperations } from '../../hooks/useDashboardOperations';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { useInstalledWidgets } from '../../hooks/useInstalledWidgets';\nimport { useWidgetCatalog } from '../../hooks/useWidgetCatalog';\nimport { useDashboardImageExport } from '../../hooks/useDashboardImageExport';\nimport { useBigConsole } from '../../context';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Section } from '../common';\nimport { DashboardGrid } from './DashboardGrid';\nimport { DashboardToolbar } from './DashboardToolbar';\nimport { GlobalFiltersBar } from './GlobalFiltersBar';\nimport { LiveBoardBanner } from './LiveBoardBanner';\nimport { TemplateSetupBanner } from './TemplateSetupBanner';\nimport { PageTabsManager, type DashboardPageTab } from './PageTabsManager';\nimport { ShareDialog } from './ShareDialog';\nimport { WidgetPalette } from './WidgetPalette';\nimport { WidgetInspector } from '../builder';\nimport { getWidgetDefinition, type WidgetDefinition } from '../widgets/WidgetRegistry';\n// Phase 2: Cross-filtering context\nimport { CrossFilterProvider } from '../../contexts';\n// Smart widget positioning\nimport { findNextAvailablePosition, calculatePositionFromDrop } from '../../utils/widgetPositioning';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardCanvasProps {\n /** Dashboard ID */\n dashboardId: string;\n /** Page ID (for loading widgets) */\n pageId?: string;\n /** Dashboard title */\n title?: string;\n /** Callback to paste widget */\n onPaste?: () => void;\n /** Callback when dashboard is saved */\n onSave?: () => void;\n /** Whether save is in progress */\n isSaving?: boolean;\n /** Callback to export dashboard */\n onExport?: () => void;\n onPublishTemplate?: () => void;\n /** Dashboard pages for PageTabsManager */\n pages?: DashboardPageTab[];\n /** Active page ID */\n activePageId?: string;\n /** Callback when page changes */\n onPageChange?: (pageId: string) => void;\n /** Callback when pages are reordered */\n onPagesReorder?: (pages: DashboardPageTab[]) => void;\n /** Callback when page is added */\n onPageAdd?: () => void;\n /** Callback when page is renamed */\n onPageRename?: (pageId: string, newName: string) => void;\n /** Callback when page is duplicated */\n onPageDuplicate?: (pageId: string) => void;\n /** Callback when page is deleted */\n onPageDelete?: (pageId: string) => void;\n /** Whether to show global filters bar */\n showFilters?: boolean;\n /** Whether to show page tabs */\n showPageTabs?: boolean;\n /** Whether to enable cross-filtering (widget clicks update filters) */\n enableCrossFilter?: boolean;\n}\n\ninterface WidgetPlacement {\n position: { x: number; y: number; width: number; height: number };\n definition?: WidgetDefinition | null;\n}\n\ninterface PaletteDropData {\n type: WidgetType;\n name?: string;\n category?: WidgetDefinition['category'];\n defaultConfig?: Record<string, unknown>;\n defaultSize?: WidgetDefinition['defaultSize'];\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardCanvas: FC<DashboardCanvasProps> = memo(function DashboardCanvas({\n dashboardId,\n pageId,\n title,\n onPaste,\n onSave,\n isSaving = false,\n onExport,\n onPublishTemplate,\n pages = [],\n activePageId,\n onPageChange,\n onPagesReorder,\n onPageAdd,\n onPageRename,\n onPageDuplicate,\n onPageDelete,\n showFilters = true,\n showPageTabs = true,\n enableCrossFilter = true,\n}) {\n const { t } = useI18n();\n const containerRef = useRef<HTMLDivElement>(null);\n // Inner board element (the grid surface) — rasterized for PNG/PDF board-pack export.\n const boardRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(1200);\n const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);\n const [widgetCreateError, setWidgetCreateError] = useState<string | null>(null);\n const [isDraggingWidget, setIsDraggingWidget] = useState(false);\n const [dragWidgetType, setDragWidgetType] = useState<WidgetType | null>(null);\n /**\n * Locally confirmed general-access level, keyed by the dashboard it belongs\n * to. Kept out of the dashboard store on purpose: `setCurrentDashboard` also\n * resets the active page and clears undo history, which changing a share\n * setting must not do.\n */\n const [visibilityOverride, setVisibilityOverride] = useState<{ id: string; value: DashboardVisibility } | null>(null);\n\n // Store state\n const isEditMode = useDashboardStore((state) => state.viewMode === 'edit');\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n\n // Mutation only — `skipFetch` keeps this from pulling the whole dashboard list.\n const { updateDashboard } = useDashboardOperations({ skipFetch: true });\n const { hasPermission } = usePermissions();\n const canUpdateDashboard = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_UPDATE);\n\n /**\n * General access for the board currently on screen. Only surfaced when the\n * loaded dashboard IS this one and its `visibility` came back from the\n * server — the share dialog hides the control otherwise rather than showing\n * a default that may not match reality.\n */\n const dashboardVisibility: DashboardVisibility | undefined =\n visibilityOverride?.id === dashboardId\n ? visibilityOverride.value\n : currentDashboard?.id === dashboardId\n ? currentDashboard.visibility\n : undefined;\n\n const handleVisibilityChange = useCallback(\n async (visibility: DashboardVisibility) => {\n // `updateDashboard` swallows Apollo errors and resolves to null, so null\n // is the failure signal the dialog needs in order to report it.\n const updated = await updateDashboard({ id: dashboardId, visibility });\n if (!updated) {\n throw new Error('The dashboard service did not accept the change.');\n }\n setVisibilityOverride({ id: dashboardId, value: updated.visibility ?? visibility });\n },\n [dashboardId, updateDashboard]\n );\n\n // Fetch installed widgets only in edit mode — the palette and add-widget dialog\n // are not usable in read-only view, so skip the request for viewer-only sessions.\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n const { installedWidgets, loading: installedWidgetsLoading } = useInstalledWidgets({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n const zoomLevel = useDashboardStore((state) => state.zoomLevel);\n const sidebarCollapsed = useDashboardStore((state) => state.sidebarCollapsed);\n const toggleSidebar = useDashboardStore((state) => state.toggleSidebar);\n const isPaletteOpen = !sidebarCollapsed;\n const setGlobalFilterValues = useDashboardStore((state) => state.setGlobalFilterValues);\n\n // Handle filter changes - update store for widget access\n const handleFilterChange = useCallback(\n (filterValues: Record<string, unknown>) => {\n // Update global filter values in dashboard store\n setGlobalFilterValues(filterValues);\n\n // Widgets can now access filter values via useGlobalFilterValues() hook\n // When backend supports filter parameters, widget queries will include these values\n },\n [setGlobalFilterValues]\n );\n\n // Widget operations hook (connects to backend) - skipFetch since parent page fetches\n const { createWidget: createWidgetApi } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n const widgetsMap = useWidgetStore((state) => state.widgets);\n // Filter widgets to only show widgets for the current page\n const widgetsArray = useMemo(() => {\n const allWidgets = Array.from(widgetsMap.values());\n if (!pageId) return allWidgets;\n return allWidgets.filter((widget) => widget.pageId === pageId);\n }, [widgetsMap, pageId]);\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetPosition = useWidgetStore((state) => state.updateWidgetPosition);\n const configPanelOpen = useWidgetStore((state) => state.configPanelOpen);\n const closeConfigPanel = useWidgetStore((state) => state.closeConfigPanel);\n\n // Keep react-grid-layout's explicit width in sync with the actual canvas.\n // A ResizeObserver is required because opening the docked inspector or widget\n // palette resizes this element without resizing the browser window.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateWidth = () => {\n setContainerWidth(container.offsetWidth);\n };\n\n updateWidth();\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(updateWidth);\n observer.observe(container);\n return () => observer.disconnect();\n }\n\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, []);\n\n // Handle layout change\n const handleLayoutChange = useCallback(\n (layout: Layout[]) => {\n layout.forEach((item) => {\n updateWidgetPosition(item.i, {\n x: item.x,\n y: item.y,\n width: item.w,\n height: item.h,\n });\n });\n },\n [updateWidgetPosition]\n );\n\n // Handle widget select\n const handleWidgetSelect = useCallback(\n (widgetId: string) => {\n selectWidget(widgetId);\n },\n [selectWidget]\n );\n\n // Create with defaults, select the result, then configure it in the inspector.\n const handleAddWidget = useCallback(\n async (widgetData: Partial<Widget>, placement?: WidgetPlacement) => {\n setWidgetCreateError(null);\n const definition = placement?.definition || (widgetData.type ? getWidgetDefinition(widgetData.type) : null);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n let position: { x: number; y: number; width: number; height: number };\n if (placement?.position) {\n position = placement.position;\n } else {\n // Use smart placement algorithm - horizontal first, then next row\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n position = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n }\n\n // Preserve legacy binding fields when creating from imported/default data.\n const extendedData = widgetData as Partial<Widget> & { dataSinkId?: string; parserId?: string };\n\n // Try to create via API first\n if (pageId) {\n const widget = await createWidgetApi({\n pageId,\n dashboardId,\n type: widgetData.type!,\n title: widgetData.title || t('bigconsole.dashboards.canvas.untitledWidget', 'Untitled Widget'),\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n config: widgetData.config || {},\n position,\n refreshInterval: widgetData.refreshInterval ?? undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n });\n\n if (widget) {\n selectWidget(widget.id);\n return;\n }\n\n setWidgetCreateError(\n t(\n 'bigconsole.dashboards.canvas.widgetCreateError',\n 'Could not create the widget. Check the connection and try again.'\n )\n );\n return;\n }\n\n // Fallback to local store if API fails or no pageId\n const newWidget: Widget = {\n id: crypto.randomUUID(),\n dashboardId,\n pageId: pageId || 'local',\n type: widgetData.type!,\n title: widgetData.title || t('bigconsole.dashboards.canvas.untitledWidget', 'Untitled Widget'),\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n refreshInterval: widgetData.refreshInterval,\n config: widgetData.config || {},\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n addWidgetToStore(newWidget);\n selectWidget(newWidget.id);\n },\n [dashboardId, pageId, createWidgetApi, addWidgetToStore, selectWidget, widgetsArray, t]\n );\n\n // Handle drag start from palette\n const handlePaletteDragStart = useCallback((type: WidgetType) => {\n setIsDraggingWidget(true);\n setDragWidgetType(type);\n }, []);\n\n // Handle drag end from palette\n const handlePaletteDragEnd = useCallback(() => {\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n }, []);\n\n // Handle drop on canvas - create immediately with defaults.\n const handleCanvasDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n\n try {\n const data = e.dataTransfer.getData('application/json');\n if (!data) return;\n\n const parsedData: unknown = JSON.parse(data);\n if (!parsedData || typeof parsedData !== 'object' || !('type' in parsedData)) return;\n const widgetData = parsedData as PaletteDropData;\n const definition = getWidgetDefinition(widgetData.type);\n\n // If the dropped data includes defaultConfig (from WidgetPalette drag serialization),\n // reconstruct a full WidgetDefinition so installed/store widget identity is preserved.\n let droppedDefinition: WidgetDefinition | null = null;\n if (widgetData.defaultConfig && widgetData.category) {\n droppedDefinition = {\n ...definition,\n name: widgetData.name || definition.name,\n category: widgetData.category,\n defaultConfig: widgetData.defaultConfig,\n defaultSize: widgetData.defaultSize || definition.defaultSize,\n };\n }\n\n // Calculate drop position relative to canvas\n const canvasRect = containerRef.current?.getBoundingClientRect();\n if (!canvasRect) return;\n\n // Use utility to calculate grid position from drop coordinates\n const widgetSize = {\n width: definition.defaultSize.width,\n height: definition.defaultSize.height,\n };\n\n const { x: gridX, y: gridY } = calculatePositionFromDrop(\n e.clientX,\n e.clientY,\n canvasRect,\n containerWidth,\n widgetSize,\n { cols: 12 }\n );\n\n const position = {\n x: gridX,\n y: gridY,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n void handleAddWidget(\n {\n type: widgetData.type,\n title: widgetData.name || definition.name,\n config: widgetData.defaultConfig || definition.defaultConfig,\n },\n { position, definition: droppedDefinition }\n );\n } catch (error) {\n console.error('Failed to handle widget drop:', error);\n }\n },\n [containerWidth, handleAddWidget]\n );\n\n // Handle drag over to allow drop\n const handleCanvasDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }, []);\n\n // Handle widget select from palette (quick add without drag)\n const handlePaletteWidgetSelect = useCallback(\n (type: WidgetType, paletteDefinition?: WidgetDefinition) => {\n const definition = paletteDefinition || getWidgetDefinition(type);\n handleAddWidget({\n type,\n title: definition.name,\n config: definition.defaultConfig,\n });\n },\n [handleAddWidget]\n );\n\n // Toggle palette\n const togglePalette = useCallback(() => {\n toggleSidebar();\n }, [toggleSidebar]);\n\n const openWidgetPalette = useCallback(() => {\n if (sidebarCollapsed) toggleSidebar();\n window.requestAnimationFrame(() => {\n const searchPlaceholder = t('bigconsole.dashboards.widgetPalette.searchPlaceholder', 'Search widgets...');\n document.querySelector<HTMLInputElement>(`input[placeholder=\"${searchPlaceholder}\"]`)?.focus();\n });\n }, [sidebarCollapsed, toggleSidebar, t]);\n\n // Board-pack export (PNG + PDF) — rasterizes the inner board element.\n const {\n exportPNG,\n exportPDF,\n isExporting: isExportingImage,\n } = useDashboardImageExport({ containerRef: boardRef, name: title || 'dashboard' });\n const handleExportImage = useCallback(() => {\n void exportPNG();\n }, [exportPNG]);\n const handleExportPdf = useCallback(() => {\n void exportPDF();\n }, [exportPDF]);\n\n return (\n <div className=\"flex flex-col h-full min-w-0 overflow-hidden\">\n {/* Toolbar */}\n <DashboardToolbar\n dashboardId={dashboardId}\n title={title}\n onAddWidget={openWidgetPalette}\n onTogglePalette={togglePalette}\n isPaletteOpen={isPaletteOpen}\n onPaste={onPaste}\n onSave={onSave}\n isSaving={isSaving}\n onExport={onExport}\n onPublishTemplate={onPublishTemplate}\n onExportImage={handleExportImage}\n onExportPdf={handleExportPdf}\n isExportingImage={isExportingImage}\n onShare={() => setIsShareDialogOpen(true)}\n />\n\n {widgetCreateError && (\n <div role=\"alert\" className=\"mx-card-padding mt-control-gap flex-shrink-0\">\n {/* The section title has no catalog key — BOFF-6004 translated only the\n message and the Dismiss action — so it stays a literal until a key\n lands in src/translations/en.ts. */}\n <Section\n tone=\"danger\"\n padding=\"compact\"\n headingLevel={3}\n data-testid=\"widget-create-error\"\n title=\"Widget not created\"\n description={widgetCreateError}\n actions={\n <Button variant=\"outline\" size=\"sm\" onClick={() => setWidgetCreateError(null)}>\n {t('bigconsole.dashboards.canvas.dismiss', 'Dismiss')}\n </Button>\n }\n />\n </div>\n )}\n\n {/* Live wall-board freshness banner — renders only when the page has\n auto-refreshing widgets (e.g. the Live Bed Board) and not in edit mode. */}\n <TemplateSetupBanner widgets={widgetsArray} isEditMode={isEditMode} />\n <LiveBoardBanner widgets={widgetsArray} isEditMode={isEditMode} />\n\n {/* Global Filters Bar */}\n {showFilters && <GlobalFiltersBar dashboardId={dashboardId} onFilterChange={handleFilterChange} collapsible />}\n\n {/* Page Tabs - always show in edit mode so user can add first page */}\n {showPageTabs && (isEditMode || pages.length > 0) && (\n <PageTabsManager\n pages={pages}\n activePageId={activePageId || pages[0]?.id}\n editable={isEditMode}\n onPageSelect={onPageChange || (() => {})}\n onPagesReorder={\n onPagesReorder\n ? (pageIds) => {\n // Convert string[] to DashboardPageTab[] for the callback\n const reorderedPages = pageIds.map((id) => pages.find((p) => p.id === id)!).filter(Boolean);\n onPagesReorder(reorderedPages);\n }\n : undefined\n }\n onPageCreate={onPageAdd}\n onPageRename={onPageRename}\n onPageDuplicate={onPageDuplicate}\n onPageDelete={onPageDelete}\n />\n )}\n\n {/* Main content area with palette and canvas.\n When the properties inspector is open we reserve equal space on the\n right so the (fixed, non-modal) panel docks beside the canvas instead\n of overlaying the selected widget. */}\n <div\n data-testid=\"dashboard-canvas-panes\"\n className={`flex flex-1 min-w-0 overflow-hidden ${\n // Only reserve the inspector gutter where there is room for both. The\n // panel is `min(420px, 100vw)` wide, so below `lg` it overlays the\n // board instead — reserving 420px there left the canvas a sliver.\n configPanelOpen ? 'lg:mr-[var(--bc-inspector-width,420px)]' : ''\n }`}\n >\n {/* Widget Palette Sidebar */}\n {isEditMode && (\n <WidgetPalette\n isOpen={isPaletteOpen}\n onClose={togglePalette}\n onWidgetSelect={handlePaletteWidgetSelect}\n onDragStart={handlePaletteDragStart}\n onDragEnd={handlePaletteDragEnd}\n installedWidgets={installedWidgets}\n installedWidgetsLoading={installedWidgetsLoading}\n definitions={widgetCatalog}\n />\n )}\n\n {/*\n The board. This is the one `primary` region on the route — everything\n above it is bare chrome or a compact status strip, so the widgets are\n what the eye lands on. It keeps the recessed canvas surface on purpose:\n the widgets are the raised cards, the board is the well they sit in.\n The drop border is always 2px (transparent when idle) so drag-over\n highlights without nudging the whole board.\n */}\n <div\n ref={containerRef}\n role=\"region\"\n aria-label=\"Dashboard board\"\n data-section-emphasis=\"primary\"\n data-testid=\"dashboard-board\"\n className=\"relative min-w-0 flex-1 overflow-auto border-2 border-transparent bg-bg-sunken motion-safe:transition-[border-color,background-color]\"\n onDrop={handleCanvasDrop}\n onDragOver={handleCanvasDragOver}\n onClick={() => {\n if (isEditMode) {\n selectWidget(null);\n }\n }}\n style={\n isDraggingWidget\n ? {\n borderStyle: 'dashed',\n borderColor: 'var(--color-action-primary-bg)',\n backgroundColor: 'var(--color-bg-accentSubtle)',\n }\n : undefined\n }\n >\n {/* Drop zone indicator */}\n {isDraggingWidget && (\n <div\n style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n padding: 'var(--space-pagePadding)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-2)',\n textAlign: 'center',\n pointerEvents: 'none',\n zIndex: 100,\n }}\n >\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-primary)', margin: 0 }}>\n {t('bigconsole.dashboards.canvas.dropToAddBefore', 'Drop to add')} <strong>{dragWidgetType}</strong>{' '}\n {t('bigconsole.dashboards.canvas.dropToAddAfter', 'widget')}\n </p>\n </div>\n )}\n\n <div\n ref={boardRef}\n className=\"min-h-full origin-top-left transition-transform\"\n style={{\n transform: `scale(${zoomLevel / 100})`,\n width: `${100 / (zoomLevel / 100)}%`,\n }}\n >\n {widgetsArray.length > 0 ? (\n <CrossFilterProvider\n enabled={enableCrossFilter}\n onCrossFilter={(_filters) => {\n // Cross-filter applied - widget queries can access filter values\n }}\n >\n <DashboardGrid\n widgets={widgetsArray}\n isEditMode={isEditMode}\n width={containerWidth}\n onLayoutChange={handleLayoutChange}\n onWidgetSelect={handleWidgetSelect}\n selectedWidgetId={selectedWidgetId}\n />\n </CrossFilterProvider>\n ) : (\n <div className=\"flex min-h-[min(400px,60vh)] items-center justify-center p-card-padding\">\n <Section\n headingLevel={3}\n className=\"w-full max-w-md\"\n data-testid=\"dashboard-empty-board\"\n headerClassName=\"mb-form-gap\"\n title={t('bigconsole.dashboards.canvas.noWidgetsTitle', 'No widgets yet')}\n description={t(\n 'bigconsole.dashboards.canvas.noWidgetsDescription',\n 'Add widgets to build your dashboard. Click the \"Add Widget\" button to get started.'\n )}\n >\n {isEditMode && (\n <Button onClick={openWidgetPalette}>\n <Plus className=\"size-4\" />\n {t('bigconsole.dashboards.canvas.addWidget', 'Add Widget')}\n </Button>\n )}\n </Section>\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Properties Panel */}\n <WidgetInspector isOpen={configPanelOpen} onClose={closeConfigPanel} />\n\n {/* Share Dialog */}\n <ShareDialog\n dashboardId={dashboardId}\n dashboardName={title || t('bigconsole.dashboards.canvas.dashboardFallbackName', 'Dashboard')}\n isOpen={isShareDialogOpen}\n onClose={() => setIsShareDialogOpen(false)}\n visibility={dashboardVisibility}\n onVisibilityChange={handleVisibilityChange}\n canChangeVisibility={canUpdateDashboard}\n />\n </div>\n );\n});\n\nexport default DashboardCanvas;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardCanvas.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardCanvas.tsx"],"sourcesContent":["/**\n * DashboardCanvas Component\n *\n * Main canvas for dashboard with grid layout and widget rendering.\n * Phase 2: Added GlobalFiltersBar and PageTabsManager integration.\n * Phase 3: Added WidgetPalette sidebar for drag-and-drop widget creation.\n */\n\nimport { type FC, memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus } from 'lucide-react';\nimport type { Layout } from 'react-grid-layout';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { DashboardVisibility, Widget, WidgetType } from '../../types';\nimport { BIGCONSOLE_PERMISSIONS } from '../../../constants/permissions';\nimport { useDashboardStore, useWidgetStore } from '../../store';\nimport { useDashboardOperations } from '../../hooks/useDashboardOperations';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { useInstalledWidgets } from '../../hooks/useInstalledWidgets';\nimport { useWidgetCatalog } from '../../hooks/useWidgetCatalog';\nimport { useDashboardImageExport } from '../../hooks/useDashboardImageExport';\nimport { useBigConsole } from '../../context';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { Section } from '../common';\nimport { DashboardGrid } from './DashboardGrid';\nimport { loadBigConsoleSettings } from '../../hooks/useBigConsoleSettings';\nimport { DashboardToolbar } from './DashboardToolbar';\nimport { GlobalFiltersBar } from './GlobalFiltersBar';\nimport { LiveBoardBanner } from './LiveBoardBanner';\nimport { TemplateSetupBanner } from './TemplateSetupBanner';\nimport { PageTabsManager, type DashboardPageTab } from './PageTabsManager';\nimport { ShareDialog } from './ShareDialog';\nimport { WidgetPalette } from './WidgetPalette';\nimport { WidgetInspector } from '../builder';\nimport { getWidgetDefinition, type WidgetDefinition } from '../widgets/WidgetRegistry';\n// Phase 2: Cross-filtering context\nimport { CrossFilterProvider } from '../../contexts';\n// Smart widget positioning\nimport { findNextAvailablePosition, calculatePositionFromDrop } from '../../utils/widgetPositioning';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardCanvasProps {\n /** Dashboard ID */\n dashboardId: string;\n /** Page ID (for loading widgets) */\n pageId?: string;\n /** Dashboard title */\n title?: string;\n /** Callback to paste widget */\n onPaste?: () => void;\n /** Callback when dashboard is saved */\n onSave?: () => void;\n /** Whether save is in progress */\n isSaving?: boolean;\n /** Callback to export dashboard */\n onExport?: () => void;\n onPublishTemplate?: () => void;\n /** Dashboard pages for PageTabsManager */\n pages?: DashboardPageTab[];\n /** Active page ID */\n activePageId?: string;\n /** Callback when page changes */\n onPageChange?: (pageId: string) => void;\n /** Callback when pages are reordered */\n onPagesReorder?: (pages: DashboardPageTab[]) => void;\n /** Callback when page is added */\n onPageAdd?: () => void;\n /** Callback when page is renamed */\n onPageRename?: (pageId: string, newName: string) => void;\n /** Callback when page is duplicated */\n onPageDuplicate?: (pageId: string) => void;\n /** Callback when page is deleted */\n onPageDelete?: (pageId: string) => void;\n /** Whether to show global filters bar */\n showFilters?: boolean;\n /** Whether to show page tabs */\n showPageTabs?: boolean;\n /** Whether to enable cross-filtering (widget clicks update filters) */\n enableCrossFilter?: boolean;\n}\n\ninterface WidgetPlacement {\n position: { x: number; y: number; width: number; height: number };\n definition?: WidgetDefinition | null;\n}\n\ninterface PaletteDropData {\n type: WidgetType;\n name?: string;\n category?: WidgetDefinition['category'];\n defaultConfig?: Record<string, unknown>;\n defaultSize?: WidgetDefinition['defaultSize'];\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardCanvas: FC<DashboardCanvasProps> = memo(function DashboardCanvas({\n dashboardId,\n pageId,\n title,\n onPaste,\n onSave,\n isSaving = false,\n onExport,\n onPublishTemplate,\n pages = [],\n activePageId,\n onPageChange,\n onPagesReorder,\n onPageAdd,\n onPageRename,\n onPageDuplicate,\n onPageDelete,\n showFilters = true,\n showPageTabs = true,\n enableCrossFilter = true,\n}) {\n const { t } = useI18n();\n const containerRef = useRef<HTMLDivElement>(null);\n // Inner board element (the grid surface) — rasterized for PNG/PDF board-pack export.\n const boardRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(1200);\n const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);\n const [widgetCreateError, setWidgetCreateError] = useState<string | null>(null);\n const [isDraggingWidget, setIsDraggingWidget] = useState(false);\n const [dragWidgetType, setDragWidgetType] = useState<WidgetType | null>(null);\n /**\n * Locally confirmed general-access level, keyed by the dashboard it belongs\n * to. Kept out of the dashboard store on purpose: `setCurrentDashboard` also\n * resets the active page and clears undo history, which changing a share\n * setting must not do.\n */\n const [visibilityOverride, setVisibilityOverride] = useState<{ id: string; value: DashboardVisibility } | null>(null);\n\n // Store state\n const isEditMode = useDashboardStore((state) => state.viewMode === 'edit');\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const gridColumns = currentDashboard?.pages.find((page) => page.id === pageId)?.gridColumns ?? 12;\n\n // Mutation only — `skipFetch` keeps this from pulling the whole dashboard list.\n const { updateDashboard } = useDashboardOperations({ skipFetch: true });\n const { hasPermission } = usePermissions();\n const canUpdateDashboard = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_UPDATE);\n\n /**\n * General access for the board currently on screen. Only surfaced when the\n * loaded dashboard IS this one and its `visibility` came back from the\n * server — the share dialog hides the control otherwise rather than showing\n * a default that may not match reality.\n */\n const dashboardVisibility: DashboardVisibility | undefined =\n visibilityOverride?.id === dashboardId\n ? visibilityOverride.value\n : currentDashboard?.id === dashboardId\n ? currentDashboard.visibility\n : undefined;\n\n const handleVisibilityChange = useCallback(\n async (visibility: DashboardVisibility) => {\n // `updateDashboard` swallows Apollo errors and resolves to null, so null\n // is the failure signal the dialog needs in order to report it.\n const updated = await updateDashboard({ id: dashboardId, visibility });\n if (!updated) {\n throw new Error('The dashboard service did not accept the change.');\n }\n setVisibilityOverride({ id: dashboardId, value: updated.visibility ?? visibility });\n },\n [dashboardId, updateDashboard]\n );\n\n // Fetch installed widgets only in edit mode — the palette and add-widget dialog\n // are not usable in read-only view, so skip the request for viewer-only sessions.\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n const { installedWidgets, loading: installedWidgetsLoading } = useInstalledWidgets({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n const zoomLevel = useDashboardStore((state) => state.zoomLevel);\n const sidebarCollapsed = useDashboardStore((state) => state.sidebarCollapsed);\n const toggleSidebar = useDashboardStore((state) => state.toggleSidebar);\n const isPaletteOpen = !sidebarCollapsed;\n const setGlobalFilterValues = useDashboardStore((state) => state.setGlobalFilterValues);\n\n // Handle filter changes - update store for widget access\n const handleFilterChange = useCallback(\n (filterValues: Record<string, unknown>) => {\n // Update global filter values in dashboard store\n setGlobalFilterValues(filterValues);\n\n // Widgets can now access filter values via useGlobalFilterValues() hook\n // When backend supports filter parameters, widget queries will include these values\n },\n [setGlobalFilterValues]\n );\n\n // Widget operations hook (connects to backend) - skipFetch since parent page fetches\n const { createWidget: createWidgetApi } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n const widgetsMap = useWidgetStore((state) => state.widgets);\n // Filter widgets to only show widgets for the current page\n const widgetsArray = useMemo(() => {\n const allWidgets = Array.from(widgetsMap.values());\n if (!pageId) return allWidgets;\n return allWidgets.filter((widget) => widget.pageId === pageId);\n }, [widgetsMap, pageId]);\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetPosition = useWidgetStore((state) => state.updateWidgetPosition);\n const configPanelOpen = useWidgetStore((state) => state.configPanelOpen);\n const closeConfigPanel = useWidgetStore((state) => state.closeConfigPanel);\n\n // Keep react-grid-layout's explicit width in sync with the actual canvas.\n // A ResizeObserver is required because opening the docked inspector or widget\n // palette resizes this element without resizing the browser window.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const updateWidth = () => {\n setContainerWidth(container.offsetWidth);\n };\n\n updateWidth();\n\n if (typeof ResizeObserver !== 'undefined') {\n const observer = new ResizeObserver(updateWidth);\n observer.observe(container);\n return () => observer.disconnect();\n }\n\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, []);\n\n // Handle layout change\n const handleLayoutChange = useCallback(\n (layout: Layout[]) => {\n layout.forEach((item) => {\n updateWidgetPosition(item.i, {\n x: item.x,\n y: item.y,\n width: item.w,\n height: item.h,\n });\n });\n },\n [updateWidgetPosition]\n );\n\n // Handle widget select\n const handleWidgetSelect = useCallback(\n (widgetId: string) => {\n selectWidget(widgetId);\n },\n [selectWidget]\n );\n\n // Create with defaults, select the result, then configure it in the inspector.\n const handleAddWidget = useCallback(\n async (widgetData: Partial<Widget>, placement?: WidgetPlacement) => {\n setWidgetCreateError(null);\n const definition = placement?.definition || (widgetData.type ? getWidgetDefinition(widgetData.type) : null);\n const defaults = loadBigConsoleSettings();\n const widgetSize = {\n width: Math.min(definition?.defaultSize?.width ?? 4, gridColumns),\n height: defaults.defaultWidgetHeight,\n };\n\n let position: { x: number; y: number; width: number; height: number };\n if (placement?.position) {\n position = placement.position;\n } else {\n // Use smart placement algorithm - horizontal first, then next row\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: gridColumns });\n position = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n }\n\n // Preserve legacy binding fields when creating from imported/default data.\n const extendedData = widgetData as Partial<Widget> & { dataSinkId?: string; parserId?: string };\n\n // Try to create via API first\n if (pageId) {\n const widget = await createWidgetApi({\n pageId,\n dashboardId,\n type: widgetData.type!,\n title: widgetData.title || t('bigconsole.dashboards.canvas.untitledWidget', 'Untitled Widget'),\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n config: widgetData.config || {},\n position,\n refreshInterval: widgetData.refreshInterval ?? defaults.defaultRefreshInterval,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n });\n\n if (widget) {\n selectWidget(widget.id);\n return;\n }\n\n setWidgetCreateError(\n t(\n 'bigconsole.dashboards.canvas.widgetCreateError',\n 'Could not create the widget. Check the connection and try again.'\n )\n );\n return;\n }\n\n // Fallback to local store if API fails or no pageId\n const newWidget: Widget = {\n id: crypto.randomUUID(),\n dashboardId,\n pageId: pageId || 'local',\n type: widgetData.type!,\n title: widgetData.title || t('bigconsole.dashboards.canvas.untitledWidget', 'Untitled Widget'),\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n refreshInterval: widgetData.refreshInterval ?? defaults.defaultRefreshInterval,\n config: widgetData.config || {},\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n addWidgetToStore(newWidget);\n selectWidget(newWidget.id);\n },\n [dashboardId, pageId, createWidgetApi, addWidgetToStore, selectWidget, widgetsArray, gridColumns, t]\n );\n\n // Handle drag start from palette\n const handlePaletteDragStart = useCallback((type: WidgetType) => {\n setIsDraggingWidget(true);\n setDragWidgetType(type);\n }, []);\n\n // Handle drag end from palette\n const handlePaletteDragEnd = useCallback(() => {\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n }, []);\n\n // Handle drop on canvas - create immediately with defaults.\n const handleCanvasDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n\n try {\n const data = e.dataTransfer.getData('application/json');\n if (!data) return;\n\n const parsedData: unknown = JSON.parse(data);\n if (!parsedData || typeof parsedData !== 'object' || !('type' in parsedData)) return;\n const widgetData = parsedData as PaletteDropData;\n const definition = getWidgetDefinition(widgetData.type);\n\n // If the dropped data includes defaultConfig (from WidgetPalette drag serialization),\n // reconstruct a full WidgetDefinition so installed/store widget identity is preserved.\n let droppedDefinition: WidgetDefinition | null = null;\n if (widgetData.defaultConfig && widgetData.category) {\n droppedDefinition = {\n ...definition,\n name: widgetData.name || definition.name,\n category: widgetData.category,\n defaultConfig: widgetData.defaultConfig,\n defaultSize: widgetData.defaultSize || definition.defaultSize,\n };\n }\n\n // Calculate drop position relative to canvas\n const canvasRect = containerRef.current?.getBoundingClientRect();\n if (!canvasRect) return;\n\n // Use utility to calculate grid position from drop coordinates\n const widgetSize = {\n width: Math.min(definition.defaultSize.width, gridColumns),\n height: loadBigConsoleSettings().defaultWidgetHeight,\n };\n\n const { x: gridX, y: gridY } = calculatePositionFromDrop(\n e.clientX,\n e.clientY,\n canvasRect,\n containerWidth,\n widgetSize,\n { cols: gridColumns }\n );\n\n const position = {\n x: gridX,\n y: gridY,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n void handleAddWidget(\n {\n type: widgetData.type,\n title: widgetData.name || definition.name,\n config: widgetData.defaultConfig || definition.defaultConfig,\n },\n { position, definition: droppedDefinition }\n );\n } catch (error) {\n console.error('Failed to handle widget drop:', error);\n }\n },\n [containerWidth, handleAddWidget, gridColumns]\n );\n\n // Handle drag over to allow drop\n const handleCanvasDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }, []);\n\n // Handle widget select from palette (quick add without drag)\n const handlePaletteWidgetSelect = useCallback(\n (type: WidgetType, paletteDefinition?: WidgetDefinition) => {\n const definition = paletteDefinition || getWidgetDefinition(type);\n handleAddWidget({\n type,\n title: definition.name,\n config: definition.defaultConfig,\n });\n },\n [handleAddWidget]\n );\n\n // Toggle palette\n const togglePalette = useCallback(() => {\n toggleSidebar();\n }, [toggleSidebar]);\n\n const openWidgetPalette = useCallback(() => {\n if (sidebarCollapsed) toggleSidebar();\n window.requestAnimationFrame(() => {\n const searchPlaceholder = t('bigconsole.dashboards.widgetPalette.searchPlaceholder', 'Search widgets...');\n document.querySelector<HTMLInputElement>(`input[placeholder=\"${searchPlaceholder}\"]`)?.focus();\n });\n }, [sidebarCollapsed, toggleSidebar, t]);\n\n // Board-pack export (PNG + PDF) — rasterizes the inner board element.\n const {\n exportPNG,\n exportPDF,\n isExporting: isExportingImage,\n } = useDashboardImageExport({ containerRef: boardRef, name: title || 'dashboard' });\n const handleExportImage = useCallback(() => {\n void exportPNG();\n }, [exportPNG]);\n const handleExportPdf = useCallback(() => {\n void exportPDF();\n }, [exportPDF]);\n\n return (\n <div className=\"flex flex-col h-full min-w-0 overflow-hidden\">\n {/* Toolbar */}\n <DashboardToolbar\n dashboardId={dashboardId}\n title={title}\n onAddWidget={openWidgetPalette}\n onTogglePalette={togglePalette}\n isPaletteOpen={isPaletteOpen}\n onPaste={onPaste}\n onSave={onSave}\n isSaving={isSaving}\n onExport={onExport}\n onPublishTemplate={onPublishTemplate}\n onExportImage={handleExportImage}\n onExportPdf={handleExportPdf}\n isExportingImage={isExportingImage}\n onShare={() => setIsShareDialogOpen(true)}\n />\n\n {widgetCreateError && (\n <div role=\"alert\" className=\"mx-card-padding mt-control-gap flex-shrink-0\">\n {/* The section title has no catalog key — BOFF-6004 translated only the\n message and the Dismiss action — so it stays a literal until a key\n lands in src/translations/en.ts. */}\n <Section\n tone=\"danger\"\n padding=\"compact\"\n headingLevel={3}\n data-testid=\"widget-create-error\"\n title=\"Widget not created\"\n description={widgetCreateError}\n actions={\n <Button variant=\"outline\" size=\"sm\" onClick={() => setWidgetCreateError(null)}>\n {t('bigconsole.dashboards.canvas.dismiss', 'Dismiss')}\n </Button>\n }\n />\n </div>\n )}\n\n {/* Live wall-board freshness banner — renders only when the page has\n auto-refreshing widgets (e.g. the Live Bed Board) and not in edit mode. */}\n <TemplateSetupBanner widgets={widgetsArray} isEditMode={isEditMode} />\n <LiveBoardBanner widgets={widgetsArray} isEditMode={isEditMode} />\n\n {/* Global Filters Bar */}\n {showFilters && <GlobalFiltersBar dashboardId={dashboardId} onFilterChange={handleFilterChange} collapsible />}\n\n {/* Page Tabs - always show in edit mode so user can add first page */}\n {showPageTabs && (isEditMode || pages.length > 0) && (\n <PageTabsManager\n pages={pages}\n activePageId={activePageId || pages[0]?.id}\n editable={isEditMode}\n onPageSelect={onPageChange || (() => {})}\n onPagesReorder={\n onPagesReorder\n ? (pageIds) => {\n // Convert string[] to DashboardPageTab[] for the callback\n const reorderedPages = pageIds.map((id) => pages.find((p) => p.id === id)!).filter(Boolean);\n onPagesReorder(reorderedPages);\n }\n : undefined\n }\n onPageCreate={onPageAdd}\n onPageRename={onPageRename}\n onPageDuplicate={onPageDuplicate}\n onPageDelete={onPageDelete}\n />\n )}\n\n {/* Main content area with palette and canvas.\n When the properties inspector is open we reserve equal space on the\n right so the (fixed, non-modal) panel docks beside the canvas instead\n of overlaying the selected widget. */}\n <div\n data-testid=\"dashboard-canvas-panes\"\n className={`flex flex-1 min-w-0 overflow-hidden ${\n // Only reserve the inspector gutter where there is room for both. The\n // panel is `min(420px, 100vw)` wide, so below `lg` it overlays the\n // board instead — reserving 420px there left the canvas a sliver.\n configPanelOpen ? 'lg:mr-[var(--bc-inspector-width,420px)]' : ''\n }`}\n >\n {/* Widget Palette Sidebar */}\n {isEditMode && (\n <WidgetPalette\n isOpen={isPaletteOpen}\n onClose={togglePalette}\n onWidgetSelect={handlePaletteWidgetSelect}\n onDragStart={handlePaletteDragStart}\n onDragEnd={handlePaletteDragEnd}\n installedWidgets={installedWidgets}\n installedWidgetsLoading={installedWidgetsLoading}\n definitions={widgetCatalog}\n />\n )}\n\n {/*\n The board. This is the one `primary` region on the route — everything\n above it is bare chrome or a compact status strip, so the widgets are\n what the eye lands on. It keeps the recessed canvas surface on purpose:\n the widgets are the raised cards, the board is the well they sit in.\n The drop border is always 2px (transparent when idle) so drag-over\n highlights without nudging the whole board.\n */}\n <div\n ref={containerRef}\n role=\"region\"\n aria-label=\"Dashboard board\"\n data-section-emphasis=\"primary\"\n data-testid=\"dashboard-board\"\n className=\"relative min-w-0 flex-1 overflow-auto border-2 border-transparent bg-bg-sunken motion-safe:transition-[border-color,background-color]\"\n onDrop={handleCanvasDrop}\n onDragOver={handleCanvasDragOver}\n onClick={() => {\n if (isEditMode) {\n selectWidget(null);\n }\n }}\n style={\n isDraggingWidget\n ? {\n borderStyle: 'dashed',\n borderColor: 'var(--color-action-primary-bg)',\n backgroundColor: 'var(--color-bg-accentSubtle)',\n }\n : undefined\n }\n >\n {/* Drop zone indicator */}\n {isDraggingWidget && (\n <div\n style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n padding: 'var(--space-pagePadding)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-2)',\n textAlign: 'center',\n pointerEvents: 'none',\n zIndex: 100,\n }}\n >\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-primary)', margin: 0 }}>\n {t('bigconsole.dashboards.canvas.dropToAddBefore', 'Drop to add')} <strong>{dragWidgetType}</strong>{' '}\n {t('bigconsole.dashboards.canvas.dropToAddAfter', 'widget')}\n </p>\n </div>\n )}\n\n <div\n ref={boardRef}\n className=\"min-h-full origin-top-left transition-transform\"\n style={{\n transform: `scale(${zoomLevel / 100})`,\n width: `${100 / (zoomLevel / 100)}%`,\n }}\n >\n {widgetsArray.length > 0 ? (\n <CrossFilterProvider\n enabled={enableCrossFilter}\n onCrossFilter={(_filters) => {\n // Cross-filter applied - widget queries can access filter values\n }}\n >\n <DashboardGrid\n widgets={widgetsArray}\n cols={gridColumns}\n isEditMode={isEditMode}\n width={containerWidth}\n onLayoutChange={handleLayoutChange}\n onWidgetSelect={handleWidgetSelect}\n selectedWidgetId={selectedWidgetId}\n />\n </CrossFilterProvider>\n ) : (\n <div className=\"flex min-h-[min(400px,60vh)] items-center justify-center p-card-padding\">\n <Section\n headingLevel={3}\n className=\"w-full max-w-md\"\n data-testid=\"dashboard-empty-board\"\n headerClassName=\"mb-form-gap\"\n title={t('bigconsole.dashboards.canvas.noWidgetsTitle', 'No widgets yet')}\n description={t(\n 'bigconsole.dashboards.canvas.noWidgetsDescription',\n 'Add widgets to build your dashboard. Click the \"Add Widget\" button to get started.'\n )}\n >\n {isEditMode && (\n <Button onClick={openWidgetPalette}>\n <Plus className=\"size-4\" />\n {t('bigconsole.dashboards.canvas.addWidget', 'Add Widget')}\n </Button>\n )}\n </Section>\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Properties Panel */}\n <WidgetInspector isOpen={configPanelOpen} onClose={closeConfigPanel} />\n\n {/* Share Dialog */}\n <ShareDialog\n dashboardId={dashboardId}\n dashboardName={title || t('bigconsole.dashboards.canvas.dashboardFallbackName', 'Dashboard')}\n isOpen={isShareDialogOpen}\n onClose={() => setIsShareDialogOpen(false)}\n visibility={dashboardVisibility}\n onVisibilityChange={handleVisibilityChange}\n canChangeVisibility={canUpdateDashboard}\n />\n </div>\n );\n});\n\nexport default DashboardCanvas;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardGrid.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/dashboard/DashboardGrid.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,EAA8B,MAAM,OAAO,CAAC;AAC5D,OAAmB,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAO1C,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAMxC,MAAM,WAAW,kBAAkB;IACjC,yBAAyB;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2BAA2B;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC5C,uCAAuC;IACvC,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AA6BD,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAiI/C,CAAC;AAEH,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"DashboardGrid.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/dashboard/DashboardGrid.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,EAA8B,MAAM,OAAO,CAAC;AAC5D,OAAmB,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAO1C,OAAO,kCAAkC,CAAC;AAC1C,OAAO,gCAAgC,CAAC;AAMxC,MAAM,WAAW,kBAAkB;IACjC,yBAAyB;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2BAA2B;IAC3B,UAAU,EAAE,OAAO,CAAC;IACpB,sBAAsB;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC5C,uCAAuC;IACvC,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AA6BD,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CA+I/C,CAAC;AAEH,eAAe,aAAa,CAAC"}
@@ -8,28 +8,46 @@ import { jsx as o } from "react/jsx-runtime";
8
8
  import s from "react-grid-layout";
9
9
  import "react-grid-layout/css/styles.css";
10
10
  //#region src/bigconsole/components/dashboard/DashboardGrid.tsx
11
- function c(e) {
12
- let t = e.position?.x ?? e.positionX ?? 0, n = e.position?.y ?? e.positionY ?? 0, r = e.position?.width ?? e.positionWidth ?? 4, i = e.position?.height ?? e.positionHeight ?? 4;
11
+ function c(e, t) {
12
+ let n = e.position?.x ?? e.positionX ?? 0, r = e.position?.y ?? e.positionY ?? 0, i = e.position?.width ?? e.positionWidth ?? 4, a = e.position?.height ?? e.positionHeight ?? 4;
13
13
  return {
14
14
  i: e.id,
15
- x: t,
16
- y: n,
17
- w: r,
18
- h: i,
15
+ x: n,
16
+ y: r,
17
+ w: i,
18
+ h: a,
19
19
  minW: 1,
20
20
  minH: 1,
21
- maxW: 12,
21
+ maxW: t,
22
22
  maxH: 12
23
23
  };
24
24
  }
25
25
  var l = r(function({ widgets: r, isEditMode: l, width: u, cols: d = 12, rowHeight: f = 60, onLayoutChange: p, onWidgetSelect: m, selectedWidgetId: h }) {
26
- let g = e(), { emitEvent: _ } = t({}), v = a(() => r.map(c), [r]), y = u < 480 ? 8 : 16, b = i((e, t) => {
26
+ let g = e(), { emitEvent: _ } = t({}), v = u < 640, y = l && !v, b = a(() => {
27
+ let e = r.map((e) => c(e, d));
28
+ if (!v) return e;
29
+ let t = 0;
30
+ return [...e].sort((e, t) => e.y - t.y || e.x - t.x).map((e) => {
31
+ let n = {
32
+ ...e,
33
+ x: 0,
34
+ y: t,
35
+ w: d,
36
+ minW: d
37
+ };
38
+ return t += e.h, n;
39
+ });
40
+ }, [
41
+ r,
42
+ d,
43
+ v
44
+ ]), x = u < 480 ? 8 : 16, S = i((e, t) => {
27
45
  l && m && m(e);
28
- }, [m, l]), x = i((e) => {
29
- p && l && p(e);
30
- }, [p, l]), S = i((e) => {
31
- p && l && p(e);
32
- }, [p, l]), C = i(async (e, t) => {
46
+ }, [m, l]), C = i((e) => {
47
+ p && y && p(e);
48
+ }, [p, y]), w = i((e) => {
49
+ p && y && p(e);
50
+ }, [p, y]), T = i(async (e, t) => {
33
51
  let n = typeof e?.name == "string" ? e.name : "unknown", r = `widget:${n.toLowerCase().replace(/\s+/g, "_")}:clicked`, i = await _({
34
52
  widgetId: t.widgetId,
35
53
  actionName: n,
@@ -46,30 +64,30 @@ var l = r(function({ widgets: r, isEditMode: l, width: u, cols: d = 12, rowHeigh
46
64
  }, [_]);
47
65
  return /* @__PURE__ */ o(s, {
48
66
  className: "dashboard-grid",
49
- layout: v,
67
+ layout: b,
50
68
  cols: d,
51
69
  rowHeight: f,
52
70
  width: u,
53
- margin: [y, y],
54
- containerPadding: [y, y],
55
- isDraggable: l,
56
- isResizable: l,
71
+ margin: [x, x],
72
+ containerPadding: [x, x],
73
+ isDraggable: y,
74
+ isResizable: y,
57
75
  draggableHandle: ".widget-drag-handle",
58
- onDragStop: x,
59
- onResizeStop: S,
76
+ onDragStop: C,
77
+ onResizeStop: w,
60
78
  useCSSTransforms: !0,
61
79
  compactType: null,
62
80
  preventCollision: !1,
63
81
  children: r.map((e) => {
64
82
  let t = e.actions || [];
65
83
  return /* @__PURE__ */ o("div", {
66
- onClick: (t) => b(e.id, t),
84
+ onClick: (t) => S(e.id, t),
67
85
  "data-selected": h === e.id && l ? "true" : void 0,
68
86
  className: `flex h-full min-w-0 flex-col overflow-visible ${h === e.id && l ? "rounded-card ring-2 ring-action-primary-bg ring-offset-2 ring-offset-bg-sunken" : ""}`,
69
87
  children: /* @__PURE__ */ o(n, {
70
88
  widget: e,
71
89
  actions: t,
72
- onAction: C,
90
+ onAction: T,
73
91
  filterValues: g
74
92
  })
75
93
  }, e.id);
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardGrid.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardGrid.tsx"],"sourcesContent":["/**\n * DashboardGrid Component\n *\n * Grid layout for widgets using react-grid-layout.\n * v2.0: Added widget event emission support\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport GridLayout, { type Layout } from 'react-grid-layout';\nimport type { Widget } from '../../types';\nimport { WidgetWrapper } from '../widgets/WidgetWrapper';\nimport { useWidgetEvents } from '../../hooks/useWidgetEvents';\nimport { useGlobalFilterValues } from '../../store';\nimport type { WidgetAction, ActionContext, ActionResult } from '../../renderers/types/renderer';\n\n// Import react-grid-layout styles\nimport 'react-grid-layout/css/styles.css';\nimport 'react-resizable/css/styles.css';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardGridProps {\n /** Widgets to display */\n widgets: Widget[];\n /** Whether in edit mode */\n isEditMode: boolean;\n /** Container width */\n width: number;\n /** Number of columns */\n cols?: number;\n /** Row height in pixels */\n rowHeight?: number;\n /** Callback when layout changes */\n onLayoutChange?: (layout: Layout[]) => void;\n /** Callback when widget is selected */\n onWidgetSelect?: (widgetId: string) => void;\n /** Currently selected widget ID */\n selectedWidgetId?: string | null;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction widgetToLayout(widget: Widget): Layout {\n const x = widget.position?.x ?? widget.positionX ?? 0;\n const y = widget.position?.y ?? widget.positionY ?? 0;\n const w = widget.position?.width ?? widget.positionWidth ?? 4;\n const h = widget.position?.height ?? widget.positionHeight ?? 4;\n\n return {\n i: widget.id,\n x,\n y,\n w,\n h,\n minW: 1,\n minH: 1,\n maxW: 12,\n maxH: 12,\n };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardGrid: FC<DashboardGridProps> = memo(function DashboardGrid({\n widgets,\n isEditMode,\n width,\n cols = 12,\n rowHeight = 60,\n onLayoutChange,\n onWidgetSelect,\n selectedWidgetId,\n}) {\n // Phase 2: Get global filter values\n const globalFilterValues = useGlobalFilterValues();\n\n // v2.0: Widget event emission hook\n const { emitEvent } = useWidgetEvents({});\n\n // Build layout from widgets\n const layout = useMemo(() => widgets.map(widgetToLayout), [widgets]);\n\n // Gutters are a fixed 16px in react-grid-layout, which on a 390px canvas spends\n // ~32px of the board on padding alone. Shrink them on narrow canvases — this is\n // purely visual and never touches the stored x/y/w/h.\n const gutter = width < 480 ? 8 : 16;\n\n // Handle widget click - only select in edit mode\n const handleWidgetClick = useCallback(\n (widgetId: string, _event: React.MouseEvent) => {\n // Only select widget in edit mode\n // In view mode, let WidgetWrapper handle drilldown\n if (isEditMode && onWidgetSelect) {\n onWidgetSelect(widgetId);\n }\n },\n [onWidgetSelect, isEditMode]\n );\n\n // Handle drag/resize stop - only update when user finishes interaction\n const handleDragStop = useCallback(\n (layout: Layout[]) => {\n if (onLayoutChange && isEditMode) {\n onLayoutChange(layout);\n }\n },\n [onLayoutChange, isEditMode]\n );\n\n const handleResizeStop = useCallback(\n (layout: Layout[]) => {\n if (onLayoutChange && isEditMode) {\n onLayoutChange(layout);\n }\n },\n [onLayoutChange, isEditMode]\n );\n\n // v2.0: Handle widget action - emit event to backend\n const handleWidgetAction = useCallback(\n async (action: WidgetAction, context: ActionContext): Promise<ActionResult | void> => {\n // Safety check: ensure action.name is a string\n const actionName = typeof action?.name === 'string' ? action.name : 'unknown';\n\n // Build event name from action name (e.g., \"Edit Profile\" -> \"widget:edit_profile:clicked\")\n const eventName = `widget:${actionName.toLowerCase().replace(/\\s+/g, '_')}:clicked`;\n\n // Emit event to backend (triggers webhook if configured)\n const eventLog = await emitEvent({\n widgetId: context.widgetId,\n actionName: actionName,\n event: eventName,\n payload: context.data || {},\n });\n\n if (eventLog) {\n return {\n success: eventLog.status !== 'FAILED',\n message: eventLog.status === 'DELIVERED' ? 'Action completed' : `Event status: ${eventLog.status}`,\n };\n }\n\n return { success: false, message: 'Failed to emit event' };\n },\n [emitEvent]\n );\n\n return (\n <GridLayout\n className=\"dashboard-grid\"\n layout={layout}\n cols={cols}\n rowHeight={rowHeight}\n width={width}\n margin={[gutter, gutter]}\n containerPadding={[gutter, gutter]}\n isDraggable={isEditMode}\n isResizable={isEditMode}\n draggableHandle=\".widget-drag-handle\"\n onDragStop={handleDragStop}\n onResizeStop={handleResizeStop}\n useCSSTransforms\n compactType={null}\n preventCollision={false}\n >\n {widgets.map((widget) => {\n // v2.0: Extract actions from widget\n const widgetWithActions = widget as Widget & { actions?: WidgetAction[] };\n const widgetActions = widgetWithActions.actions || [];\n\n return (\n <div\n key={widget.id}\n onClick={(e) => handleWidgetClick(widget.id, e)}\n data-selected={selectedWidgetId === widget.id && isEditMode ? 'true' : undefined}\n className={`flex h-full min-w-0 flex-col overflow-visible ${\n selectedWidgetId === widget.id && isEditMode\n ? 'rounded-card ring-2 ring-action-primary-bg ring-offset-2 ring-offset-bg-sunken'\n : ''\n }`}\n >\n <WidgetWrapper\n widget={widget}\n actions={widgetActions}\n onAction={handleWidgetAction}\n filterValues={globalFilterValues}\n />\n </div>\n );\n })}\n </GridLayout>\n );\n});\n\nexport default DashboardGrid;\n"],"mappings":";;;;;;;;;;AA8CA,SAAS,EAAe,GAAwB;CAC9C,IAAM,IAAI,EAAO,UAAU,KAAK,EAAO,aAAa,GAC9C,IAAI,EAAO,UAAU,KAAK,EAAO,aAAa,GAC9C,IAAI,EAAO,UAAU,SAAS,EAAO,iBAAiB,GACtD,IAAI,EAAO,UAAU,UAAU,EAAO,kBAAkB;AAE9D,QAAO;EACL,GAAG,EAAO;EACV;EACA;EACA;EACA;EACA,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACP;;AAOH,IAAa,IAAwC,EAAK,SAAuB,EAC/E,YACA,eACA,UACA,UAAO,IACP,eAAY,IACZ,mBACA,mBACA,uBACC;CAED,IAAM,IAAqB,GAAuB,EAG5C,EAAE,iBAAc,EAAgB,EAAE,CAAC,EAGnC,IAAS,QAAc,EAAQ,IAAI,EAAe,EAAE,CAAC,EAAQ,CAAC,EAK9D,IAAS,IAAQ,MAAM,IAAI,IAG3B,IAAoB,GACvB,GAAkB,MAA6B;AAG9C,EAAI,KAAc,KAChB,EAAe,EAAS;IAG5B,CAAC,GAAgB,EAAW,CAC7B,EAGK,IAAiB,GACpB,MAAqB;AACpB,EAAI,KAAkB,KACpB,EAAe,EAAO;IAG1B,CAAC,GAAgB,EAAW,CAC7B,EAEK,IAAmB,GACtB,MAAqB;AACpB,EAAI,KAAkB,KACpB,EAAe,EAAO;IAG1B,CAAC,GAAgB,EAAW,CAC7B,EAGK,IAAqB,EACzB,OAAO,GAAsB,MAAyD;EAEpF,IAAM,IAAa,OAAO,GAAQ,QAAS,WAAW,EAAO,OAAO,WAG9D,IAAY,UAAU,EAAW,aAAa,CAAC,QAAQ,QAAQ,IAAI,CAAC,WAGpE,IAAW,MAAM,EAAU;GAC/B,UAAU,EAAQ;GACN;GACZ,OAAO;GACP,SAAS,EAAQ,QAAQ,EAAE;GAC5B,CAAC;AASF,SAPI,IACK;GACL,SAAS,EAAS,WAAW;GAC7B,SAAS,EAAS,WAAW,cAAc,qBAAqB,iBAAiB,EAAS;GAC3F,GAGI;GAAE,SAAS;GAAO,SAAS;GAAwB;IAE5D,CAAC,EAAU,CACZ;AAED,QACE,kBAAC,GAAD;EACE,WAAU;EACF;EACF;EACK;EACJ;EACP,QAAQ,CAAC,GAAQ,EAAO;EACxB,kBAAkB,CAAC,GAAQ,EAAO;EAClC,aAAa;EACb,aAAa;EACb,iBAAgB;EAChB,YAAY;EACZ,cAAc;EACd,kBAAA;EACA,aAAa;EACb,kBAAkB;YAEjB,EAAQ,KAAK,MAAW;GAGvB,IAAM,IADoB,EACc,WAAW,EAAE;AAErD,UACE,kBAAC,OAAD;IAEE,UAAU,MAAM,EAAkB,EAAO,IAAI,EAAE;IAC/C,iBAAe,MAAqB,EAAO,MAAM,IAAa,SAAS,KAAA;IACvE,WAAW,iDACT,MAAqB,EAAO,MAAM,IAC9B,mFACA;cAGN,kBAAC,GAAD;KACU;KACR,SAAS;KACT,UAAU;KACV,cAAc;KACd,CAAA;IACE,EAfC,EAAO,GAeR;IAER;EACS,CAAA;EAEf"}
1
+ {"version":3,"file":"DashboardGrid.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardGrid.tsx"],"sourcesContent":["/**\n * DashboardGrid Component\n *\n * Grid layout for widgets using react-grid-layout.\n * v2.0: Added widget event emission support\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport GridLayout, { type Layout } from 'react-grid-layout';\nimport type { Widget } from '../../types';\nimport { WidgetWrapper } from '../widgets/WidgetWrapper';\nimport { useWidgetEvents } from '../../hooks/useWidgetEvents';\nimport { useGlobalFilterValues } from '../../store';\nimport type { WidgetAction, ActionContext, ActionResult } from '../../renderers/types/renderer';\n\n// Import react-grid-layout styles\nimport 'react-grid-layout/css/styles.css';\nimport 'react-resizable/css/styles.css';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardGridProps {\n /** Widgets to display */\n widgets: Widget[];\n /** Whether in edit mode */\n isEditMode: boolean;\n /** Container width */\n width: number;\n /** Number of columns */\n cols?: number;\n /** Row height in pixels */\n rowHeight?: number;\n /** Callback when layout changes */\n onLayoutChange?: (layout: Layout[]) => void;\n /** Callback when widget is selected */\n onWidgetSelect?: (widgetId: string) => void;\n /** Currently selected widget ID */\n selectedWidgetId?: string | null;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction widgetToLayout(widget: Widget, cols: number): Layout {\n const x = widget.position?.x ?? widget.positionX ?? 0;\n const y = widget.position?.y ?? widget.positionY ?? 0;\n const w = widget.position?.width ?? widget.positionWidth ?? 4;\n const h = widget.position?.height ?? widget.positionHeight ?? 4;\n\n return {\n i: widget.id,\n x,\n y,\n w,\n h,\n minW: 1,\n minH: 1,\n maxW: cols,\n maxH: 12,\n };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardGrid: FC<DashboardGridProps> = memo(function DashboardGrid({\n widgets,\n isEditMode,\n width,\n cols = 12,\n rowHeight = 60,\n onLayoutChange,\n onWidgetSelect,\n selectedWidgetId,\n}) {\n // Phase 2: Get global filter values\n const globalFilterValues = useGlobalFilterValues();\n\n // v2.0: Widget event emission hook\n const { emitEvent } = useWidgetEvents({});\n\n // A phone displays a reading layout. Never save this temporary layout over\n // the authored desktop positions when a drag/resize callback fires.\n const isNarrow = width < 640;\n const canEditLayout = isEditMode && !isNarrow;\n const layout = useMemo(() => {\n const authored = widgets.map((widget) => widgetToLayout(widget, cols));\n if (!isNarrow) return authored;\n let nextRow = 0;\n return [...authored]\n .sort((a, b) => a.y - b.y || a.x - b.x)\n .map((item) => {\n const stacked = { ...item, x: 0, y: nextRow, w: cols, minW: cols };\n nextRow += item.h;\n return stacked;\n });\n }, [widgets, cols, isNarrow]);\n\n // Gutters are a fixed 16px in react-grid-layout, which on a 390px canvas spends\n // ~32px of the board on padding alone. Shrink them on narrow canvases — this is\n // purely visual and never touches the stored x/y/w/h.\n const gutter = width < 480 ? 8 : 16;\n\n // Handle widget click - only select in edit mode\n const handleWidgetClick = useCallback(\n (widgetId: string, _event: React.MouseEvent) => {\n // Only select widget in edit mode\n // In view mode, let WidgetWrapper handle drilldown\n if (isEditMode && onWidgetSelect) {\n onWidgetSelect(widgetId);\n }\n },\n [onWidgetSelect, isEditMode]\n );\n\n // Handle drag/resize stop - only update when user finishes interaction\n const handleDragStop = useCallback(\n (layout: Layout[]) => {\n if (onLayoutChange && canEditLayout) {\n onLayoutChange(layout);\n }\n },\n [onLayoutChange, canEditLayout]\n );\n\n const handleResizeStop = useCallback(\n (layout: Layout[]) => {\n if (onLayoutChange && canEditLayout) {\n onLayoutChange(layout);\n }\n },\n [onLayoutChange, canEditLayout]\n );\n\n // v2.0: Handle widget action - emit event to backend\n const handleWidgetAction = useCallback(\n async (action: WidgetAction, context: ActionContext): Promise<ActionResult | void> => {\n // Safety check: ensure action.name is a string\n const actionName = typeof action?.name === 'string' ? action.name : 'unknown';\n\n // Build event name from action name (e.g., \"Edit Profile\" -> \"widget:edit_profile:clicked\")\n const eventName = `widget:${actionName.toLowerCase().replace(/\\s+/g, '_')}:clicked`;\n\n // Emit event to backend (triggers webhook if configured)\n const eventLog = await emitEvent({\n widgetId: context.widgetId,\n actionName: actionName,\n event: eventName,\n payload: context.data || {},\n });\n\n if (eventLog) {\n return {\n success: eventLog.status !== 'FAILED',\n message: eventLog.status === 'DELIVERED' ? 'Action completed' : `Event status: ${eventLog.status}`,\n };\n }\n\n return { success: false, message: 'Failed to emit event' };\n },\n [emitEvent]\n );\n\n return (\n <GridLayout\n className=\"dashboard-grid\"\n layout={layout}\n cols={cols}\n rowHeight={rowHeight}\n width={width}\n margin={[gutter, gutter]}\n containerPadding={[gutter, gutter]}\n isDraggable={canEditLayout}\n isResizable={canEditLayout}\n draggableHandle=\".widget-drag-handle\"\n onDragStop={handleDragStop}\n onResizeStop={handleResizeStop}\n useCSSTransforms\n compactType={null}\n preventCollision={false}\n >\n {widgets.map((widget) => {\n // v2.0: Extract actions from widget\n const widgetWithActions = widget as Widget & { actions?: WidgetAction[] };\n const widgetActions = widgetWithActions.actions || [];\n\n return (\n <div\n key={widget.id}\n onClick={(e) => handleWidgetClick(widget.id, e)}\n data-selected={selectedWidgetId === widget.id && isEditMode ? 'true' : undefined}\n className={`flex h-full min-w-0 flex-col overflow-visible ${\n selectedWidgetId === widget.id && isEditMode\n ? 'rounded-card ring-2 ring-action-primary-bg ring-offset-2 ring-offset-bg-sunken'\n : ''\n }`}\n >\n <WidgetWrapper\n widget={widget}\n actions={widgetActions}\n onAction={handleWidgetAction}\n filterValues={globalFilterValues}\n />\n </div>\n );\n })}\n </GridLayout>\n );\n});\n\nexport default DashboardGrid;\n"],"mappings":";;;;;;;;;;AA8CA,SAAS,EAAe,GAAgB,GAAsB;CAC5D,IAAM,IAAI,EAAO,UAAU,KAAK,EAAO,aAAa,GAC9C,IAAI,EAAO,UAAU,KAAK,EAAO,aAAa,GAC9C,IAAI,EAAO,UAAU,SAAS,EAAO,iBAAiB,GACtD,IAAI,EAAO,UAAU,UAAU,EAAO,kBAAkB;AAE9D,QAAO;EACL,GAAG,EAAO;EACV;EACA;EACA;EACA;EACA,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACP;;AAOH,IAAa,IAAwC,EAAK,SAAuB,EAC/E,YACA,eACA,UACA,UAAO,IACP,eAAY,IACZ,mBACA,mBACA,uBACC;CAED,IAAM,IAAqB,GAAuB,EAG5C,EAAE,iBAAc,EAAgB,EAAE,CAAC,EAInC,IAAW,IAAQ,KACnB,IAAgB,KAAc,CAAC,GAC/B,IAAS,QAAc;EAC3B,IAAM,IAAW,EAAQ,KAAK,MAAW,EAAe,GAAQ,EAAK,CAAC;AACtE,MAAI,CAAC,EAAU,QAAO;EACtB,IAAI,IAAU;AACd,SAAO,CAAC,GAAG,EAAS,CACjB,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CACtC,KAAK,MAAS;GACb,IAAM,IAAU;IAAE,GAAG;IAAM,GAAG;IAAG,GAAG;IAAS,GAAG;IAAM,MAAM;IAAM;AAElE,UADA,KAAW,EAAK,GACT;IACP;IACH;EAAC;EAAS;EAAM;EAAS,CAAC,EAKvB,IAAS,IAAQ,MAAM,IAAI,IAG3B,IAAoB,GACvB,GAAkB,MAA6B;AAG9C,EAAI,KAAc,KAChB,EAAe,EAAS;IAG5B,CAAC,GAAgB,EAAW,CAC7B,EAGK,IAAiB,GACpB,MAAqB;AACpB,EAAI,KAAkB,KACpB,EAAe,EAAO;IAG1B,CAAC,GAAgB,EAAc,CAChC,EAEK,IAAmB,GACtB,MAAqB;AACpB,EAAI,KAAkB,KACpB,EAAe,EAAO;IAG1B,CAAC,GAAgB,EAAc,CAChC,EAGK,IAAqB,EACzB,OAAO,GAAsB,MAAyD;EAEpF,IAAM,IAAa,OAAO,GAAQ,QAAS,WAAW,EAAO,OAAO,WAG9D,IAAY,UAAU,EAAW,aAAa,CAAC,QAAQ,QAAQ,IAAI,CAAC,WAGpE,IAAW,MAAM,EAAU;GAC/B,UAAU,EAAQ;GACN;GACZ,OAAO;GACP,SAAS,EAAQ,QAAQ,EAAE;GAC5B,CAAC;AASF,SAPI,IACK;GACL,SAAS,EAAS,WAAW;GAC7B,SAAS,EAAS,WAAW,cAAc,qBAAqB,iBAAiB,EAAS;GAC3F,GAGI;GAAE,SAAS;GAAO,SAAS;GAAwB;IAE5D,CAAC,EAAU,CACZ;AAED,QACE,kBAAC,GAAD;EACE,WAAU;EACF;EACF;EACK;EACJ;EACP,QAAQ,CAAC,GAAQ,EAAO;EACxB,kBAAkB,CAAC,GAAQ,EAAO;EAClC,aAAa;EACb,aAAa;EACb,iBAAgB;EAChB,YAAY;EACZ,cAAc;EACd,kBAAA;EACA,aAAa;EACb,kBAAkB;YAEjB,EAAQ,KAAK,MAAW;GAGvB,IAAM,IADoB,EACc,WAAW,EAAE;AAErD,UACE,kBAAC,OAAD;IAEE,UAAU,MAAM,EAAkB,EAAO,IAAI,EAAE;IAC/C,iBAAe,MAAqB,EAAO,MAAM,IAAa,SAAS,KAAA;IACvE,WAAW,iDACT,MAAqB,EAAO,MAAM,IAC9B,mFACA;cAGN,kBAAC,GAAD;KACU;KACR,SAAS;KACT,UAAU;KACV,cAAc;KACd,CAAA;IACE,EAfC,EAAO,GAeR;IAER;EACS,CAAA;EAEf"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExportDashboardDialog.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/dashboard/ExportDashboardDialog.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,EAA0C,MAAM,OAAO,CAAC;AAYxE,MAAM,WAAW,0BAA0B;IACzC,6BAA6B;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AA+ND,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,0BAA0B,CAoR/D,CAAC;AAEH,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"ExportDashboardDialog.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/dashboard/ExportDashboardDialog.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,EAAE,EAA0C,MAAM,OAAO,CAAC;AAiBxE,MAAM,WAAW,0BAA0B;IACzC,6BAA6B;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,6BAA6B;IAC7B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAkOD,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,0BAA0B,CAsR/D,CAAC;AAEH,eAAe,qBAAqB,CAAC"}
@@ -5,7 +5,7 @@ import "../../hooks/index.js";
5
5
  import { nativeCopyText as r } from "../../../utils/nativeBridge.js";
6
6
  import { memo as i, useCallback as a, useEffect as o, useState as s } from "react";
7
7
  import { Check as c, Copy as l, Download as u, FileJson as d } from "lucide-react";
8
- import { Dialog as f, DialogContent as p, DialogDescription as m, DialogTitle as h, Spinner as g } from "@burdenoff/fe-libs/ui";
8
+ import { ResponsiveDialog as f, ResponsiveDialogContent as p, ResponsiveDialogDescription as m, ResponsiveDialogTitle as h, Spinner as g } from "@burdenoff/fe-libs/ui";
9
9
  import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
10
10
  //#region src/bigconsole/components/dashboard/ExportDashboardDialog.tsx
11
11
  var b = {
@@ -169,39 +169,42 @@ var b = {
169
169
  cursor: "not-allowed"
170
170
  }
171
171
  }, x = i(function({ checked: e, onChange: t, disabled: n, label: r, description: i }) {
172
- return /* @__PURE__ */ y("div", {
172
+ return /* @__PURE__ */ y("label", {
173
173
  style: {
174
174
  ...b.optionItem,
175
175
  ...e ? b.optionItemActive : {},
176
176
  opacity: n ? .6 : 1,
177
177
  cursor: n ? "not-allowed" : "pointer"
178
178
  },
179
- onClick: () => !n && t(!e),
180
- children: [/* @__PURE__ */ v("div", {
179
+ children: [/* @__PURE__ */ v("input", {
180
+ type: "checkbox",
181
+ checked: e,
182
+ onChange: (e) => t(e.target.checked),
183
+ disabled: n,
184
+ "aria-label": r,
181
185
  style: {
182
- ...b.checkbox,
183
- ...e ? b.checkboxChecked : {}
184
- },
185
- children: e && /* @__PURE__ */ v(c, { style: {
186
- width: "14px",
187
- height: "14px",
188
- color: "var(--color-action-primary-text)"
189
- } })
190
- }), /* @__PURE__ */ y("div", {
186
+ width: "18px",
187
+ height: "18px",
188
+ flexShrink: 0,
189
+ accentColor: "var(--color-action-primary-bg)"
190
+ }
191
+ }), /* @__PURE__ */ y("span", {
191
192
  style: b.optionText,
192
- children: [/* @__PURE__ */ v("p", {
193
+ children: [/* @__PURE__ */ v("span", {
194
+ className: "block",
193
195
  style: b.optionLabel,
194
196
  children: r
195
- }), /* @__PURE__ */ v("p", {
197
+ }), /* @__PURE__ */ v("span", {
198
+ className: "block",
196
199
  style: b.optionDescription,
197
200
  children: i
198
201
  })]
199
202
  })]
200
203
  });
201
204
  }), S = i(function({ isOpen: i, dashboardId: S, dashboardName: C, onClose: w, onSuccess: T }) {
202
- let [E, D] = s(!0), [O, k] = s(!1), [A, j] = s(!0), [M, N] = s(null), [P, F] = s(null), { loading: I, error: L, downloadDashboard: R, clearError: z } = n(), { loading: B, error: V, exportDashboard: H } = n(), U = `${E}|${O}|${A}`, W = M?.key === U ? M.json : null, G = P === U;
205
+ let [E, D] = s(!1), [O, k] = s(!1), [A, j] = s(!0), [M, N] = s(null), [P, F] = s(null), { loading: I, error: L, downloadDashboard: R, clearError: z } = n(), { loading: B, error: V, exportDashboard: H } = n(), U = `${S}|${E}|${O}|${A}`, W = M?.key === U ? M.json : null, G = P === U;
203
206
  o(() => {
204
- i && (D(!0), k(!1), j(!0), z());
207
+ i && (D(!1), k(!1), j(!0), z());
205
208
  }, [i, z]), o(() => {
206
209
  if (!i || !S) return;
207
210
  let e = !1;
@@ -229,10 +232,11 @@ var b = {
229
232
  let K = a(async () => {
230
233
  W && await r(W) && (F(U), window.setTimeout(() => F(null), 2e3));
231
234
  }, [W, U]), q = a(async () => {
232
- S && await R(S, C, {
235
+ !S || !W || B || await R(S, C, {
233
236
  includeDataSourceRefs: E,
234
237
  includeWebhookMappings: O,
235
- prettyPrint: A
238
+ prettyPrint: A,
239
+ preparedContent: W
236
240
  }) && (T?.(), w());
237
241
  }, [
238
242
  S,
@@ -241,6 +245,8 @@ var b = {
241
245
  O,
242
246
  A,
243
247
  R,
248
+ W,
249
+ B,
244
250
  T,
245
251
  w
246
252
  ]);
@@ -296,16 +302,16 @@ var b = {
296
302
  /* @__PURE__ */ v(x, {
297
303
  checked: E,
298
304
  onChange: D,
299
- disabled: I,
305
+ disabled: !0,
300
306
  label: "Include Data Source References",
301
- description: "Store data source names for reference (IDs are not transferred)"
307
+ description: "Not available for exports from this session."
302
308
  }),
303
309
  /* @__PURE__ */ v(x, {
304
310
  checked: O,
305
311
  onChange: k,
306
- disabled: I,
312
+ disabled: !0,
307
313
  label: "Include Webhook Mappings",
308
- description: "Export webhook configurations (URLs will need to be reconfigured)"
314
+ description: "Not available for exports from this session."
309
315
  }),
310
316
  /* @__PURE__ */ v(x, {
311
317
  checked: A,
@@ -390,10 +396,10 @@ var b = {
390
396
  }), /* @__PURE__ */ v("button", {
391
397
  style: {
392
398
  ...b.exportButton,
393
- ...I ? b.exportButtonDisabled : {}
399
+ ...I || B || !W ? b.exportButtonDisabled : {}
394
400
  },
395
401
  onClick: q,
396
- disabled: I,
402
+ disabled: I || B || !W,
397
403
  onMouseEnter: (e) => {
398
404
  I || (e.currentTarget.style.opacity = "0.9");
399
405
  },
@@ -1 +1 @@
1
- {"version":3,"file":"ExportDashboardDialog.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/ExportDashboardDialog.tsx"],"sourcesContent":["/**\n * ExportDashboardDialog Component\n *\n * Modal dialog for exporting a dashboard to JSON.\n * Uses inline styles for consistent styling across environments.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport { Dialog, DialogContent, DialogTitle, DialogDescription } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Download, FileJson, Check, Copy } from 'lucide-react';\nimport { Section, SectionStack } from '../common';\nimport { useExportDashboard, type ExportDashboardOptions } from '../../hooks';\nimport { nativeCopyText } from '../../../utils/nativeBridge';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ExportDashboardDialogProps {\n /** Whether dialog is open */\n isOpen: boolean;\n /** Dashboard ID to export */\n dashboardId: string | null;\n /** Dashboard name for filename */\n dashboardName?: string;\n /** Callback when dialog is closed */\n onClose: () => void;\n /** Callback when export succeeds */\n onSuccess?: () => void;\n}\n\n// ============================================================================\n// Styles\n// ============================================================================\n\nconst styles = {\n dialogContent: {\n // Wider than the old 450px so the JSON preview has a usable line length;\n // still capped to the viewport on a phone.\n maxWidth: '640px',\n width: '95vw',\n maxHeight: '90dvh',\n padding: 0,\n borderRadius: '12px',\n overflow: 'hidden',\n display: 'flex',\n flexDirection: 'column',\n } as React.CSSProperties,\n header: {\n display: 'flex',\n alignItems: 'center',\n gap: '12px',\n padding: '20px 24px',\n borderBottom: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-sunken)',\n } as React.CSSProperties,\n iconWrapper: {\n width: '44px',\n height: '44px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: '10px',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n flexShrink: 0,\n } as React.CSSProperties,\n headerText: {\n flex: 1,\n } as React.CSSProperties,\n title: {\n fontSize: '16px',\n fontWeight: 600,\n color: 'var(--color-text-primary)',\n margin: 0,\n } as React.CSSProperties,\n description: {\n fontSize: '13px',\n color: 'var(--color-text-secondary)',\n margin: '4px 0 0 0',\n } as React.CSSProperties,\n content: {\n padding: '20px 24px',\n flex: '1 1 auto',\n minHeight: 0,\n overflowY: 'auto',\n } as React.CSSProperties,\n previewBlock: {\n margin: 0,\n maxHeight: '260px',\n overflow: 'auto',\n padding: '12px',\n borderRadius: '8px',\n border: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-sunken)',\n color: 'var(--color-text-primary)',\n fontFamily: 'var(--font-family-mono, monospace)',\n fontSize: '12px',\n lineHeight: 1.5,\n whiteSpace: 'pre',\n tabSize: 2,\n } as React.CSSProperties,\n previewAction: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '6px',\n minHeight: 'var(--size-controlHeight-sm)',\n padding: '4px 10px',\n fontSize: '12px',\n fontWeight: 500,\n borderRadius: '6px',\n border: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n color: 'var(--color-text-secondary)',\n cursor: 'pointer',\n } as React.CSSProperties,\n optionItem: {\n display: 'flex',\n alignItems: 'flex-start',\n gap: '12px',\n padding: '12px',\n borderRadius: '8px',\n marginBottom: '8px',\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px solid var(--color-border-subtle)',\n cursor: 'pointer',\n transition: 'all 0.15s ease',\n } as React.CSSProperties,\n optionItemActive: {\n backgroundColor: 'var(--color-action-primary-bg)',\n borderColor: 'var(--color-action-primary-bg)',\n } as React.CSSProperties,\n checkbox: {\n width: '20px',\n height: '20px',\n borderRadius: '4px',\n border: '2px solid var(--color-border-subtle)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n marginTop: '2px',\n transition: 'all 0.15s ease',\n cursor: 'pointer',\n } as React.CSSProperties,\n checkboxChecked: {\n backgroundColor: 'var(--color-action-primary-bg)',\n borderColor: 'var(--color-action-primary-bg)',\n } as React.CSSProperties,\n optionText: {\n flex: 1,\n } as React.CSSProperties,\n optionLabel: {\n fontSize: '14px',\n fontWeight: 500,\n color: 'var(--color-text-primary)',\n margin: 0,\n } as React.CSSProperties,\n optionDescription: {\n fontSize: '12px',\n color: 'var(--color-text-secondary)',\n margin: '4px 0 0 0',\n } as React.CSSProperties,\n footer: {\n display: 'flex',\n justifyContent: 'flex-end',\n gap: '12px',\n padding: '16px 24px',\n borderTop: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-sunken)',\n } as React.CSSProperties,\n cancelButton: {\n padding: '10px 20px',\n fontSize: '14px',\n fontWeight: 500,\n borderRadius: '8px',\n border: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n color: 'var(--color-text-secondary)',\n cursor: 'pointer',\n transition: 'all 0.15s ease',\n } as React.CSSProperties,\n exportButton: {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n padding: '10px 20px',\n fontSize: '14px',\n fontWeight: 500,\n borderRadius: '8px',\n border: 'none',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n cursor: 'pointer',\n transition: 'all 0.15s ease',\n } as React.CSSProperties,\n exportButtonDisabled: {\n backgroundColor: 'var(--color-action-primary-bg)',\n opacity: 0.5,\n cursor: 'not-allowed',\n } as React.CSSProperties,\n};\n\n// ============================================================================\n// Custom Checkbox Component\n// ============================================================================\n\ninterface CustomCheckboxProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n disabled?: boolean;\n label: string;\n description: string;\n}\n\nconst CustomCheckbox: FC<CustomCheckboxProps> = memo(function CustomCheckbox({\n checked,\n onChange,\n disabled,\n label,\n description,\n}) {\n return (\n <div\n style={{\n ...styles.optionItem,\n ...(checked ? styles.optionItemActive : {}),\n opacity: disabled ? 0.6 : 1,\n cursor: disabled ? 'not-allowed' : 'pointer',\n }}\n onClick={() => !disabled && onChange(!checked)}\n >\n <div\n style={{\n ...styles.checkbox,\n ...(checked ? styles.checkboxChecked : {}),\n }}\n >\n {checked && <Check style={{ width: '14px', height: '14px', color: 'var(--color-action-primary-text)' }} />}\n </div>\n <div style={styles.optionText}>\n <p style={styles.optionLabel}>{label}</p>\n <p style={styles.optionDescription}>{description}</p>\n </div>\n </div>\n );\n});\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ExportDashboardDialog: FC<ExportDashboardDialogProps> = memo(function ExportDashboardDialog({\n isOpen,\n dashboardId,\n dashboardName,\n onClose,\n onSuccess,\n}) {\n const [includeDataSources, setIncludeDataSources] = useState(true);\n const [includeWebhooks, setIncludeWebhooks] = useState(false);\n const [prettyPrint, setPrettyPrint] = useState(true);\n /**\n * The preview and the \"Copied\" flag are keyed by the option set that produced\n * them and DERIVED, not reset in an effect: toggling an option must invalidate\n * both instantly, and clearing them from the effect body would cascade an\n * extra render on every open (`react-hooks/set-state-in-effect`).\n */\n const [previewResult, setPreviewResult] = useState<{ key: string; json: string | null } | null>(null);\n const [copiedKey, setCopiedKey] = useState<string | null>(null);\n\n const { loading, error, downloadDashboard, clearError } = useExportDashboard();\n\n /**\n * The preview runs on its OWN hook instance. `useExportDashboard` keeps a\n * single `loading`/`error` pair per instance, so sharing one would make the\n * footer button read \"Exporting…\" while only the preview was refreshing, and\n * a preview failure would look like a failed download.\n */\n const { loading: previewLoading, error: previewError, exportDashboard: fetchPreview } = useExportDashboard();\n\n /** Identity of the current option set — one export document per combination. */\n const previewKey = `${includeDataSources}|${includeWebhooks}|${prettyPrint}`;\n const previewJson = previewResult?.key === previewKey ? previewResult.json : null;\n const copied = copiedKey === previewKey;\n\n // Reset state when dialog opens\n useEffect(() => {\n if (isOpen) {\n setIncludeDataSources(true);\n setIncludeWebhooks(false);\n setPrettyPrint(true);\n clearError();\n }\n }, [isOpen, clearError]);\n\n /**\n * Load the exact document the Download button will write, so what is reviewed\n * IS what is exported — same query, same options. Re-runs when an option\n * changes because each option changes the document.\n */\n useEffect(() => {\n if (!isOpen || !dashboardId) return;\n\n let cancelled = false;\n\n void fetchPreview(dashboardId, {\n includeDataSourceRefs: includeDataSources,\n includeWebhookMappings: includeWebhooks,\n prettyPrint,\n }).then((content) => {\n // Guard the stale response: options can change while a fetch is in flight.\n if (!cancelled) setPreviewResult({ key: previewKey, json: content });\n });\n\n return () => {\n cancelled = true;\n };\n }, [isOpen, dashboardId, includeDataSources, includeWebhooks, prettyPrint, previewKey, fetchPreview]);\n\n const handleCopyPreview = useCallback(async () => {\n if (!previewJson) return;\n const ok = await nativeCopyText(previewJson);\n if (ok) {\n setCopiedKey(previewKey);\n window.setTimeout(() => setCopiedKey(null), 2000);\n }\n }, [previewJson, previewKey]);\n\n // Handle export\n const handleExport = useCallback(async () => {\n if (!dashboardId) return;\n\n const options: ExportDashboardOptions = {\n includeDataSourceRefs: includeDataSources,\n includeWebhookMappings: includeWebhooks,\n prettyPrint,\n };\n\n const success = await downloadDashboard(dashboardId, dashboardName, options);\n\n if (success) {\n onSuccess?.();\n onClose();\n }\n }, [\n dashboardId,\n dashboardName,\n includeDataSources,\n includeWebhooks,\n prettyPrint,\n downloadDashboard,\n onSuccess,\n onClose,\n ]);\n\n // Don't render if no dashboard\n if (!dashboardId) return null;\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !loading && onClose()}>\n <DialogContent style={styles.dialogContent}>\n {/* Header */}\n <div style={styles.header}>\n <div style={styles.iconWrapper}>\n <FileJson style={{ width: '22px', height: '22px' }} />\n </div>\n <div style={styles.headerText}>\n <DialogTitle asChild>\n <h2 style={styles.title}>Export Dashboard</h2>\n </DialogTitle>\n <DialogDescription asChild>\n <p style={styles.description}>Export your dashboard configuration to a JSON file.</p>\n </DialogDescription>\n </div>\n </div>\n\n {/* Content */}\n <div style={styles.content}>\n <SectionStack>\n {/* What is being exported — supporting context, recessed. */}\n {dashboardName && (\n <Section\n headingLevel={3}\n emphasis=\"muted\"\n padding=\"compact\"\n title=\"Exporting\"\n description={dashboardName}\n data-testid=\"export-target\"\n />\n )}\n\n {/* The choices you actually make here — one bounded group instead of\n a floating label above three unbounded rows. */}\n <Section\n headingLevel={3}\n title=\"Export options\"\n description=\"What to include in the JSON file.\"\n data-testid=\"export-options\"\n bodyClassName=\"flex flex-col gap-form-gap\"\n >\n <CustomCheckbox\n checked={includeDataSources}\n onChange={setIncludeDataSources}\n disabled={loading}\n label=\"Include Data Source References\"\n description=\"Store data source names for reference (IDs are not transferred)\"\n />\n\n <CustomCheckbox\n checked={includeWebhooks}\n onChange={setIncludeWebhooks}\n disabled={loading}\n label=\"Include Webhook Mappings\"\n description=\"Export webhook configurations (URLs will need to be reconfigured)\"\n />\n\n <CustomCheckbox\n checked={prettyPrint}\n onChange={setPrettyPrint}\n disabled={loading}\n label=\"Format JSON (Pretty Print)\"\n description=\"Makes the export file human-readable (larger file size)\"\n />\n </Section>\n\n {/* The document itself. Reviewing before download is the point of\n the dialog — the options above only make sense once you can see\n what they change. */}\n <Section\n headingLevel={3}\n title=\"Preview\"\n description=\"The exact JSON the download writes. Credentials are never serialized — only data source references.\"\n data-testid=\"export-preview\"\n actions={\n previewJson ? (\n <button type=\"button\" style={styles.previewAction} onClick={handleCopyPreview}>\n {copied ? (\n <Check style={{ width: '14px', height: '14px' }} aria-hidden=\"true\" />\n ) : (\n <Copy style={{ width: '14px', height: '14px' }} aria-hidden=\"true\" />\n )}\n {copied ? 'Copied' : 'Copy'}\n </button>\n ) : undefined\n }\n >\n {previewLoading && !previewJson && (\n <div\n style={{ display: 'flex', alignItems: 'center', gap: '8px', color: 'var(--color-text-secondary)' }}\n >\n <Spinner style={{ width: '16px', height: '16px' }} />\n <span style={{ fontSize: '13px' }}>Building the export…</span>\n </div>\n )}\n\n {!previewLoading && previewError && (\n <p style={{ margin: 0, fontSize: '13px', color: 'var(--color-status-error-text)' }}>\n Preview unavailable: {previewError.message}\n </p>\n )}\n\n {previewJson && (\n <pre style={styles.previewBlock} data-testid=\"export-preview-json\">\n {previewJson}\n </pre>\n )}\n </Section>\n\n {/* Error */}\n {error && (\n <Section\n headingLevel={3}\n tone=\"danger\"\n padding=\"compact\"\n title=\"Export failed\"\n description={error.message}\n data-testid=\"export-error\"\n />\n )}\n </SectionStack>\n </div>\n\n {/* Footer */}\n <div style={styles.footer}>\n <button\n style={styles.cancelButton}\n onClick={onClose}\n disabled={loading}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = 'var(--color-bg-muted)';\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'var(--color-bg-surface)';\n }}\n >\n Cancel\n </button>\n <button\n style={{\n ...styles.exportButton,\n ...(loading ? styles.exportButtonDisabled : {}),\n }}\n onClick={handleExport}\n disabled={loading}\n onMouseEnter={(e) => {\n if (!loading) e.currentTarget.style.opacity = '0.9';\n }}\n onMouseLeave={(e) => {\n if (!loading) e.currentTarget.style.opacity = '1';\n }}\n >\n {loading ? (\n <>\n <Spinner style={{ width: '16px', height: '16px' }} />\n Exporting...\n </>\n ) : (\n <>\n <Download style={{ width: '16px', height: '16px' }} />\n Download Export\n </>\n )}\n </button>\n </div>\n </DialogContent>\n </Dialog>\n );\n});\n\nexport default ExportDashboardDialog;\n"],"mappings":"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"}
1
+ {"version":3,"file":"ExportDashboardDialog.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/ExportDashboardDialog.tsx"],"sourcesContent":["/**\n * ExportDashboardDialog Component\n *\n * Modal dialog for exporting a dashboard to JSON.\n * Uses inline styles for consistent styling across environments.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport {\n ResponsiveDialog as Dialog,\n ResponsiveDialogContent as DialogContent,\n ResponsiveDialogTitle as DialogTitle,\n ResponsiveDialogDescription as DialogDescription,\n} from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Download, FileJson, Check, Copy } from 'lucide-react';\nimport { Section, SectionStack } from '../common';\nimport { useExportDashboard, type ExportDashboardOptions } from '../../hooks';\nimport { nativeCopyText } from '../../../utils/nativeBridge';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ExportDashboardDialogProps {\n /** Whether dialog is open */\n isOpen: boolean;\n /** Dashboard ID to export */\n dashboardId: string | null;\n /** Dashboard name for filename */\n dashboardName?: string;\n /** Callback when dialog is closed */\n onClose: () => void;\n /** Callback when export succeeds */\n onSuccess?: () => void;\n}\n\n// ============================================================================\n// Styles\n// ============================================================================\n\nconst styles = {\n dialogContent: {\n // Wider than the old 450px so the JSON preview has a usable line length;\n // still capped to the viewport on a phone.\n maxWidth: '640px',\n width: '95vw',\n maxHeight: '90dvh',\n padding: 0,\n borderRadius: '12px',\n overflow: 'hidden',\n display: 'flex',\n flexDirection: 'column',\n } as React.CSSProperties,\n header: {\n display: 'flex',\n alignItems: 'center',\n gap: '12px',\n padding: '20px 24px',\n borderBottom: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-sunken)',\n } as React.CSSProperties,\n iconWrapper: {\n width: '44px',\n height: '44px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: '10px',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n flexShrink: 0,\n } as React.CSSProperties,\n headerText: {\n flex: 1,\n } as React.CSSProperties,\n title: {\n fontSize: '16px',\n fontWeight: 600,\n color: 'var(--color-text-primary)',\n margin: 0,\n } as React.CSSProperties,\n description: {\n fontSize: '13px',\n color: 'var(--color-text-secondary)',\n margin: '4px 0 0 0',\n } as React.CSSProperties,\n content: {\n padding: '20px 24px',\n flex: '1 1 auto',\n minHeight: 0,\n overflowY: 'auto',\n } as React.CSSProperties,\n previewBlock: {\n margin: 0,\n maxHeight: '260px',\n overflow: 'auto',\n padding: '12px',\n borderRadius: '8px',\n border: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-sunken)',\n color: 'var(--color-text-primary)',\n fontFamily: 'var(--font-family-mono, monospace)',\n fontSize: '12px',\n lineHeight: 1.5,\n whiteSpace: 'pre',\n tabSize: 2,\n } as React.CSSProperties,\n previewAction: {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '6px',\n minHeight: 'var(--size-controlHeight-sm)',\n padding: '4px 10px',\n fontSize: '12px',\n fontWeight: 500,\n borderRadius: '6px',\n border: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n color: 'var(--color-text-secondary)',\n cursor: 'pointer',\n } as React.CSSProperties,\n optionItem: {\n display: 'flex',\n alignItems: 'flex-start',\n gap: '12px',\n padding: '12px',\n borderRadius: '8px',\n marginBottom: '8px',\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px solid var(--color-border-subtle)',\n cursor: 'pointer',\n transition: 'all 0.15s ease',\n } as React.CSSProperties,\n optionItemActive: {\n backgroundColor: 'var(--color-action-primary-bg)',\n borderColor: 'var(--color-action-primary-bg)',\n } as React.CSSProperties,\n checkbox: {\n width: '20px',\n height: '20px',\n borderRadius: '4px',\n border: '2px solid var(--color-border-subtle)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n marginTop: '2px',\n transition: 'all 0.15s ease',\n cursor: 'pointer',\n } as React.CSSProperties,\n checkboxChecked: {\n backgroundColor: 'var(--color-action-primary-bg)',\n borderColor: 'var(--color-action-primary-bg)',\n } as React.CSSProperties,\n optionText: {\n flex: 1,\n } as React.CSSProperties,\n optionLabel: {\n fontSize: '14px',\n fontWeight: 500,\n color: 'var(--color-text-primary)',\n margin: 0,\n } as React.CSSProperties,\n optionDescription: {\n fontSize: '12px',\n color: 'var(--color-text-secondary)',\n margin: '4px 0 0 0',\n } as React.CSSProperties,\n footer: {\n display: 'flex',\n justifyContent: 'flex-end',\n gap: '12px',\n padding: '16px 24px',\n borderTop: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-sunken)',\n } as React.CSSProperties,\n cancelButton: {\n padding: '10px 20px',\n fontSize: '14px',\n fontWeight: 500,\n borderRadius: '8px',\n border: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n color: 'var(--color-text-secondary)',\n cursor: 'pointer',\n transition: 'all 0.15s ease',\n } as React.CSSProperties,\n exportButton: {\n display: 'flex',\n alignItems: 'center',\n gap: '8px',\n padding: '10px 20px',\n fontSize: '14px',\n fontWeight: 500,\n borderRadius: '8px',\n border: 'none',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n cursor: 'pointer',\n transition: 'all 0.15s ease',\n } as React.CSSProperties,\n exportButtonDisabled: {\n backgroundColor: 'var(--color-action-primary-bg)',\n opacity: 0.5,\n cursor: 'not-allowed',\n } as React.CSSProperties,\n};\n\n// ============================================================================\n// Custom Checkbox Component\n// ============================================================================\n\ninterface CustomCheckboxProps {\n checked: boolean;\n onChange: (checked: boolean) => void;\n disabled?: boolean;\n label: string;\n description: string;\n}\n\nconst CustomCheckbox: FC<CustomCheckboxProps> = memo(function CustomCheckbox({\n checked,\n onChange,\n disabled,\n label,\n description,\n}) {\n return (\n <label\n style={{\n ...styles.optionItem,\n ...(checked ? styles.optionItemActive : {}),\n opacity: disabled ? 0.6 : 1,\n cursor: disabled ? 'not-allowed' : 'pointer',\n }}\n >\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={(event) => onChange(event.target.checked)}\n disabled={disabled}\n aria-label={label}\n style={{ width: '18px', height: '18px', flexShrink: 0, accentColor: 'var(--color-action-primary-bg)' }}\n />\n <span style={styles.optionText}>\n <span className=\"block\" style={styles.optionLabel}>\n {label}\n </span>\n <span className=\"block\" style={styles.optionDescription}>\n {description}\n </span>\n </span>\n </label>\n );\n});\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const ExportDashboardDialog: FC<ExportDashboardDialogProps> = memo(function ExportDashboardDialog({\n isOpen,\n dashboardId,\n dashboardName,\n onClose,\n onSuccess,\n}) {\n const [includeDataSources, setIncludeDataSources] = useState(false);\n const [includeWebhooks, setIncludeWebhooks] = useState(false);\n const [prettyPrint, setPrettyPrint] = useState(true);\n /**\n * The preview and the \"Copied\" flag are keyed by the option set that produced\n * them and DERIVED, not reset in an effect: toggling an option must invalidate\n * both instantly, and clearing them from the effect body would cascade an\n * extra render on every open (`react-hooks/set-state-in-effect`).\n */\n const [previewResult, setPreviewResult] = useState<{ key: string; json: string | null } | null>(null);\n const [copiedKey, setCopiedKey] = useState<string | null>(null);\n\n const { loading, error, downloadDashboard, clearError } = useExportDashboard();\n\n /**\n * The preview runs on its OWN hook instance. `useExportDashboard` keeps a\n * single `loading`/`error` pair per instance, so sharing one would make the\n * footer button read \"Exporting…\" while only the preview was refreshing, and\n * a preview failure would look like a failed download.\n */\n const { loading: previewLoading, error: previewError, exportDashboard: fetchPreview } = useExportDashboard();\n\n /** Identity of the current option set — one export document per combination. */\n const previewKey = `${dashboardId}|${includeDataSources}|${includeWebhooks}|${prettyPrint}`;\n const previewJson = previewResult?.key === previewKey ? previewResult.json : null;\n const copied = copiedKey === previewKey;\n\n // Reset state when dialog opens\n useEffect(() => {\n if (isOpen) {\n setIncludeDataSources(false);\n setIncludeWebhooks(false);\n setPrettyPrint(true);\n clearError();\n }\n }, [isOpen, clearError]);\n\n /**\n * Load the exact document the Download button will write, so what is reviewed\n * IS what is exported — same query, same options. Re-runs when an option\n * changes because each option changes the document.\n */\n useEffect(() => {\n if (!isOpen || !dashboardId) return;\n\n let cancelled = false;\n\n void fetchPreview(dashboardId, {\n includeDataSourceRefs: includeDataSources,\n includeWebhookMappings: includeWebhooks,\n prettyPrint,\n }).then((content) => {\n // Guard the stale response: options can change while a fetch is in flight.\n if (!cancelled) setPreviewResult({ key: previewKey, json: content });\n });\n\n return () => {\n cancelled = true;\n };\n }, [isOpen, dashboardId, includeDataSources, includeWebhooks, prettyPrint, previewKey, fetchPreview]);\n\n const handleCopyPreview = useCallback(async () => {\n if (!previewJson) return;\n const ok = await nativeCopyText(previewJson);\n if (ok) {\n setCopiedKey(previewKey);\n window.setTimeout(() => setCopiedKey(null), 2000);\n }\n }, [previewJson, previewKey]);\n\n // Handle export\n const handleExport = useCallback(async () => {\n if (!dashboardId || !previewJson || previewLoading) return;\n\n const options: ExportDashboardOptions = {\n includeDataSourceRefs: includeDataSources,\n includeWebhookMappings: includeWebhooks,\n prettyPrint,\n };\n\n const success = await downloadDashboard(dashboardId, dashboardName, { ...options, preparedContent: previewJson });\n\n if (success) {\n onSuccess?.();\n onClose();\n }\n }, [\n dashboardId,\n dashboardName,\n includeDataSources,\n includeWebhooks,\n prettyPrint,\n downloadDashboard,\n previewJson,\n previewLoading,\n onSuccess,\n onClose,\n ]);\n\n // Don't render if no dashboard\n if (!dashboardId) return null;\n\n return (\n <Dialog open={isOpen} onOpenChange={(open) => !open && !loading && onClose()}>\n <DialogContent style={styles.dialogContent}>\n {/* Header */}\n <div style={styles.header}>\n <div style={styles.iconWrapper}>\n <FileJson style={{ width: '22px', height: '22px' }} />\n </div>\n <div style={styles.headerText}>\n <DialogTitle asChild>\n <h2 style={styles.title}>Export Dashboard</h2>\n </DialogTitle>\n <DialogDescription asChild>\n <p style={styles.description}>Export your dashboard configuration to a JSON file.</p>\n </DialogDescription>\n </div>\n </div>\n\n {/* Content */}\n <div style={styles.content}>\n <SectionStack>\n {/* What is being exported — supporting context, recessed. */}\n {dashboardName && (\n <Section\n headingLevel={3}\n emphasis=\"muted\"\n padding=\"compact\"\n title=\"Exporting\"\n description={dashboardName}\n data-testid=\"export-target\"\n />\n )}\n\n {/* The choices you actually make here — one bounded group instead of\n a floating label above three unbounded rows. */}\n <Section\n headingLevel={3}\n title=\"Export options\"\n description=\"What to include in the JSON file.\"\n data-testid=\"export-options\"\n bodyClassName=\"flex flex-col gap-form-gap\"\n >\n <CustomCheckbox\n checked={includeDataSources}\n onChange={setIncludeDataSources}\n disabled\n label=\"Include Data Source References\"\n description=\"Not available for exports from this session.\"\n />\n\n <CustomCheckbox\n checked={includeWebhooks}\n onChange={setIncludeWebhooks}\n disabled\n label=\"Include Webhook Mappings\"\n description=\"Not available for exports from this session.\"\n />\n\n <CustomCheckbox\n checked={prettyPrint}\n onChange={setPrettyPrint}\n disabled={loading}\n label=\"Format JSON (Pretty Print)\"\n description=\"Makes the export file human-readable (larger file size)\"\n />\n </Section>\n\n {/* The document itself. Reviewing before download is the point of\n the dialog — the options above only make sense once you can see\n what they change. */}\n <Section\n headingLevel={3}\n title=\"Preview\"\n description=\"The exact JSON the download writes. Credentials are never serialized — only data source references.\"\n data-testid=\"export-preview\"\n actions={\n previewJson ? (\n <button type=\"button\" style={styles.previewAction} onClick={handleCopyPreview}>\n {copied ? (\n <Check style={{ width: '14px', height: '14px' }} aria-hidden=\"true\" />\n ) : (\n <Copy style={{ width: '14px', height: '14px' }} aria-hidden=\"true\" />\n )}\n {copied ? 'Copied' : 'Copy'}\n </button>\n ) : undefined\n }\n >\n {previewLoading && !previewJson && (\n <div\n style={{ display: 'flex', alignItems: 'center', gap: '8px', color: 'var(--color-text-secondary)' }}\n >\n <Spinner style={{ width: '16px', height: '16px' }} />\n <span style={{ fontSize: '13px' }}>Building the export…</span>\n </div>\n )}\n\n {!previewLoading && previewError && (\n <p style={{ margin: 0, fontSize: '13px', color: 'var(--color-status-error-text)' }}>\n Preview unavailable: {previewError.message}\n </p>\n )}\n\n {previewJson && (\n <pre style={styles.previewBlock} data-testid=\"export-preview-json\">\n {previewJson}\n </pre>\n )}\n </Section>\n\n {/* Error */}\n {error && (\n <Section\n headingLevel={3}\n tone=\"danger\"\n padding=\"compact\"\n title=\"Export failed\"\n description={error.message}\n data-testid=\"export-error\"\n />\n )}\n </SectionStack>\n </div>\n\n {/* Footer */}\n <div style={styles.footer}>\n <button\n style={styles.cancelButton}\n onClick={onClose}\n disabled={loading}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = 'var(--color-bg-muted)';\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = 'var(--color-bg-surface)';\n }}\n >\n Cancel\n </button>\n <button\n style={{\n ...styles.exportButton,\n ...(loading || previewLoading || !previewJson ? styles.exportButtonDisabled : {}),\n }}\n onClick={handleExport}\n disabled={loading || previewLoading || !previewJson}\n onMouseEnter={(e) => {\n if (!loading) e.currentTarget.style.opacity = '0.9';\n }}\n onMouseLeave={(e) => {\n if (!loading) e.currentTarget.style.opacity = '1';\n }}\n >\n {loading ? (\n <>\n <Spinner style={{ width: '16px', height: '16px' }} />\n Exporting...\n </>\n ) : (\n <>\n <Download style={{ width: '16px', height: '16px' }} />\n Download Export\n </>\n )}\n </button>\n </div>\n </DialogContent>\n </Dialog>\n );\n});\n\nexport default ExportDashboardDialog;\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ export interface FilterEditorProps {
9
9
  config: Record<string, unknown>;
10
10
  defaultValue?: unknown;
11
11
  isRequired?: boolean;
12
- }) => void;
12
+ }) => void | Promise<void>;
13
13
  onClose: () => void;
14
14
  }
15
15
  export declare function FilterEditor({ filter, onSave, onClose }: FilterEditorProps): React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"FilterEditor.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAsB/D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,CAAC,IAAI,EAAE;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,UAAU,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,KAAK,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,iBAAiB,qBAub1E;AAED,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"FilterEditor.d.ts","sourceRoot":"","sources":["../../../../../src/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAsB/D,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,CAAC,IAAI,EAAE;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,UAAU,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAMD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,iBAAiB,qBA0d1E;AAED,eAAe,YAAY,CAAC"}