@burdenoff/microfe-bigconsole 2026.613.1 → 2026.613.3

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 { 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":";;;;;;;;;;;;;;;;;;;;;AAoBA,SAAS,IAAwB;AAC/B,KAAI;AACF,SACE,OAGA;SACI;AACN;;;AAIJ,SAAS,EAAuB,GAAY,GAAc;CACxD,IAAM,IAAW,GAAuB;AACxC,CAAI,GAAU,YACZ,EAAS,SAAS,GAAI,EAAK;;AAI/B,SAAS,EAAyB,GAAY;CAC5C,IAAM,IAAW,GAAuB;AACxC,CAAI,GAAU,cACZ,EAAS,WAAW,EAAG;;AAQ3B,IAAa,IAA2B,EAAK,WAAgC;CAC3E,IAAM,EAAE,mBAAgB,GAAoC,EACtD,IAAW,GAAuB,EAClC,CAAC,GAAmB,KAAwB,EAAS,GAAM,EAC3D,CAAC,GAAU,KAAe,EAAS,GAAM,EACzC,CAAC,GAAoB,KAAyB,EAAS,GAAM,EAC7D,CAAC,GAAc,KAAmB,EAAwB,KAAK,EAG/D,EACJ,mBACA,oBACA,SAAS,GACT,OAAO,MACL,GAAwB,EAGtB,EACJ,cAAc,GACd,cAAc,GACd,cAAc,GACd,iBAAiB,GACjB,4BACE,EAAoB,KAAA,GAAW,EAAY,EAGzC,EACJ,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,cAAc,MACZ,GAAmB,EAGjB,IAAsB,GAAmB,MAAU,EAAM,oBAAoB,EAC7E,IAAmB,GAAmB,MAAU,EAAM,iBAAiB,EACvE,IAAc,GAAmB,MAAU,EAAM,YAAY,EAC7D,IAAe,GAAgB,MAAU,EAAM,aAAa,EAC5D,IAAU,GAAgB,MAAU,EAAM,QAAQ,EAClD,IAAoB,GAAgB,MAAU,EAAM,kBAAkB,EACtE,IAAkB,GAAgB,MAAU,EAAM,gBAAgB,EAClE,IAAiB,GAAgB,MAAU,EAAM,eAAe;AAStE,CANA,SACE,EAAY,OAAO,QACN,EAAY,OAAO,GAC/B,CAAC,EAAY,CAAC,EAGjB,QAAgB;AACd,MAAI,CAAC,GAAa;AAChB,KAAS,cAAc;AACvB;;AAmCF,SA/BA,EAAe,EAAY,CAAC,MAAM,MAAc;AAC9C,GAAI,KACF,EAAoB;IAClB,IAAI,EAAU;IACd,OAAO,EAAU;IACjB,aAAa,EAAU,eAAe;IACtC,OAAO,EAAU,MAAM,KAAK,GAAM,OAAW;KAC3C,IAAI,EAAK;KACT,OAAO,EAAK,QAAQ;KACpB;KACA,cAAc,EAAK,gBAAgB,EAAE;KACtC,EAAE;IACH,WAAW,EAAU;IACrB,WAAW,EAAU;IACtB,CAAyB,EAEtB,EAAU,MAAM,SAAS,KAAK,CAAC,KACjC,EAAgB,EAAU,MAAM,GAAG,GAAG,EAIpC,EAAU,QACZ,EAAuB,GAAa,EAAU,KAAK,IAIrD,EAAS,cAAc;IAEzB,QAGW;AACX,GAAI,KACF,EAAyB,EAAY;;IAGxC;EAAC;EAAa;EAAU;EAAqB;EAAgB;EAAa,CAAC;CAG9E,IAAM,IAAc,EAAY,YAAY;AAC1C,MAAI,CAAC,KAAqB,CAAC,KAAe,CAAC,GAAkB,QAAQ,IAAI,GACvE;EAGF,IAAM,IAAe,EAAQ,IAAI,EAAkB;AACnD,MAAI,CAAC,GAAc;AACjB,MAAgB;AAChB;;EAGF,IAAM,IAAS,EAAiB,MAAM,GAAG,IAGnC,IAAe,MAAM,KAAK,EAAQ,QAAQ,CAAC,EAC3C,IAAa;GACjB,OAAO,EAAa,iBAAiB;GACrC,QAAQ,EAAa,kBAAkB;GACxC,EAEK,EAAE,MAAG,SAAM,EAA0B,GAAc,GAAY,EAAE,MAAM,IAAI,CAAC,EAE5E,IAAc;GAClB;GACA;GACA,OAAO,EAAW;GAClB,QAAQ,EAAW;GACpB,EAEK,IAAW,MAAoB,SAAS,GAAG,EAAa,MAAM,WAAW,EAAa,OAExF,IAAY;AAGhB,MAAI;AACF,OAAY,MAAM,EAAwB,GAAmB,GAAa,EAAS;UACvE;AA+Bd,EA5BA,AACE,MAAY,MAAM,EAAqB;GACrC;GACA;GACA,MAAM,EAAa;GACnB,OAAO;GACP,aAAa,EAAa;GAE1B,YAAY,EAAa,cAAc,KAAA;GACvC,WAAW,EAAa,aAAa,KAAA;GACrC,UAAU,EAAa,YAAY,KAAA;GACnC,aAAa,EAAa,eAAe,KAAA;GACzC,QAAQ,EAAa,UAAU,EAAE;GACjC,UAAU;GACV,iBAAiB,EAAa,mBAAmB,KAAA;GACjD,UAAU,EAAa;GACxB,CAAC,EAGA,MACF,EAAa,EAAU,GAAG,EAGtB,MAAoB,SACtB,MAAM,EAAqB,EAAkB,GAIjD,GAAgB;IACf;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,EAGI,IAAuB,EAC3B,OAAO,MAAqB;AAC1B,MAAI,CAAC,KAAe,CAAC,GAAkB,QAAQ,IAAI,GAAI;EAEvD,IAAM,IAAS,EAAiB,MAAM,GAAG,IACnC,IAAQ,EACX,QAAQ,MAAM,IAAI,CAClB,aAAa,CACb,QAAQ,QAAQ,MAAM,EAAE,aAAa,CAAC,EAGnC,IAAe,MAAM,KAAK,EAAQ,QAAQ,CAAC,EAC3C,IAAa,EAAoB,EAAK,EACtC,IAAa;GACjB,OAAO,GAAY,aAAa,SAAS;GACzC,QAAQ,GAAY,aAAa,UAAU;GAC5C,EAEK,EAAE,MAAG,SAAM,EAA0B,GAAc,GAAY,EAAE,MAAM,IAAI,CAAC,EAG5E,IAAY,MAAM,EAAqB;GAC3C;GACA;GACA;GACA;GACA,QAAQ,EAAE;GACV,UAAU;IACR;IACA;IACA,OAAO,EAAW;IAClB,QAAQ,EAAW;IACpB;GACF,CAAC;AAEF,EAAI,MACF,EAAa,EAAU,GAAG,EAC1B,EAAqB,GAAM;IAG/B;EAAC;EAAa;EAAkB;EAAsB;EAAc;EAAQ,CAC7E,EAGK,KAAa,QAAkB;AACnC,IAAS,cAAc;IACtB,CAAC,EAAS,CAAC,EAGR,KAAa,EAAY,YAAY;AACpC,SAEL;KAAY,GAAK;AACjB,OAAI;IAEF,IAAM,IAAa,EAAe,UAAU,EACtC,IAAiB,MAAM,KAAK,EAAW,QAAQ,QAAQ,CAAC,EACxD,IAAkB,EAAW,gBAK7B,IAAe,EAAe,QAAQ,MAAM,EAAgB,IAAI,EAAE,GAAG,CAAC;AAE5E,QAAI,EAAa,SAAS,GAAG;KAE3B,IAAM,IAAiB,EAAa,IAAI,OAAO,MAAW;MACxD,IAAM,IAAc;OAClB,IAAI,EAAO;OACX,OAAO,EAAO;OACd,QAAQ,EAAO;OACf,iBAAiB,EAAO,mBAAmB,KAAA;OAE3C,YAAY,EAAO,cAAc,KAAA;OACjC,WAAW,EAAO,aAAa,KAAA;OAC/B,UAAU,EAAO,YAAY,KAAA;OAC7B,aAAa,EAAO,eAAe,KAAA;OACpC;AAED,UAAI;AAEF,cADe,MAAM,EAAqB,EAAY;eAE/C,GAAK;AACZ,aAAM;;OAER;AAKF,KAHgB,MAAM,QAAQ,IAAI,EAAe,EAGjD,EAAW,eAAe;;AAoB5B,IAfI,EAAe,SAAS,KAW1B,MAAM,EAVkB,EAAe,KAAK,OAAY;KACtD,IAAI,EAAO;KACX,UAAU,EAAO,YAAY;MAC3B,GAAG,EAAO,aAAa;MACvB,GAAG,EAAO,aAAa;MACvB,OAAO,EAAO,iBAAiB;MAC/B,QAAQ,EAAO,kBAAkB;MAClC;KACF,EAAE,CAEwC,EAIzC,KACF,MAAM,EAAgB;KACpB,IAAI;KACJ,MAAM,EAAiB;KACvB,aAAa,EAAiB;KAC/B,CAAC;WAEU,WACN;AACR,MAAY,GAAM;;;IAEnB;EAAC;EAAa;EAAkB;EAAsB;EAAsB;EAAgB,CAAC,EAG1F,KAAe,QAAkB;AACrC,IAAsB,GAAK;IAC1B,EAAE,CAAC,EAEA,KAAoB,QAAkB;AAC1C,IAAsB,GAAM;IAC3B,EAAE,CAAC,EAOA,KAAmB,GAAa,MAAmB;AACvD,IAAgB,EAAO;IACtB,EAAE,CAAC,EAGA,KAAgB,EAAY,YAAY;AAC5C,MAAI,CAAC,KAAoB,CAAC,EAAa;EAOvC,IAAM,IAAU,MAAM,EAAmB;GACvC;GACA,MANkB,QADC,EAAiB,MAAM,SAAS;GAQpD,CAAC;AAEF,EAAI,MAEF,EAAoB;GAClB,GAAG;GACH,OAAO,CACL,GAAG,EAAiB,OACpB;IACE,IAAI,EAAQ;IACZ,OAAO,EAAQ;IACf,OAAO,EAAQ;IACf,cAAc,EAAQ,gBAAgB,EAAE;IACzC,CACF;GACF,CAAC,EAGF,EAAgB,EAAQ,GAAG;IAE5B;EAAC;EAAkB;EAAa;EAAqB;EAAmB,CAAC,EAGtE,KAAmB,EACvB,OAAO,GAAgB,MAAoB;AACpC,OAGe,MAAM,EAAmB;GAC3C,IAAI;GACJ,MAAM;GACP,CAAC,IAGA,EAAoB;GAClB,GAAG;GACH,OAAO,EAAiB,MAAM,KAAK,MAAU,EAAK,OAAO,IAAS;IAAE,GAAG;IAAM,OAAO;IAAS,GAAG,EAAM;GACvG,CAAC;IAGN;EAAC;EAAkB;EAAqB;EAAmB,CAC5D,EAGK,KAAsB,EAC1B,OAAO,MAAmB;AACxB,MAAI,CAAC,KAAoB,CAAC,EAAa;EAEvC,IAAM,IAAa,EAAiB,MAAM,MAAM,MAAM,EAAE,OAAO,EAAO;AACtE,MAAI,CAAC,EAAY;EAKjB,IAAM,IAAU,MAAM,EAAmB;GACvC;GACA,MAAM,GAAG,EAAW,MAAM;GAC3B,CAAC;AAEF,EAAI,MACF,EAAoB;GAClB,GAAG;GACH,OAAO,CACL,GAAG,EAAiB,OACpB;IACE,IAAI,EAAQ;IACZ,OAAO,EAAQ;IACf,OAAO,EAAQ;IACf,cAAc,EAAQ,gBAAgB,EAAE;IACzC,CACF;GACF,CAAC,EAEF,EAAgB,EAAQ,GAAG;IAG/B;EAAC;EAAkB;EAAa;EAAqB;EAAmB,CACzE,EAGK,KAAmB,EACvB,OAAO,MAAmB;AACpB,SAAC,KAAoB,EAAiB,MAAM,UAAU,MAG1C,MAAM,EAAmB,EAAO,EAEnC;GACX,IAAM,IAAe,EAAiB,MAAM,QAAQ,MAAM,EAAE,OAAO,EAAO,CAAC,KAAK,GAAG,OAAW;IAAE,GAAG;IAAG;IAAO,EAAE;AAQ/G,GANA,EAAoB;IAClB,GAAG;IACH,OAAO;IACR,CAAC,EAGE,MAAiB,KACnB,EAAgB,EAAa,IAAI,MAAM,KAAK;;IAIlD;EAAC;EAAkB;EAAqB;EAAc;EAAmB,CAC1E,EAGK,KAAqB,EACzB,OAAO,MAAoC;AACrC,GAAC,KAAoB,CAAC,MAG1B,EAAoB;GAClB,GAAG;GACH,OAAO,EAAe,KAAK,GAAM,OAAW;IAC1C,GAAG;IACH;IACD,EAAE;GACJ,CAAC,EAIF,MAAM,EAAqB,GADX,EAAe,KAAK,MAAM,EAAE,GAAG,CACC;IAElD;EAAC;EAAkB;EAAa;EAAqB;EAAqB,CAC3E;AA8BD,QA3BI,KAAoB,CAAC,IAErB,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD,EAAK,WAAU,4FAA6F,CAAA;EACxG,CAAA,GAKN,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,MAAD;KAAI,WAAU;eAA6C;KAA6B,CAAA;IACxF,kBAAC,KAAD;KAAG,WAAU;eAAoC,EAAe;KAAY,CAAA;IAC5E,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAS,cAAc;KACtC,WAAU;eACX;KAEQ,CAAA;IACL;;EACF,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,SAAS;IACT,SAAQ;IACR,MAAK;IACL,OAAM;IACN,cAAW;cALb,CAOE,kBAAC,GAAD,EAAW,WAAU,gBAAiB,CAAA,EAAA,OAE/B;OACT,kBAAC,MAAD;IAAI,WAAU;cAAoD,EAAiB;IAAW,CAAA,CAC1F;MAGN,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KACE,WAAW;YACT,IAAoB,SAAS,MAAM;;;;;;;eAQpC,KACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,WAAU;kBAA0C;QAAY,CAAA,EACpE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAqB,GAAM;QAC1C,WAAU;kBAOV,kBAAC,OAAD;SAAK,WAAU;SAAU,MAAK;SAAO,QAAO;SAAe,SAAQ;mBACjE,kBAAC,QAAD;UAAM,eAAc;UAAQ,gBAAe;UAAQ,aAAa;UAAG,GAAE;UAAyB,CAAA;SAC1F,CAAA;QACC,CAAA,CACL;UACN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAoB,UAAU,GAAwB,CAAA;OAClD,CAAA,CACF;;KAEJ,CAAA;IAGL,CAAC,KACA,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAqB,GAAK;KACzC,WAAU;KAYV,OAAM;eAEN,kBAAC,OAAD;MAAK,WAAU;MAAU,MAAK;MAAO,QAAO;MAAe,SAAQ;gBACjE,kBAAC,QAAD;OAAM,eAAc;OAAQ,gBAAe;OAAQ,aAAa;OAAG,GAAE;OAAiB,CAAA;MAClF,CAAA;KACC,CAAA;IAIX,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,aAAa,EAAiB;MAC9B,QAAQ,KAAgB,EAAiB,QAAQ,IAAI;MACrD,OAAO,EAAiB;MACxB,SAAS;MACT,QAAQ;MACE;MACV,UAAU;MAEV,OAAO,EAAiB,MAAM,KAAK,OAAU;OAC3C,IAAI,EAAK;OACT,MAAM,EAAK,SAAS,SAAS,EAAK,SAAS,KAAK;OAChD,OAAO,EAAK,SAAS;OACtB,EAAE;MACH,cAAc,KAAgB,EAAiB,QAAQ,IAAI;MAC3D,cAAc;MACd,WAAW;MACX,cAAc;MACd,iBAAiB;MACjB,cAAc;MACd,gBAAgB;MAChB,cAAc;MACd,CAAA;KACE,CAAA;IAGN,kBAAC,GAAD;KACE,QAAQ;KACR,aAAa,EAAiB;KAC9B,eAAe,EAAiB;KAChC,SAAS;KACT,CAAA;IACE;KACF;;EAER"}
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, useBigConsole } from '../context';\nimport { DashboardCanvas, ExportDashboardDialog } from '../components/dashboard';\nimport { WidgetTypeSelector } from '../components/widgets/WidgetTypeSelector';\nimport { useDashboardStore, useWidgetStore } from '../store';\nimport { useDashboardOperations, useWidgetOperations, usePageOperations, useWidgetCatalog } 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 { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\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({ workspaceId, apiGatewayUrl, authToken });\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 // Prefer the backend-sourced catalog entry; fall back to the static registry.\n const widgetsArray = Array.from(widgets.values());\n const definition = widgetCatalog.find((d) => d.type === type) ?? 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, widgetCatalog]\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} definitions={widgetCatalog} />\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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"widgetPositioning.js","names":[],"sources":["../../../src/bigconsole/utils/widgetPositioning.ts"],"sourcesContent":["/**\n * Widget Positioning Utilities\n *\n * Smart placement algorithm for dashboard widgets.\n * Places widgets horizontally first (left-to-right), then moves to next row.\n * Uses a grid occupancy map to find the first available slot.\n */\n\nimport type { Widget } from '../types';\n\nexport interface WidgetSize {\n width: number;\n height: number;\n}\n\nexport interface WidgetPosition {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface GridConfig {\n cols: number;\n maxRows?: number;\n}\n\n/**\n * Default grid configuration\n */\nexport const DEFAULT_GRID_CONFIG: GridConfig = {\n cols: 12,\n maxRows: 100,\n};\n\n/**\n * Responsive breakpoints configuration\n */\nexport const RESPONSIVE_BREAKPOINTS = {\n lg: 1200,\n md: 996,\n sm: 768,\n xs: 480,\n} as const;\n\n/**\n * Columns per breakpoint for responsive layout\n */\nexport const RESPONSIVE_COLS = {\n lg: 12,\n md: 10,\n sm: 6,\n xs: 4,\n} as const;\n\n/**\n * Creates an occupancy map from existing widgets\n * Each occupied cell is marked with the widget ID\n */\nfunction createOccupancyMap(widgets: Widget[]): Map<string, string> {\n const occupancyMap = new Map<string, string>();\n\n widgets.forEach((widget) => {\n const x = widget.positionX ?? 0;\n const y = widget.positionY ?? 0;\n const w = widget.positionWidth ?? 4;\n const h = widget.positionHeight ?? 4;\n\n for (let dx = 0; dx < w; dx++) {\n for (let dy = 0; dy < h; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n });\n\n return occupancyMap;\n}\n\n/**\n * Checks if a widget can be placed at the given position\n */\nfunction canPlaceWidget(\n occupancyMap: Map<string, string>,\n x: number,\n y: number,\n width: number,\n height: number,\n cols: number\n): boolean {\n // Check if widget fits within grid columns\n if (x + width > cols) {\n return false;\n }\n\n // Check if all required cells are available\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Finds the next available position for a widget using smart placement.\n * Scans row-by-row, left-to-right to find the first slot that fits.\n *\n * @param existingWidgets - Array of existing widgets on the dashboard\n * @param newWidgetSize - Size of the new widget to place\n * @param config - Grid configuration (columns, max rows)\n * @returns Position where the new widget should be placed\n */\nexport function findNextAvailablePosition(\n existingWidgets: Widget[],\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols, maxRows = 100 } = config;\n const { width, height } = newWidgetSize;\n\n // Handle empty dashboard - place at origin\n if (existingWidgets.length === 0) {\n return { x: 0, y: 0 };\n }\n\n // Create occupancy map\n const occupancyMap = createOccupancyMap(existingWidgets);\n\n // Find the maximum Y to limit our search\n const maxExistingY = Math.max(...existingWidgets.map((w) => (w.positionY ?? 0) + (w.positionHeight ?? 4)));\n\n // Scan row by row, left to right, to find first available slot\n for (let y = 0; y <= maxExistingY + height && y < maxRows; y++) {\n for (let x = 0; x <= cols - width; x++) {\n if (canPlaceWidget(occupancyMap, x, y, width, height, cols)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: place at the bottom of existing widgets\n return { x: 0, y: maxExistingY };\n}\n\n/**\n * Finds position to the right of a specific widget\n * Falls back to smart placement if no space to the right\n */\nexport function findPositionNextToWidget(\n existingWidgets: Widget[],\n referenceWidgetId: string,\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const referenceWidget = existingWidgets.find((w) => w.id === referenceWidgetId);\n\n if (!referenceWidget) {\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n }\n\n const refX = referenceWidget.positionX ?? 0;\n const refY = referenceWidget.positionY ?? 0;\n const refW = referenceWidget.positionWidth ?? 4;\n\n // Try to place to the right of reference widget\n const newX = refX + refW;\n const newY = refY;\n\n // Check if it fits\n if (newX + newWidgetSize.width <= cols) {\n const occupancyMap = createOccupancyMap(existingWidgets.filter((w) => w.id !== referenceWidgetId));\n\n if (canPlaceWidget(occupancyMap, newX, newY, newWidgetSize.width, newWidgetSize.height, cols)) {\n return { x: newX, y: newY };\n }\n }\n\n // Fall back to smart placement\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n}\n\n/**\n * Calculates position from drop coordinates (for drag-and-drop)\n */\nexport function calculatePositionFromDrop(\n dropX: number,\n dropY: number,\n canvasRect: DOMRect,\n containerWidth: number,\n widgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const columnWidth = containerWidth / cols;\n const rowHeight = 60; // Match DashboardGrid rowHeight\n\n const relativeX = dropX - canvasRect.left;\n const relativeY = dropY - canvasRect.top;\n\n const gridX = Math.max(0, Math.min(cols - widgetSize.width, Math.floor(relativeX / columnWidth)));\n const gridY = Math.max(0, Math.floor(relativeY / rowHeight));\n\n return { x: gridX, y: gridY };\n}\n\n/**\n * Gets responsive columns for a given container width\n */\nexport function getResponsiveCols(containerWidth: number): number {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return RESPONSIVE_COLS.lg;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return RESPONSIVE_COLS.md;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return RESPONSIVE_COLS.sm;\n }\n return RESPONSIVE_COLS.xs;\n}\n\n/**\n * Adjusts widget positions for a new column count (responsive)\n * Ensures widgets don't overflow the grid\n */\nexport function adjustPositionsForCols(\n widgets: Widget[],\n newCols: number\n): Map<string, { x: number; y: number; width: number; height: number }> {\n const adjustedPositions = new Map<string, { x: number; y: number; width: number; height: number }>();\n\n // Sort widgets by their original position (top-left first)\n const sortedWidgets = [...widgets].sort((a, b) => {\n const aY = a.positionY ?? 0;\n const bY = b.positionY ?? 0;\n const aX = a.positionX ?? 0;\n const bX = b.positionX ?? 0;\n return aY !== bY ? aY - bY : aX - bX;\n });\n\n // Recalculate positions for the new column count\n const occupancyMap = new Map<string, string>();\n\n sortedWidgets.forEach((widget) => {\n const width = Math.min(widget.positionWidth ?? 4, newCols);\n const height = widget.positionHeight ?? 4;\n\n // Find next available position\n let placed = false;\n for (let y = 0; y < 100 && !placed; y++) {\n for (let x = 0; x <= newCols - width && !placed; x++) {\n let canPlace = true;\n for (let dx = 0; dx < width && canPlace; dx++) {\n for (let dy = 0; dy < height && canPlace; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n canPlace = false;\n }\n }\n }\n\n if (canPlace) {\n // Mark cells as occupied\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n\n adjustedPositions.set(widget.id, { x, y, width, height });\n placed = true;\n }\n }\n }\n });\n\n return adjustedPositions;\n}\n"],"mappings":";AA8BA,IAAa,IAAkC;CAC7C,MAAM;CACN,SAAS;CACV;AA0BD,SAAS,EAAmB,GAAwC;CAClE,IAAM,oBAAe,IAAI,KAAqB;AAe9C,QAbA,EAAQ,SAAS,MAAW;EAC1B,IAAM,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,iBAAiB,GAC5B,IAAI,EAAO,kBAAkB;AAEnC,OAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,MAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,GAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,KAAM,EAAO,GAAG;GAGtD,EAEK;;AAMT,SAAS,EACP,GACA,GACA,GACA,GACA,GACA,GACS;AAET,KAAI,IAAI,IAAQ,EACd,QAAO;AAIT,MAAK,IAAI,IAAK,GAAG,IAAK,GAAO,IAC3B,MAAK,IAAI,IAAK,GAAG,IAAK,GAAQ,IAC5B,KAAI,EAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,IAAK,CACzC,QAAO;AAKb,QAAO;;AAYT,SAAgB,EACd,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,SAAM,aAAU,QAAQ,GAC1B,EAAE,UAAO,cAAW;AAG1B,KAAI,EAAgB,WAAW,EAC7B,QAAO;EAAE,GAAG;EAAG,GAAG;EAAG;CAIvB,IAAM,IAAe,EAAmB,EAAgB,EAGlD,IAAe,KAAK,IAAI,GAAG,EAAgB,KAAK,OAAO,EAAE,aAAa,MAAM,EAAE,kBAAkB,GAAG,CAAC;AAG1G,MAAK,IAAI,IAAI,GAAG,KAAK,IAAe,KAAU,IAAI,GAAS,IACzD,MAAK,IAAI,IAAI,GAAG,KAAK,IAAO,GAAO,IACjC,KAAI,EAAe,GAAc,GAAG,GAAG,GAAO,GAAQ,EAAK,CACzD,QAAO;EAAE;EAAG;EAAG;AAMrB,QAAO;EAAE,GAAG;EAAG,GAAG;EAAc;;AA4ClC,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,YAAS,GACX,IAAc,IAAiB,GAG/B,IAAY,IAAQ,EAAW,MAC/B,IAAY,IAAQ,EAAW;AAKrC,QAAO;EAAE,GAHK,KAAK,IAAI,GAAG,KAAK,IAAI,IAAO,EAAW,OAAO,KAAK,MAAM,IAAY,EAAY,CAAC,CAAC;EAG9E,GAFL,KAAK,IAAI,GAAG,KAAK,MAAM,IAAY,GAAU,CAAC;EAE/B"}
1
+ {"version":3,"file":"widgetPositioning.js","names":[],"sources":["../../../src/bigconsole/utils/widgetPositioning.ts"],"sourcesContent":["/**\n * Widget Positioning Utilities\n *\n * Smart placement algorithm for dashboard widgets.\n * Places widgets horizontally first (left-to-right), then moves to next row.\n * Uses a grid occupancy map to find the first available slot.\n */\n\nimport type { ResponsivePosition, Widget } from '../types';\n\nexport interface WidgetSize {\n width: number;\n height: number;\n}\n\nexport interface WidgetPosition {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface GridConfig {\n cols: number;\n maxRows?: number;\n}\n\n/**\n * Default grid configuration\n */\nexport const DEFAULT_GRID_CONFIG: GridConfig = {\n cols: 12,\n maxRows: 100,\n};\n\n/**\n * Responsive breakpoints configuration\n */\nexport const RESPONSIVE_BREAKPOINTS = {\n xl: 1536,\n lg: 1200,\n md: 996,\n sm: 768,\n xs: 480,\n} as const;\n\n/**\n * Columns per breakpoint for responsive layout.\n * xl keeps the same 12-column grid as lg so existing widget spans remain\n * valid; the extra width simply yields wider columns on very large screens.\n */\nexport const RESPONSIVE_COLS = {\n xl: 12,\n lg: 12,\n md: 10,\n sm: 6,\n xs: 4,\n} as const;\n\n/**\n * Creates an occupancy map from existing widgets\n * Each occupied cell is marked with the widget ID\n */\nfunction createOccupancyMap(widgets: Widget[]): Map<string, string> {\n const occupancyMap = new Map<string, string>();\n\n widgets.forEach((widget) => {\n const x = widget.positionX ?? 0;\n const y = widget.positionY ?? 0;\n const w = widget.positionWidth ?? 4;\n const h = widget.positionHeight ?? 4;\n\n for (let dx = 0; dx < w; dx++) {\n for (let dy = 0; dy < h; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n });\n\n return occupancyMap;\n}\n\n/**\n * Checks if a widget can be placed at the given position\n */\nfunction canPlaceWidget(\n occupancyMap: Map<string, string>,\n x: number,\n y: number,\n width: number,\n height: number,\n cols: number\n): boolean {\n // Check if widget fits within grid columns\n if (x + width > cols) {\n return false;\n }\n\n // Check if all required cells are available\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Finds the next available position for a widget using smart placement.\n * Scans row-by-row, left-to-right to find the first slot that fits.\n *\n * @param existingWidgets - Array of existing widgets on the dashboard\n * @param newWidgetSize - Size of the new widget to place\n * @param config - Grid configuration (columns, max rows)\n * @returns Position where the new widget should be placed\n */\nexport function findNextAvailablePosition(\n existingWidgets: Widget[],\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols, maxRows = 100 } = config;\n const { width, height } = newWidgetSize;\n\n // Handle empty dashboard - place at origin\n if (existingWidgets.length === 0) {\n return { x: 0, y: 0 };\n }\n\n // Create occupancy map\n const occupancyMap = createOccupancyMap(existingWidgets);\n\n // Find the maximum Y to limit our search\n const maxExistingY = Math.max(...existingWidgets.map((w) => (w.positionY ?? 0) + (w.positionHeight ?? 4)));\n\n // Scan row by row, left to right, to find first available slot\n for (let y = 0; y <= maxExistingY + height && y < maxRows; y++) {\n for (let x = 0; x <= cols - width; x++) {\n if (canPlaceWidget(occupancyMap, x, y, width, height, cols)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: place at the bottom of existing widgets\n return { x: 0, y: maxExistingY };\n}\n\n/**\n * Finds position to the right of a specific widget\n * Falls back to smart placement if no space to the right\n */\nexport function findPositionNextToWidget(\n existingWidgets: Widget[],\n referenceWidgetId: string,\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const referenceWidget = existingWidgets.find((w) => w.id === referenceWidgetId);\n\n if (!referenceWidget) {\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n }\n\n const refX = referenceWidget.positionX ?? 0;\n const refY = referenceWidget.positionY ?? 0;\n const refW = referenceWidget.positionWidth ?? 4;\n\n // Try to place to the right of reference widget\n const newX = refX + refW;\n const newY = refY;\n\n // Check if it fits\n if (newX + newWidgetSize.width <= cols) {\n const occupancyMap = createOccupancyMap(existingWidgets.filter((w) => w.id !== referenceWidgetId));\n\n if (canPlaceWidget(occupancyMap, newX, newY, newWidgetSize.width, newWidgetSize.height, cols)) {\n return { x: newX, y: newY };\n }\n }\n\n // Fall back to smart placement\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n}\n\n/**\n * Calculates position from drop coordinates (for drag-and-drop)\n */\nexport function calculatePositionFromDrop(\n dropX: number,\n dropY: number,\n canvasRect: DOMRect,\n containerWidth: number,\n widgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const columnWidth = containerWidth / cols;\n const rowHeight = 60; // Match DashboardGrid rowHeight\n\n const relativeX = dropX - canvasRect.left;\n const relativeY = dropY - canvasRect.top;\n\n const gridX = Math.max(0, Math.min(cols - widgetSize.width, Math.floor(relativeX / columnWidth)));\n const gridY = Math.max(0, Math.floor(relativeY / rowHeight));\n\n return { x: gridX, y: gridY };\n}\n\n/**\n * Gets responsive columns for a given container width\n */\nexport function getResponsiveCols(containerWidth: number): number {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.xl) {\n return RESPONSIVE_COLS.xl;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return RESPONSIVE_COLS.lg;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return RESPONSIVE_COLS.md;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return RESPONSIVE_COLS.sm;\n }\n return RESPONSIVE_COLS.xs;\n}\n\n/** Responsive breakpoint tier identifiers, ordered smallest → largest. */\nexport type ResponsiveTier = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/**\n * Resolves the column span for a responsive tier, applying the documented\n * fallback chain. `xl` is optional and falls back to `lg` when not set, so\n * existing dashboards (xs/sm/md/lg only) are unaffected on xl screens.\n */\nexport function getResponsiveSpan(position: ResponsivePosition, tier: ResponsiveTier): number {\n switch (tier) {\n case 'xl':\n return position.xl ?? position.lg;\n case 'lg':\n return position.lg;\n case 'md':\n return position.md;\n case 'sm':\n return position.sm;\n case 'xs':\n default:\n return position.xs;\n }\n}\n\n/**\n * Maps a container width to its responsive tier.\n */\nexport function getResponsiveTier(containerWidth: number): ResponsiveTier {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.xl) {\n return 'xl';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return 'lg';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return 'md';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return 'sm';\n }\n return 'xs';\n}\n\n/**\n * Adjusts widget positions for a new column count (responsive)\n * Ensures widgets don't overflow the grid\n */\nexport function adjustPositionsForCols(\n widgets: Widget[],\n newCols: number\n): Map<string, { x: number; y: number; width: number; height: number }> {\n const adjustedPositions = new Map<string, { x: number; y: number; width: number; height: number }>();\n\n // Sort widgets by their original position (top-left first)\n const sortedWidgets = [...widgets].sort((a, b) => {\n const aY = a.positionY ?? 0;\n const bY = b.positionY ?? 0;\n const aX = a.positionX ?? 0;\n const bX = b.positionX ?? 0;\n return aY !== bY ? aY - bY : aX - bX;\n });\n\n // Recalculate positions for the new column count\n const occupancyMap = new Map<string, string>();\n\n sortedWidgets.forEach((widget) => {\n const width = Math.min(widget.positionWidth ?? 4, newCols);\n const height = widget.positionHeight ?? 4;\n\n // Find next available position\n let placed = false;\n for (let y = 0; y < 100 && !placed; y++) {\n for (let x = 0; x <= newCols - width && !placed; x++) {\n let canPlace = true;\n for (let dx = 0; dx < width && canPlace; dx++) {\n for (let dy = 0; dy < height && canPlace; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n canPlace = false;\n }\n }\n }\n\n if (canPlace) {\n // Mark cells as occupied\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n\n adjustedPositions.set(widget.id, { x, y, width, height });\n placed = true;\n }\n }\n }\n });\n\n return adjustedPositions;\n}\n"],"mappings":";AA8BA,IAAa,IAAkC;CAC7C,MAAM;CACN,SAAS;CACV;AA8BD,SAAS,EAAmB,GAAwC;CAClE,IAAM,oBAAe,IAAI,KAAqB;AAe9C,QAbA,EAAQ,SAAS,MAAW;EAC1B,IAAM,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,iBAAiB,GAC5B,IAAI,EAAO,kBAAkB;AAEnC,OAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,MAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,GAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,KAAM,EAAO,GAAG;GAGtD,EAEK;;AAMT,SAAS,EACP,GACA,GACA,GACA,GACA,GACA,GACS;AAET,KAAI,IAAI,IAAQ,EACd,QAAO;AAIT,MAAK,IAAI,IAAK,GAAG,IAAK,GAAO,IAC3B,MAAK,IAAI,IAAK,GAAG,IAAK,GAAQ,IAC5B,KAAI,EAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,IAAK,CACzC,QAAO;AAKb,QAAO;;AAYT,SAAgB,EACd,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,SAAM,aAAU,QAAQ,GAC1B,EAAE,UAAO,cAAW;AAG1B,KAAI,EAAgB,WAAW,EAC7B,QAAO;EAAE,GAAG;EAAG,GAAG;EAAG;CAIvB,IAAM,IAAe,EAAmB,EAAgB,EAGlD,IAAe,KAAK,IAAI,GAAG,EAAgB,KAAK,OAAO,EAAE,aAAa,MAAM,EAAE,kBAAkB,GAAG,CAAC;AAG1G,MAAK,IAAI,IAAI,GAAG,KAAK,IAAe,KAAU,IAAI,GAAS,IACzD,MAAK,IAAI,IAAI,GAAG,KAAK,IAAO,GAAO,IACjC,KAAI,EAAe,GAAc,GAAG,GAAG,GAAO,GAAQ,EAAK,CACzD,QAAO;EAAE;EAAG;EAAG;AAMrB,QAAO;EAAE,GAAG;EAAG,GAAG;EAAc;;AA4ClC,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,YAAS,GACX,IAAc,IAAiB,GAG/B,IAAY,IAAQ,EAAW,MAC/B,IAAY,IAAQ,EAAW;AAKrC,QAAO;EAAE,GAHK,KAAK,IAAI,GAAG,KAAK,IAAI,IAAO,EAAW,OAAO,KAAK,MAAM,IAAY,EAAY,CAAC,CAAC;EAG9E,GAFL,KAAK,IAAI,GAAG,KAAK,MAAM,IAAY,GAAU,CAAC;EAE/B"}
@@ -1813,14 +1813,14 @@ var wt = e`
1813
1813
  ...WidgetActionFields
1814
1814
  }
1815
1815
  }
1816
- ${k}`;
1817
- e`
1816
+ ${k}`, Et = e`
1818
1817
  query GetWidgetTypes {
1819
1818
  getWidgetTypes {
1820
1819
  ...WidgetTypeInfoFields
1821
1820
  }
1822
1821
  }
1823
- ${N}`, e`
1822
+ ${N}`;
1823
+ e`
1824
1824
  query GetWidgetWithData($id: ID!) {
1825
1825
  getWidget(id: $id) {
1826
1826
  ...WidgetCoreFields
@@ -1839,7 +1839,7 @@ e`
1839
1839
  ${E}
1840
1840
  ${D}
1841
1841
  ${O}`;
1842
- var Et = e`
1842
+ var $ = e`
1843
1843
  query ListEventLogs($dashboardId: ID, $widgetId: ID, $eventName: String, $status: EventStatus, $first: Int, $after: String) {
1844
1844
  listEventLogs(
1845
1845
  dashboardId: $dashboardId
@@ -1862,7 +1862,7 @@ var Et = e`
1862
1862
  }
1863
1863
  }
1864
1864
  ${S}
1865
- ${w}`, $ = e`
1865
+ ${w}`, Dt = e`
1866
1866
  query ListEventWebhookMappings($dashboardId: ID, $widgetId: ID, $isActive: Boolean) {
1867
1867
  listEventWebhookMappings(
1868
1868
  dashboardId: $dashboardId
@@ -1890,13 +1890,13 @@ e`
1890
1890
  }
1891
1891
  ${M}
1892
1892
  ${w}`;
1893
- var Dt = e`
1893
+ var Ot = e`
1894
1894
  query ListWidgetsByDashboard($dashboardId: ID!, $type: WidgetType) {
1895
1895
  listWidgetsByDashboard(dashboardId: $dashboardId, type: $type) {
1896
1896
  ...WidgetListFields
1897
1897
  }
1898
1898
  }
1899
- ${j}`, Ot = e`
1899
+ ${j}`, kt = e`
1900
1900
  query ListWidgetsForPage($pageId: ID!, $type: WidgetType) {
1901
1901
  listWidgets(pageId: $pageId, type: $type, first: 100) {
1902
1902
  edges {
@@ -1907,29 +1907,29 @@ var Dt = e`
1907
1907
  totalCount
1908
1908
  }
1909
1909
  }
1910
- ${j}`, kt = e`
1910
+ ${j}`, At = e`
1911
1911
  mutation CreateWorkflow($input: CreateWorkflowInput!) {
1912
1912
  createWorkflow(input: $input) {
1913
1913
  ...WorkflowFullFields
1914
1914
  }
1915
1915
  }
1916
- ${I}`, At = e`
1916
+ ${I}`, jt = e`
1917
1917
  mutation DeleteWorkflow($id: ID!) {
1918
1918
  deleteWorkflow(id: $id)
1919
1919
  }
1920
- `, jt = e`
1920
+ `, Mt = e`
1921
1921
  mutation ExecuteWorkflow($id: ID!, $triggerData: JSON) {
1922
1922
  executeWorkflow(id: $id, triggerData: $triggerData) {
1923
1923
  ...WorkflowFullFields
1924
1924
  }
1925
1925
  }
1926
- ${I}`, Mt = e`
1926
+ ${I}`, Nt = e`
1927
1927
  mutation LinkDataSinkToWorkflow($workflowId: ID!, $input: LinkDataSinkInput!) {
1928
1928
  linkDataSinkToWorkflow(workflowId: $workflowId, input: $input) {
1929
1929
  ...DataSinkWorkflowLinkFields
1930
1930
  }
1931
1931
  }
1932
- ${F}`, Nt = e`
1932
+ ${F}`, Pt = e`
1933
1933
  mutation UnlinkDataSinkFromWorkflow($workflowId: ID!, $dataSinkId: ID!) {
1934
1934
  unlinkDataSinkFromWorkflow(workflowId: $workflowId, dataSinkId: $dataSinkId)
1935
1935
  }
@@ -1941,25 +1941,25 @@ e`
1941
1941
  }
1942
1942
  }
1943
1943
  ${F}`;
1944
- var Pt = e`
1944
+ var Ft = e`
1945
1945
  mutation UpdateWorkflow($id: ID!, $input: UpdateWorkflowInput!, $version: Int!) {
1946
1946
  updateWorkflow(id: $id, input: $input, version: $version) {
1947
1947
  ...WorkflowFullFields
1948
1948
  }
1949
1949
  }
1950
- ${I}`, Ft = e`
1950
+ ${I}`, It = e`
1951
1951
  query GetWorkflow($id: ID!) {
1952
1952
  workflow(id: $id) {
1953
1953
  ...WorkflowFullFields
1954
1954
  }
1955
1955
  }
1956
- ${I}`, It = e`
1956
+ ${I}`, Lt = e`
1957
1957
  query GetWorkflowByKey($key: String!) {
1958
1958
  workflowByKey(key: $key) {
1959
1959
  ...WorkflowFullFields
1960
1960
  }
1961
1961
  }
1962
- ${I}`, Lt = e`
1962
+ ${I}`, Rt = e`
1963
1963
  query ListWorkflows($filter: FluidGridsWorkflowFilterInput) {
1964
1964
  workflows(filter: $filter) {
1965
1965
  ...WorkflowListItem
@@ -1967,6 +1967,6 @@ var Pt = e`
1967
1967
  }
1968
1968
  ${L}`;
1969
1969
  //#endregion
1970
- export { z as ArchiveTemplateDocument, Ae as BatchCreateDataSinksDocument, je as BatchDeleteDataSinksDocument, it as BatchUpdateWidgetPositionsDocument, B as CloneDashboardDocument, V as CreateDashboardDocument, H as CreateDashboardPageDocument, U as CreateDashboardViewDocument, Me as CreateDataSinkDocument, at as CreateEventWebhookMappingDocument, W as CreateGlobalFilterDocument, He as CreateParserDocument, Ye as CreatePipelineDocument, G as CreateTemplateDocument, st as CreateWidgetActionDocument, ot as CreateWidgetDocument, ct as CreateWidgetDrilldownDocument, kt as CreateWorkflowDocument, Be as DataSinkDataUpdatedDocument, Ve as DataSinkUpdatedDocument, K as DeleteDashboardDocument, q as DeleteDashboardPageDocument, J as DeleteDashboardViewDocument, Ne as DeleteDataSinkDocument, lt as DeleteEventWebhookMappingDocument, Y as DeleteGlobalFilterDocument, Ue as DeleteParserDocument, Xe as DeletePipelineDocument, X as DeleteTemplateDocument, dt as DeleteWidgetActionDocument, ut as DeleteWidgetDocument, ft as DeleteWidgetDrilldownDocument, At as DeleteWorkflowDocument, Z as DeprecateTemplateDocument, pt as DuplicateWidgetDocument, mt as EmitWidgetEventDocument, We as ExecuteParserDocument, Ze as ExecutePipelineDocument, jt as ExecuteWorkflowDocument, _e as ExportDashboardDocument, ve as GetDashboardDocument, ye as GetDashboardViewDocument, Re as GetDataSinkDataDocument, Le as GetDataSinkDocument, be as GetDefaultDashboardViewDocument, Ke as GetParserDocument, et as GetPipelineDocument, tt as GetPipelineExecutionDocument, xe as GetTemplateDocument, Tt as GetWidgetActionsDocument, wt as GetWidgetDocument, It as GetWorkflowByKeyDocument, Ft as GetWorkflowDocument, Q as ImportDashboardDocument, ne as InstantiateTemplateDocument, Pe as InvalidateDataSinkCacheDocument, Mt as LinkDataSinkToWorkflowDocument, Se as ListDashboardSharesDocument, Ce as ListDashboardViewsDocument, we as ListDashboardsDocument, ze as ListDataSinksDocument, Et as ListEventLogsDocument, $ as ListEventWebhookMappingsDocument, Te as ListFeaturedTemplatesDocument, qe as ListParsersDocument, nt as ListPipelineExecutionsDocument, rt as ListPipelinesDocument, Ee as ListSharedWithMeDocument, De as ListTemplatesDocument, Dt as ListWidgetsByDashboardDocument, Ot as ListWidgetsForPageDocument, Lt as ListWorkflowsDocument, re as PublishTemplateDocument, ie as RateTemplateDocument, Fe as RefreshDataSinkDocument, ht as RefreshWidgetDataDocument, ae as ReorderDashboardPagesDocument, oe as ReorderGlobalFiltersDocument, gt as ReorderWidgetActionsDocument, _t as RetryEventDeliveryDocument, se as RevokeAllSharesDocument, ce as RevokeDashboardShareDocument, Oe as SearchTemplatesDocument, le as SetDefaultViewDocument, ue as ShareDashboardDocument, Je as TestParserExpressionDocument, Qe as TestPipelineDocument, vt as TestWebhookMappingDocument, yt as ToggleEventWebhookMappingDocument, Nt as UnlinkDataSinkFromWorkflowDocument, de as UpdateDashboardDocument, fe as UpdateDashboardPageDocument, pe as UpdateDashboardShareDocument, me as UpdateDashboardViewDocument, Ie as UpdateDataSinkDocument, bt as UpdateEventWebhookMappingDocument, he as UpdateGlobalFilterDocument, Ge as UpdateParserDocument, $e as UpdatePipelineDocument, ge as UpdateTemplateDocument, St as UpdateWidgetActionDocument, xt as UpdateWidgetDocument, Ct as UpdateWidgetDrilldownDocument, Pt as UpdateWorkflowDocument, ke as ValidateDashboardImportDocument, R as WorkspaceInstalledAppsDocument };
1970
+ export { z as ArchiveTemplateDocument, Ae as BatchCreateDataSinksDocument, je as BatchDeleteDataSinksDocument, it as BatchUpdateWidgetPositionsDocument, B as CloneDashboardDocument, V as CreateDashboardDocument, H as CreateDashboardPageDocument, U as CreateDashboardViewDocument, Me as CreateDataSinkDocument, at as CreateEventWebhookMappingDocument, W as CreateGlobalFilterDocument, He as CreateParserDocument, Ye as CreatePipelineDocument, G as CreateTemplateDocument, st as CreateWidgetActionDocument, ot as CreateWidgetDocument, ct as CreateWidgetDrilldownDocument, At as CreateWorkflowDocument, Be as DataSinkDataUpdatedDocument, Ve as DataSinkUpdatedDocument, K as DeleteDashboardDocument, q as DeleteDashboardPageDocument, J as DeleteDashboardViewDocument, Ne as DeleteDataSinkDocument, lt as DeleteEventWebhookMappingDocument, Y as DeleteGlobalFilterDocument, Ue as DeleteParserDocument, Xe as DeletePipelineDocument, X as DeleteTemplateDocument, dt as DeleteWidgetActionDocument, ut as DeleteWidgetDocument, ft as DeleteWidgetDrilldownDocument, jt as DeleteWorkflowDocument, Z as DeprecateTemplateDocument, pt as DuplicateWidgetDocument, mt as EmitWidgetEventDocument, We as ExecuteParserDocument, Ze as ExecutePipelineDocument, Mt as ExecuteWorkflowDocument, _e as ExportDashboardDocument, ve as GetDashboardDocument, ye as GetDashboardViewDocument, Re as GetDataSinkDataDocument, Le as GetDataSinkDocument, be as GetDefaultDashboardViewDocument, Ke as GetParserDocument, et as GetPipelineDocument, tt as GetPipelineExecutionDocument, xe as GetTemplateDocument, Tt as GetWidgetActionsDocument, wt as GetWidgetDocument, Et as GetWidgetTypesDocument, Lt as GetWorkflowByKeyDocument, It as GetWorkflowDocument, Q as ImportDashboardDocument, ne as InstantiateTemplateDocument, Pe as InvalidateDataSinkCacheDocument, Nt as LinkDataSinkToWorkflowDocument, Se as ListDashboardSharesDocument, Ce as ListDashboardViewsDocument, we as ListDashboardsDocument, ze as ListDataSinksDocument, $ as ListEventLogsDocument, Dt as ListEventWebhookMappingsDocument, Te as ListFeaturedTemplatesDocument, qe as ListParsersDocument, nt as ListPipelineExecutionsDocument, rt as ListPipelinesDocument, Ee as ListSharedWithMeDocument, De as ListTemplatesDocument, Ot as ListWidgetsByDashboardDocument, kt as ListWidgetsForPageDocument, Rt as ListWorkflowsDocument, re as PublishTemplateDocument, ie as RateTemplateDocument, Fe as RefreshDataSinkDocument, ht as RefreshWidgetDataDocument, ae as ReorderDashboardPagesDocument, oe as ReorderGlobalFiltersDocument, gt as ReorderWidgetActionsDocument, _t as RetryEventDeliveryDocument, se as RevokeAllSharesDocument, ce as RevokeDashboardShareDocument, Oe as SearchTemplatesDocument, le as SetDefaultViewDocument, ue as ShareDashboardDocument, Je as TestParserExpressionDocument, Qe as TestPipelineDocument, vt as TestWebhookMappingDocument, yt as ToggleEventWebhookMappingDocument, Pt as UnlinkDataSinkFromWorkflowDocument, de as UpdateDashboardDocument, fe as UpdateDashboardPageDocument, pe as UpdateDashboardShareDocument, me as UpdateDashboardViewDocument, Ie as UpdateDataSinkDocument, bt as UpdateEventWebhookMappingDocument, he as UpdateGlobalFilterDocument, Ge as UpdateParserDocument, $e as UpdatePipelineDocument, ge as UpdateTemplateDocument, St as UpdateWidgetActionDocument, xt as UpdateWidgetDocument, Ct as UpdateWidgetDrilldownDocument, Ft as UpdateWorkflowDocument, ke as ValidateDashboardImportDocument, R as WorkspaceInstalledAppsDocument };
1971
1971
 
1972
1972
  //# sourceMappingURL=wspace-operations.js.map