@burdenoff/microfe-bigconsole 2026.924.2 → 2026.924.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.
Files changed (124) hide show
  1. package/dist/bigconsole/BigConsoleRoutes.js +137 -138
  2. package/dist/bigconsole/BigConsoleRoutes.js.map +1 -1
  3. package/dist/bigconsole/assistant/previewController.js +10 -10
  4. package/dist/bigconsole/components/DrilldownDashboardRenderer.js +17 -17
  5. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js +32 -32
  6. package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js +52 -52
  7. package/dist/bigconsole/components/dashboard/CreateDashboardDialog.js.map +1 -1
  8. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +7 -7
  9. package/dist/bigconsole/components/dashboard/DashboardStatusPill.js +53 -0
  10. package/dist/bigconsole/components/dashboard/DashboardStatusPill.js.map +1 -0
  11. package/dist/bigconsole/components/dashboard/DashboardToolbar.js +60 -60
  12. package/dist/bigconsole/components/dashboard/DrilldownBreadcrumb.js +21 -21
  13. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterChip.js +30 -30
  14. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js +36 -36
  15. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterPresetSelector.js +34 -34
  16. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js +38 -38
  17. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js.map +1 -1
  18. package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js +121 -121
  19. package/dist/bigconsole/components/dashboard/ImportDashboardDialog.js.map +1 -1
  20. package/dist/bigconsole/components/dashboard/PublishToStoreDialog.js +22 -22
  21. package/dist/bigconsole/components/dashboard/SavedViewsDropdown/SavedViewsDropdown.js +46 -46
  22. package/dist/bigconsole/components/dashboard/ShareDialog/EmbedTab.js +53 -53
  23. package/dist/bigconsole/components/dashboard/TemplatePicker/TemplatePicker.js +1 -1
  24. package/dist/bigconsole/components/dashboard/WidgetPalette.js +47 -47
  25. package/dist/bigconsole/components/dashboard/index.js +1 -0
  26. package/dist/bigconsole/components/datasink/CacheTTLSelector.js +35 -35
  27. package/dist/bigconsole/components/datasink/DataSinkDeleteDialog.js +18 -18
  28. package/dist/bigconsole/components/datasink/DataSinkRefreshDialog.js +20 -20
  29. package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js +30 -29
  30. package/dist/bigconsole/components/datasink/DataSinkStatusBadge.js.map +1 -1
  31. package/dist/bigconsole/components/datasink/ImportDataGuideDialog.js +48 -48
  32. package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js +103 -103
  33. package/dist/bigconsole/components/datasink/ImportDataSinkDialog.js.map +1 -1
  34. package/dist/bigconsole/components/datasink/RefreshTypeSelector.js +35 -35
  35. package/dist/bigconsole/components/overview/DashboardGuide.js +214 -0
  36. package/dist/bigconsole/components/overview/DashboardGuide.js.map +1 -0
  37. package/dist/bigconsole/components/overview/DataHealthCard.js +97 -0
  38. package/dist/bigconsole/components/overview/DataHealthCard.js.map +1 -0
  39. package/dist/bigconsole/components/overview/OverviewQuickActions.js +32 -0
  40. package/dist/bigconsole/components/overview/OverviewQuickActions.js.map +1 -0
  41. package/dist/bigconsole/components/overview/OverviewStatCard.js +57 -0
  42. package/dist/bigconsole/components/overview/OverviewStatCard.js.map +1 -0
  43. package/dist/bigconsole/components/overview/RecentDashboardsPanel.js +107 -0
  44. package/dist/bigconsole/components/overview/RecentDashboardsPanel.js.map +1 -0
  45. package/dist/bigconsole/components/overview/dashboardGuideSteps.js +119 -0
  46. package/dist/bigconsole/components/overview/dashboardGuideSteps.js.map +1 -0
  47. package/dist/bigconsole/components/overview/index.js +8 -0
  48. package/dist/bigconsole/components/overview/overviewModel.js +119 -0
  49. package/dist/bigconsole/components/overview/overviewModel.js.map +1 -0
  50. package/dist/bigconsole/components/overview/useOverviewData.js +48 -0
  51. package/dist/bigconsole/components/overview/useOverviewData.js.map +1 -0
  52. package/dist/bigconsole/components/parser/DataSinkSelector.js +40 -40
  53. package/dist/bigconsole/components/parser/ImportParserDialog.js +127 -127
  54. package/dist/bigconsole/components/parser/ImportParserDialog.js.map +1 -1
  55. package/dist/bigconsole/components/parser/ParserSelector.js +51 -51
  56. package/dist/bigconsole/components/preview/AiPreviewContent.js +5 -5
  57. package/dist/bigconsole/components/preview/AiPreviewMiniPanel.js +33 -33
  58. package/dist/bigconsole/components/preview/AiPreviewPanel.js +38 -38
  59. package/dist/bigconsole/components/widgets/ActionConfigDialog.js +13 -13
  60. package/dist/bigconsole/components/widgets/DrilldownConfigPanel.js +70 -70
  61. package/dist/bigconsole/components/widgets/PropertiesPanel.js +28 -28
  62. package/dist/bigconsole/components/widgets/calendar/CalendarWidget.js +25 -25
  63. package/dist/bigconsole/components/widgets/chart/ChartConfig.js +48 -48
  64. package/dist/bigconsole/components/widgets/comments/WidgetComments.js +46 -46
  65. package/dist/bigconsole/components/widgets/event-logs/EventLogViewer.js +61 -61
  66. package/dist/bigconsole/components/widgets/form/FormWidget.js +36 -36
  67. package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js +42 -42
  68. package/dist/bigconsole/components/widgets/heatmap/HeatmapConfig.js +25 -25
  69. package/dist/bigconsole/components/widgets/kanban/KanbanWidget.js +21 -21
  70. package/dist/bigconsole/components/widgets/list/ListConfig.js +30 -30
  71. package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js +38 -38
  72. package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js +9 -9
  73. package/dist/bigconsole/components/widgets/metric-card/TrendIndicator.js +9 -9
  74. package/dist/bigconsole/components/widgets/pivot-table/PivotTableWidget.js +49 -49
  75. package/dist/bigconsole/components/widgets/progress/ProgressConfig.js +39 -39
  76. package/dist/bigconsole/components/widgets/table/TableConfig.js +73 -73
  77. package/dist/bigconsole/components/widgets/webhook-config/WebhookConfigPanel.js +64 -64
  78. package/dist/bigconsole/customWidget/CustomWidgetBuilderSheet.js +10 -10
  79. package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js +68 -68
  80. package/dist/bigconsole/customWidget/CustomWidgetProposalPanel.js.map +1 -1
  81. package/dist/bigconsole/pages/AdminPage.js +136 -136
  82. package/dist/bigconsole/pages/DashboardBuilderPage.js +10 -10
  83. package/dist/bigconsole/pages/DashboardViewPage.js +61 -61
  84. package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
  85. package/dist/bigconsole/pages/DashboardsPage.js +327 -348
  86. package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
  87. package/dist/bigconsole/pages/DataParsersPage.js +5 -5
  88. package/dist/bigconsole/pages/DataSinkBuilderPage.js +10 -10
  89. package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
  90. package/dist/bigconsole/pages/DataSinkViewPage.js +83 -83
  91. package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
  92. package/dist/bigconsole/pages/DataSinksPage.js +11 -11
  93. package/dist/bigconsole/pages/DataSourcesPage.js +5 -5
  94. package/dist/bigconsole/pages/InsightsPage.js +315 -315
  95. package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
  96. package/dist/bigconsole/pages/OverviewPage.js +244 -0
  97. package/dist/bigconsole/pages/OverviewPage.js.map +1 -0
  98. package/dist/bigconsole/pages/ParserBuilderPage.js +5 -5
  99. package/dist/bigconsole/pages/ParserViewPage.js +98 -98
  100. package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
  101. package/dist/bigconsole/pages/PipelineBuilderPage.js +8 -8
  102. package/dist/bigconsole/pages/PipelineViewPage.js +46 -46
  103. package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
  104. package/dist/bigconsole/pages/PipelinesPage.js +80 -80
  105. package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
  106. package/dist/bigconsole/pages/SettingsPage.js +70 -70
  107. package/dist/bigconsole/pages/WidgetDemoPage.js +42 -42
  108. package/dist/bigconsole/pages/WorkflowViewPage.js +200 -200
  109. package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
  110. package/dist/bigconsole/pages/WorkflowsPage.js +78 -78
  111. package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
  112. package/dist/bigconsole/renderers/adapters/AdaptiveCardRenderer.js +34 -34
  113. package/dist/bigconsole/renderers/adapters/BlockKitRenderer.js +79 -79
  114. package/dist/bigconsole/renderers/adapters/CustomRenderer.js +46 -46
  115. package/dist/bigconsole/utils/createDialogParam.js +14 -0
  116. package/dist/bigconsole/utils/createDialogParam.js.map +1 -0
  117. package/dist/generated/global-operations.js +1 -1
  118. package/dist/generated/wspace-operations.js +285 -222
  119. package/dist/generated/wspace-operations.js.map +1 -1
  120. package/dist/shared/hooks/useTr.js +10 -7
  121. package/dist/shared/hooks/useTr.js.map +1 -1
  122. package/package.json +1 -1
  123. package/dist/bigconsole/pages/HomePage.js +0 -318
  124. package/dist/bigconsole/pages/HomePage.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardsPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardsPage.tsx"],"sourcesContent":["/**\n * DashboardsPage\n *\n * List and manage dashboards - fetches data from real backend.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\nimport { useBigConsoleNavigate } from '../context';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport {\n AuroraBackground,\n GradientText,\n GlassCard,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Plus,\n Upload,\n Search,\n LayoutDashboard,\n Calendar,\n Layers,\n FileText,\n Pencil,\n Trash2,\n Copy,\n LayoutGrid,\n List,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport { useDashboardOperations } from '../hooks';\nimport type { DashboardListStatus } from '../hooks/useDashboardOperations';\nimport { CreateDashboardDialog } from '../components/dashboard/CreateDashboardDialog';\nimport { ImportDashboardDialog } from '../components/dashboard/ImportDashboardDialog';\nimport { Section, SectionStack } from '../components/common';\nimport { reportBigConsoleQuotaError } from '../utils/quota';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst VIEW_MODE_STORAGE_KEY = 'bf-bigconsole-dashboards-view';\n\n/** Number of dashboards shown per page in the list/grid views. */\nconst PAGE_SIZE = 12;\n\n/** How many numbered page buttons to render before falling back to a window. */\nconst MAX_PAGE_BUTTONS = 7;\n\n/**\n * Numbered pages to render around the current one.\n *\n * Rendering one button per page turns a workspace with a hundred boards into a\n * multi-line wall of numbers — on a 390px viewport that block is taller than\n * the result it paginates. Windowing keeps the footer to a single row at every\n * breakpoint; `null` marks an elided run.\n */\nfunction getPageWindow(currentPage: number, totalPages: number): Array<number | null> {\n if (totalPages <= MAX_PAGE_BUTTONS) {\n return Array.from({ length: totalPages }, (_, i) => i + 1);\n }\n\n const pages = new Set<number>([1, totalPages, currentPage]);\n if (currentPage - 1 > 1) pages.add(currentPage - 1);\n if (currentPage + 1 < totalPages) pages.add(currentPage + 1);\n\n const ordered = Array.from(pages).sort((a, b) => a - b);\n const windowed: Array<number | null> = [];\n ordered.forEach((page, index) => {\n if (index > 0 && page - ordered[index - 1] > 1) windowed.push(null);\n windowed.push(page);\n });\n return windowed;\n}\n\n// ============================================================================\n// Page header band\n// ============================================================================\n\ninterface DashboardsHeaderBandProps {\n title: string;\n subtitle: string;\n createLabel: string;\n importLabel: string;\n onCreate: () => void;\n onImport: () => void;\n /** RBAC: the CTA is rendered but disabled without dashboard:create. */\n canCreate: boolean;\n /** RBAC: the overflow import action is disabled without dashboard:import. */\n canImport: boolean;\n}\n\n/**\n * The route's identity strip. It is deliberately the only decorated band on the\n * page: everything below it steps down to plain sections, so the eye always\n * knows where the page starts and where the content starts. Shared with the\n * error state so a failed fetch never costs the user the page identity.\n */\nconst DashboardsHeaderBand: FC<DashboardsHeaderBandProps> = memo(function DashboardsHeaderBand({\n title,\n subtitle,\n createLabel,\n importLabel,\n onCreate,\n onImport,\n canCreate,\n canImport,\n}) {\n return (\n <div className=\"relative overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: 'var(--space-formGap)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-formGap)', minWidth: 0 }}>\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n flexShrink: 0,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <LayoutDashboard\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n <div style={{ minWidth: 0 }}>\n <h1\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n margin: 0,\n color: 'var(--color-text-primary)',\n }}\n >\n <GradientText>{title}</GradientText>\n </h1>\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-secondary)', margin: 0 }}>\n {subtitle}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={{\n label: createLabel,\n onSelect: onCreate,\n disabled: !canCreate,\n icon: (\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n ),\n }}\n actions={[\n {\n label: importLabel,\n onSelect: onImport,\n disabled: !canImport,\n icon: <Upload style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />,\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n});\n\n// ============================================================================\n// Status pill\n// ============================================================================\n\n/**\n * Lifecycle status of a dashboard.\n *\n * Backed by `Dashboard.status` (DashboardStatus: DRAFT | PUBLISHED | ARCHIVED),\n * which the list query now selects. Whether a board is a published surface or\n * still a draft is the fact a list of boards is most often scanned for, and it\n * was the one thing the row did not say.\n */\nconst DASHBOARD_STATUS_DOT: Record<DashboardListStatus, string> = {\n PUBLISHED: 'var(--color-status-success-bg)',\n DRAFT: 'var(--color-text-muted)',\n ARCHIVED: 'var(--color-status-warning-bg)',\n};\n\ninterface DashboardStatusPillProps {\n status: DashboardListStatus;\n label: string;\n}\n\nconst DashboardStatusPill: FC<DashboardStatusPillProps> = memo(function DashboardStatusPill({ status, label }) {\n return (\n <span\n data-testid=\"dashboard-status\"\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n padding: '2px 8px',\n borderRadius: 'var(--radius-pill)',\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px solid var(--color-border-subtle)',\n fontSize: '10px',\n fontWeight: 'var(--font-weight-semibold)',\n textTransform: 'uppercase',\n letterSpacing: '0.02em',\n color: 'var(--color-text-secondary)',\n whiteSpace: 'nowrap',\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n width: 6,\n height: 6,\n borderRadius: '50%',\n backgroundColor: DASHBOARD_STATUS_DOT[status],\n flexShrink: 0,\n }}\n />\n {label}\n </span>\n );\n});\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardsPage: FC = memo(function DashboardsPage() {\n const navigate = useBigConsoleNavigate();\n const tr = useTr();\n const [searchQuery, setSearchQuery] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);\n const [isCreating, setIsCreating] = useState(false);\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n\n // View mode (grid/list) persisted to localStorage so it survives reloads\n const [viewMode, setViewMode] = useState<'grid' | 'list'>(() => {\n try {\n const stored = window.localStorage.getItem(VIEW_MODE_STORAGE_KEY);\n return stored === 'list' ? 'list' : 'grid';\n } catch {\n return 'grid';\n }\n });\n\n const handleSetViewMode = useCallback((mode: 'grid' | 'list') => {\n setViewMode(mode);\n try {\n window.localStorage.setItem(VIEW_MODE_STORAGE_KEY, mode);\n } catch {\n // Ignore storage errors (e.g. private mode) - view mode still works in-session\n }\n }, []);\n\n const { loading, error, dashboards, createDashboard, deleteDashboard, cloneDashboard, refetch } =\n useDashboardOperations();\n\n // RBAC gating — permission set is resolved by the root PermissionProvider.\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_READ);\n const canCreate = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_CREATE);\n const canImport = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_IMPORT);\n\n // Filter dashboards by search query\n const filteredDashboards = dashboards.filter(\n (d) =>\n d.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n d.description?.toLowerCase().includes(searchQuery.toLowerCase())\n );\n\n // Client-side pagination over the filtered result set\n const totalPages = Math.max(1, Math.ceil(filteredDashboards.length / PAGE_SIZE));\n const pageStartIndex = (currentPage - 1) * PAGE_SIZE;\n const paginatedDashboards = filteredDashboards.slice(pageStartIndex, pageStartIndex + PAGE_SIZE);\n\n // Reset to the first page when the search query or view changes so the\n // visible slice always starts from the top of the (possibly new) result set.\n useEffect(() => {\n setCurrentPage(1);\n }, [searchQuery, viewMode]);\n\n // Guard against the current page exceeding the available pages (e.g. after a\n // delete removes the last item on the final page).\n useEffect(() => {\n if (currentPage > totalPages) {\n setCurrentPage(totalPages);\n }\n }, [currentPage, totalPages]);\n\n const handlePrevPage = useCallback(() => {\n setCurrentPage((prev) => Math.max(1, prev - 1));\n }, []);\n\n const handleNextPage = useCallback(() => {\n setCurrentPage((prev) => Math.min(totalPages, prev + 1));\n }, [totalPages]);\n\n const handleGoToPage = useCallback((page: number) => {\n setCurrentPage(page);\n }, []);\n\n const handleOpenCreateDialog = useCallback(() => {\n setIsCreateDialogOpen(true);\n }, []);\n\n /**\n * A template install creates the dashboard inside the dialog rather than\n * through onCreate, so the list has no idea it happened. Refetch and open the\n * new board — landing on it is what makes the \"connect your data\" prompts\n * discoverable.\n */\n const handleTemplateInstalled = useCallback(\n ({ dashboardId }: { dashboardId: string; widgetsNeedingData: number }) => {\n refetch();\n navigate(`/dashboards/${dashboardId}`);\n },\n [refetch, navigate]\n );\n\n const handleCloseCreateDialog = useCallback(() => {\n if (!isCreating) {\n setIsCreateDialogOpen(false);\n }\n }, [isCreating]);\n\n const handleCreateDashboard = useCallback(\n async (data: { name: string; description: string; category: string; tags?: string[] }) => {\n setIsCreating(true);\n try {\n const newDashboard = await createDashboard({\n name: data.name,\n description: data.description,\n category: data.category,\n tags: data.tags,\n });\n\n if (newDashboard) {\n setIsCreateDialogOpen(false);\n navigate(`/dashboards/${newDashboard.id}/builder`);\n }\n } catch (err) {\n if (reportBigConsoleQuotaError(err, 'project')) {\n setIsCreateDialogOpen(false);\n return;\n }\n // Surface the failure instead of swallowing it: log for debugging,\n // notify on native, and rethrow so CreateDashboardDialog displays the error.\n console.error('Failed to create dashboard:', err);\n if (isNative()) void nativeNotify('error');\n throw err;\n } finally {\n setIsCreating(false);\n }\n },\n [createDashboard, navigate]\n );\n\n const handleOpenDashboard = useCallback(\n (dashboardId: string) => {\n navigate(`/dashboards/${dashboardId}`);\n },\n [navigate]\n );\n\n const handleEditDashboard = useCallback(\n (dashboardId: string, e: React.MouseEvent) => {\n e.stopPropagation();\n navigate(`/dashboards/${dashboardId}/builder`);\n },\n [navigate]\n );\n\n const handleDeleteDashboard = useCallback(\n async (dashboardId: string, e: React.MouseEvent) => {\n e.stopPropagation();\n const msg = tr('bigconsole.dashboards.confirmDelete', 'Are you sure you want to delete this dashboard?');\n const native = await nativeConfirm({\n title: 'Delete dashboard?',\n message: msg,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok = native === null ? window.confirm(msg) : native;\n if (ok) {\n nativeImpact('medium');\n try {\n await deleteDashboard(dashboardId);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n }\n },\n [deleteDashboard, tr]\n );\n\n const handleCloneDashboard = useCallback(\n async (dashboardId: string, sourceName: string, e: React.MouseEvent) => {\n e.stopPropagation();\n const defaultName = tr('bigconsole.dashboards.clone.defaultSuffix', '{{sourceName}} (Copy)', {\n sourceName,\n });\n const name = window.prompt(\n tr('bigconsole.dashboards.clone.namePrompt', 'Name for the cloned dashboard:'),\n defaultName\n );\n if (!name || !name.trim()) return;\n nativeImpact('medium');\n try {\n const cloned = await cloneDashboard(dashboardId, name.trim());\n if (cloned) {\n if (isNative()) void nativeNotify('success');\n navigate(`/dashboards/${cloned.id}`);\n }\n } catch (err) {\n if (reportBigConsoleQuotaError(err, 'project')) {\n return;\n }\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n },\n [cloneDashboard, navigate, tr]\n );\n\n const handleOpenImportDialog = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleCloseImportDialog = useCallback(() => {\n setIsImportDialogOpen(false);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_dashboardId: string, _dashboardName: string) => {\n refetch();\n },\n [refetch]\n );\n\n // Format relative date\n const formatRelativeDate = (dateStr: string) => {\n const date = new Date(dateStr);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMs / 3600000);\n const diffDays = Math.floor(diffMs / 86400000);\n\n if (diffMins < 1) return tr('bigconsole.dashboards.time.justNow', 'Just now');\n if (diffMins < 60) return `${diffMins}${tr('bigconsole.dashboards.time.minutesAgoSuffix', 'm ago')}`;\n if (diffHours < 24) return `${diffHours}${tr('bigconsole.dashboards.time.hoursAgoSuffix', 'h ago')}`;\n if (diffDays < 7) return `${diffDays}${tr('bigconsole.dashboards.time.daysAgoSuffix', 'd ago')}`;\n\n return new Intl.DateTimeFormat('en-US', {\n month: 'short',\n day: 'numeric',\n year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined,\n }).format(date);\n };\n\n // Human label for the dashboard lifecycle status.\n const getStatusLabel = useCallback(\n (status: DashboardListStatus) => {\n switch (status) {\n case 'PUBLISHED':\n return tr('bigconsole.dashboards.status.published', 'Published');\n case 'ARCHIVED':\n return tr('bigconsole.dashboards.status.archived', 'Archived');\n default:\n return tr('bigconsole.dashboards.status.draft', 'Draft');\n }\n },\n [tr]\n );\n\n // Get widget count - uses widgetCount field from backend\n const getWidgetCount = (dashboard: (typeof dashboards)[0]) => {\n return dashboard.widgetCount ?? 0;\n };\n\n // Get page count\n const getPageCount = (dashboard: (typeof dashboards)[0]) => {\n return dashboard.pageCount || 0;\n };\n\n const headerBand = (\n <DashboardsHeaderBand\n title={tr('bigconsole.dashboards.title', 'Dashboards')}\n subtitle={tr('bigconsole.dashboards.subtitle', 'Create and manage your analytics dashboards')}\n createLabel={tr('bigconsole.dashboards.actions.create', 'Create Dashboard')}\n importLabel={tr('bigconsole.dashboards.actions.import', 'Import')}\n onCreate={handleOpenCreateDialog}\n onImport={handleOpenImportDialog}\n canCreate={canCreate}\n canImport={canImport}\n />\n );\n\n // Permission read gate — wait for the provider to resolve before deciding,\n // then block the page body for callers without dashboard read access.\n if (permissionsLoading) {\n return (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '80px 0' }}>\n <Spinner\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n }}\n />\n </div>\n );\n }\n\n if (!canRead) {\n return (\n <AccessDenied\n title={tr('bigconsole.dashboards.accessDenied.title', 'Access Denied')}\n description={tr(\n 'bigconsole.dashboards.accessDenied.description',\n \"You don't have permission to view dashboards.\"\n )}\n />\n );\n }\n\n // Error state — keeps the page identity so the user still knows where they are.\n if (error) {\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {headerBand}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Section\n headingLevel={2}\n tone=\"danger\"\n title={tr('bigconsole.dashboards.error.title', 'Error loading dashboards')}\n description={error.message}\n data-testid=\"dashboards-error\"\n actions={\n <Button size=\"sm\" onClick={() => refetch()}>\n {tr('bigconsole.dashboards.error.retry', 'Retry')}\n </Button>\n }\n />\n </div>\n </div>\n );\n }\n\n const resultCount = filteredDashboards.length;\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Header Section */}\n {headerBand}\n\n {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <SectionStack>\n {/* Supporting context — deliberately recessed so it never competes\n with the results it introduces. */}\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n aria-label={tr('bigconsole.dashboards.about', 'About dashboards')}\n >\n <PagePurpose className=\"max-w-[80ch]\">\n Dashboards are live, shareable views built from widgets that query your data sinks — charts, tables,\n metrics, and drill-downs in one place. Compose a board to monitor a system, share KPIs with your team, or\n explore data interactively.\n </PagePurpose>\n </Section>\n\n {/* The point of this route: the boards themselves. */}\n <Section\n headingLevel={2}\n emphasis=\"primary\"\n data-testid=\"dashboards-results\"\n title={tr('bigconsole.dashboards.results.title', 'Your dashboards')}\n description={tr(\n 'bigconsole.dashboards.results.description',\n 'Open a board to view it, or use the card actions to edit, duplicate or delete.'\n )}\n meta={\n <Badge\n variant=\"secondary\"\n aria-label={tr('bigconsole.dashboards.results.count', `${resultCount} dashboards`)}\n >\n {resultCount}\n </Badge>\n }\n actions={\n <div\n role=\"group\"\n aria-label={tr('bigconsole.dashboards.view.label', 'Result layout')}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n padding: 'var(--space-iconGap)',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-bg-sunken)',\n }}\n >\n <Button\n variant={viewMode === 'grid' ? 'default' : 'ghost'}\n size=\"sm\"\n onClick={() => handleSetViewMode('grid')}\n aria-label={tr('bigconsole.dashboards.view.grid', 'Grid view')}\n aria-pressed={viewMode === 'grid'}\n title={tr('bigconsole.dashboards.view.grid', 'Grid view')}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n >\n <LayoutGrid style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'default' : 'ghost'}\n size=\"sm\"\n onClick={() => handleSetViewMode('list')}\n aria-label={tr('bigconsole.dashboards.view.list', 'List view')}\n aria-pressed={viewMode === 'list'}\n title={tr('bigconsole.dashboards.view.list', 'List view')}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n >\n <List style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </div>\n }\n bodyClassName=\"flex flex-col gap-form-gap\"\n >\n {/* Search — grouped with the results it filters, not floating above them */}\n <div style={{ position: 'relative', width: '100%', maxWidth: '420px' }}>\n <Search\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n left: 'var(--space-controlGap)',\n top: '50%',\n transform: 'translateY(-50%)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <Input\n type=\"search\"\n aria-label={tr('bigconsole.dashboards.search.placeholder', 'Search dashboards...')}\n placeholder={tr('bigconsole.dashboards.search.placeholder', 'Search dashboards...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Loading state */}\n {loading && dashboards.length === 0 && (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '80px 0' }}>\n <div style={{ textAlign: 'center' }}>\n <Spinner\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n margin: '0 auto var(--space-formGap)',\n }}\n />\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.loading', 'Loading dashboards...')}\n </p>\n </div>\n </div>\n )}\n\n {/* Dashboard Grid */}\n {!loading && viewMode === 'grid' && filteredDashboards.length > 0 && (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(320px, 100%), 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {paginatedDashboards.map((dashboard) => (\n <Card\n key={dashboard.id}\n onClick={() => handleOpenDashboard(dashboard.id)}\n style={{ cursor: 'pointer' }}\n className=\"motion-safe:transition-[box-shadow,border-color] hover:border-[var(--color-border-hover)] hover:shadow-[var(--shadow-elevation-2)]\"\n >\n <CardContent style={{ padding: 'var(--space-cardPadding)' }}>\n {/* Card Header with Icon and Name */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n flexShrink: 0,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-bg-accentSubtle)',\n }}\n >\n <LayoutDashboard\n aria-hidden=\"true\"\n style={{\n height: 'var(--size-icon-md)',\n width: 'var(--size-icon-md)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n </div>\n <div style={{ flex: 1, minWidth: 0 }}>\n <h3\n style={{\n fontWeight: 'var(--font-weight-bold)',\n fontSize: 'var(--font-size-body-md)',\n margin: 0,\n color: 'var(--color-text-primary)',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {dashboard.name}\n </h3>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n marginTop: 'var(--space-controlGap)',\n }}\n >\n {dashboard.category && (\n <Badge\n variant=\"secondary\"\n style={{\n fontSize: '10px',\n fontWeight: 'var(--font-weight-semibold)',\n textTransform: 'uppercase',\n }}\n >\n {dashboard.category}\n </Badge>\n )}\n {dashboard.status && (\n <DashboardStatusPill status={dashboard.status} label={getStatusLabel(dashboard.status)} />\n )}\n </div>\n </div>\n </div>\n\n {/* Description */}\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n lineHeight: 'var(--font-lineHeight-normal)',\n color: 'var(--color-text-secondary)',\n margin: '0 0 var(--space-formGap) 0',\n minHeight: '40px',\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden',\n }}\n >\n {dashboard.description ||\n tr('bigconsole.dashboards.card.noDescription', 'No description provided')}\n </p>\n\n {/* Stats Row */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n marginBottom: 'var(--space-formGap)',\n padding: 'var(--space-controlGap) var(--space-cardPadding)',\n backgroundColor: 'var(--color-bg-sunken)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Layers\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n {getPageCount(dashboard)}\n </span>\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.card.pages', 'pages')}\n </span>\n </div>\n <div\n style={{\n width: '1px',\n height: 'var(--size-icon-sm)',\n backgroundColor: 'var(--color-border-subtle)',\n }}\n />\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <FileText\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n {getWidgetCount(dashboard)}\n </span>\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.card.widgets', 'widgets')}\n </span>\n </div>\n </div>\n\n {/* Footer with Date and Actions */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: 'var(--space-controlGap)',\n paddingTop: 'var(--space-controlGap)',\n borderTop: '1px solid var(--color-border-subtle)',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n }}\n >\n <Calendar\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-muted)',\n }}\n >\n {formatRelativeDate(dashboard.updatedAt)}\n </span>\n </div>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-controlGap)' }}>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_UPDATE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleEditDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n title={tr('bigconsole.dashboards.actions.editTitle', 'Edit Dashboard')}\n >\n <Pencil style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_CREATE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleCloneDashboard(dashboard.id, dashboard.name, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n title={tr('bigconsole.dashboards.actions.cloneTitle', 'Clone Dashboard')}\n >\n <Copy style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_DELETE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleDeleteDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n className=\"hover:text-status-error-text hover:bg-status-error-bg/10\"\n title={tr('bigconsole.dashboards.actions.deleteTitle', 'Delete Dashboard')}\n >\n <Trash2 style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* Dashboard List */}\n {!loading && viewMode === 'list' && filteredDashboards.length > 0 && (\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: 'var(--space-controlGap)',\n }}\n >\n {paginatedDashboards.map((dashboard) => (\n <Card\n key={dashboard.id}\n onClick={() => handleOpenDashboard(dashboard.id)}\n style={{ cursor: 'pointer' }}\n className=\"motion-safe:transition-[box-shadow,border-color] hover:border-[var(--color-border-hover)] hover:shadow-[var(--shadow-elevation-2)]\"\n >\n <CardContent style={{ padding: 'var(--space-cardPadding)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n flexWrap: 'wrap',\n }}\n >\n {/* Icon */}\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n flexShrink: 0,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-bg-accentSubtle)',\n }}\n >\n <LayoutDashboard\n aria-hidden=\"true\"\n style={{\n height: 'var(--size-icon-md)',\n width: 'var(--size-icon-md)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n </div>\n\n {/* Name + Description */}\n <div style={{ flex: '1 1 12rem', minWidth: 0 }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n }}\n >\n <h3\n style={{\n fontWeight: 'var(--font-weight-bold)',\n fontSize: 'var(--font-size-body-md)',\n margin: 0,\n color: 'var(--color-text-primary)',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n maxWidth: '100%',\n }}\n >\n {dashboard.name}\n </h3>\n {dashboard.category && (\n <Badge\n variant=\"secondary\"\n style={{\n fontSize: '10px',\n fontWeight: 'var(--font-weight-semibold)',\n textTransform: 'uppercase',\n }}\n >\n {dashboard.category}\n </Badge>\n )}\n {dashboard.status && (\n <DashboardStatusPill status={dashboard.status} label={getStatusLabel(dashboard.status)} />\n )}\n </div>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n lineHeight: 'var(--font-lineHeight-normal)',\n color: 'var(--color-text-secondary)',\n margin: 'var(--space-controlGap) 0 0 0',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {dashboard.description ||\n tr('bigconsole.dashboards.card.noDescription', 'No description provided')}\n </p>\n </div>\n\n {/* Stats + Date */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n flexWrap: 'wrap',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Layers\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n {getPageCount(dashboard)}\n </span>\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.card.pages', 'pages')}\n </span>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <FileText\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n {getWidgetCount(dashboard)}\n </span>\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.card.widgets', 'widgets')}\n </span>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Calendar\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-muted)',\n }}\n >\n {formatRelativeDate(dashboard.updatedAt)}\n </span>\n </div>\n </div>\n\n {/* Actions */}\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-controlGap)' }}>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_UPDATE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleEditDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n title={tr('bigconsole.dashboards.actions.editTitle', 'Edit Dashboard')}\n >\n <Pencil style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_CREATE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleCloneDashboard(dashboard.id, dashboard.name, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n title={tr('bigconsole.dashboards.actions.cloneTitle', 'Clone Dashboard')}\n >\n <Copy style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_DELETE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleDeleteDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n className=\"hover:text-status-error-text hover:bg-status-error-bg/10\"\n title={tr('bigconsole.dashboards.actions.deleteTitle', 'Delete Dashboard')}\n >\n <Trash2 style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* How much of the result set is on screen. Without it a paged list\n says nothing about how many boards there are in total — and the\n counts are already in hand, no extra query. */}\n {!loading && filteredDashboards.length > 0 && (\n <p\n data-testid=\"dashboards-range\"\n style={{\n margin: 0,\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n }}\n >\n {tr('bigconsole.dashboards.pagination.range', 'Showing {{from}}–{{to}} of {{total}} dashboards', {\n from: pageStartIndex + 1,\n to: pageStartIndex + paginatedDashboards.length,\n total: filteredDashboards.length,\n })}\n </p>\n )}\n\n {/* Pagination — a footer control, separated from the results it pages */}\n {!loading && filteredDashboards.length > PAGE_SIZE && (\n <nav\n aria-label={tr('bigconsole.dashboards.pagination.label', 'Dashboard pages')}\n className=\"flex flex-wrap items-center justify-center gap-control-gap border-t border-[var(--color-border-subtle)] pt-control-gap\"\n >\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handlePrevPage}\n disabled={currentPage <= 1}\n aria-label={tr('bigconsole.dashboards.pagination.previous', 'Previous')}\n style={{ minHeight: 'var(--size-controlHeight-md)' }}\n >\n <ChevronLeft\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dashboards.pagination.previous', 'Previous')}\n </Button>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)', flexWrap: 'wrap' }}>\n {getPageWindow(currentPage, totalPages).map((page, index) =>\n page === null ? (\n <span\n key={`gap-${index}`}\n aria-hidden=\"true\"\n style={{ color: 'var(--color-text-muted)', padding: '0 var(--space-iconGap)' }}\n >\n …\n </span>\n ) : (\n <Button\n key={page}\n variant={page === currentPage ? 'default' : 'ghost'}\n size=\"sm\"\n onClick={() => handleGoToPage(page)}\n aria-current={page === currentPage ? 'page' : undefined}\n aria-label={tr('bigconsole.dashboards.pagination.page', `Page ${page}`)}\n style={{\n minWidth: 'var(--size-controlHeight-md)',\n height: 'var(--size-controlHeight-md)',\n padding: '0 var(--space-controlGap)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n {page}\n </Button>\n )\n )}\n </div>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleNextPage}\n disabled={currentPage >= totalPages}\n aria-label={tr('bigconsole.dashboards.pagination.next', 'Next')}\n style={{ minHeight: 'var(--size-controlHeight-md)' }}\n >\n {tr('bigconsole.dashboards.pagination.next', 'Next')}\n <ChevronRight\n style={{\n marginLeft: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n </Button>\n\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n }}\n >\n {tr('bigconsole.dashboards.pagination.pageOf', 'Page {{currentPage}} of {{totalPages}}', {\n currentPage,\n totalPages,\n })}\n </span>\n </nav>\n )}\n\n {/* Empty state */}\n {!loading && filteredDashboards.length === 0 && (\n <GlassCard treatment=\"insight\" className=\"border-[var(--color-border-seam)]\">\n <IllustratedEmptyState\n illustration={searchQuery ? 'empty-search' : 'empty-data'}\n title={\n searchQuery\n ? tr('bigconsole.dashboards.empty.filteredTitle', 'No dashboards found')\n : tr('bigconsole.dashboards.empty.title', 'No dashboards yet')\n }\n description={\n searchQuery\n ? tr('bigconsole.dashboards.empty.filteredDescription', 'Try adjusting your search terms')\n : tr(\n 'bigconsole.dashboards.empty.description',\n 'Get started by creating your first dashboard to visualize your data'\n )\n }\n action={\n !searchQuery ? (\n <div\n style={{\n display: 'flex',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n justifyContent: 'center',\n }}\n >\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_CREATE} deniedBehavior=\"hide\">\n <Button onClick={handleOpenCreateDialog}>\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dashboards.actions.create', 'Create Dashboard')}\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_IMPORT} deniedBehavior=\"hide\">\n <Button variant=\"outline\" onClick={handleOpenImportDialog}>\n <Upload\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dashboards.actions.importDashboard', 'Import Dashboard')}\n </Button>\n </PermissionButton>\n </div>\n ) : undefined\n }\n />\n </GlassCard>\n )}\n </Section>\n </SectionStack>\n </div>\n\n {/* Create Dashboard Dialog */}\n <CreateDashboardDialog\n isOpen={isCreateDialogOpen}\n isCreating={isCreating}\n onClose={handleCloseCreateDialog}\n onCreate={handleCreateDashboard}\n onTemplateInstalled={handleTemplateInstalled}\n />\n\n {/* Import Dashboard Dialog */}\n <ImportDashboardDialog\n isOpen={isImportDialogOpen}\n onClose={handleCloseImportDialog}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DashboardsPage;\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardsPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardsPage.tsx"],"sourcesContent":["/**\n * DashboardsPage\n *\n * List and manage dashboards - fetches data from real backend.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useTr } from '../../shared/hooks/useTr';\nimport { AccessDenied, PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { BIGCONSOLE_PERMISSIONS } from '../../constants/permissions';\nimport { useBigConsoleNavigate } from '../context';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport {\n AuroraBackground,\n GradientText,\n GlassCard,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Plus,\n Upload,\n Search,\n LayoutDashboard,\n Calendar,\n Layers,\n FileText,\n Pencil,\n Trash2,\n Copy,\n LayoutGrid,\n List,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport { useDashboardOperations } from '../hooks';\nimport { CreateDashboardDialog } from '../components/dashboard/CreateDashboardDialog';\nimport { DashboardStatusPill, useDashboardStatusLabel } from '../components/dashboard/DashboardStatusPill';\nimport { ImportDashboardDialog } from '../components/dashboard/ImportDashboardDialog';\nimport { Section, SectionStack } from '../components/common';\nimport { reportBigConsoleQuotaError } from '../utils/quota';\nimport { resolveCreateParam, withoutCreateParam } from '../utils/createDialogParam';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst VIEW_MODE_STORAGE_KEY = 'bf-bigconsole-dashboards-view';\n\n/** Number of dashboards shown per page in the list/grid views. */\nconst PAGE_SIZE = 12;\n\n/** How many numbered page buttons to render before falling back to a window. */\nconst MAX_PAGE_BUTTONS = 7;\n\n/**\n * Numbered pages to render around the current one.\n *\n * Rendering one button per page turns a workspace with a hundred boards into a\n * multi-line wall of numbers — on a 390px viewport that block is taller than\n * the result it paginates. Windowing keeps the footer to a single row at every\n * breakpoint; `null` marks an elided run.\n */\nfunction getPageWindow(currentPage: number, totalPages: number): Array<number | null> {\n if (totalPages <= MAX_PAGE_BUTTONS) {\n return Array.from({ length: totalPages }, (_, i) => i + 1);\n }\n\n const pages = new Set<number>([1, totalPages, currentPage]);\n if (currentPage - 1 > 1) pages.add(currentPage - 1);\n if (currentPage + 1 < totalPages) pages.add(currentPage + 1);\n\n const ordered = Array.from(pages).sort((a, b) => a - b);\n const windowed: Array<number | null> = [];\n ordered.forEach((page, index) => {\n if (index > 0 && page - ordered[index - 1] > 1) windowed.push(null);\n windowed.push(page);\n });\n return windowed;\n}\n\n// ============================================================================\n// Page header band\n// ============================================================================\n\ninterface DashboardsHeaderBandProps {\n title: string;\n subtitle: string;\n createLabel: string;\n importLabel: string;\n onCreate: () => void;\n onImport: () => void;\n /** RBAC: the CTA is rendered but disabled without dashboard:create. */\n canCreate: boolean;\n /** RBAC: the overflow import action is disabled without dashboard:import. */\n canImport: boolean;\n}\n\n/**\n * The route's identity strip. It is deliberately the only decorated band on the\n * page: everything below it steps down to plain sections, so the eye always\n * knows where the page starts and where the content starts. Shared with the\n * error state so a failed fetch never costs the user the page identity.\n */\nconst DashboardsHeaderBand: FC<DashboardsHeaderBandProps> = memo(function DashboardsHeaderBand({\n title,\n subtitle,\n createLabel,\n importLabel,\n onCreate,\n onImport,\n canCreate,\n canImport,\n}) {\n return (\n <div className=\"relative overflow-hidden border-b border-[var(--color-border-seam)] bg-[image:var(--gradient-gloss-card)]\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: 'var(--space-formGap)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-formGap)', minWidth: 0 }}>\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n flexShrink: 0,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <LayoutDashboard\n style={{\n height: 'var(--size-icon-lg)',\n width: 'var(--size-icon-lg)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n <div style={{ minWidth: 0 }}>\n <h1\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n margin: 0,\n color: 'var(--color-text-primary)',\n }}\n >\n <GradientText>{title}</GradientText>\n </h1>\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-secondary)', margin: 0 }}>\n {subtitle}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={{\n label: createLabel,\n onSelect: onCreate,\n disabled: !canCreate,\n icon: (\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n ),\n }}\n actions={[\n {\n label: importLabel,\n onSelect: onImport,\n disabled: !canImport,\n icon: <Upload style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />,\n },\n ]}\n />\n </div>\n </div>\n </div>\n );\n});\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardsPage: FC = memo(function DashboardsPage() {\n const navigate = useBigConsoleNavigate();\n const tr = useTr();\n const [searchQuery, setSearchQuery] = useState('');\n const [currentPage, setCurrentPage] = useState(1);\n const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);\n const [isCreating, setIsCreating] = useState(false);\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n\n // View mode (grid/list) persisted to localStorage so it survives reloads\n const [viewMode, setViewMode] = useState<'grid' | 'list'>(() => {\n try {\n const stored = window.localStorage.getItem(VIEW_MODE_STORAGE_KEY);\n return stored === 'list' ? 'list' : 'grid';\n } catch {\n return 'grid';\n }\n });\n\n const handleSetViewMode = useCallback((mode: 'grid' | 'list') => {\n setViewMode(mode);\n try {\n window.localStorage.setItem(VIEW_MODE_STORAGE_KEY, mode);\n } catch {\n // Ignore storage errors (e.g. private mode) - view mode still works in-session\n }\n }, []);\n\n const { loading, error, dashboards, createDashboard, deleteDashboard, cloneDashboard, refetch } =\n useDashboardOperations();\n\n // RBAC gating — permission set is resolved by the root PermissionProvider.\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canRead = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_READ);\n const canCreate = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_CREATE);\n const canImport = hasPermission(BIGCONSOLE_PERMISSIONS.DASHBOARD_IMPORT);\n\n // `?create=1` (linked from the Overview) opens the Create Dashboard dialog.\n // Declared before the permission early-returns below; it waits for the\n // permission set so the deep link is not dropped on the first render, then\n // always removes the param so a reload or back-navigation doesn't reopen it.\n const [searchParams, setSearchParams] = useSearchParams();\n useEffect(() => {\n const decision = resolveCreateParam(searchParams, { permissionsLoading, canRead, canCreate });\n if (decision === 'none' || decision === 'wait') return;\n if (decision === 'open') setIsCreateDialogOpen(true);\n setSearchParams(withoutCreateParam(searchParams), { replace: true });\n }, [searchParams, setSearchParams, permissionsLoading, canRead, canCreate]);\n\n // Filter dashboards by search query\n const filteredDashboards = dashboards.filter(\n (d) =>\n d.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n d.description?.toLowerCase().includes(searchQuery.toLowerCase())\n );\n\n // Client-side pagination over the filtered result set\n const totalPages = Math.max(1, Math.ceil(filteredDashboards.length / PAGE_SIZE));\n const pageStartIndex = (currentPage - 1) * PAGE_SIZE;\n const paginatedDashboards = filteredDashboards.slice(pageStartIndex, pageStartIndex + PAGE_SIZE);\n\n // Reset to the first page when the search query or view changes so the\n // visible slice always starts from the top of the (possibly new) result set.\n useEffect(() => {\n setCurrentPage(1);\n }, [searchQuery, viewMode]);\n\n // Guard against the current page exceeding the available pages (e.g. after a\n // delete removes the last item on the final page).\n useEffect(() => {\n if (currentPage > totalPages) {\n setCurrentPage(totalPages);\n }\n }, [currentPage, totalPages]);\n\n const handlePrevPage = useCallback(() => {\n setCurrentPage((prev) => Math.max(1, prev - 1));\n }, []);\n\n const handleNextPage = useCallback(() => {\n setCurrentPage((prev) => Math.min(totalPages, prev + 1));\n }, [totalPages]);\n\n const handleGoToPage = useCallback((page: number) => {\n setCurrentPage(page);\n }, []);\n\n const handleOpenCreateDialog = useCallback(() => {\n setIsCreateDialogOpen(true);\n }, []);\n\n /**\n * A template install creates the dashboard inside the dialog rather than\n * through onCreate, so the list has no idea it happened. Refetch and open the\n * new board — landing on it is what makes the \"connect your data\" prompts\n * discoverable.\n */\n const handleTemplateInstalled = useCallback(\n ({ dashboardId }: { dashboardId: string; widgetsNeedingData: number }) => {\n refetch();\n navigate(`/dashboards/${dashboardId}`);\n },\n [refetch, navigate]\n );\n\n const handleCloseCreateDialog = useCallback(() => {\n if (!isCreating) {\n setIsCreateDialogOpen(false);\n }\n }, [isCreating]);\n\n const handleCreateDashboard = useCallback(\n async (data: { name: string; description: string; category: string; tags?: string[] }) => {\n setIsCreating(true);\n try {\n const newDashboard = await createDashboard({\n name: data.name,\n description: data.description,\n category: data.category,\n tags: data.tags,\n });\n\n if (newDashboard) {\n setIsCreateDialogOpen(false);\n navigate(`/dashboards/${newDashboard.id}/builder`);\n }\n } catch (err) {\n if (reportBigConsoleQuotaError(err, 'project')) {\n setIsCreateDialogOpen(false);\n return;\n }\n // Surface the failure instead of swallowing it: log for debugging,\n // notify on native, and rethrow so CreateDashboardDialog displays the error.\n console.error('Failed to create dashboard:', err);\n if (isNative()) void nativeNotify('error');\n throw err;\n } finally {\n setIsCreating(false);\n }\n },\n [createDashboard, navigate]\n );\n\n const handleOpenDashboard = useCallback(\n (dashboardId: string) => {\n navigate(`/dashboards/${dashboardId}`);\n },\n [navigate]\n );\n\n const handleEditDashboard = useCallback(\n (dashboardId: string, e: React.MouseEvent) => {\n e.stopPropagation();\n navigate(`/dashboards/${dashboardId}/builder`);\n },\n [navigate]\n );\n\n const handleDeleteDashboard = useCallback(\n async (dashboardId: string, e: React.MouseEvent) => {\n e.stopPropagation();\n const msg = tr('bigconsole.dashboards.confirmDelete', 'Are you sure you want to delete this dashboard?');\n const native = await nativeConfirm({\n title: 'Delete dashboard?',\n message: msg,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok = native === null ? window.confirm(msg) : native;\n if (ok) {\n nativeImpact('medium');\n try {\n await deleteDashboard(dashboardId);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n }\n },\n [deleteDashboard, tr]\n );\n\n const handleCloneDashboard = useCallback(\n async (dashboardId: string, sourceName: string, e: React.MouseEvent) => {\n e.stopPropagation();\n const defaultName = tr('bigconsole.dashboards.clone.defaultSuffix', '{{sourceName}} (Copy)', {\n sourceName,\n });\n const name = window.prompt(\n tr('bigconsole.dashboards.clone.namePrompt', 'Name for the cloned dashboard:'),\n defaultName\n );\n if (!name || !name.trim()) return;\n nativeImpact('medium');\n try {\n const cloned = await cloneDashboard(dashboardId, name.trim());\n if (cloned) {\n if (isNative()) void nativeNotify('success');\n navigate(`/dashboards/${cloned.id}`);\n }\n } catch (err) {\n if (reportBigConsoleQuotaError(err, 'project')) {\n return;\n }\n if (isNative()) void nativeNotify('error');\n throw err;\n }\n },\n [cloneDashboard, navigate, tr]\n );\n\n const handleOpenImportDialog = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleCloseImportDialog = useCallback(() => {\n setIsImportDialogOpen(false);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_dashboardId: string, _dashboardName: string) => {\n refetch();\n },\n [refetch]\n );\n\n // Format relative date\n const formatRelativeDate = (dateStr: string) => {\n const date = new Date(dateStr);\n const now = new Date();\n const diffMs = now.getTime() - date.getTime();\n const diffMins = Math.floor(diffMs / 60000);\n const diffHours = Math.floor(diffMs / 3600000);\n const diffDays = Math.floor(diffMs / 86400000);\n\n if (diffMins < 1) return tr('bigconsole.dashboards.time.justNow', 'Just now');\n if (diffMins < 60) return `${diffMins}${tr('bigconsole.dashboards.time.minutesAgoSuffix', 'm ago')}`;\n if (diffHours < 24) return `${diffHours}${tr('bigconsole.dashboards.time.hoursAgoSuffix', 'h ago')}`;\n if (diffDays < 7) return `${diffDays}${tr('bigconsole.dashboards.time.daysAgoSuffix', 'd ago')}`;\n\n return new Intl.DateTimeFormat('en-US', {\n month: 'short',\n day: 'numeric',\n year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined,\n }).format(date);\n };\n\n // Human label for the dashboard lifecycle status.\n const getStatusLabel = useDashboardStatusLabel();\n\n // Get widget count - uses widgetCount field from backend\n const getWidgetCount = (dashboard: (typeof dashboards)[0]) => {\n return dashboard.widgetCount ?? 0;\n };\n\n // Get page count\n const getPageCount = (dashboard: (typeof dashboards)[0]) => {\n return dashboard.pageCount || 0;\n };\n\n const headerBand = (\n <DashboardsHeaderBand\n title={tr('bigconsole.dashboards.title', 'Dashboards')}\n subtitle={tr('bigconsole.dashboards.subtitle', 'Create and manage your analytics dashboards')}\n createLabel={tr('bigconsole.dashboards.actions.create', 'Create Dashboard')}\n importLabel={tr('bigconsole.dashboards.actions.import', 'Import')}\n onCreate={handleOpenCreateDialog}\n onImport={handleOpenImportDialog}\n canCreate={canCreate}\n canImport={canImport}\n />\n );\n\n // Permission read gate — wait for the provider to resolve before deciding,\n // then block the page body for callers without dashboard read access.\n if (permissionsLoading) {\n return (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '80px 0' }}>\n <Spinner\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n }}\n />\n </div>\n );\n }\n\n if (!canRead) {\n return (\n <AccessDenied\n title={tr('bigconsole.dashboards.accessDenied.title', 'Access Denied')}\n description={tr(\n 'bigconsole.dashboards.accessDenied.description',\n \"You don't have permission to view dashboards.\"\n )}\n />\n );\n }\n\n // Error state — keeps the page identity so the user still knows where they are.\n if (error) {\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {headerBand}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Section\n headingLevel={2}\n tone=\"danger\"\n title={tr('bigconsole.dashboards.error.title', 'Error loading dashboards')}\n description={error.message}\n data-testid=\"dashboards-error\"\n actions={\n <Button size=\"sm\" onClick={() => refetch()}>\n {tr('bigconsole.dashboards.error.retry', 'Retry')}\n </Button>\n }\n />\n </div>\n </div>\n );\n }\n\n const resultCount = filteredDashboards.length;\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Header Section */}\n {headerBand}\n\n {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <SectionStack>\n {/* Supporting context — deliberately recessed so it never competes\n with the results it introduces. */}\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n aria-label={tr('bigconsole.dashboards.about', 'About dashboards')}\n >\n <PagePurpose className=\"max-w-[80ch]\">\n Dashboards are live, shareable views built from widgets that query your data sinks — charts, tables,\n metrics, and drill-downs in one place. Compose a board to monitor a system, share KPIs with your team, or\n explore data interactively.\n </PagePurpose>\n </Section>\n\n {/* The point of this route: the boards themselves. */}\n <Section\n headingLevel={2}\n emphasis=\"primary\"\n data-testid=\"dashboards-results\"\n title={tr('bigconsole.dashboards.results.title', 'Your dashboards')}\n description={tr(\n 'bigconsole.dashboards.results.description',\n 'Open a board to view it, or use the card actions to edit, duplicate or delete.'\n )}\n meta={\n <Badge\n variant=\"secondary\"\n aria-label={tr('bigconsole.dashboards.results.count', `${resultCount} dashboards`)}\n >\n {resultCount}\n </Badge>\n }\n actions={\n <div\n role=\"group\"\n aria-label={tr('bigconsole.dashboards.view.label', 'Result layout')}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n padding: 'var(--space-iconGap)',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-bg-sunken)',\n }}\n >\n <Button\n variant={viewMode === 'grid' ? 'default' : 'ghost'}\n size=\"sm\"\n onClick={() => handleSetViewMode('grid')}\n aria-label={tr('bigconsole.dashboards.view.grid', 'Grid view')}\n aria-pressed={viewMode === 'grid'}\n title={tr('bigconsole.dashboards.view.grid', 'Grid view')}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n >\n <LayoutGrid style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'default' : 'ghost'}\n size=\"sm\"\n onClick={() => handleSetViewMode('list')}\n aria-label={tr('bigconsole.dashboards.view.list', 'List view')}\n aria-pressed={viewMode === 'list'}\n title={tr('bigconsole.dashboards.view.list', 'List view')}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n >\n <List style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </div>\n }\n bodyClassName=\"flex flex-col gap-form-gap\"\n >\n {/* Search — grouped with the results it filters, not floating above them */}\n <div style={{ position: 'relative', width: '100%', maxWidth: '420px' }}>\n <Search\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n left: 'var(--space-controlGap)',\n top: '50%',\n transform: 'translateY(-50%)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <Input\n type=\"search\"\n aria-label={tr('bigconsole.dashboards.search.placeholder', 'Search dashboards...')}\n placeholder={tr('bigconsole.dashboards.search.placeholder', 'Search dashboards...')}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Loading state */}\n {loading && dashboards.length === 0 && (\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '80px 0' }}>\n <div style={{ textAlign: 'center' }}>\n <Spinner\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n margin: '0 auto var(--space-formGap)',\n }}\n />\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.loading', 'Loading dashboards...')}\n </p>\n </div>\n </div>\n )}\n\n {/* Dashboard Grid */}\n {!loading && viewMode === 'grid' && filteredDashboards.length > 0 && (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(320px, 100%), 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {paginatedDashboards.map((dashboard) => (\n <Card\n key={dashboard.id}\n onClick={() => handleOpenDashboard(dashboard.id)}\n style={{ cursor: 'pointer' }}\n className=\"motion-safe:transition-[box-shadow,border-color] hover:border-[var(--color-border-hover)] hover:shadow-[var(--shadow-elevation-2)]\"\n >\n <CardContent style={{ padding: 'var(--space-cardPadding)' }}>\n {/* Card Header with Icon and Name */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n flexShrink: 0,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-bg-accentSubtle)',\n }}\n >\n <LayoutDashboard\n aria-hidden=\"true\"\n style={{\n height: 'var(--size-icon-md)',\n width: 'var(--size-icon-md)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n </div>\n <div style={{ flex: 1, minWidth: 0 }}>\n <h3\n style={{\n fontWeight: 'var(--font-weight-bold)',\n fontSize: 'var(--font-size-body-md)',\n margin: 0,\n color: 'var(--color-text-primary)',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {dashboard.name}\n </h3>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n marginTop: 'var(--space-controlGap)',\n }}\n >\n {dashboard.category && (\n <Badge\n variant=\"secondary\"\n style={{\n fontSize: '10px',\n fontWeight: 'var(--font-weight-semibold)',\n textTransform: 'uppercase',\n }}\n >\n {dashboard.category}\n </Badge>\n )}\n {dashboard.status && (\n <DashboardStatusPill status={dashboard.status} label={getStatusLabel(dashboard.status)} />\n )}\n </div>\n </div>\n </div>\n\n {/* Description */}\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n lineHeight: 'var(--font-lineHeight-normal)',\n color: 'var(--color-text-secondary)',\n margin: '0 0 var(--space-formGap) 0',\n minHeight: '40px',\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden',\n }}\n >\n {dashboard.description ||\n tr('bigconsole.dashboards.card.noDescription', 'No description provided')}\n </p>\n\n {/* Stats Row */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n marginBottom: 'var(--space-formGap)',\n padding: 'var(--space-controlGap) var(--space-cardPadding)',\n backgroundColor: 'var(--color-bg-sunken)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Layers\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n {getPageCount(dashboard)}\n </span>\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.card.pages', 'pages')}\n </span>\n </div>\n <div\n style={{\n width: '1px',\n height: 'var(--size-icon-sm)',\n backgroundColor: 'var(--color-border-subtle)',\n }}\n />\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <FileText\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n {getWidgetCount(dashboard)}\n </span>\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.card.widgets', 'widgets')}\n </span>\n </div>\n </div>\n\n {/* Footer with Date and Actions */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: 'var(--space-controlGap)',\n paddingTop: 'var(--space-controlGap)',\n borderTop: '1px solid var(--color-border-subtle)',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n }}\n >\n <Calendar\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-muted)',\n }}\n >\n {formatRelativeDate(dashboard.updatedAt)}\n </span>\n </div>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-controlGap)' }}>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_UPDATE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleEditDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n title={tr('bigconsole.dashboards.actions.editTitle', 'Edit Dashboard')}\n >\n <Pencil style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_CREATE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleCloneDashboard(dashboard.id, dashboard.name, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n title={tr('bigconsole.dashboards.actions.cloneTitle', 'Clone Dashboard')}\n >\n <Copy style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_DELETE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleDeleteDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n className=\"hover:text-status-error-text hover:bg-status-error-bg/10\"\n title={tr('bigconsole.dashboards.actions.deleteTitle', 'Delete Dashboard')}\n >\n <Trash2 style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* Dashboard List */}\n {!loading && viewMode === 'list' && filteredDashboards.length > 0 && (\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: 'var(--space-controlGap)',\n }}\n >\n {paginatedDashboards.map((dashboard) => (\n <Card\n key={dashboard.id}\n onClick={() => handleOpenDashboard(dashboard.id)}\n style={{ cursor: 'pointer' }}\n className=\"motion-safe:transition-[box-shadow,border-color] hover:border-[var(--color-border-hover)] hover:shadow-[var(--shadow-elevation-2)]\"\n >\n <CardContent style={{ padding: 'var(--space-cardPadding)' }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n flexWrap: 'wrap',\n }}\n >\n {/* Icon */}\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n flexShrink: 0,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-bg-accentSubtle)',\n }}\n >\n <LayoutDashboard\n aria-hidden=\"true\"\n style={{\n height: 'var(--size-icon-md)',\n width: 'var(--size-icon-md)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n </div>\n\n {/* Name + Description */}\n <div style={{ flex: '1 1 12rem', minWidth: 0 }}>\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n }}\n >\n <h3\n style={{\n fontWeight: 'var(--font-weight-bold)',\n fontSize: 'var(--font-size-body-md)',\n margin: 0,\n color: 'var(--color-text-primary)',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n maxWidth: '100%',\n }}\n >\n {dashboard.name}\n </h3>\n {dashboard.category && (\n <Badge\n variant=\"secondary\"\n style={{\n fontSize: '10px',\n fontWeight: 'var(--font-weight-semibold)',\n textTransform: 'uppercase',\n }}\n >\n {dashboard.category}\n </Badge>\n )}\n {dashboard.status && (\n <DashboardStatusPill status={dashboard.status} label={getStatusLabel(dashboard.status)} />\n )}\n </div>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n lineHeight: 'var(--font-lineHeight-normal)',\n color: 'var(--color-text-secondary)',\n margin: 'var(--space-controlGap) 0 0 0',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {dashboard.description ||\n tr('bigconsole.dashboards.card.noDescription', 'No description provided')}\n </p>\n </div>\n\n {/* Stats + Date */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-formGap)',\n flexWrap: 'wrap',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Layers\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n {getPageCount(dashboard)}\n </span>\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.card.pages', 'pages')}\n </span>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <FileText\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-action-primary-bg)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n {getWidgetCount(dashboard)}\n </span>\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n {tr('bigconsole.dashboards.card.widgets', 'widgets')}\n </span>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Calendar\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-text-muted)',\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-muted)',\n }}\n >\n {formatRelativeDate(dashboard.updatedAt)}\n </span>\n </div>\n </div>\n\n {/* Actions */}\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-controlGap)' }}>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_UPDATE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleEditDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n title={tr('bigconsole.dashboards.actions.editTitle', 'Edit Dashboard')}\n >\n <Pencil style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_CREATE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleCloneDashboard(dashboard.id, dashboard.name, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n title={tr('bigconsole.dashboards.actions.cloneTitle', 'Clone Dashboard')}\n >\n <Copy style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_DELETE} deniedBehavior=\"hide\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleDeleteDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-md)',\n width: 'var(--size-controlHeight-md)',\n padding: 0,\n borderRadius: 'var(--radius-button)',\n }}\n className=\"hover:text-status-error-text hover:bg-status-error-bg/10\"\n title={tr('bigconsole.dashboards.actions.deleteTitle', 'Delete Dashboard')}\n >\n <Trash2 style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </Button>\n </PermissionButton>\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* How much of the result set is on screen. Without it a paged list\n says nothing about how many boards there are in total — and the\n counts are already in hand, no extra query. */}\n {!loading && filteredDashboards.length > 0 && (\n <p\n data-testid=\"dashboards-range\"\n style={{\n margin: 0,\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n }}\n >\n {tr('bigconsole.dashboards.pagination.range', 'Showing {{from}}–{{to}} of {{total}} dashboards', {\n from: pageStartIndex + 1,\n to: pageStartIndex + paginatedDashboards.length,\n total: filteredDashboards.length,\n })}\n </p>\n )}\n\n {/* Pagination — a footer control, separated from the results it pages */}\n {!loading && filteredDashboards.length > PAGE_SIZE && (\n <nav\n aria-label={tr('bigconsole.dashboards.pagination.label', 'Dashboard pages')}\n className=\"flex flex-wrap items-center justify-center gap-control-gap border-t border-[var(--color-border-subtle)] pt-control-gap\"\n >\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handlePrevPage}\n disabled={currentPage <= 1}\n aria-label={tr('bigconsole.dashboards.pagination.previous', 'Previous')}\n style={{ minHeight: 'var(--size-controlHeight-md)' }}\n >\n <ChevronLeft\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dashboards.pagination.previous', 'Previous')}\n </Button>\n\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)', flexWrap: 'wrap' }}>\n {getPageWindow(currentPage, totalPages).map((page, index) =>\n page === null ? (\n <span\n key={`gap-${index}`}\n aria-hidden=\"true\"\n style={{ color: 'var(--color-text-muted)', padding: '0 var(--space-iconGap)' }}\n >\n …\n </span>\n ) : (\n <Button\n key={page}\n variant={page === currentPage ? 'default' : 'ghost'}\n size=\"sm\"\n onClick={() => handleGoToPage(page)}\n aria-current={page === currentPage ? 'page' : undefined}\n aria-label={tr('bigconsole.dashboards.pagination.page', `Page ${page}`)}\n style={{\n minWidth: 'var(--size-controlHeight-md)',\n height: 'var(--size-controlHeight-md)',\n padding: '0 var(--space-controlGap)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n {page}\n </Button>\n )\n )}\n </div>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleNextPage}\n disabled={currentPage >= totalPages}\n aria-label={tr('bigconsole.dashboards.pagination.next', 'Next')}\n style={{ minHeight: 'var(--size-controlHeight-md)' }}\n >\n {tr('bigconsole.dashboards.pagination.next', 'Next')}\n <ChevronRight\n style={{\n marginLeft: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n </Button>\n\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n }}\n >\n {tr('bigconsole.dashboards.pagination.pageOf', 'Page {{currentPage}} of {{totalPages}}', {\n currentPage,\n totalPages,\n })}\n </span>\n </nav>\n )}\n\n {/* Empty state */}\n {!loading && filteredDashboards.length === 0 && (\n <GlassCard treatment=\"insight\" className=\"border-[var(--color-border-seam)]\">\n <IllustratedEmptyState\n illustration={searchQuery ? 'empty-search' : 'empty-data'}\n title={\n searchQuery\n ? tr('bigconsole.dashboards.empty.filteredTitle', 'No dashboards found')\n : tr('bigconsole.dashboards.empty.title', 'No dashboards yet')\n }\n description={\n searchQuery\n ? tr('bigconsole.dashboards.empty.filteredDescription', 'Try adjusting your search terms')\n : tr(\n 'bigconsole.dashboards.empty.description',\n 'Get started by creating your first dashboard to visualize your data'\n )\n }\n action={\n !searchQuery ? (\n <div\n style={{\n display: 'flex',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n justifyContent: 'center',\n }}\n >\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_CREATE} deniedBehavior=\"hide\">\n <Button onClick={handleOpenCreateDialog}>\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dashboards.actions.create', 'Create Dashboard')}\n </Button>\n </PermissionButton>\n <PermissionButton permission={BIGCONSOLE_PERMISSIONS.DASHBOARD_IMPORT} deniedBehavior=\"hide\">\n <Button variant=\"outline\" onClick={handleOpenImportDialog}>\n <Upload\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n {tr('bigconsole.dashboards.actions.importDashboard', 'Import Dashboard')}\n </Button>\n </PermissionButton>\n </div>\n ) : undefined\n }\n />\n </GlassCard>\n )}\n </Section>\n </SectionStack>\n </div>\n\n {/* Create Dashboard Dialog */}\n <CreateDashboardDialog\n isOpen={isCreateDialogOpen}\n isCreating={isCreating}\n onClose={handleCloseCreateDialog}\n onCreate={handleCreateDashboard}\n onTemplateInstalled={handleTemplateInstalled}\n />\n\n {/* Import Dashboard Dialog */}\n <ImportDashboardDialog\n isOpen={isImportDialogOpen}\n onClose={handleCloseImportDialog}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DashboardsPage;\n"],"mappings":"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"}
@@ -6,8 +6,8 @@ import "../components/common/index.js";
6
6
  import i from "../hooks/useParserOperations.js";
7
7
  import a from "../hooks/useExportParser.js";
8
8
  import { BIGCONSOLE_PERMISSIONS as o } from "../../constants/permissions.js";
9
- import { isNative as s, nativeConfirm as ee, nativeImpact as te, nativeNotify as c } from "../../utils/nativeBridge.js";
10
- import { useTr as ne } from "../../shared/hooks/useTr.js";
9
+ import { useTr as ee } from "../../shared/hooks/useTr.js";
10
+ import { isNative as s, nativeConfirm as te, nativeImpact as ne, nativeNotify as c } from "../../utils/nativeBridge.js";
11
11
  import re from "../components/parser/ParserCard.js";
12
12
  import ie from "../components/parser/ImportParserDialog.js";
13
13
  import { memo as l, useCallback as u, useMemo as d, useState as f } from "react";
@@ -19,7 +19,7 @@ import { AuroraBackground as pe, Badge as h, Button as g, CTAOverflowMenu as me,
19
19
  import { jsx as w, jsxs as T } from "react/jsx-runtime";
20
20
  //#region src/bigconsole/pages/DataParsersPage.tsx
21
21
  var ve = "default", E = l(function() {
22
- let l = t(), { consoleId: E } = ae(), D = ne(), ye = d(() => [
22
+ let l = t(), { consoleId: E } = ae(), D = ee(), ye = d(() => [
23
23
  {
24
24
  value: "all",
25
25
  label: D("bigconsole.parsers.filters.allTypes", "All Types")
@@ -73,14 +73,14 @@ var ve = "default", E = l(function() {
73
73
  }, [l, E]), Oe = u((e) => {
74
74
  l(E ? `/dashboards/${E}/parsers/${e}/edit` : `/parsers/${e}/edit`);
75
75
  }, [l, E]), ke = u(async (e) => {
76
- let t = await ee({
76
+ let t = await te({
77
77
  title: D("bigconsole.parsers.confirmDelete.title", "Delete parser?"),
78
78
  message: D("bigconsole.parsers.confirmDelete.message", "Are you sure you want to delete this parser?"),
79
79
  okButtonTitle: D("bigconsole.parsers.actions.delete", "Delete"),
80
80
  cancelButtonTitle: D("bigconsole.parsers.actions.cancel", "Cancel")
81
81
  });
82
82
  if (t === null ? window.confirm(D("bigconsole.parsers.confirmDelete.message", "Are you sure you want to delete this parser?")) : t) {
83
- te("medium"), V(!0), H(e);
83
+ ne("medium"), V(!0), H(e);
84
84
  try {
85
85
  await q(e), s() && c("success");
86
86
  } catch {
@@ -11,10 +11,10 @@ import { memo as a, useCallback as o, useEffect as s, useMemo as ae, useState as
11
11
  import { useParams as oe } from "react-router";
12
12
  import { AccessDenied as se, PermissionButton as l } from "@burdenoff/fe-libs/shared/components";
13
13
  import { usePermissions as u } from "@burdenoff/fe-libs/shared/providers/shell";
14
- import { AlertCircle as d, ArrowLeft as f, Check as ce, Copy as le, Database as p, Eye as m, ListChecks as ue, Loader2 as h, Save as de, Settings as fe } from "lucide-react";
15
- import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
16
- import { Button as _, IllustratedEmptyState as v, Input as y, Label as b, PagePurpose as pe, Textarea as x } from "@burdenoff/fe-libs/ui";
14
+ import { AlertCircle as d, ArrowLeft as f, Check as ce, Copy as le, Database as p, Eye as m, ListChecks as h, Loader2 as g, Save as ue, Settings as de } from "lucide-react";
15
+ import { Button as _, IllustratedEmptyState as v, Input as y, Label as b, PagePurpose as fe, Textarea as x } from "@burdenoff/fe-libs/ui";
17
16
  import { Fragment as S, jsx as C, jsxs as w } from "react/jsx-runtime";
17
+ import { useI18n as pe } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
18
18
  //#region src/bigconsole/pages/DataSinkBuilderPage.tsx
19
19
  function me(e) {
20
20
  return e.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").substring(0, 64);
@@ -29,7 +29,7 @@ function he(e, t) {
29
29
  return n;
30
30
  }
31
31
  var T = a(function() {
32
- let { dataSinkId: a } = oe(), T = e(), { t: E } = g(), D = a !== "new" && !!a, { hasPermission: ge, isLoading: _e } = u(), ve = ge(r.DATASINK_READ), ye = D ? r.DATASINK_UPDATE : r.DATASINK_CREATE, [O, k] = c({
32
+ let { dataSinkId: a } = oe(), T = e(), { t: E } = pe(), D = a !== "new" && !!a, { hasPermission: ge, isLoading: _e } = u(), ve = ge(r.DATASINK_READ), ye = D ? r.DATASINK_UPDATE : r.DATASINK_CREATE, [O, k] = c({
33
33
  name: "",
34
34
  key: "",
35
35
  description: "",
@@ -224,7 +224,7 @@ var T = a(function() {
224
224
  ]);
225
225
  return xe && D ? /* @__PURE__ */ C("div", {
226
226
  className: "flex min-h-[240px] items-center justify-center p-card-padding",
227
- children: /* @__PURE__ */ C(h, { className: "h-icon-lg w-icon-lg animate-spin text-text-secondary" })
227
+ children: /* @__PURE__ */ C(g, { className: "h-icon-lg w-icon-lg animate-spin text-text-secondary" })
228
228
  }) : G ? /* @__PURE__ */ C("div", {
229
229
  className: "mx-auto w-full max-w-3xl p-card-padding",
230
230
  children: /* @__PURE__ */ C(t, {
@@ -266,7 +266,7 @@ var T = a(function() {
266
266
  })
267
267
  ]
268
268
  }),
269
- /* @__PURE__ */ C(pe, {
269
+ /* @__PURE__ */ C(fe, {
270
270
  className: "mb-section-gap",
271
271
  children: E("bigconsole.dataSinks.builder.header.purpose", "A data sink is the named, structured store your dashboards read from. Give it a unique key, configure how long data is cached and how it refreshes, then point a workflow at that key to push data in. Get the basics right here and everything downstream connects to it.")
272
272
  }),
@@ -380,7 +380,7 @@ var T = a(function() {
380
380
  headingLevel: 2,
381
381
  title: E("bigconsole.dataSinks.builder.config.title", "Configuration"),
382
382
  description: E("bigconsole.dataSinks.builder.config.subtitle", "Cache and refresh settings"),
383
- icon: /* @__PURE__ */ C(fe, { className: "h-icon-sm w-icon-sm" }),
383
+ icon: /* @__PURE__ */ C(de, { className: "h-icon-sm w-icon-sm" }),
384
384
  bodyClassName: "space-y-form-gap",
385
385
  children: /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ w("div", {
386
386
  className: "space-y-2",
@@ -445,7 +445,7 @@ var T = a(function() {
445
445
  variant: "outline",
446
446
  size: "sm",
447
447
  disabled: z,
448
- children: z ? /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(h, { className: "w-4 h-4 mr-2 animate-spin" }), E("bigconsole.dataSinks.builder.preview.loading", "Loading...")] }) : /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(m, { className: "w-4 h-4 mr-2" }), E("bigconsole.dataSinks.builder.preview.showData", "Show Data")] })
448
+ children: z ? /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(g, { className: "w-4 h-4 mr-2 animate-spin" }), E("bigconsole.dataSinks.builder.preview.loading", "Loading...")] }) : /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(m, { className: "w-4 h-4 mr-2" }), E("bigconsole.dataSinks.builder.preview.showData", "Show Data")] })
449
449
  }),
450
450
  children: U ? /* @__PURE__ */ C(t, {
451
451
  tone: "danger",
@@ -473,7 +473,7 @@ var T = a(function() {
473
473
  emphasis: "muted",
474
474
  headingLevel: 2,
475
475
  title: E("bigconsole.dataSinks.builder.whatsNext.title", "What's Next?"),
476
- icon: /* @__PURE__ */ C(ue, { className: "h-icon-sm w-icon-sm" }),
476
+ icon: /* @__PURE__ */ C(h, { className: "h-icon-sm w-icon-sm" }),
477
477
  children: /* @__PURE__ */ w("ol", {
478
478
  className: "list-inside list-decimal space-y-1 text-xs text-text-secondary",
479
479
  children: [
@@ -514,7 +514,7 @@ var T = a(function() {
514
514
  type: "submit",
515
515
  disabled: !Ne || P,
516
516
  variant: "default",
517
- children: P ? /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(h, { className: "w-4 h-4 mr-2 animate-spin" }), E("bigconsole.dataSinks.builder.actions.saving", "Saving...")] }) : /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(de, { className: "w-4 h-4 mr-2" }), D ? E("bigconsole.dataSinks.builder.actions.saveChanges", "Save Changes") : E("bigconsole.dataSinks.builder.actions.create", "Create DataSink")] })
517
+ children: P ? /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(g, { className: "w-4 h-4 mr-2 animate-spin" }), E("bigconsole.dataSinks.builder.actions.saving", "Saving...")] }) : /* @__PURE__ */ w(S, { children: [/* @__PURE__ */ C(ue, { className: "w-4 h-4 mr-2" }), D ? E("bigconsole.dataSinks.builder.actions.saveChanges", "Save Changes") : E("bigconsole.dataSinks.builder.actions.create", "Create DataSink")] })
518
518
  })
519
519
  })]
520
520
  })