@burdenoff/microfe-bigconsole 2026.828.1 → 2026.829.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigconsole/pages/DashboardsPage.js +282 -286
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataParsersPage.js +60 -60
- package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
- package/dist/shared/hooks/useTr.js +14 -0
- package/dist/shared/hooks/useTr.js.map +1 -0
- package/package.json +1 -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 { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\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 { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\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 = t('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, t]\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 <AccessDenied title=\"Access Denied\" description=\"You don't have permission to view dashboards.\" />;\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 {t('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 {t('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":";;;;;;;;;;;;;;;;;;;AAsDA,IAAM,IAAwB,iCAGxB,IAAY,IAGZ,IAAmB;AAUzB,SAAS,GAAc,GAAqB,GAA0C;AACpF,KAAI,KAAc,EAChB,QAAO,MAAM,KAAK,EAAE,QAAQ,GAAY,GAAG,GAAG,MAAM,IAAI,EAAE;CAG5D,IAAM,IAAQ,IAAI,IAAY;EAAC;EAAG;EAAY;EAAY,CAAC;AAE3D,CADI,IAAc,IAAI,KAAG,EAAM,IAAI,IAAc,EAAE,EAC/C,IAAc,IAAI,KAAY,EAAM,IAAI,IAAc,EAAE;CAE5D,IAAM,IAAU,MAAM,KAAK,EAAM,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE,EACjD,IAAiC,EAAE;AAKzC,QAJA,EAAQ,SAAS,GAAM,MAAU;AAE/B,EADI,IAAQ,KAAK,IAAO,EAAQ,IAAQ,KAAK,KAAG,EAAS,KAAK,KAAK,EACnE,EAAS,KAAK,EAAK;GACnB,EACK;;AA0BT,IAAM,KAAsD,EAAK,SAA8B,EAC7F,UACA,aACA,gBACA,gBACA,aACA,aACA,cACA,gBACC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAkB,WAAU,UAAW,CAAA,EACvC,kBAAC,OAAD;GAAK,OAAO,EAAE,SAAS,4BAA4B;aACjD,kBAAC,OAAD;IACE,OAAO;KACL,SAAS;KACT,YAAY;KACZ,gBAAgB;KAChB,UAAU;KACV,KAAK;KACN;cAPH,CASE,kBAAC,OAAD;KAAK,OAAO;MAAE,SAAS;MAAQ,YAAY;MAAU,KAAK;MAAwB,UAAU;MAAG;eAA/F,CACE,kBAAC,OAAD;MACE,OAAO;OACL,SAAS;OACT,QAAQ;OACR,OAAO;OACP,YAAY;OACZ,YAAY;OACZ,gBAAgB;OAChB,cAAc;OACd,iBAAiB;OAClB;gBAED,kBAAC,GAAD,EACE,OAAO;OACL,QAAQ;OACR,OAAO;OACP,OAAO;OACR,EACD,CAAA;MACE,CAAA,EACN,kBAAC,OAAD;MAAK,OAAO,EAAE,UAAU,GAAG;gBAA3B,CACE,kBAAC,MAAD;OACE,OAAO;QACL,UAAU;QACV,YAAY;QACZ,QAAQ;QACR,OAAO;QACR;iBAED,kBAAC,GAAD,EAAA,UAAe,GAAqB,CAAA;OACjC,CAAA,EACL,kBAAC,KAAD;OAAG,OAAO;QAAE,UAAU;QAA4B,OAAO;QAA+B,QAAQ;QAAG;iBAChG;OACC,CAAA,CACA;QACF;QAEN,kBAAC,GAAD;KACE,SAAS;MACP,OAAO;MACP,UAAU;MACV,UAAU,CAAC;MACX,MACE,kBAAC,IAAD,EACE,OAAO;OACL,aAAa;OACb,QAAQ;OACR,OAAO;OACR,EACD,CAAA;MAEL;KACD,SAAS,CACP;MACE,OAAO;MACP,UAAU;MACV,UAAU,CAAC;MACX,MAAM,kBAAC,IAAD,EAAQ,OAAO;OAAE,QAAQ;OAAuB,OAAO;OAAuB,EAAI,CAAA;MACzF,CACF;KACD,CAAA,CACE;;GACF,CAAA,CACF;;EAER,EAcI,IAA4D;CAChE,WAAW;CACX,OAAO;CACP,UAAU;CACX,EAOK,IAAoD,EAAK,SAA6B,EAAE,WAAQ,YAAS;AAC7G,QACE,kBAAC,QAAD;EACE,eAAY;EACZ,OAAO;GACL,SAAS;GACT,YAAY;GACZ,KAAK;GACL,SAAS;GACT,cAAc;GACd,iBAAiB;GACjB,QAAQ;GACR,UAAU;GACV,YAAY;GACZ,eAAe;GACf,eAAe;GACf,OAAO;GACP,YAAY;GACb;YAhBH,CAkBE,kBAAC,QAAD;GACE,eAAY;GACZ,OAAO;IACL,OAAO;IACP,QAAQ;IACR,cAAc;IACd,iBAAiB,EAAqB;IACtC,YAAY;IACb;GACD,CAAA,EACD,EACI;;EAET,EAMW,IAAqB,EAAK,WAA0B;CAC/D,IAAM,IAAW,GAAuB,EAClC,EAAE,SAAM,IAAS,EACjB,IAAK,GACR,GAAa,MAAqB;EACjC,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEzC,CAAC,EAAE,CACJ,EACK,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAa,KAAkB,EAAS,EAAE,EAC3C,CAAC,IAAoB,KAAyB,EAAS,GAAM,EAC7D,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,IAAoB,KAAyB,EAAS,GAAM,EAG7D,CAAC,GAAU,MAAe,QAAgC;AAC9D,MAAI;AAEF,UADe,OAAO,aAAa,QAAQ,EAAsB,KAC/C,SAAS,SAAS;UAC9B;AACN,UAAO;;GAET,EAEI,IAAoB,GAAa,MAA0B;AAC/D,KAAY,EAAK;AACjB,MAAI;AACF,UAAO,aAAa,QAAQ,GAAuB,EAAK;UAClD;IAGP,EAAE,CAAC,EAEA,EAAE,YAAS,UAAO,gBAAY,oBAAiB,oBAAiB,mBAAgB,eACpF,GAAwB,EAGpB,EAAE,kBAAe,WAAW,OAAuB,IAAgB,EACnE,KAAU,EAAc,EAAuB,eAAe,EAC9D,KAAY,EAAc,EAAuB,iBAAiB,EAClE,KAAY,EAAc,EAAuB,iBAAiB,EAGlE,IAAqB,GAAW,QACnC,MACC,EAAE,KAAK,aAAa,CAAC,SAAS,EAAY,aAAa,CAAC,IACxD,EAAE,aAAa,aAAa,CAAC,SAAS,EAAY,aAAa,CAAC,CACnE,EAGK,IAAa,KAAK,IAAI,GAAG,KAAK,KAAK,EAAmB,SAAS,EAAU,CAAC,EAC1E,KAAkB,IAAc,KAAK,GACrC,IAAsB,EAAmB,MAAM,GAAgB,IAAiB,EAAU;AAUhG,CANA,SAAgB;AACd,IAAe,EAAE;IAChB,CAAC,GAAa,EAAS,CAAC,EAI3B,SAAgB;AACd,EAAI,IAAc,KAChB,EAAe,EAAW;IAE3B,CAAC,GAAa,EAAW,CAAC;CAE7B,IAAM,KAAiB,QAAkB;AACvC,KAAgB,MAAS,KAAK,IAAI,GAAG,IAAO,EAAE,CAAC;IAC9C,EAAE,CAAC,EAEA,KAAiB,QAAkB;AACvC,KAAgB,MAAS,KAAK,IAAI,GAAY,IAAO,EAAE,CAAC;IACvD,CAAC,EAAW,CAAC,EAEV,KAAiB,GAAa,MAAiB;AACnD,IAAe,EAAK;IACnB,EAAE,CAAC,EAEA,KAAyB,QAAkB;AAC/C,IAAsB,GAAK;IAC1B,EAAE,CAAC,EAQA,KAA0B,GAC7B,EAAE,qBAAuE;AAExE,EADA,GAAS,EACT,EAAS,eAAe,IAAc;IAExC,CAAC,GAAS,EAAS,CACpB,EAEK,KAA0B,QAAkB;AAChD,EAAK,KACH,EAAsB,GAAM;IAE7B,CAAC,EAAW,CAAC,EAEV,KAAwB,EAC5B,OAAO,MAAmF;AACxF,IAAc,GAAK;AACnB,MAAI;GACF,IAAM,IAAe,MAAM,EAAgB;IACzC,MAAM,EAAK;IACX,aAAa,EAAK;IAClB,UAAU,EAAK;IACf,MAAM,EAAK;IACZ,CAAC;AAEF,GAAI,MACF,EAAsB,GAAM,EAC5B,EAAS,eAAe,EAAa,GAAG,UAAU;WAE7C,GAAK;AACZ,OAAI,EAA2B,GAAK,UAAU,EAAE;AAC9C,MAAsB,GAAM;AAC5B;;AAMF,SAFA,QAAQ,MAAM,+BAA+B,EAAI,EAC7C,GAAU,IAAO,EAAa,QAAQ,EACpC;YACE;AACR,KAAc,GAAM;;IAGxB,CAAC,GAAiB,EAAS,CAC5B,EAEK,IAAsB,GACzB,MAAwB;AACvB,IAAS,eAAe,IAAc;IAExC,CAAC,EAAS,CACX,EAEK,KAAsB,GACzB,GAAqB,MAAwB;AAE5C,EADA,EAAE,iBAAiB,EACnB,EAAS,eAAe,EAAY,UAAU;IAEhD,CAAC,EAAS,CACX,EAEK,KAAwB,EAC5B,OAAO,GAAqB,MAAwB;AAClD,IAAE,iBAAiB;EACnB,IAAM,IAAM,EAAG,uCAAuC,kDAAkD,EAClG,IAAS,MAAM,GAAc;GACjC,OAAO;GACP,SAAS;GACT,eAAe;GACf,mBAAmB;GACpB,CAAC;AAEF,MADW,MAAW,OAAO,OAAO,QAAQ,EAAI,GAAG,GAC3C;AACN,KAAa,SAAS;AACtB,OAAI;AAEF,IADA,MAAM,EAAgB,EAAY,EAC9B,GAAU,IAAO,EAAa,UAAU;YACrC,GAAK;AAEZ,UADI,GAAU,IAAO,EAAa,QAAQ,EACpC;;;IAIZ,CAAC,GAAiB,EAAG,CACtB,EAEK,KAAuB,EAC3B,OAAO,GAAqB,GAAoB,MAAwB;AACtE,IAAE,iBAAiB;EACnB,IAAM,IAAc,EAAE,6CAA6C,yBAAyB,EAC1F,eACD,CAAC,EACI,IAAO,OAAO,OAClB,EAAG,0CAA0C,iCAAiC,EAC9E,EACD;AACG,SAAC,KAAQ,CAAC,EAAK,MAAM,GACzB;KAAa,SAAS;AACtB,OAAI;IACF,IAAM,IAAS,MAAM,EAAe,GAAa,EAAK,MAAM,CAAC;AAC7D,IAAI,MACE,GAAU,IAAO,EAAa,UAAU,EAC5C,EAAS,eAAe,EAAO,KAAK;YAE/B,GAAK;AACZ,QAAI,EAA2B,GAAK,UAAU,CAC5C;AAGF,UADI,GAAU,IAAO,EAAa,QAAQ,EACpC;;;IAGV;EAAC;EAAgB;EAAU;EAAI;EAAE,CAClC,EAEK,KAAyB,QAAkB;AAC/C,IAAsB,GAAK;IAC1B,EAAE,CAAC,EAEA,KAA0B,QAAkB;AAChD,IAAsB,GAAM;IAC3B,EAAE,CAAC,EAEA,KAAsB,GACzB,GAAsB,MAA2B;AAChD,KAAS;IAEX,CAAC,EAAQ,CACV,EAGK,MAAsB,MAAoB;EAC9C,IAAM,IAAO,IAAI,KAAK,EAAQ,EACxB,oBAAM,IAAI,MAAM,EAChB,IAAS,EAAI,SAAS,GAAG,EAAK,SAAS,EACvC,IAAW,KAAK,MAAM,IAAS,IAAM,EACrC,IAAY,KAAK,MAAM,IAAS,KAAQ,EACxC,IAAW,KAAK,MAAM,IAAS,MAAS;AAO9C,SALI,IAAW,IAAU,EAAG,sCAAsC,WAAW,GACzE,IAAW,KAAW,GAAG,IAAW,EAAG,+CAA+C,QAAQ,KAC9F,IAAY,KAAW,GAAG,IAAY,EAAG,6CAA6C,QAAQ,KAC9F,IAAW,IAAU,GAAG,IAAW,EAAG,4CAA4C,QAAQ,KAEvF,IAAI,KAAK,eAAe,SAAS;GACtC,OAAO;GACP,KAAK;GACL,MAAM,EAAK,aAAa,KAAK,EAAI,aAAa,GAAe,KAAA,IAAZ;GAClD,CAAC,CAAC,OAAO,EAAK;IAIX,KAAiB,GACpB,MAAgC;AAC/B,UAAQ,GAAR;GACE,KAAK,YACH,QAAO,EAAG,0CAA0C,YAAY;GAClE,KAAK,WACH,QAAO,EAAG,yCAAyC,WAAW;GAChE,QACE,QAAO,EAAG,sCAAsC,QAAQ;;IAG9D,CAAC,EAAG,CACL,EAGK,MAAkB,MACf,EAAU,eAAe,GAI5B,MAAgB,MACb,EAAU,aAAa,GAG1B,KACJ,kBAAC,IAAD;EACE,OAAO,EAAG,+BAA+B,aAAa;EACtD,UAAU,EAAG,kCAAkC,8CAA8C;EAC7F,aAAa,EAAG,wCAAwC,mBAAmB;EAC3E,aAAa,EAAG,wCAAwC,SAAS;EACjE,UAAU;EACV,UAAU;EACC;EACA;EACX,CAAA;AAKJ,KAAI,GACF,QACE,kBAAC,OAAD;EAAK,OAAO;GAAE,SAAS;GAAQ,YAAY;GAAU,gBAAgB;GAAU,SAAS;GAAU;YAChG,kBAAC,GAAD,EACE,OAAO;GACL,QAAQ;GACR,OAAO;GACR,EACD,CAAA;EACE,CAAA;AAIV,KAAI,CAAC,GACH,QAAO,kBAAC,IAAD;EAAc,OAAM;EAAgB,aAAY;EAAkD,CAAA;AAI3G,KAAI,EACF,QACE,kBAAC,OAAD;EAAK,OAAO;GAAE,WAAW;GAAS,iBAAiB;GAA0B;YAA7E,CACG,IACD,kBAAC,OAAD;GAAK,OAAO,EAAE,SAAS,4BAA4B;aACjD,kBAAC,GAAD;IACE,cAAc;IACd,MAAK;IACL,OAAO,EAAG,qCAAqC,2BAA2B;IAC1E,aAAa,EAAM;IACnB,eAAY;IACZ,SACE,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,GAAS;eACvC,EAAG,qCAAqC,QAAQ;KAC1C,CAAA;IAEX,CAAA;GACE,CAAA,CACF;;CAIV,IAAM,IAAc,EAAmB;AAEvC,QACE,kBAAC,OAAD;EAAK,OAAO;GAAE,WAAW;GAAS,iBAAiB;GAA0B;YAA7E;GAEG;GAGD,kBAAC,OAAD;IAAK,OAAO,EAAE,SAAS,4BAA4B;cACjD,kBAAC,GAAD,EAAA,UAAA,CAGE,kBAAC,GAAD;KACE,UAAS;KACT,SAAQ;KACR,cAAY,EAAG,+BAA+B,mBAAmB;eAEjE,kBAAC,IAAD;MAAa,WAAU;gBAAe;MAIxB,CAAA;KACN,CAAA,EAGV,kBAAC,GAAD;KACE,cAAc;KACd,UAAS;KACT,eAAY;KACZ,OAAO,EAAG,uCAAuC,kBAAkB;KACnE,aAAa,EACX,6CACA,iFACD;KACD,MACE,kBAAC,GAAD;MACE,SAAQ;MACR,cAAY,EAAG,uCAAuC,GAAG,EAAY,aAAa;gBAEjF;MACK,CAAA;KAEV,SACE,kBAAC,OAAD;MACE,MAAK;MACL,cAAY,EAAG,oCAAoC,gBAAgB;MACnE,OAAO;OACL,SAAS;OACT,YAAY;OACZ,KAAK;OACL,SAAS;OACT,cAAc;OACd,iBAAiB;OAClB;gBAVH,CAYE,kBAAC,GAAD;OACE,SAAS,MAAa,SAAS,YAAY;OAC3C,MAAK;OACL,eAAe,EAAkB,OAAO;OACxC,cAAY,EAAG,mCAAmC,YAAY;OAC9D,gBAAc,MAAa;OAC3B,OAAO,EAAG,mCAAmC,YAAY;OACzD,OAAO;QACL,QAAQ;QACR,OAAO;QACP,SAAS;QACT,cAAc;QACf;iBAED,kBAAC,IAAD,EAAY,OAAO;QAAE,QAAQ;QAAuB,OAAO;QAAuB,EAAI,CAAA;OAC/E,CAAA,EACT,kBAAC,GAAD;OACE,SAAS,MAAa,SAAS,YAAY;OAC3C,MAAK;OACL,eAAe,EAAkB,OAAO;OACxC,cAAY,EAAG,mCAAmC,YAAY;OAC9D,gBAAc,MAAa;OAC3B,OAAO,EAAG,mCAAmC,YAAY;OACzD,OAAO;QACL,QAAQ;QACR,OAAO;QACP,SAAS;QACT,cAAc;QACf;iBAED,kBAAC,IAAD,EAAM,OAAO;QAAE,QAAQ;QAAuB,OAAO;QAAuB,EAAI,CAAA;OACzE,CAAA,CACL;;KAER,eAAc;eAhEhB;MAmEE,kBAAC,OAAD;OAAK,OAAO;QAAE,UAAU;QAAY,OAAO;QAAQ,UAAU;QAAS;iBAAtE,CACE,kBAAC,IAAD;QACE,eAAY;QACZ,OAAO;SACL,UAAU;SACV,MAAM;SACN,KAAK;SACL,WAAW;SACX,QAAQ;SACR,OAAO;SACP,OAAO;SACR;QACD,CAAA,EACF,kBAAC,IAAD;QACE,MAAK;QACL,cAAY,EAAG,4CAA4C,uBAAuB;QAClF,aAAa,EAAG,4CAA4C,uBAAuB;QACnF,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,OAAO;SAAE,aAAa;SAAgC,QAAQ;SAAgC;QAC9F,CAAA,CACE;;MAGL,KAAW,GAAW,WAAW,KAChC,kBAAC,OAAD;OAAK,OAAO;QAAE,SAAS;QAAQ,YAAY;QAAU,gBAAgB;QAAU,SAAS;QAAU;iBAChG,kBAAC,OAAD;QAAK,OAAO,EAAE,WAAW,UAAU;kBAAnC,CACE,kBAAC,GAAD,EACE,OAAO;SACL,QAAQ;SACR,OAAO;SACP,QAAQ;SACT,EACD,CAAA,EACF,kBAAC,KAAD;SAAG,OAAO;UAAE,UAAU;UAA4B,OAAO;UAA2B;mBACjF,EAAG,iCAAiC,wBAAwB;SAC3D,CAAA,CACA;;OACF,CAAA;MAIP,CAAC,KAAW,MAAa,UAAU,EAAmB,SAAS,KAC9D,kBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,qBAAqB;QACrB,KAAK;QACN;iBAEA,EAAoB,KAAK,MACxB,kBAAC,GAAD;QAEE,eAAe,EAAoB,EAAU,GAAG;QAChD,OAAO,EAAE,QAAQ,WAAW;QAC5B,WAAU;kBAEV,kBAAC,GAAD;SAAa,OAAO,EAAE,SAAS,4BAA4B;mBAA3D;UAEE,kBAAC,OAAD;WACE,OAAO;YACL,SAAS;YACT,YAAY;YACZ,KAAK;YACL,cAAc;YACf;qBANH,CAQE,kBAAC,OAAD;YACE,OAAO;aACL,SAAS;aACT,QAAQ;aACR,OAAO;aACP,YAAY;aACZ,YAAY;aACZ,gBAAgB;aAChB,cAAc;aACd,iBAAiB;aAClB;sBAED,kBAAC,GAAD;aACE,eAAY;aACZ,OAAO;cACL,QAAQ;cACR,OAAO;cACP,OAAO;cACR;aACD,CAAA;YACE,CAAA,EACN,kBAAC,OAAD;YAAK,OAAO;aAAE,MAAM;aAAG,UAAU;aAAG;sBAApC,CACE,kBAAC,MAAD;aACE,OAAO;cACL,YAAY;cACZ,UAAU;cACV,QAAQ;cACR,OAAO;cACP,UAAU;cACV,cAAc;cACd,YAAY;cACb;uBAEA,EAAU;aACR,CAAA,EACL,kBAAC,OAAD;aACE,OAAO;cACL,SAAS;cACT,YAAY;cACZ,KAAK;cACL,UAAU;cACV,WAAW;cACZ;uBAPH,CASG,EAAU,YACT,kBAAC,GAAD;cACE,SAAQ;cACR,OAAO;eACL,UAAU;eACV,YAAY;eACZ,eAAe;eAChB;wBAEA,EAAU;cACL,CAAA,EAET,EAAU,UACT,kBAAC,GAAD;cAAqB,QAAQ,EAAU;cAAQ,OAAO,GAAe,EAAU,OAAO;cAAI,CAAA,CAExF;eACF;cACF;;UAGN,kBAAC,KAAD;WACE,OAAO;YACL,UAAU;YACV,YAAY;YACZ,OAAO;YACP,QAAQ;YACR,WAAW;YACX,SAAS;YACT,iBAAiB;YACjB,iBAAiB;YACjB,UAAU;YACX;qBAEA,EAAU,eACT,EAAG,4CAA4C,0BAA0B;WACzE,CAAA;UAGJ,kBAAC,OAAD;WACE,OAAO;YACL,SAAS;YACT,YAAY;YACZ,KAAK;YACL,cAAc;YACd,SAAS;YACT,iBAAiB;YACjB,cAAc;YACf;qBATH;YAWE,kBAAC,OAAD;aAAK,OAAO;cAAE,SAAS;cAAQ,YAAY;cAAU,KAAK;cAAwB;uBAAlF;cACE,kBAAC,GAAD,EACE,OAAO;eACL,QAAQ;eACR,OAAO;eACP,OAAO;eACR,EACD,CAAA;cACF,kBAAC,QAAD;eACE,OAAO;gBACL,UAAU;gBACV,YAAY;gBACZ,OAAO;gBACR;yBAEA,GAAa,EAAU;eACnB,CAAA;cACP,kBAAC,QAAD;eAAM,OAAO;gBAAE,UAAU;gBAA4B,OAAO;gBAA2B;yBACpF,EAAG,oCAAoC,QAAQ;eAC3C,CAAA;cACH;;YACN,kBAAC,OAAD,EACE,OAAO;aACL,OAAO;aACP,QAAQ;aACR,iBAAiB;aAClB,EACD,CAAA;YACF,kBAAC,OAAD;aAAK,OAAO;cAAE,SAAS;cAAQ,YAAY;cAAU,KAAK;cAAwB;uBAAlF;cACE,kBAAC,GAAD,EACE,OAAO;eACL,QAAQ;eACR,OAAO;eACP,OAAO;eACR,EACD,CAAA;cACF,kBAAC,QAAD;eACE,OAAO;gBACL,UAAU;gBACV,YAAY;gBACZ,OAAO;gBACR;yBAEA,GAAe,EAAU;eACrB,CAAA;cACP,kBAAC,QAAD;eAAM,OAAO;gBAAE,UAAU;gBAA4B,OAAO;gBAA2B;yBACpF,EAAG,sCAAsC,UAAU;eAC/C,CAAA;cACH;;YACF;;UAGN,kBAAC,OAAD;WACE,OAAO;YACL,SAAS;YACT,YAAY;YACZ,gBAAgB;YAChB,UAAU;YACV,KAAK;YACL,YAAY;YACZ,WAAW;YACZ;qBATH,CAWE,kBAAC,OAAD;YACE,OAAO;aACL,SAAS;aACT,YAAY;aACZ,KAAK;aACN;sBALH,CAOE,kBAAC,GAAD,EACE,OAAO;aACL,QAAQ;aACR,OAAO;aACP,OAAO;aACR,EACD,CAAA,EACF,kBAAC,QAAD;aACE,OAAO;cACL,UAAU;cACV,YAAY;cACZ,OAAO;cACR;uBAEA,GAAmB,EAAU,UAAU;aACnC,CAAA,CACH;eAEN,kBAAC,OAAD;YAAK,OAAO;aAAE,SAAS;aAAQ,YAAY;aAAU,KAAK;aAA2B;sBAArF;aACE,kBAAC,GAAD;cAAkB,YAAY,EAAuB;cAAkB,gBAAe;wBACpF,kBAAC,GAAD;eACE,SAAQ;eACR,MAAK;eACL,UAAU,MAAM,GAAoB,EAAU,IAAI,EAAE;eACpD,OAAO;gBACL,QAAQ;gBACR,OAAO;gBACP,SAAS;gBACT,cAAc;gBACf;eACD,OAAO,EAAG,2CAA2C,iBAAiB;yBAEtE,kBAAC,IAAD,EAAQ,OAAO;gBAAE,QAAQ;gBAAuB,OAAO;gBAAuB,EAAI,CAAA;eAC3E,CAAA;cACQ,CAAA;aACnB,kBAAC,GAAD;cAAkB,YAAY,EAAuB;cAAkB,gBAAe;wBACpF,kBAAC,GAAD;eACE,SAAQ;eACR,MAAK;eACL,UAAU,MAAM,GAAqB,EAAU,IAAI,EAAU,MAAM,EAAE;eACrE,OAAO;gBACL,QAAQ;gBACR,OAAO;gBACP,SAAS;gBACT,cAAc;gBACf;eACD,OAAO,EAAG,4CAA4C,kBAAkB;yBAExE,kBAAC,GAAD,EAAM,OAAO;gBAAE,QAAQ;gBAAuB,OAAO;gBAAuB,EAAI,CAAA;eACzE,CAAA;cACQ,CAAA;aACnB,kBAAC,GAAD;cAAkB,YAAY,EAAuB;cAAkB,gBAAe;wBACpF,kBAAC,GAAD;eACE,SAAQ;eACR,MAAK;eACL,UAAU,MAAM,GAAsB,EAAU,IAAI,EAAE;eACtD,OAAO;gBACL,QAAQ;gBACR,OAAO;gBACP,SAAS;gBACT,cAAc;gBACf;eACD,WAAU;eACV,OAAO,EAAG,6CAA6C,mBAAmB;yBAE1E,kBAAC,IAAD,EAAQ,OAAO;gBAAE,QAAQ;gBAAuB,OAAO;gBAAuB,EAAI,CAAA;eAC3E,CAAA;cACQ,CAAA;aACf;cACF;;UACM;;QACT,EAzPA,EAAU,GAyPV,CACP;OACE,CAAA;MAIP,CAAC,KAAW,MAAa,UAAU,EAAmB,SAAS,KAC9D,kBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,eAAe;QACf,KAAK;QACN;iBAEA,EAAoB,KAAK,MACxB,kBAAC,GAAD;QAEE,eAAe,EAAoB,EAAU,GAAG;QAChD,OAAO,EAAE,QAAQ,WAAW;QAC5B,WAAU;kBAEV,kBAAC,GAAD;SAAa,OAAO,EAAE,SAAS,4BAA4B;mBACzD,kBAAC,OAAD;UACE,OAAO;WACL,SAAS;WACT,YAAY;WACZ,KAAK;WACL,UAAU;WACX;oBANH;WASE,kBAAC,OAAD;YACE,OAAO;aACL,SAAS;aACT,QAAQ;aACR,OAAO;aACP,YAAY;aACZ,YAAY;aACZ,gBAAgB;aAChB,cAAc;aACd,iBAAiB;aAClB;sBAED,kBAAC,GAAD;aACE,eAAY;aACZ,OAAO;cACL,QAAQ;cACR,OAAO;cACP,OAAO;cACR;aACD,CAAA;YACE,CAAA;WAGN,kBAAC,OAAD;YAAK,OAAO;aAAE,MAAM;aAAa,UAAU;aAAG;sBAA9C,CACE,kBAAC,OAAD;aACE,OAAO;cACL,SAAS;cACT,YAAY;cACZ,KAAK;cACL,UAAU;cACX;uBANH;cAQE,kBAAC,MAAD;eACE,OAAO;gBACL,YAAY;gBACZ,UAAU;gBACV,QAAQ;gBACR,OAAO;gBACP,UAAU;gBACV,cAAc;gBACd,YAAY;gBACZ,UAAU;gBACX;yBAEA,EAAU;eACR,CAAA;cACJ,EAAU,YACT,kBAAC,GAAD;eACE,SAAQ;eACR,OAAO;gBACL,UAAU;gBACV,YAAY;gBACZ,eAAe;gBAChB;yBAEA,EAAU;eACL,CAAA;cAET,EAAU,UACT,kBAAC,GAAD;eAAqB,QAAQ,EAAU;eAAQ,OAAO,GAAe,EAAU,OAAO;eAAI,CAAA;cAExF;gBACN,kBAAC,KAAD;aACE,OAAO;cACL,UAAU;cACV,YAAY;cACZ,OAAO;cACP,QAAQ;cACR,UAAU;cACV,cAAc;cACd,YAAY;cACb;uBAEA,EAAU,eACT,EAAG,4CAA4C,0BAA0B;aACzE,CAAA,CACA;;WAGN,kBAAC,OAAD;YACE,OAAO;aACL,SAAS;aACT,YAAY;aACZ,KAAK;aACL,UAAU;aACX;sBANH;aAQE,kBAAC,OAAD;cAAK,OAAO;eAAE,SAAS;eAAQ,YAAY;eAAU,KAAK;eAAwB;wBAAlF;eACE,kBAAC,GAAD,EACE,OAAO;gBACL,QAAQ;gBACR,OAAO;gBACP,OAAO;gBACR,EACD,CAAA;eACF,kBAAC,QAAD;gBACE,OAAO;iBACL,UAAU;iBACV,YAAY;iBACZ,OAAO;iBACR;0BAEA,GAAa,EAAU;gBACnB,CAAA;eACP,kBAAC,QAAD;gBAAM,OAAO;iBAAE,UAAU;iBAA4B,OAAO;iBAA2B;0BACpF,EAAG,oCAAoC,QAAQ;gBAC3C,CAAA;eACH;;aACN,kBAAC,OAAD;cAAK,OAAO;eAAE,SAAS;eAAQ,YAAY;eAAU,KAAK;eAAwB;wBAAlF;eACE,kBAAC,GAAD,EACE,OAAO;gBACL,QAAQ;gBACR,OAAO;gBACP,OAAO;gBACR,EACD,CAAA;eACF,kBAAC,QAAD;gBACE,OAAO;iBACL,UAAU;iBACV,YAAY;iBACZ,OAAO;iBACR;0BAEA,GAAe,EAAU;gBACrB,CAAA;eACP,kBAAC,QAAD;gBAAM,OAAO;iBAAE,UAAU;iBAA4B,OAAO;iBAA2B;0BACpF,EAAG,sCAAsC,UAAU;gBAC/C,CAAA;eACH;;aACN,kBAAC,OAAD;cAAK,OAAO;eAAE,SAAS;eAAQ,YAAY;eAAU,KAAK;eAAwB;wBAAlF,CACE,kBAAC,GAAD,EACE,OAAO;eACL,QAAQ;eACR,OAAO;eACP,OAAO;eACR,EACD,CAAA,EACF,kBAAC,QAAD;eACE,OAAO;gBACL,UAAU;gBACV,YAAY;gBACZ,OAAO;gBACR;yBAEA,GAAmB,EAAU,UAAU;eACnC,CAAA,CACH;;aACF;;WAGN,kBAAC,OAAD;YAAK,OAAO;aAAE,SAAS;aAAQ,YAAY;aAAU,KAAK;aAA2B;sBAArF;aACE,kBAAC,GAAD;cAAkB,YAAY,EAAuB;cAAkB,gBAAe;wBACpF,kBAAC,GAAD;eACE,SAAQ;eACR,MAAK;eACL,UAAU,MAAM,GAAoB,EAAU,IAAI,EAAE;eACpD,OAAO;gBACL,QAAQ;gBACR,OAAO;gBACP,SAAS;gBACT,cAAc;gBACf;eACD,OAAO,EAAG,2CAA2C,iBAAiB;yBAEtE,kBAAC,IAAD,EAAQ,OAAO;gBAAE,QAAQ;gBAAuB,OAAO;gBAAuB,EAAI,CAAA;eAC3E,CAAA;cACQ,CAAA;aACnB,kBAAC,GAAD;cAAkB,YAAY,EAAuB;cAAkB,gBAAe;wBACpF,kBAAC,GAAD;eACE,SAAQ;eACR,MAAK;eACL,UAAU,MAAM,GAAqB,EAAU,IAAI,EAAU,MAAM,EAAE;eACrE,OAAO;gBACL,QAAQ;gBACR,OAAO;gBACP,SAAS;gBACT,cAAc;gBACf;eACD,OAAO,EAAG,4CAA4C,kBAAkB;yBAExE,kBAAC,GAAD,EAAM,OAAO;gBAAE,QAAQ;gBAAuB,OAAO;gBAAuB,EAAI,CAAA;eACzE,CAAA;cACQ,CAAA;aACnB,kBAAC,GAAD;cAAkB,YAAY,EAAuB;cAAkB,gBAAe;wBACpF,kBAAC,GAAD;eACE,SAAQ;eACR,MAAK;eACL,UAAU,MAAM,GAAsB,EAAU,IAAI,EAAE;eACtD,OAAO;gBACL,QAAQ;gBACR,OAAO;gBACP,SAAS;gBACT,cAAc;gBACf;eACD,WAAU;eACV,OAAO,EAAG,6CAA6C,mBAAmB;yBAE1E,kBAAC,IAAD,EAAQ,OAAO;gBAAE,QAAQ;gBAAuB,OAAO;gBAAuB,EAAI,CAAA;eAC3E,CAAA;cACQ,CAAA;aACf;;WACF;;SACM,CAAA;QACT,EA1NA,EAAU,GA0NV,CACP;OACE,CAAA;MAMP,CAAC,KAAW,EAAmB,SAAS,KACvC,kBAAC,KAAD;OACE,eAAY;OACZ,OAAO;QACL,QAAQ;QACR,UAAU;QACV,OAAO;QACR;iBAEA,EAAE,0CAA0C,mDAAmD;QAC9F,MAAM,IAAiB;QACvB,IAAI,IAAiB,EAAoB;QACzC,OAAO,EAAmB;QAC3B,CAAC;OACA,CAAA;MAIL,CAAC,KAAW,EAAmB,SAAS,KACvC,kBAAC,OAAD;OACE,cAAY,EAAG,0CAA0C,kBAAkB;OAC3E,WAAU;iBAFZ;QAIE,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,SAAS;SACT,UAAU,KAAe;SACzB,cAAY,EAAG,6CAA6C,WAAW;SACvE,OAAO,EAAE,WAAW,gCAAgC;mBANtD,CAQE,kBAAC,IAAD,EACE,OAAO;UACL,aAAa;UACb,QAAQ;UACR,OAAO;UACR,EACD,CAAA,EACD,EAAG,6CAA6C,WAAW,CACrD;;QAET,kBAAC,OAAD;SAAK,OAAO;UAAE,SAAS;UAAQ,YAAY;UAAU,KAAK;UAAwB,UAAU;UAAQ;mBACjG,GAAc,GAAa,EAAW,CAAC,KAAK,GAAM,MACjD,MAAS,OACP,kBAAC,QAAD;UAEE,eAAY;UACZ,OAAO;WAAE,OAAO;WAA2B,SAAS;WAA0B;oBAC/E;UAEM,EALA,OAAO,IAKP,GAEP,kBAAC,GAAD;UAEE,SAAS,MAAS,IAAc,YAAY;UAC5C,MAAK;UACL,eAAe,GAAe,EAAK;UACnC,gBAAc,MAAS,IAAc,SAAS,KAAA;UAC9C,cAAY,EAAG,yCAAyC,QAAQ,IAAO;UACvE,OAAO;WACL,UAAU;WACV,QAAQ;WACR,SAAS;WACT,cAAc;WACf;oBAEA;UACM,EAdF,EAcE,CAEZ;SACG,CAAA;QAEN,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,SAAS;SACT,UAAU,KAAe;SACzB,cAAY,EAAG,yCAAyC,OAAO;SAC/D,OAAO,EAAE,WAAW,gCAAgC;mBANtD,CAQG,EAAG,yCAAyC,OAAO,EACpD,kBAAC,IAAD,EACE,OAAO;UACL,YAAY;UACZ,QAAQ;UACR,OAAO;UACR,EACD,CAAA,CACK;;QAET,kBAAC,QAAD;SACE,OAAO;UACL,UAAU;UACV,OAAO;UACR;mBAEA,EAAE,2CAA2C,0CAA0C;UACtF;UACA;UACD,CAAC;SACG,CAAA;QACH;;MAIP,CAAC,KAAW,EAAmB,WAAW,KACzC,kBAAC,IAAD;OAAW,WAAU;OAAU,WAAU;iBACvC,kBAAC,IAAD;QACE,cAAc,IAAc,iBAAiB;QAC7C,OACE,IACI,EAAG,6CAA6C,sBAAsB,GACtE,EAAG,qCAAqC,oBAAoB;QAElE,aACE,IACI,EAAG,mDAAmD,kCAAkC,GACxF,EACE,2CACA,sEACD;QAEP,QACG,IAkCG,KAAA,IAjCF,kBAAC,OAAD;SACE,OAAO;UACL,SAAS;UACT,KAAK;UACL,UAAU;UACV,gBAAgB;UACjB;mBANH,CAQE,kBAAC,GAAD;UAAkB,YAAY,EAAuB;UAAkB,gBAAe;oBACpF,kBAAC,GAAD;WAAQ,SAAS;qBAAjB,CACE,kBAAC,IAAD,EACE,OAAO;YACL,aAAa;YACb,QAAQ;YACR,OAAO;YACR,EACD,CAAA,EACD,EAAG,wCAAwC,mBAAmB,CACxD;;UACQ,CAAA,EACnB,kBAAC,GAAD;UAAkB,YAAY,EAAuB;UAAkB,gBAAe;oBACpF,kBAAC,GAAD;WAAQ,SAAQ;WAAU,SAAS;qBAAnC,CACE,kBAAC,IAAD,EACE,OAAO;YACL,aAAa;YACb,QAAQ;YACR,OAAO;YACR,EACD,CAAA,EACD,EAAG,iDAAiD,mBAAmB,CACjE;;UACQ,CAAA,CACf;;QAGV,CAAA;OACQ,CAAA;MAEN;OACG,EAAA,CAAA;IACX,CAAA;GAGN,kBAAC,GAAD;IACE,QAAQ;IACI;IACZ,SAAS;IACT,UAAU;IACV,qBAAqB;IACrB,CAAA;GAGF,kBAAC,IAAD;IACE,QAAQ;IACR,SAAS;IACT,WAAW;IACX,CAAA;GACE;;EAER"}
|
|
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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"}
|