@burdenoff/microfe-bigconsole 2026.612.1 → 2026.612.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardBuilderPage.tsx"],"sourcesContent":["/**\n * DashboardBuilderPage\n *\n * Full dashboard builder experience with side panels.\n */\n\nimport { type FC, memo, useState, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBigConsoleNavigate } from '../context';\nimport { DashboardCanvas, ExportDashboardDialog } from '../components/dashboard';\nimport { WidgetTypeSelector } from '../components/widgets/WidgetTypeSelector';\nimport { useDashboardStore, useWidgetStore } from '../store';\nimport { useDashboardOperations, useWidgetOperations, usePageOperations } from '../hooks';\nimport type { WidgetType, Dashboard, DashboardPage } from '../types';\nimport { findNextAvailablePosition } from '../utils/widgetPositioning';\nimport { getWidgetDefinition } from '../components/widgets/WidgetRegistry';\n\n// Breadcrumb name registration helpers (get from shell at call time to avoid timing issues)\nfunction getBreadcrumbRegistry() {\n try {\n return (\n window as unknown as {\n __breadcrumbRegistry?: { register: (id: string, name: string) => void; unregister: (id: string) => void };\n }\n ).__breadcrumbRegistry;\n } catch {\n return undefined;\n }\n}\n\nfunction registerBreadcrumbName(id: string, name: string) {\n const registry = getBreadcrumbRegistry();\n if (registry?.register) {\n registry.register(id, name);\n }\n}\n\nfunction unregisterBreadcrumbName(id: string) {\n const registry = getBreadcrumbRegistry();\n if (registry?.unregister) {\n registry.unregister(id);\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardBuilderPage: FC = memo(function DashboardBuilderPage() {\n const { dashboardId } = useParams<{ dashboardId: string }>();\n const navigate = useBigConsoleNavigate();\n const [isWidgetPanelOpen, setIsWidgetPanelOpen] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n const [activePageId, setActivePageId] = useState<string | null>(null);\n\n // Dashboard operations from GraphQL\n const {\n fetchDashboard,\n updateDashboard,\n loading: dashboardLoading,\n error: dashboardError,\n } = useDashboardOperations();\n\n // Widget operations from GraphQL\n const {\n createWidget: createWidgetMutation,\n updateWidget: updateWidgetMutation,\n deleteWidget: deleteWidgetMutation,\n duplicateWidget: duplicateWidgetMutation,\n batchUpdatePositions,\n } = useWidgetOperations(undefined, dashboardId);\n\n // Page operations from GraphQL\n const {\n createPage: createPageMutation,\n updatePage: updatePageMutation,\n deletePage: deletePageMutation,\n reorderPages: reorderPagesMutation,\n } = usePageOperations();\n\n // Store actions\n const setCurrentDashboard = useDashboardStore((state) => state.setCurrentDashboard);\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const setViewMode = useDashboardStore((state) => state.setViewMode);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const widgets = useWidgetStore((state) => state.widgets);\n const clipboardWidgetId = useWidgetStore((state) => state.clipboardWidgetId);\n const clipboardAction = useWidgetStore((state) => state.clipboardAction);\n const clearClipboard = useWidgetStore((state) => state.clearClipboard);\n\n // Set edit mode on mount\n useEffect(() => {\n setViewMode('edit');\n return () => setViewMode('view');\n }, [setViewMode]);\n\n // Load dashboard data from API\n useEffect(() => {\n if (!dashboardId) {\n navigate('/dashboards');\n return;\n }\n\n // Fetch dashboard from API\n fetchDashboard(dashboardId).then((dashboard) => {\n if (dashboard) {\n setCurrentDashboard({\n id: dashboard.id,\n title: dashboard.name,\n description: dashboard.description || '',\n pages: dashboard.pages.map((page, index) => ({\n id: page.id,\n title: page.name || '',\n index,\n layoutConfig: page.layoutConfig || {},\n })) as any,\n createdAt: dashboard.createdAt,\n updatedAt: dashboard.updatedAt,\n } as unknown as Dashboard);\n // Set active page to first page\n if (dashboard.pages.length > 0 && !activePageId) {\n setActivePageId(dashboard.pages[0].id);\n }\n\n // Register dashboard name for breadcrumb display\n if (dashboard.name) {\n registerBreadcrumbName(dashboardId, dashboard.name);\n }\n } else {\n // Dashboard not found, redirect\n navigate('/dashboards');\n }\n });\n\n // Cleanup: unregister breadcrumb name on unmount\n return () => {\n if (dashboardId) {\n unregisterBreadcrumbName(dashboardId);\n }\n };\n }, [dashboardId, navigate, setCurrentDashboard, fetchDashboard, activePageId]);\n\n // Handle paste widget from clipboard\n const handlePaste = useCallback(async () => {\n if (!clipboardWidgetId || !dashboardId || !currentDashboard?.pages?.[0]?.id) {\n return;\n }\n\n const sourceWidget = widgets.get(clipboardWidgetId);\n if (!sourceWidget) {\n clearClipboard();\n return;\n }\n\n const pageId = currentDashboard.pages[0].id;\n\n // Use smart placement algorithm - horizontal first, then next row\n const widgetsArray = Array.from(widgets.values());\n const widgetSize = {\n width: sourceWidget.positionWidth ?? 4,\n height: sourceWidget.positionHeight ?? 4,\n };\n\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n\n const newPosition = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n const newTitle = clipboardAction === 'copy' ? `${sourceWidget.title} (Copy)` : sourceWidget.title;\n\n let newWidget = null;\n\n // Try duplicateWidget API first\n try {\n newWidget = await duplicateWidgetMutation(clipboardWidgetId, newPosition, newTitle);\n } catch (err) {}\n\n // Fallback: Create new widget with source widget's data\n if (!newWidget) {\n newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type: sourceWidget.type,\n title: newTitle,\n description: sourceWidget.description,\n // v2.0: Use dataSinkId and parserId instead of dataSourceId/dataPath\n dataSinkId: sourceWidget.dataSinkId ?? undefined,\n datasetId: sourceWidget.datasetId ?? undefined,\n parserId: sourceWidget.parserId ?? undefined,\n parserRules: sourceWidget.parserRules ?? undefined,\n config: sourceWidget.config || {},\n position: newPosition,\n refreshInterval: sourceWidget.refreshInterval ?? undefined,\n metadata: sourceWidget.metadata,\n });\n }\n\n if (newWidget) {\n selectWidget(newWidget.id);\n\n // If cut action, delete the original widget from backend\n if (clipboardAction === 'cut') {\n await deleteWidgetMutation(clipboardWidgetId);\n }\n }\n\n clearClipboard();\n }, [\n clipboardWidgetId,\n clipboardAction,\n dashboardId,\n currentDashboard,\n widgets,\n duplicateWidgetMutation,\n createWidgetMutation,\n deleteWidgetMutation,\n selectWidget,\n clearClipboard,\n ]);\n\n // Handle quick add widget from panel\n const handleQuickAddWidget = useCallback(\n async (type: WidgetType) => {\n if (!dashboardId || !currentDashboard?.pages?.[0]?.id) return;\n\n const pageId = currentDashboard.pages[0].id;\n const title = type\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/^\\w/, (c) => c.toUpperCase());\n\n // Use smart placement algorithm - horizontal first, then next row\n const widgetsArray = Array.from(widgets.values());\n const definition = getWidgetDefinition(type);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n\n // Create widget via API\n const newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type,\n title,\n config: {},\n position: {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n },\n });\n\n if (newWidget) {\n selectWidget(newWidget.id);\n setIsWidgetPanelOpen(false);\n }\n },\n [dashboardId, currentDashboard, createWidgetMutation, selectWidget, widgets]\n );\n\n // Handle save - save dirty widgets and batch update positions\n const handleSave = useCallback(async () => {\n if (!dashboardId) return;\n\n setIsSaving(true);\n try {\n // Get the LATEST state directly from store (avoid stale closure)\n const storeState = useWidgetStore.getState();\n const currentWidgets = Array.from(storeState.widgets.values());\n const currentDirtyIds = storeState.dirtyWidgetIds;\n\n // Debug: Log dirty widget IDs\n\n // 1. Save dirty widgets (property changes like title, config, etc.)\n const dirtyWidgets = currentWidgets.filter((w) => currentDirtyIds.has(w.id));\n\n if (dirtyWidgets.length > 0) {\n // Save each dirty widget\n const updatePromises = dirtyWidgets.map(async (widget) => {\n const updateInput = {\n id: widget.id,\n title: widget.title,\n config: widget.config,\n refreshInterval: widget.refreshInterval ?? undefined,\n // v2.0: Use dataSinkId and parserId instead of dataSourceId/dataPath\n dataSinkId: widget.dataSinkId ?? undefined,\n datasetId: widget.datasetId ?? undefined,\n parserId: widget.parserId ?? undefined,\n parserRules: widget.parserRules ?? undefined,\n };\n\n try {\n const result = await updateWidgetMutation(updateInput);\n return result;\n } catch (err) {\n throw err;\n }\n });\n\n const results = await Promise.all(updatePromises);\n\n // Clear dirty flags after successful save\n storeState.clearAllDirty();\n } else {\n }\n\n // 2. Batch update widget positions\n if (currentWidgets.length > 0) {\n const positionUpdates = currentWidgets.map((widget) => ({\n id: widget.id,\n position: widget.position || {\n x: widget.positionX ?? 0,\n y: widget.positionY ?? 0,\n width: widget.positionWidth ?? 4,\n height: widget.positionHeight ?? 4,\n },\n }));\n\n await batchUpdatePositions(positionUpdates);\n }\n\n // 3. Update dashboard metadata if needed\n if (currentDashboard) {\n await updateDashboard({\n id: dashboardId,\n name: currentDashboard.title,\n description: currentDashboard.description,\n });\n }\n } catch (error) {\n } finally {\n setIsSaving(false);\n }\n }, [dashboardId, currentDashboard, updateWidgetMutation, batchUpdatePositions, updateDashboard]);\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // ============================================================================\n // Page Management Callbacks\n // ============================================================================\n\n // Handle page selection\n const handlePageChange = useCallback((pageId: string) => {\n setActivePageId(pageId);\n }, []);\n\n // Handle adding a new page\n const handlePageAdd = useCallback(async () => {\n if (!currentDashboard || !dashboardId) return;\n\n const newPageCount = currentDashboard.pages.length + 1;\n const newPageName = `Page ${newPageCount}`;\n\n // Call GraphQL mutation to create page\n // Note: 'order' is not available for create - backend auto-assigns order\n const newPage = await createPageMutation({\n dashboardId,\n name: newPageName,\n });\n\n if (newPage) {\n // Update local state with the created page\n setCurrentDashboard({\n ...currentDashboard,\n pages: [\n ...currentDashboard.pages,\n {\n id: newPage.id,\n title: newPage.name,\n index: newPage.order,\n layoutConfig: newPage.layoutConfig || {},\n },\n ],\n });\n\n // Switch to the new page\n setActivePageId(newPage.id);\n }\n }, [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]);\n\n // Handle renaming a page\n const handlePageRename = useCallback(\n async (pageId: string, newName: string) => {\n if (!currentDashboard) return;\n\n // Call GraphQL mutation to update page\n const updatedPage = await updatePageMutation({\n id: pageId,\n name: newName,\n });\n\n if (updatedPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: currentDashboard.pages.map((page) => (page.id === pageId ? { ...page, title: newName } : page)),\n });\n }\n },\n [currentDashboard, setCurrentDashboard, updatePageMutation]\n );\n\n // Handle duplicating a page\n const handlePageDuplicate = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || !dashboardId) return;\n\n const sourcePage = currentDashboard.pages.find((p) => p.id === pageId);\n if (!sourcePage) return;\n\n // Call GraphQL mutation to create a copy of the page\n // Note: 'order' is not available for create - backend auto-assigns order\n // Note: 'layout' should be PageLayout enum ('GRID', 'FLEX', 'FREEFORM'), not layoutConfig object\n const newPage = await createPageMutation({\n dashboardId,\n name: `${sourcePage.title} (Copy)`,\n });\n\n if (newPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: [\n ...currentDashboard.pages,\n {\n id: newPage.id,\n title: newPage.name,\n index: newPage.order,\n layoutConfig: newPage.layoutConfig || {},\n },\n ],\n });\n\n setActivePageId(newPage.id);\n }\n },\n [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]\n );\n\n // Handle deleting a page\n const handlePageDelete = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || currentDashboard.pages.length <= 1) return;\n\n // Call GraphQL mutation to delete page\n const success = await deletePageMutation(pageId);\n\n if (success) {\n const updatedPages = currentDashboard.pages.filter((p) => p.id !== pageId).map((p, index) => ({ ...p, index }));\n\n setCurrentDashboard({\n ...currentDashboard,\n pages: updatedPages,\n });\n\n // Switch to first page if current page was deleted\n if (activePageId === pageId) {\n setActivePageId(updatedPages[0]?.id || null);\n }\n }\n },\n [currentDashboard, setCurrentDashboard, activePageId, deletePageMutation]\n );\n\n // Handle reordering pages\n const handlePagesReorder = useCallback(\n async (reorderedPages: DashboardPage[]) => {\n if (!currentDashboard || !dashboardId) return;\n\n // Update local state optimistically\n setCurrentDashboard({\n ...currentDashboard,\n pages: reorderedPages.map((page, index) => ({\n ...page,\n index,\n })),\n });\n\n // Call GraphQL mutation to persist the reorder\n const pageIds = reorderedPages.map((p) => p.id);\n await reorderPagesMutation(dashboardId, pageIds);\n },\n [currentDashboard, dashboardId, setCurrentDashboard, reorderPagesMutation]\n );\n\n // Show loading state\n if (dashboardLoading || !currentDashboard) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"animate-spin w-8 h-8 border-4 border-action-primary-bg border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n // Show error state\n if (dashboardError) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full p-6\">\n <div className=\"bg-status-error-bg border border-status-error-border rounded-lg p-4 max-w-md\">\n <h3 className=\"text-sm font-medium text-status-error-text\">Failed to load dashboard</h3>\n <p className=\"text-sm text-text-secondary mt-1\">{dashboardError.message}</p>\n <button\n type=\"button\"\n onClick={() => navigate('/dashboards')}\n className=\"mt-3 px-3 py-1.5 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md\"\n >\n Back to Dashboards\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex h-full\">\n {/* Widget Panel (Left) */}\n <div\n className={`\n ${isWidgetPanelOpen ? 'w-72' : 'w-0'}\n flex-shrink-0\n bg-bg-surface\n border-r border-border-default\n transition-all duration-300\n overflow-hidden\n `}\n >\n {isWidgetPanelOpen && (\n <div className=\"h-full flex flex-col\">\n <div className=\"flex items-center justify-between p-3 border-b border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Widgets</h3>\n <button\n type=\"button\"\n onClick={() => setIsWidgetPanelOpen(false)}\n className=\"\n p-1 rounded-md\n text-text-secondary\n hover:text-text-primary\n hover:bg-bg-muted\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n <WidgetTypeSelector onSelect={handleQuickAddWidget} />\n </div>\n </div>\n )}\n </div>\n\n {/* Toggle Widget Panel Button */}\n {!isWidgetPanelOpen && (\n <button\n type=\"button\"\n onClick={() => setIsWidgetPanelOpen(true)}\n className=\"\n absolute left-0 top-1/2 -translate-y-1/2 z-20\n w-8 h-24\n bg-bg-surface\n border border-l-0 border-border-default\n rounded-r-lg\n flex items-center justify-center\n text-text-secondary\n hover:text-text-primary\n hover:bg-bg-muted\n transition-colors\n \"\n title=\"Open widget panel\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n )}\n\n {/* Main Canvas */}\n <div className=\"flex-1 relative\">\n <DashboardCanvas\n dashboardId={currentDashboard.id}\n pageId={activePageId || currentDashboard.pages?.[0]?.id}\n title={currentDashboard.title}\n onPaste={handlePaste}\n onSave={handleSave}\n isSaving={isSaving}\n onExport={handleExport}\n // Page tabs props\n pages={currentDashboard.pages.map((page) => ({\n id: page.id,\n name: page.title || `Page ${(page.index ?? 0) + 1}`,\n order: page.index ?? 0,\n }))}\n activePageId={activePageId || currentDashboard.pages?.[0]?.id}\n onPageChange={handlePageChange}\n onPageAdd={handlePageAdd}\n onPageRename={handlePageRename}\n onPageDuplicate={handlePageDuplicate}\n onPageDelete={handlePageDelete}\n onPagesReorder={handlePagesReorder}\n showPageTabs={true}\n />\n </div>\n\n {/* Export Dashboard Dialog */}\n <ExportDashboardDialog\n isOpen={isExportDialogOpen}\n dashboardId={currentDashboard.id}\n dashboardName={currentDashboard.title}\n onClose={handleExportClose}\n />\n </div>\n );\n});\n\nexport default DashboardBuilderPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardBuilderPage.tsx"],"sourcesContent":["/**\n * DashboardBuilderPage\n *\n * Full dashboard builder experience with side panels.\n */\n\nimport { type FC, memo, useState, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useBigConsoleNavigate } from '../context';\nimport { DashboardCanvas, ExportDashboardDialog } from '../components/dashboard';\nimport { WidgetTypeSelector } from '../components/widgets/WidgetTypeSelector';\nimport { useDashboardStore, useWidgetStore } from '../store';\nimport { useDashboardOperations, useWidgetOperations, usePageOperations } from '../hooks';\nimport type { WidgetType, Dashboard, DashboardPage } from '../types';\nimport { findNextAvailablePosition } from '../utils/widgetPositioning';\nimport { getWidgetDefinition } from '../components/widgets/WidgetRegistry';\n\n// Breadcrumb name registration helpers (get from shell at call time to avoid timing issues)\nfunction getBreadcrumbRegistry() {\n try {\n return (\n window as unknown as {\n __breadcrumbRegistry?: { register: (id: string, name: string) => void; unregister: (id: string) => void };\n }\n ).__breadcrumbRegistry;\n } catch {\n return undefined;\n }\n}\n\nfunction registerBreadcrumbName(id: string, name: string) {\n const registry = getBreadcrumbRegistry();\n if (registry?.register) {\n registry.register(id, name);\n }\n}\n\nfunction unregisterBreadcrumbName(id: string) {\n const registry = getBreadcrumbRegistry();\n if (registry?.unregister) {\n registry.unregister(id);\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardBuilderPage: FC = memo(function DashboardBuilderPage() {\n const { dashboardId } = useParams<{ dashboardId: string }>();\n const navigate = useBigConsoleNavigate();\n const [isWidgetPanelOpen, setIsWidgetPanelOpen] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n const [activePageId, setActivePageId] = useState<string | null>(null);\n\n // Dashboard operations from GraphQL\n const {\n fetchDashboard,\n updateDashboard,\n loading: dashboardLoading,\n error: dashboardError,\n } = useDashboardOperations();\n\n // Widget operations from GraphQL\n const {\n createWidget: createWidgetMutation,\n updateWidget: updateWidgetMutation,\n deleteWidget: deleteWidgetMutation,\n duplicateWidget: duplicateWidgetMutation,\n batchUpdatePositions,\n } = useWidgetOperations(undefined, dashboardId);\n\n // Page operations from GraphQL\n const {\n createPage: createPageMutation,\n updatePage: updatePageMutation,\n deletePage: deletePageMutation,\n reorderPages: reorderPagesMutation,\n } = usePageOperations();\n\n // Store actions\n const setCurrentDashboard = useDashboardStore((state) => state.setCurrentDashboard);\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const setViewMode = useDashboardStore((state) => state.setViewMode);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const widgets = useWidgetStore((state) => state.widgets);\n const clipboardWidgetId = useWidgetStore((state) => state.clipboardWidgetId);\n const clipboardAction = useWidgetStore((state) => state.clipboardAction);\n const clearClipboard = useWidgetStore((state) => state.clearClipboard);\n\n // Set edit mode on mount\n useEffect(() => {\n setViewMode('edit');\n return () => setViewMode('view');\n }, [setViewMode]);\n\n // Load dashboard data from API\n useEffect(() => {\n if (!dashboardId) {\n navigate('/dashboards');\n return;\n }\n\n // Fetch dashboard from API\n fetchDashboard(dashboardId).then((dashboard) => {\n if (dashboard) {\n setCurrentDashboard({\n id: dashboard.id,\n title: dashboard.name,\n description: dashboard.description || '',\n pages: dashboard.pages.map((page, index) => ({\n id: page.id,\n title: page.name || '',\n index,\n layoutConfig: page.layoutConfig || {},\n })) as any,\n createdAt: dashboard.createdAt,\n updatedAt: dashboard.updatedAt,\n } as unknown as Dashboard);\n // Set active page to first page\n if (dashboard.pages.length > 0 && !activePageId) {\n setActivePageId(dashboard.pages[0].id);\n }\n\n // Register dashboard name for breadcrumb display\n if (dashboard.name) {\n registerBreadcrumbName(dashboardId, dashboard.name);\n }\n } else {\n // Dashboard not found, redirect\n navigate('/dashboards');\n }\n });\n\n // Cleanup: unregister breadcrumb name on unmount\n return () => {\n if (dashboardId) {\n unregisterBreadcrumbName(dashboardId);\n }\n };\n }, [dashboardId, navigate, setCurrentDashboard, fetchDashboard, activePageId]);\n\n // Handle paste widget from clipboard\n const handlePaste = useCallback(async () => {\n if (!clipboardWidgetId || !dashboardId || !currentDashboard?.pages?.[0]?.id) {\n return;\n }\n\n const sourceWidget = widgets.get(clipboardWidgetId);\n if (!sourceWidget) {\n clearClipboard();\n return;\n }\n\n const pageId = currentDashboard.pages[0].id;\n\n // Use smart placement algorithm - horizontal first, then next row\n const widgetsArray = Array.from(widgets.values());\n const widgetSize = {\n width: sourceWidget.positionWidth ?? 4,\n height: sourceWidget.positionHeight ?? 4,\n };\n\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n\n const newPosition = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n const newTitle = clipboardAction === 'copy' ? `${sourceWidget.title} (Copy)` : sourceWidget.title;\n\n let newWidget = null;\n\n // Try duplicateWidget API first\n try {\n newWidget = await duplicateWidgetMutation(clipboardWidgetId, newPosition, newTitle);\n } catch (err) {}\n\n // Fallback: Create new widget with source widget's data\n if (!newWidget) {\n newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type: sourceWidget.type,\n title: newTitle,\n description: sourceWidget.description,\n // v2.0: Use dataSinkId and parserId instead of dataSourceId/dataPath\n dataSinkId: sourceWidget.dataSinkId ?? undefined,\n datasetId: sourceWidget.datasetId ?? undefined,\n parserId: sourceWidget.parserId ?? undefined,\n parserRules: sourceWidget.parserRules ?? undefined,\n config: sourceWidget.config || {},\n position: newPosition,\n refreshInterval: sourceWidget.refreshInterval ?? undefined,\n metadata: sourceWidget.metadata,\n });\n }\n\n if (newWidget) {\n selectWidget(newWidget.id);\n\n // If cut action, delete the original widget from backend\n if (clipboardAction === 'cut') {\n await deleteWidgetMutation(clipboardWidgetId);\n }\n }\n\n clearClipboard();\n }, [\n clipboardWidgetId,\n clipboardAction,\n dashboardId,\n currentDashboard,\n widgets,\n duplicateWidgetMutation,\n createWidgetMutation,\n deleteWidgetMutation,\n selectWidget,\n clearClipboard,\n ]);\n\n // Handle quick add widget from panel\n const handleQuickAddWidget = useCallback(\n async (type: WidgetType) => {\n if (!dashboardId || !currentDashboard?.pages?.[0]?.id) return;\n\n const pageId = currentDashboard.pages[0].id;\n const title = type\n .replace(/_/g, ' ')\n .toLowerCase()\n .replace(/^\\w/, (c) => c.toUpperCase());\n\n // Use smart placement algorithm - horizontal first, then next row\n const widgetsArray = Array.from(widgets.values());\n const definition = getWidgetDefinition(type);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n\n // Create widget via API\n const newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type,\n title,\n config: {},\n position: {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n },\n });\n\n if (newWidget) {\n selectWidget(newWidget.id);\n setIsWidgetPanelOpen(false);\n }\n },\n [dashboardId, currentDashboard, createWidgetMutation, selectWidget, widgets]\n );\n\n // Navigate back to the dashboards list\n const handleBack = useCallback(() => {\n navigate('/dashboards');\n }, [navigate]);\n\n // Handle save - save dirty widgets and batch update positions\n const handleSave = useCallback(async () => {\n if (!dashboardId) return;\n\n setIsSaving(true);\n try {\n // Get the LATEST state directly from store (avoid stale closure)\n const storeState = useWidgetStore.getState();\n const currentWidgets = Array.from(storeState.widgets.values());\n const currentDirtyIds = storeState.dirtyWidgetIds;\n\n // Debug: Log dirty widget IDs\n\n // 1. Save dirty widgets (property changes like title, config, etc.)\n const dirtyWidgets = currentWidgets.filter((w) => currentDirtyIds.has(w.id));\n\n if (dirtyWidgets.length > 0) {\n // Save each dirty widget\n const updatePromises = dirtyWidgets.map(async (widget) => {\n const updateInput = {\n id: widget.id,\n title: widget.title,\n config: widget.config,\n refreshInterval: widget.refreshInterval ?? undefined,\n // v2.0: Use dataSinkId and parserId instead of dataSourceId/dataPath\n dataSinkId: widget.dataSinkId ?? undefined,\n datasetId: widget.datasetId ?? undefined,\n parserId: widget.parserId ?? undefined,\n parserRules: widget.parserRules ?? undefined,\n };\n\n try {\n const result = await updateWidgetMutation(updateInput);\n return result;\n } catch (err) {\n throw err;\n }\n });\n\n const results = await Promise.all(updatePromises);\n\n // Clear dirty flags after successful save\n storeState.clearAllDirty();\n } else {\n }\n\n // 2. Batch update widget positions\n if (currentWidgets.length > 0) {\n const positionUpdates = currentWidgets.map((widget) => ({\n id: widget.id,\n position: widget.position || {\n x: widget.positionX ?? 0,\n y: widget.positionY ?? 0,\n width: widget.positionWidth ?? 4,\n height: widget.positionHeight ?? 4,\n },\n }));\n\n await batchUpdatePositions(positionUpdates);\n }\n\n // 3. Update dashboard metadata if needed\n if (currentDashboard) {\n await updateDashboard({\n id: dashboardId,\n name: currentDashboard.title,\n description: currentDashboard.description,\n });\n }\n } catch (error) {\n } finally {\n setIsSaving(false);\n }\n }, [dashboardId, currentDashboard, updateWidgetMutation, batchUpdatePositions, updateDashboard]);\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // ============================================================================\n // Page Management Callbacks\n // ============================================================================\n\n // Handle page selection\n const handlePageChange = useCallback((pageId: string) => {\n setActivePageId(pageId);\n }, []);\n\n // Handle adding a new page\n const handlePageAdd = useCallback(async () => {\n if (!currentDashboard || !dashboardId) return;\n\n const newPageCount = currentDashboard.pages.length + 1;\n const newPageName = `Page ${newPageCount}`;\n\n // Call GraphQL mutation to create page\n // Note: 'order' is not available for create - backend auto-assigns order\n const newPage = await createPageMutation({\n dashboardId,\n name: newPageName,\n });\n\n if (newPage) {\n // Update local state with the created page\n setCurrentDashboard({\n ...currentDashboard,\n pages: [\n ...currentDashboard.pages,\n {\n id: newPage.id,\n title: newPage.name,\n index: newPage.order,\n layoutConfig: newPage.layoutConfig || {},\n },\n ],\n });\n\n // Switch to the new page\n setActivePageId(newPage.id);\n }\n }, [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]);\n\n // Handle renaming a page\n const handlePageRename = useCallback(\n async (pageId: string, newName: string) => {\n if (!currentDashboard) return;\n\n // Call GraphQL mutation to update page\n const updatedPage = await updatePageMutation({\n id: pageId,\n name: newName,\n });\n\n if (updatedPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: currentDashboard.pages.map((page) => (page.id === pageId ? { ...page, title: newName } : page)),\n });\n }\n },\n [currentDashboard, setCurrentDashboard, updatePageMutation]\n );\n\n // Handle duplicating a page\n const handlePageDuplicate = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || !dashboardId) return;\n\n const sourcePage = currentDashboard.pages.find((p) => p.id === pageId);\n if (!sourcePage) return;\n\n // Call GraphQL mutation to create a copy of the page\n // Note: 'order' is not available for create - backend auto-assigns order\n // Note: 'layout' should be PageLayout enum ('GRID', 'FLEX', 'FREEFORM'), not layoutConfig object\n const newPage = await createPageMutation({\n dashboardId,\n name: `${sourcePage.title} (Copy)`,\n });\n\n if (newPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: [\n ...currentDashboard.pages,\n {\n id: newPage.id,\n title: newPage.name,\n index: newPage.order,\n layoutConfig: newPage.layoutConfig || {},\n },\n ],\n });\n\n setActivePageId(newPage.id);\n }\n },\n [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]\n );\n\n // Handle deleting a page\n const handlePageDelete = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || currentDashboard.pages.length <= 1) return;\n\n // Call GraphQL mutation to delete page\n const success = await deletePageMutation(pageId);\n\n if (success) {\n const updatedPages = currentDashboard.pages.filter((p) => p.id !== pageId).map((p, index) => ({ ...p, index }));\n\n setCurrentDashboard({\n ...currentDashboard,\n pages: updatedPages,\n });\n\n // Switch to first page if current page was deleted\n if (activePageId === pageId) {\n setActivePageId(updatedPages[0]?.id || null);\n }\n }\n },\n [currentDashboard, setCurrentDashboard, activePageId, deletePageMutation]\n );\n\n // Handle reordering pages\n const handlePagesReorder = useCallback(\n async (reorderedPages: DashboardPage[]) => {\n if (!currentDashboard || !dashboardId) return;\n\n // Update local state optimistically\n setCurrentDashboard({\n ...currentDashboard,\n pages: reorderedPages.map((page, index) => ({\n ...page,\n index,\n })),\n });\n\n // Call GraphQL mutation to persist the reorder\n const pageIds = reorderedPages.map((p) => p.id);\n await reorderPagesMutation(dashboardId, pageIds);\n },\n [currentDashboard, dashboardId, setCurrentDashboard, reorderPagesMutation]\n );\n\n // Show loading state\n if (dashboardLoading || !currentDashboard) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"animate-spin w-8 h-8 border-4 border-action-primary-bg border-t-transparent rounded-full\" />\n </div>\n );\n }\n\n // Show error state\n if (dashboardError) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full p-6\">\n <div className=\"bg-status-error-bg border border-status-error-border rounded-lg p-4 max-w-md\">\n <h3 className=\"text-sm font-medium text-status-error-text\">Failed to load dashboard</h3>\n <p className=\"text-sm text-text-secondary mt-1\">{dashboardError.message}</p>\n <button\n type=\"button\"\n onClick={() => navigate('/dashboards')}\n className=\"mt-3 px-3 py-1.5 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md\"\n >\n Back to Dashboards\n </button>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Header / Toolbar */}\n <div className=\"flex items-center gap-3 px-3 py-2 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <Button\n onClick={handleBack}\n variant=\"ghost\"\n size=\"sm\"\n title=\"Back to Dashboards\"\n aria-label=\"Back to Dashboards\"\n >\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back\n </Button>\n <h2 className=\"text-sm font-semibold text-text-primary truncate\">{currentDashboard.title}</h2>\n </div>\n\n {/* Builder Body */}\n <div className=\"flex flex-1 min-h-0 relative\">\n {/* Widget Panel (Left) */}\n <div\n className={`\n ${isWidgetPanelOpen ? 'w-72' : 'w-0'}\n flex-shrink-0\n bg-bg-surface\n border-r border-border-default\n transition-all duration-300\n overflow-hidden\n `}\n >\n {isWidgetPanelOpen && (\n <div className=\"h-full flex flex-col\">\n <div className=\"flex items-center justify-between p-3 border-b border-border-default\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Widgets</h3>\n <button\n type=\"button\"\n onClick={() => setIsWidgetPanelOpen(false)}\n className=\"\n p-1 rounded-md\n text-text-secondary\n hover:text-text-primary\n hover:bg-bg-muted\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"flex-1 overflow-auto\">\n <WidgetTypeSelector onSelect={handleQuickAddWidget} />\n </div>\n </div>\n )}\n </div>\n\n {/* Toggle Widget Panel Button */}\n {!isWidgetPanelOpen && (\n <button\n type=\"button\"\n onClick={() => setIsWidgetPanelOpen(true)}\n className=\"\n absolute left-0 top-1/2 -translate-y-1/2 z-20\n w-8 h-24\n bg-bg-surface\n border border-l-0 border-border-default\n rounded-r-lg\n flex items-center justify-center\n text-text-secondary\n hover:text-text-primary\n hover:bg-bg-muted\n transition-colors\n \"\n title=\"Open widget panel\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n )}\n\n {/* Main Canvas */}\n <div className=\"flex-1 relative\">\n <DashboardCanvas\n dashboardId={currentDashboard.id}\n pageId={activePageId || currentDashboard.pages?.[0]?.id}\n title={currentDashboard.title}\n onPaste={handlePaste}\n onSave={handleSave}\n isSaving={isSaving}\n onExport={handleExport}\n // Page tabs props\n pages={currentDashboard.pages.map((page) => ({\n id: page.id,\n name: page.title || `Page ${(page.index ?? 0) + 1}`,\n order: page.index ?? 0,\n }))}\n activePageId={activePageId || currentDashboard.pages?.[0]?.id}\n onPageChange={handlePageChange}\n onPageAdd={handlePageAdd}\n onPageRename={handlePageRename}\n onPageDuplicate={handlePageDuplicate}\n onPageDelete={handlePageDelete}\n onPagesReorder={handlePagesReorder}\n showPageTabs={true}\n />\n </div>\n\n {/* Export Dashboard Dialog */}\n <ExportDashboardDialog\n isOpen={isExportDialogOpen}\n dashboardId={currentDashboard.id}\n dashboardName={currentDashboard.title}\n onClose={handleExportClose}\n />\n </div>\n </div>\n );\n});\n\nexport default DashboardBuilderPage;\n"],"mappings":"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"}
@@ -203,20 +203,21 @@ var g = u(function() {
203
203
  J
204
204
  ]);
205
205
  return R ? /* @__PURE__ */ m("div", {
206
- className: "flex flex-col items-center justify-center h-full p-6",
206
+ className: "flex flex-col items-center justify-center h-full",
207
+ style: { padding: "var(--space-pagePadding)" },
207
208
  children: /* @__PURE__ */ h("div", {
208
- className: "bg-status-error-bg border border-status-error-border rounded-lg p-6 max-w-md",
209
+ className: "bg-status-error-bg border border-status-error-border rounded-lg p-6 w-full max-w-md",
209
210
  children: [
210
211
  /* @__PURE__ */ m("h3", {
211
212
  className: "text-lg font-medium text-status-error-text mb-2",
212
213
  children: "Error loading dashboard"
213
214
  }),
214
215
  /* @__PURE__ */ m("p", {
215
- className: "text-sm text-text-secondary mb-4",
216
+ className: "text-sm text-text-secondary mb-4 break-words",
216
217
  children: R.message
217
218
  }),
218
219
  /* @__PURE__ */ h("div", {
219
- className: "flex gap-3",
220
+ className: "flex flex-wrap gap-3",
220
221
  children: [/* @__PURE__ */ m("button", {
221
222
  type: "button",
222
223
  onClick: () => z(u),
@@ -232,9 +233,10 @@ var g = u(function() {
232
233
  ]
233
234
  })
234
235
  }) : x ? /* @__PURE__ */ m("div", {
235
- className: "flex flex-col items-center justify-center h-full p-6",
236
+ className: "flex flex-col items-center justify-center h-full",
237
+ style: { padding: "var(--space-pagePadding)" },
236
238
  children: /* @__PURE__ */ h("div", {
237
- className: "bg-bg-surface border border-border-default rounded-lg p-6 max-w-md text-center",
239
+ className: "bg-bg-surface border border-border-default rounded-lg p-6 w-full max-w-md text-center",
238
240
  children: [
239
241
  /* @__PURE__ */ m("svg", {
240
242
  className: "w-16 h-16 mx-auto mb-4 text-text-tertiary",
@@ -257,7 +259,7 @@ var g = u(function() {
257
259
  children: "The dashboard you're looking for doesn't exist or has been deleted."
258
260
  }),
259
261
  /* @__PURE__ */ h("p", {
260
- className: "text-xs text-text-tertiary mb-4 font-mono",
262
+ className: "text-xs text-text-tertiary mb-4 font-mono break-all",
261
263
  children: ["ID: ", u]
262
264
  }),
263
265
  /* @__PURE__ */ m("button", {
@@ -278,7 +280,7 @@ var g = u(function() {
278
280
  })]
279
281
  })
280
282
  }) : /* @__PURE__ */ h("div", {
281
- className: "h-full flex flex-col",
283
+ className: "h-full flex flex-col overflow-hidden",
282
284
  children: [
283
285
  T && /* @__PURE__ */ m(l, {
284
286
  currentDashboardId: O.id,
@@ -286,7 +288,7 @@ var g = u(function() {
286
288
  basePath: "/bigconsole/dashboards"
287
289
  }),
288
290
  /* @__PURE__ */ m("div", {
289
- className: "flex-1",
291
+ className: "flex-1 min-w-0 overflow-hidden",
290
292
  children: /* @__PURE__ */ m(s, {
291
293
  dashboardId: O.id,
292
294
  pageId: _ || O.pages?.[0]?.id,
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardViewPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardViewPage.tsx"],"sourcesContent":["/**\n * DashboardViewPage\n *\n * View/edit a single dashboard - fetches data from real backend.\n * Supports drilldown context propagation via URL parameters.\n */\n\nimport { type FC, memo, useEffect, useCallback, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBigConsoleNavigate } from '../context';\nimport { DashboardCanvas, ExportDashboardDialog, DrilldownBreadcrumb } from '../components/dashboard';\nimport { useDashboardStore, useWidgetStore } from '../store';\nimport { useDashboardOperations, useWidgetOperations, usePageOperations } from '../hooks';\nimport { useFilterUrlSync } from '../hooks/useFilterUrlSync';\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardViewPage: FC = memo(function DashboardViewPage() {\n const { dashboardId } = useParams<{ dashboardId: string }>();\n const navigate = useBigConsoleNavigate();\n const [currentPageId, setCurrentPageId] = useState<string | null>(null);\n const [isSaving, setIsSaving] = useState(false);\n const [notFound, setNotFound] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n\n // Drilldown context from URL\n const { isInDrilldown, getContextFromUrl, getDrilldownMetadata } = useFilterUrlSync();\n\n // Store actions\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const setCurrentDashboard = useDashboardStore((state) => state.setCurrentDashboard);\n const setGlobalFilterValue = useDashboardStore((state) => state.setGlobalFilterValue);\n const clearAllGlobalFilters = useDashboardStore((state) => state.clearAllGlobalFilters);\n const widgets = useWidgetStore((state) => state.widgets);\n const clipboardWidgetId = useWidgetStore((state) => state.clipboardWidgetId);\n const clipboardAction = useWidgetStore((state) => state.clipboardAction);\n const clearClipboard = useWidgetStore((state) => state.clearClipboard);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n\n // Dashboard operations hook\n const { loading, error, fetchDashboard, updateDashboard } = useDashboardOperations();\n\n // Widget operations hook\n const {\n createWidget: createWidgetMutation,\n deleteWidget: deleteWidgetMutation,\n duplicateWidget: duplicateWidgetMutation,\n batchUpdatePositions,\n } = useWidgetOperations(currentPageId || undefined, dashboardId);\n\n // Page operations hook\n const {\n createPage: createPageMutation,\n updatePage: updatePageMutation,\n deletePage: deletePageMutation,\n reorderPages: reorderPagesMutation,\n } = usePageOperations();\n\n // Load dashboard data\n useEffect(() => {\n if (!dashboardId) {\n navigate('/dashboards');\n return;\n }\n\n // Clear stale global filters from previous dashboard before loading new one.\n // This prevents drilldown context filters (ctx_*) from the source dashboard\n // from persisting and incorrectly filtering data on the target dashboard.\n clearAllGlobalFilters();\n\n setNotFound(false);\n\n // Fetch dashboard from backend\n fetchDashboard(dashboardId).then((dashboard) => {\n if (dashboard) {\n // Set the first page as current page\n if (dashboard.pages && dashboard.pages.length > 0) {\n setCurrentPageId(dashboard.pages[0].id);\n }\n } else {\n // Dashboard not found\n setNotFound(true);\n }\n });\n\n // Cleanup\n return () => {\n setCurrentDashboard(null);\n clearAllGlobalFilters();\n setNotFound(false);\n };\n }, [dashboardId, navigate, fetchDashboard, setCurrentDashboard, clearAllGlobalFilters]);\n\n // Load drilldown context from URL and apply to global filters\n useEffect(() => {\n if (!isInDrilldown) return;\n\n // Get context params from URL (ctx_* params)\n const contextParams = getContextFromUrl();\n const drilldownMeta = getDrilldownMetadata();\n\n // Apply context to dashboard state (available for widgets to use)\n // Store as special filter values that widgets can reference\n Object.entries(contextParams).forEach(([key, value]) => {\n // Store context params as global filter values with ctx_ prefix for clarity\n setGlobalFilterValue(`ctx_${key}`, value);\n });\n\n // Store drilldown metadata as special values\n if (drilldownMeta.depth > 0) {\n setGlobalFilterValue('__drilldown_depth', drilldownMeta.depth);\n setGlobalFilterValue('__drilldown_parent', drilldownMeta.parent);\n setGlobalFilterValue('__drilldown_path', drilldownMeta.path);\n }\n\n // Log for debugging\n if (Object.keys(contextParams).length > 0) {\n console.debug('[DashboardViewPage] Loaded drilldown context:', {\n context: contextParams,\n metadata: drilldownMeta,\n });\n }\n }, [isInDrilldown, getContextFromUrl, getDrilldownMetadata, setGlobalFilterValue]);\n\n // Handle save - batch update widget positions and dashboard metadata\n const handleSave = useCallback(async () => {\n if (!dashboardId) return;\n\n setIsSaving(true);\n try {\n // Get all widgets and their positions\n const widgetList = Array.from(widgets.values());\n if (widgetList.length > 0) {\n const positionUpdates = widgetList.map((widget) => ({\n id: widget.id,\n position: widget.position,\n }));\n\n await batchUpdatePositions(positionUpdates);\n }\n\n // Update dashboard metadata if needed\n if (currentDashboard) {\n await updateDashboard({\n id: dashboardId,\n name: currentDashboard.name,\n description: currentDashboard.description,\n });\n }\n } catch (error) {\n } finally {\n setIsSaving(false);\n }\n }, [dashboardId, widgets, currentDashboard, batchUpdatePositions, updateDashboard]);\n\n // Handle page change\n const handlePageChange = useCallback((pageId: string) => {\n setCurrentPageId(pageId);\n }, []);\n\n // Handle paste widget from clipboard\n const handlePaste = useCallback(async () => {\n const pageId = currentPageId || currentDashboard?.pages?.[0]?.id;\n if (!clipboardWidgetId || !dashboardId || !pageId) {\n return;\n }\n\n const sourceWidget = widgets.get(clipboardWidgetId);\n if (!sourceWidget) {\n clearClipboard();\n return;\n }\n\n // Calculate new position (place at bottom of existing widgets)\n const widgetsArray = Array.from(widgets.values());\n const maxY = widgetsArray.reduce((max, w) => {\n const widgetBottom = (w.positionY ?? 0) + (w.positionHeight ?? 4);\n return Math.max(max, widgetBottom);\n }, 0);\n\n const newPosition = {\n x: 0,\n y: maxY,\n width: sourceWidget.positionWidth ?? 4,\n height: sourceWidget.positionHeight ?? 4,\n };\n\n const newTitle = clipboardAction === 'copy' ? `${sourceWidget.title} (Copy)` : sourceWidget.title;\n\n let newWidget = null;\n\n // Try duplicateWidget API first\n try {\n newWidget = await duplicateWidgetMutation(clipboardWidgetId, newPosition, newTitle);\n } catch (err) {}\n\n // Fallback: Create new widget with source widget's data\n if (!newWidget) {\n newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type: sourceWidget.type,\n title: newTitle,\n description: sourceWidget.description,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: (sourceWidget as unknown as Record<string, string | undefined>).dataSinkId,\n datasetId: (sourceWidget as unknown as Record<string, string | undefined>).datasetId,\n parserId: (sourceWidget as unknown as Record<string, string | undefined>).parserId,\n parserRules: (sourceWidget as unknown as Record<string, string | undefined>).parserRules,\n config: sourceWidget.config || {},\n position: newPosition,\n refreshInterval: sourceWidget.refreshInterval ?? undefined,\n metadata: sourceWidget.metadata,\n });\n }\n\n if (newWidget) {\n selectWidget(newWidget.id);\n\n // If cut action, delete the original widget from backend\n if (clipboardAction === 'cut') {\n await deleteWidgetMutation(clipboardWidgetId);\n }\n }\n\n clearClipboard();\n }, [\n clipboardWidgetId,\n clipboardAction,\n dashboardId,\n currentPageId,\n currentDashboard,\n widgets,\n duplicateWidgetMutation,\n createWidgetMutation,\n deleteWidgetMutation,\n selectWidget,\n clearClipboard,\n ]);\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // ============================================================================\n // Page Management Callbacks\n // ============================================================================\n\n // Handle adding a new page\n const handlePageAdd = useCallback(async () => {\n if (!currentDashboard || !dashboardId) return;\n\n const newPageCount = (currentDashboard.pages?.length || 0) + 1;\n const newPageName = `Page ${newPageCount}`;\n\n // Call GraphQL mutation to create page\n // Note: 'order' is not available for create - backend auto-assigns order\n const newPage = await createPageMutation({\n dashboardId,\n name: newPageName,\n });\n\n if (newPage) {\n // Update local state with the created page\n setCurrentDashboard({\n ...currentDashboard,\n pages: [...(currentDashboard.pages || []), newPage],\n });\n\n // Switch to the new page\n setCurrentPageId(newPage.id);\n }\n }, [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]);\n\n // Handle renaming a page\n const handlePageRename = useCallback(\n async (pageId: string, newName: string) => {\n if (!currentDashboard) return;\n\n // Call GraphQL mutation to update page\n const updatedPage = await updatePageMutation({\n id: pageId,\n name: newName,\n });\n\n if (updatedPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: currentDashboard.pages?.map((page) => (page.id === pageId ? { ...page, name: newName } : page)),\n });\n }\n },\n [currentDashboard, setCurrentDashboard, updatePageMutation]\n );\n\n // Handle duplicating a page\n const handlePageDuplicate = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || !dashboardId) return;\n\n const sourcePage = currentDashboard.pages?.find((p) => p.id === pageId);\n if (!sourcePage) return;\n\n // Call GraphQL mutation to create a copy of the page\n // Note: 'order' is not available for create - backend auto-assigns order\n const newPage = await createPageMutation({\n dashboardId,\n name: `${sourcePage.name} (Copy)`,\n });\n\n if (newPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: [...(currentDashboard.pages || []), newPage],\n });\n\n setCurrentPageId(newPage.id);\n }\n // Note: Widget duplication would require additional backend support\n },\n [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]\n );\n\n // Handle deleting a page\n const handlePageDelete = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || (currentDashboard.pages?.length || 0) <= 1) return;\n\n // Call GraphQL mutation to delete page\n const success = await deletePageMutation(pageId);\n\n if (success) {\n const updatedPages = currentDashboard.pages\n ?.filter((p) => p.id !== pageId)\n .map((p, index) => ({ ...p, order: index }));\n\n setCurrentDashboard({\n ...currentDashboard,\n pages: updatedPages,\n });\n\n // Switch to first page if current page was deleted\n if (currentPageId === pageId) {\n setCurrentPageId(updatedPages?.[0]?.id || null);\n }\n }\n },\n [currentDashboard, setCurrentDashboard, currentPageId, deletePageMutation]\n );\n\n // Handle reordering pages\n const handlePagesReorder = useCallback(\n async (reorderedPages: Array<{ id: string; name?: string; order?: number }>) => {\n if (!currentDashboard || !dashboardId) return;\n\n // Update local state optimistically\n setCurrentDashboard({\n ...currentDashboard,\n pages: reorderedPages.map((page, index) => ({\n ...page,\n order: index,\n })),\n });\n\n // Call GraphQL mutation to persist the reorder\n const pageIds = reorderedPages.map((p) => p.id);\n await reorderPagesMutation(dashboardId, pageIds);\n },\n [currentDashboard, dashboardId, setCurrentDashboard, reorderPagesMutation]\n );\n\n // Error state\n if (error) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full p-6\">\n <div className=\"bg-status-error-bg border border-status-error-border rounded-lg p-6 max-w-md\">\n <h3 className=\"text-lg font-medium text-status-error-text mb-2\">Error loading dashboard</h3>\n <p className=\"text-sm text-text-secondary mb-4\">{error.message}</p>\n <div className=\"flex gap-3\">\n <button\n type=\"button\"\n onClick={() => fetchDashboard(dashboardId!)}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md\"\n >\n Retry\n </button>\n <button\n type=\"button\"\n onClick={() => navigate('/dashboards')}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary bg-bg-muted hover:bg-bg-muted/80 rounded-md\"\n >\n Back to Dashboards\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n // Not found state\n if (notFound) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full p-6\">\n <div className=\"bg-bg-surface border border-border-default rounded-lg p-6 max-w-md text-center\">\n <svg\n className=\"w-16 h-16 mx-auto mb-4 text-text-tertiary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <h3 className=\"text-lg font-medium text-text-primary mb-2\">Dashboard not found</h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n The dashboard you&apos;re looking for doesn&apos;t exist or has been deleted.\n </p>\n <p className=\"text-xs text-text-tertiary mb-4 font-mono\">ID: {dashboardId}</p>\n <button\n type=\"button\"\n onClick={() => navigate('/dashboards')}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md\"\n >\n Back to Dashboards\n </button>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (loading || !currentDashboard) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"flex flex-col items-center gap-4\">\n <div className=\"animate-spin w-8 h-8 border-4 border-action-primary-bg border-t-transparent rounded-full\" />\n <p className=\"text-sm text-text-secondary\">Loading dashboard...</p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"h-full flex flex-col\">\n {/* Drilldown Breadcrumb - shown when navigating via drilldown */}\n {isInDrilldown && (\n <DrilldownBreadcrumb\n currentDashboardId={currentDashboard.id}\n currentDashboardName={currentDashboard.name}\n basePath=\"/bigconsole/dashboards\"\n />\n )}\n\n {/* Dashboard Canvas with Page Tabs */}\n <div className=\"flex-1\">\n <DashboardCanvas\n dashboardId={currentDashboard.id}\n pageId={currentPageId || currentDashboard.pages?.[0]?.id}\n title={currentDashboard.name}\n onPaste={handlePaste}\n onSave={handleSave}\n isSaving={isSaving}\n onExport={handleExport}\n // Page tabs props\n pages={(currentDashboard.pages || []).map((page, index) => ({\n id: page.id,\n name: page.name || `Page ${index + 1}`,\n order: page.order ?? 0,\n }))}\n activePageId={currentPageId || currentDashboard.pages?.[0]?.id}\n onPageChange={handlePageChange}\n onPageAdd={handlePageAdd}\n onPageRename={handlePageRename}\n onPageDuplicate={handlePageDuplicate}\n onPageDelete={handlePageDelete}\n onPagesReorder={handlePagesReorder}\n showPageTabs={true}\n />\n </div>\n\n {/* Export Dashboard Dialog */}\n <ExportDashboardDialog\n isOpen={isExportDialogOpen}\n dashboardId={currentDashboard.id}\n dashboardName={currentDashboard.name}\n onClose={handleExportClose}\n />\n </div>\n );\n});\n\nexport default DashboardViewPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardViewPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardViewPage.tsx"],"sourcesContent":["/**\n * DashboardViewPage\n *\n * View/edit a single dashboard - fetches data from real backend.\n * Supports drilldown context propagation via URL parameters.\n */\n\nimport { type FC, memo, useEffect, useCallback, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useBigConsoleNavigate } from '../context';\nimport { DashboardCanvas, ExportDashboardDialog, DrilldownBreadcrumb } from '../components/dashboard';\nimport { useDashboardStore, useWidgetStore } from '../store';\nimport { useDashboardOperations, useWidgetOperations, usePageOperations } from '../hooks';\nimport { useFilterUrlSync } from '../hooks/useFilterUrlSync';\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardViewPage: FC = memo(function DashboardViewPage() {\n const { dashboardId } = useParams<{ dashboardId: string }>();\n const navigate = useBigConsoleNavigate();\n const [currentPageId, setCurrentPageId] = useState<string | null>(null);\n const [isSaving, setIsSaving] = useState(false);\n const [notFound, setNotFound] = useState(false);\n const [isExportDialogOpen, setIsExportDialogOpen] = useState(false);\n\n // Drilldown context from URL\n const { isInDrilldown, getContextFromUrl, getDrilldownMetadata } = useFilterUrlSync();\n\n // Store actions\n const currentDashboard = useDashboardStore((state) => state.currentDashboard);\n const setCurrentDashboard = useDashboardStore((state) => state.setCurrentDashboard);\n const setGlobalFilterValue = useDashboardStore((state) => state.setGlobalFilterValue);\n const clearAllGlobalFilters = useDashboardStore((state) => state.clearAllGlobalFilters);\n const widgets = useWidgetStore((state) => state.widgets);\n const clipboardWidgetId = useWidgetStore((state) => state.clipboardWidgetId);\n const clipboardAction = useWidgetStore((state) => state.clipboardAction);\n const clearClipboard = useWidgetStore((state) => state.clearClipboard);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n\n // Dashboard operations hook\n const { loading, error, fetchDashboard, updateDashboard } = useDashboardOperations();\n\n // Widget operations hook\n const {\n createWidget: createWidgetMutation,\n deleteWidget: deleteWidgetMutation,\n duplicateWidget: duplicateWidgetMutation,\n batchUpdatePositions,\n } = useWidgetOperations(currentPageId || undefined, dashboardId);\n\n // Page operations hook\n const {\n createPage: createPageMutation,\n updatePage: updatePageMutation,\n deletePage: deletePageMutation,\n reorderPages: reorderPagesMutation,\n } = usePageOperations();\n\n // Load dashboard data\n useEffect(() => {\n if (!dashboardId) {\n navigate('/dashboards');\n return;\n }\n\n // Clear stale global filters from previous dashboard before loading new one.\n // This prevents drilldown context filters (ctx_*) from the source dashboard\n // from persisting and incorrectly filtering data on the target dashboard.\n clearAllGlobalFilters();\n\n setNotFound(false);\n\n // Fetch dashboard from backend\n fetchDashboard(dashboardId).then((dashboard) => {\n if (dashboard) {\n // Set the first page as current page\n if (dashboard.pages && dashboard.pages.length > 0) {\n setCurrentPageId(dashboard.pages[0].id);\n }\n } else {\n // Dashboard not found\n setNotFound(true);\n }\n });\n\n // Cleanup\n return () => {\n setCurrentDashboard(null);\n clearAllGlobalFilters();\n setNotFound(false);\n };\n }, [dashboardId, navigate, fetchDashboard, setCurrentDashboard, clearAllGlobalFilters]);\n\n // Load drilldown context from URL and apply to global filters\n useEffect(() => {\n if (!isInDrilldown) return;\n\n // Get context params from URL (ctx_* params)\n const contextParams = getContextFromUrl();\n const drilldownMeta = getDrilldownMetadata();\n\n // Apply context to dashboard state (available for widgets to use)\n // Store as special filter values that widgets can reference\n Object.entries(contextParams).forEach(([key, value]) => {\n // Store context params as global filter values with ctx_ prefix for clarity\n setGlobalFilterValue(`ctx_${key}`, value);\n });\n\n // Store drilldown metadata as special values\n if (drilldownMeta.depth > 0) {\n setGlobalFilterValue('__drilldown_depth', drilldownMeta.depth);\n setGlobalFilterValue('__drilldown_parent', drilldownMeta.parent);\n setGlobalFilterValue('__drilldown_path', drilldownMeta.path);\n }\n\n // Log for debugging\n if (Object.keys(contextParams).length > 0) {\n console.debug('[DashboardViewPage] Loaded drilldown context:', {\n context: contextParams,\n metadata: drilldownMeta,\n });\n }\n }, [isInDrilldown, getContextFromUrl, getDrilldownMetadata, setGlobalFilterValue]);\n\n // Handle save - batch update widget positions and dashboard metadata\n const handleSave = useCallback(async () => {\n if (!dashboardId) return;\n\n setIsSaving(true);\n try {\n // Get all widgets and their positions\n const widgetList = Array.from(widgets.values());\n if (widgetList.length > 0) {\n const positionUpdates = widgetList.map((widget) => ({\n id: widget.id,\n position: widget.position,\n }));\n\n await batchUpdatePositions(positionUpdates);\n }\n\n // Update dashboard metadata if needed\n if (currentDashboard) {\n await updateDashboard({\n id: dashboardId,\n name: currentDashboard.name,\n description: currentDashboard.description,\n });\n }\n } catch (error) {\n } finally {\n setIsSaving(false);\n }\n }, [dashboardId, widgets, currentDashboard, batchUpdatePositions, updateDashboard]);\n\n // Handle page change\n const handlePageChange = useCallback((pageId: string) => {\n setCurrentPageId(pageId);\n }, []);\n\n // Handle paste widget from clipboard\n const handlePaste = useCallback(async () => {\n const pageId = currentPageId || currentDashboard?.pages?.[0]?.id;\n if (!clipboardWidgetId || !dashboardId || !pageId) {\n return;\n }\n\n const sourceWidget = widgets.get(clipboardWidgetId);\n if (!sourceWidget) {\n clearClipboard();\n return;\n }\n\n // Calculate new position (place at bottom of existing widgets)\n const widgetsArray = Array.from(widgets.values());\n const maxY = widgetsArray.reduce((max, w) => {\n const widgetBottom = (w.positionY ?? 0) + (w.positionHeight ?? 4);\n return Math.max(max, widgetBottom);\n }, 0);\n\n const newPosition = {\n x: 0,\n y: maxY,\n width: sourceWidget.positionWidth ?? 4,\n height: sourceWidget.positionHeight ?? 4,\n };\n\n const newTitle = clipboardAction === 'copy' ? `${sourceWidget.title} (Copy)` : sourceWidget.title;\n\n let newWidget = null;\n\n // Try duplicateWidget API first\n try {\n newWidget = await duplicateWidgetMutation(clipboardWidgetId, newPosition, newTitle);\n } catch (err) {}\n\n // Fallback: Create new widget with source widget's data\n if (!newWidget) {\n newWidget = await createWidgetMutation({\n pageId,\n dashboardId,\n type: sourceWidget.type,\n title: newTitle,\n description: sourceWidget.description,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: (sourceWidget as unknown as Record<string, string | undefined>).dataSinkId,\n datasetId: (sourceWidget as unknown as Record<string, string | undefined>).datasetId,\n parserId: (sourceWidget as unknown as Record<string, string | undefined>).parserId,\n parserRules: (sourceWidget as unknown as Record<string, string | undefined>).parserRules,\n config: sourceWidget.config || {},\n position: newPosition,\n refreshInterval: sourceWidget.refreshInterval ?? undefined,\n metadata: sourceWidget.metadata,\n });\n }\n\n if (newWidget) {\n selectWidget(newWidget.id);\n\n // If cut action, delete the original widget from backend\n if (clipboardAction === 'cut') {\n await deleteWidgetMutation(clipboardWidgetId);\n }\n }\n\n clearClipboard();\n }, [\n clipboardWidgetId,\n clipboardAction,\n dashboardId,\n currentPageId,\n currentDashboard,\n widgets,\n duplicateWidgetMutation,\n createWidgetMutation,\n deleteWidgetMutation,\n selectWidget,\n clearClipboard,\n ]);\n\n // Handle export\n const handleExport = useCallback(() => {\n setIsExportDialogOpen(true);\n }, []);\n\n const handleExportClose = useCallback(() => {\n setIsExportDialogOpen(false);\n }, []);\n\n // ============================================================================\n // Page Management Callbacks\n // ============================================================================\n\n // Handle adding a new page\n const handlePageAdd = useCallback(async () => {\n if (!currentDashboard || !dashboardId) return;\n\n const newPageCount = (currentDashboard.pages?.length || 0) + 1;\n const newPageName = `Page ${newPageCount}`;\n\n // Call GraphQL mutation to create page\n // Note: 'order' is not available for create - backend auto-assigns order\n const newPage = await createPageMutation({\n dashboardId,\n name: newPageName,\n });\n\n if (newPage) {\n // Update local state with the created page\n setCurrentDashboard({\n ...currentDashboard,\n pages: [...(currentDashboard.pages || []), newPage],\n });\n\n // Switch to the new page\n setCurrentPageId(newPage.id);\n }\n }, [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]);\n\n // Handle renaming a page\n const handlePageRename = useCallback(\n async (pageId: string, newName: string) => {\n if (!currentDashboard) return;\n\n // Call GraphQL mutation to update page\n const updatedPage = await updatePageMutation({\n id: pageId,\n name: newName,\n });\n\n if (updatedPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: currentDashboard.pages?.map((page) => (page.id === pageId ? { ...page, name: newName } : page)),\n });\n }\n },\n [currentDashboard, setCurrentDashboard, updatePageMutation]\n );\n\n // Handle duplicating a page\n const handlePageDuplicate = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || !dashboardId) return;\n\n const sourcePage = currentDashboard.pages?.find((p) => p.id === pageId);\n if (!sourcePage) return;\n\n // Call GraphQL mutation to create a copy of the page\n // Note: 'order' is not available for create - backend auto-assigns order\n const newPage = await createPageMutation({\n dashboardId,\n name: `${sourcePage.name} (Copy)`,\n });\n\n if (newPage) {\n setCurrentDashboard({\n ...currentDashboard,\n pages: [...(currentDashboard.pages || []), newPage],\n });\n\n setCurrentPageId(newPage.id);\n }\n // Note: Widget duplication would require additional backend support\n },\n [currentDashboard, dashboardId, setCurrentDashboard, createPageMutation]\n );\n\n // Handle deleting a page\n const handlePageDelete = useCallback(\n async (pageId: string) => {\n if (!currentDashboard || (currentDashboard.pages?.length || 0) <= 1) return;\n\n // Call GraphQL mutation to delete page\n const success = await deletePageMutation(pageId);\n\n if (success) {\n const updatedPages = currentDashboard.pages\n ?.filter((p) => p.id !== pageId)\n .map((p, index) => ({ ...p, order: index }));\n\n setCurrentDashboard({\n ...currentDashboard,\n pages: updatedPages,\n });\n\n // Switch to first page if current page was deleted\n if (currentPageId === pageId) {\n setCurrentPageId(updatedPages?.[0]?.id || null);\n }\n }\n },\n [currentDashboard, setCurrentDashboard, currentPageId, deletePageMutation]\n );\n\n // Handle reordering pages\n const handlePagesReorder = useCallback(\n async (reorderedPages: Array<{ id: string; name?: string; order?: number }>) => {\n if (!currentDashboard || !dashboardId) return;\n\n // Update local state optimistically\n setCurrentDashboard({\n ...currentDashboard,\n pages: reorderedPages.map((page, index) => ({\n ...page,\n order: index,\n })),\n });\n\n // Call GraphQL mutation to persist the reorder\n const pageIds = reorderedPages.map((p) => p.id);\n await reorderPagesMutation(dashboardId, pageIds);\n },\n [currentDashboard, dashboardId, setCurrentDashboard, reorderPagesMutation]\n );\n\n // Error state\n if (error) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full\" style={{ padding: 'var(--space-pagePadding)' }}>\n <div className=\"bg-status-error-bg border border-status-error-border rounded-lg p-6 w-full max-w-md\">\n <h3 className=\"text-lg font-medium text-status-error-text mb-2\">Error loading dashboard</h3>\n <p className=\"text-sm text-text-secondary mb-4 break-words\">{error.message}</p>\n <div className=\"flex flex-wrap gap-3\">\n <button\n type=\"button\"\n onClick={() => fetchDashboard(dashboardId!)}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md\"\n >\n Retry\n </button>\n <button\n type=\"button\"\n onClick={() => navigate('/dashboards')}\n className=\"px-4 py-2 text-sm font-medium text-text-secondary bg-bg-muted hover:bg-bg-muted/80 rounded-md\"\n >\n Back to Dashboards\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n // Not found state\n if (notFound) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full\" style={{ padding: 'var(--space-pagePadding)' }}>\n <div className=\"bg-bg-surface border border-border-default rounded-lg p-6 w-full max-w-md text-center\">\n <svg\n className=\"w-16 h-16 mx-auto mb-4 text-text-tertiary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <h3 className=\"text-lg font-medium text-text-primary mb-2\">Dashboard not found</h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n The dashboard you&apos;re looking for doesn&apos;t exist or has been deleted.\n </p>\n <p className=\"text-xs text-text-tertiary mb-4 font-mono break-all\">ID: {dashboardId}</p>\n <button\n type=\"button\"\n onClick={() => navigate('/dashboards')}\n className=\"px-4 py-2 text-sm font-medium text-action-primary-fg bg-action-primary-bg hover:bg-action-primary-bgHover rounded-md\"\n >\n Back to Dashboards\n </button>\n </div>\n </div>\n );\n }\n\n // Loading state\n if (loading || !currentDashboard) {\n return (\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"flex flex-col items-center gap-4\">\n <div className=\"animate-spin w-8 h-8 border-4 border-action-primary-bg border-t-transparent rounded-full\" />\n <p className=\"text-sm text-text-secondary\">Loading dashboard...</p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"h-full flex flex-col overflow-hidden\">\n {/* Drilldown Breadcrumb - shown when navigating via drilldown */}\n {isInDrilldown && (\n <DrilldownBreadcrumb\n currentDashboardId={currentDashboard.id}\n currentDashboardName={currentDashboard.name}\n basePath=\"/bigconsole/dashboards\"\n />\n )}\n\n {/* Dashboard Canvas with Page Tabs */}\n <div className=\"flex-1 min-w-0 overflow-hidden\">\n <DashboardCanvas\n dashboardId={currentDashboard.id}\n pageId={currentPageId || currentDashboard.pages?.[0]?.id}\n title={currentDashboard.name}\n onPaste={handlePaste}\n onSave={handleSave}\n isSaving={isSaving}\n onExport={handleExport}\n // Page tabs props\n pages={(currentDashboard.pages || []).map((page, index) => ({\n id: page.id,\n name: page.name || `Page ${index + 1}`,\n order: page.order ?? 0,\n }))}\n activePageId={currentPageId || currentDashboard.pages?.[0]?.id}\n onPageChange={handlePageChange}\n onPageAdd={handlePageAdd}\n onPageRename={handlePageRename}\n onPageDuplicate={handlePageDuplicate}\n onPageDelete={handlePageDelete}\n onPagesReorder={handlePagesReorder}\n showPageTabs={true}\n />\n </div>\n\n {/* Export Dashboard Dialog */}\n <ExportDashboardDialog\n isOpen={isExportDialogOpen}\n dashboardId={currentDashboard.id}\n dashboardName={currentDashboard.name}\n onClose={handleExportClose}\n />\n </div>\n );\n});\n\nexport default DashboardViewPage;\n"],"mappings":"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"}