@burdenoff/microfe-bigconsole 2026.625.1 → 2026.626.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/BigConsoleRoutes.js +176 -119
- package/dist/bigconsole/BigConsoleRoutes.js.map +1 -1
- package/dist/bigconsole/assistant/index.js +3 -2
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/HomePage.js.map +1 -1
- package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
- package/dist/bigconsole/pages/SettingsPage.js.map +1 -1
- package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
- 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 { useBigConsoleNavigate } from '../context';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport {\n AuroraBackground,\n GradientText,\n GlassCard,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Plus,\n Upload,\n Search,\n LayoutDashboard,\n Calendar,\n Layers,\n FileText,\n Pencil,\n Trash2,\n Copy,\n LayoutGrid,\n List,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport { useDashboardOperations } from '../hooks';\nimport { CreateDashboardDialog } from '../components/dashboard/CreateDashboardDialog';\nimport { ImportDashboardDialog } from '../components/dashboard/ImportDashboardDialog';\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// ============================================================================\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 // 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 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 // 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 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 (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 // 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 // Error state\n if (error) {\n return (\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Card\n style={{\n borderColor: 'var(--color-status-error-border)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n }}\n >\n <CardContent style={{ padding: 'var(--space-pagePadding)' }}>\n <h3\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-status-error-text)',\n }}\n >\n {tr('bigconsole.dashboards.error.title', 'Error loading dashboards')}\n </h3>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n marginTop: 'var(--space-controlGap)',\n }}\n >\n {error.message}\n </p>\n <Button size=\"sm\" onClick={() => refetch()} style={{ marginTop: 'var(--space-formGap)' }}>\n {tr('bigconsole.dashboards.error.retry', 'Retry')}\n </Button>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Header Section */}\n <div className=\"relative overflow-hidden border-b border-border-seam bg-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 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>\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>{tr('bigconsole.dashboards.title', 'Dashboards')}</GradientText>\n </h1>\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-secondary)', margin: 0 }}>\n {tr('bigconsole.dashboards.subtitle', 'Create and manage your analytics dashboards')}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={{\n label: tr('bigconsole.dashboards.actions.create', 'Create Dashboard'),\n onSelect: handleOpenCreateDialog,\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: tr('bigconsole.dashboards.actions.import', 'Import'),\n onSelect: handleOpenImportDialog,\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 {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Dashboards are live, shareable views built from widgets that query your data sinks —\n charts, tables, metrics, and drill-downs in one place. Compose a board to monitor a\n system, share KPIs with your team, or explore data interactively.\n </PagePurpose>\n\n {/* Search Bar + View Toggle */}\n <div\n style={{\n marginBottom: 'var(--space-sectionGap)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: 'var(--space-formGap)',\n }}\n >\n <div style={{ position: 'relative', flex: 1, minWidth: 0, maxWidth: '400px' }}>\n <Search\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=\"text\"\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 {/* Grid / List view toggle */}\n <div\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-sm)',\n width: 'var(--size-controlHeight-sm)',\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-sm)',\n width: 'var(--size-controlHeight-sm)',\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 </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={{\n cursor: 'pointer',\n transition: 'all 0.2s ease',\n }}\n className=\"hover:shadow-md hover:border-primary/50\"\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-action-primary-bg)',\n }}\n >\n <LayoutDashboard\n style={{\n height: 'var(--size-icon-md)',\n width: 'var(--size-icon-md)',\n color: 'var(--color-action-primary-text)',\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 {dashboard.category && (\n <Badge\n variant=\"secondary\"\n style={{\n marginTop: 'var(--space-controlGap)',\n fontSize: '10px',\n fontWeight: 'var(--font-weight-semibold)',\n textTransform: 'uppercase',\n }}\n >\n {dashboard.category}\n </Badge>\n )}\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 || 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={{ width: '1px', height: 'var(--size-icon-sm)', backgroundColor: 'var(--color-border)' }}\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 paddingTop: 'var(--space-controlGap)',\n borderTop: '1px solid var(--color-border)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleEditDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleCloneDashboard(dashboard.id, dashboard.name, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleDeleteDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 </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={{\n cursor: 'pointer',\n transition: 'all 0.2s ease',\n }}\n className=\"hover:shadow-md hover:border-primary/50\"\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-action-primary-bg)',\n }}\n >\n <LayoutDashboard\n style={{\n height: 'var(--size-icon-md)',\n width: 'var(--size-icon-md)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n\n {/* Name + Description */}\n <div style={{ flex: 1, minWidth: '180px' }}>\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 </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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleEditDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleCloneDashboard(dashboard.id, dashboard.name, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleDeleteDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* Pagination controls */}\n {!loading && filteredDashboards.length > PAGE_SIZE && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n marginTop: 'var(--space-sectionGap)',\n }}\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 >\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 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (\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 style={{\n minWidth: 'var(--size-controlHeight-sm)',\n height: 'var(--size-controlHeight-sm)',\n padding: '0 var(--space-controlGap)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n {page}\n </Button>\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 >\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 marginLeft: 'var(--space-controlGap)',\n }}\n >\n {tr('bigconsole.dashboards.pagination.pageOf', `Page ${currentPage} of ${totalPages}`)}\n </span>\n </div>\n )}\n\n {/* Empty state */}\n {!loading && filteredDashboards.length === 0 && (\n <GlassCard treatment=\"insight\" className=\"border-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 <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 <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 </div>\n ) : undefined\n }\n />\n </GlassCard>\n )}\n </div>\n\n {/* Create Dashboard Dialog */}\n <CreateDashboardDialog\n isOpen={isCreateDialogOpen}\n isCreating={isCreating}\n onClose={handleCloseCreateDialog}\n onCreate={handleCreateDashboard}\n />\n\n {/* Import Dashboard Dialog */}\n <ImportDashboardDialog\n isOpen={isImportDialogOpen}\n onClose={handleCloseImportDialog}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DashboardsPage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardsPage.js","names":[],"sources":["../../../src/bigconsole/pages/DashboardsPage.tsx"],"sourcesContent":["/**\n * DashboardsPage\n *\n * List and manage dashboards - fetches data from real backend.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useBigConsoleNavigate } from '../context';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent } from '@burdenoff/fe-libs/ui';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport {\n AuroraBackground,\n GradientText,\n GlassCard,\n IllustratedEmptyState,\n CTAOverflowMenu,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport {\n Plus,\n Upload,\n Search,\n LayoutDashboard,\n Calendar,\n Layers,\n FileText,\n Pencil,\n Trash2,\n Copy,\n LayoutGrid,\n List,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\nimport { useDashboardOperations } from '../hooks';\nimport { CreateDashboardDialog } from '../components/dashboard/CreateDashboardDialog';\nimport { ImportDashboardDialog } from '../components/dashboard/ImportDashboardDialog';\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// ============================================================================\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 // 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 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 // 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 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 (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 // 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 // Error state\n if (error) {\n return (\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Card\n style={{\n borderColor: 'var(--color-status-error-border)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n }}\n >\n <CardContent style={{ padding: 'var(--space-pagePadding)' }}>\n <h3\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-status-error-text)',\n }}\n >\n {tr('bigconsole.dashboards.error.title', 'Error loading dashboards')}\n </h3>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n marginTop: 'var(--space-controlGap)',\n }}\n >\n {error.message}\n </p>\n <Button size=\"sm\" onClick={() => refetch()} style={{ marginTop: 'var(--space-formGap)' }}>\n {tr('bigconsole.dashboards.error.retry', 'Retry')}\n </Button>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Header Section */}\n <div className=\"relative overflow-hidden border-b border-border-seam bg-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 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>\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>{tr('bigconsole.dashboards.title', 'Dashboards')}</GradientText>\n </h1>\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-secondary)', margin: 0 }}>\n {tr('bigconsole.dashboards.subtitle', 'Create and manage your analytics dashboards')}\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={{\n label: tr('bigconsole.dashboards.actions.create', 'Create Dashboard'),\n onSelect: handleOpenCreateDialog,\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: tr('bigconsole.dashboards.actions.import', 'Import'),\n onSelect: handleOpenImportDialog,\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 {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Dashboards are live, shareable views built from widgets that query your data sinks — charts, tables, metrics,\n and drill-downs in one place. Compose a board to monitor a system, share KPIs with your team, or explore data\n interactively.\n </PagePurpose>\n\n {/* Search Bar + View Toggle */}\n <div\n style={{\n marginBottom: 'var(--space-sectionGap)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: 'var(--space-formGap)',\n }}\n >\n <div style={{ position: 'relative', flex: 1, minWidth: 0, maxWidth: '400px' }}>\n <Search\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=\"text\"\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 {/* Grid / List view toggle */}\n <div\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-sm)',\n width: 'var(--size-controlHeight-sm)',\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-sm)',\n width: 'var(--size-controlHeight-sm)',\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 </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={{\n cursor: 'pointer',\n transition: 'all 0.2s ease',\n }}\n className=\"hover:shadow-md hover:border-primary/50\"\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-action-primary-bg)',\n }}\n >\n <LayoutDashboard\n style={{\n height: 'var(--size-icon-md)',\n width: 'var(--size-icon-md)',\n color: 'var(--color-action-primary-text)',\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 {dashboard.category && (\n <Badge\n variant=\"secondary\"\n style={{\n marginTop: 'var(--space-controlGap)',\n fontSize: '10px',\n fontWeight: 'var(--font-weight-semibold)',\n textTransform: 'uppercase',\n }}\n >\n {dashboard.category}\n </Badge>\n )}\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 || 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={{ width: '1px', height: 'var(--size-icon-sm)', backgroundColor: 'var(--color-border)' }}\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 paddingTop: 'var(--space-controlGap)',\n borderTop: '1px solid var(--color-border)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleEditDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleCloneDashboard(dashboard.id, dashboard.name, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleDeleteDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 </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={{\n cursor: 'pointer',\n transition: 'all 0.2s ease',\n }}\n className=\"hover:shadow-md hover:border-primary/50\"\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-action-primary-bg)',\n }}\n >\n <LayoutDashboard\n style={{\n height: 'var(--size-icon-md)',\n width: 'var(--size-icon-md)',\n color: 'var(--color-action-primary-text)',\n }}\n />\n </div>\n\n {/* Name + Description */}\n <div style={{ flex: 1, minWidth: '180px' }}>\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 </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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleEditDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleCloneDashboard(dashboard.id, dashboard.name, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={(e) => handleDeleteDashboard(dashboard.id, e)}\n style={{\n height: 'var(--size-controlHeight-sm)',\n width: 'var(--size-controlHeight-sm)',\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 </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* Pagination controls */}\n {!loading && filteredDashboards.length > PAGE_SIZE && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 'var(--space-controlGap)',\n flexWrap: 'wrap',\n marginTop: 'var(--space-sectionGap)',\n }}\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 >\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 {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (\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 style={{\n minWidth: 'var(--size-controlHeight-sm)',\n height: 'var(--size-controlHeight-sm)',\n padding: '0 var(--space-controlGap)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n {page}\n </Button>\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 >\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 marginLeft: 'var(--space-controlGap)',\n }}\n >\n {tr('bigconsole.dashboards.pagination.pageOf', `Page ${currentPage} of ${totalPages}`)}\n </span>\n </div>\n )}\n\n {/* Empty state */}\n {!loading && filteredDashboards.length === 0 && (\n <GlassCard treatment=\"insight\" className=\"border-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 <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 <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 </div>\n ) : undefined\n }\n />\n </GlassCard>\n )}\n </div>\n\n {/* Create Dashboard Dialog */}\n <CreateDashboardDialog\n isOpen={isCreateDialogOpen}\n isCreating={isCreating}\n onClose={handleCloseCreateDialog}\n onCreate={handleCreateDashboard}\n />\n\n {/* Import Dashboard Dialog */}\n <ImportDashboardDialog\n isOpen={isImportDialogOpen}\n onClose={handleCloseImportDialog}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DashboardsPage;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataParsersPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataParsersPage.tsx"],"sourcesContent":["/**\n * Data Parsers Page\n *\n * Page for listing and managing parsers with filtering and search.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { memo, useState, useMemo, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { useParserStore } from '../store/parserStore';\nimport { useParserOperations } from '../hooks/useParserOperations';\nimport { useExportParser } from '../hooks/useExportParser';\nimport { ParserCard } from '../components/parser/ParserCard';\nimport { ImportParserDialog } from '../components/parser/ImportParserDialog';\nimport type { Parser, ParserType, ParserStatus } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } 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';\n\n// Lucide icons\nimport { Plus, Search, AlertCircle, FileCode2, X, Upload } from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface ParserFilterOption<T> {\n value: T | 'all';\n label: string;\n}\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst TYPE_OPTIONS: ParserFilterOption<ParserType>[] = [\n { value: 'all', label: 'All Types' },\n { value: 'jq', label: 'JQ' },\n { value: 'jsonata', label: 'JSONata' },\n];\n\nconst STATUS_OPTIONS: ParserFilterOption<ParserStatus>[] = [\n { value: 'all', label: 'All Status' },\n { value: 'active', label: 'Active' },\n { value: 'inactive', label: 'Inactive' },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\n// Default console ID for standalone parser listing\nconst DEFAULT_CONSOLE_ID = 'default';\n\nexport const DataParsersPage = memo(function DataParsersPage() {\n const navigate = useAppNavigate();\n const { consoleId: urlConsoleId } = useParams<{ consoleId?: string }>();\n\n // Use URL consoleId or fall back to default\n const consoleId = urlConsoleId || DEFAULT_CONSOLE_ID;\n\n // Store state\n const typeFilter = useParserStore((state) => state.typeFilter);\n const statusFilter = useParserStore((state) => state.statusFilter);\n const searchQuery = useParserStore((state) => state.searchQuery);\n const setTypeFilter = useParserStore((state) => state.setTypeFilter);\n const setStatusFilter = useParserStore((state) => state.setStatusFilter);\n const setSearchQuery = useParserStore((state) => state.setSearchQuery);\n\n // Local state\n const [isDeleting, setIsDeleting] = useState(false);\n const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null);\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n\n // Operations - use consoleId from URL or default\n const { loading, error, parsers, deleteParser, refetch } = useParserOperations({\n consoleId,\n skipFetch: false,\n });\n\n // Export hook\n const { downloadParser, loading: exportLoading } = useExportParser();\n\n // Filter parsers\n const filteredParsers = useMemo(() => {\n if (!parsers) return [];\n\n return parsers.filter((parser: Parser) => {\n if (typeFilter && parser.type !== typeFilter) {\n return false;\n }\n if (statusFilter && parser.status !== statusFilter) {\n return false;\n }\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const nameMatch = parser.name?.toLowerCase().includes(query);\n const descMatch = parser.description?.toLowerCase().includes(query);\n const inputKeyMatch = parser.inputSinkKey?.toLowerCase().includes(query);\n const outputKeyMatch = parser.outputKey?.toLowerCase().includes(query);\n if (!nameMatch && !descMatch && !inputKeyMatch && !outputKeyMatch) {\n return false;\n }\n }\n return true;\n });\n }, [parsers, typeFilter, statusFilter, searchQuery]);\n\n // Handlers\n const handleTypeFilterChange = useCallback(\n (value: string) => {\n setTypeFilter(value === 'all' ? null : (value as ParserType));\n },\n [setTypeFilter]\n );\n\n const handleStatusFilterChange = useCallback(\n (value: string) => {\n setStatusFilter(value === 'all' ? null : (value as ParserStatus));\n },\n [setStatusFilter]\n );\n\n const handleSearchChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchQuery(e.target.value);\n },\n [setSearchQuery]\n );\n\n const handleCreateParser = useCallback(() => {\n // Navigate to dashboard-scoped parser creation if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/new`);\n } else {\n navigate('/parsers/new');\n }\n }, [navigate, urlConsoleId]);\n\n const handleViewParser = useCallback(\n (id: string) => {\n // Navigate to dashboard-scoped parser view if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/${id}`);\n } else {\n navigate(`/parsers/${id}`);\n }\n },\n [navigate, urlConsoleId]\n );\n\n const handleEditParser = useCallback(\n (id: string) => {\n // Navigate to dashboard-scoped parser edit if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/${id}/edit`);\n } else {\n navigate(`/parsers/${id}/edit`);\n }\n },\n [navigate, urlConsoleId]\n );\n\n const handleDeleteParser = useCallback(\n async (id: string) => {\n const native = await nativeConfirm({\n title: 'Delete parser?',\n message: 'Are you sure you want to delete this parser?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok = native === null ? window.confirm('Are you sure you want to delete this parser?') : native;\n if (!ok) return;\n nativeImpact('medium');\n setIsDeleting(true);\n setDeleteTargetId(id);\n try {\n await deleteParser(id);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n setDeleteTargetId(null);\n }\n },\n [deleteParser]\n );\n\n const handleClearFilters = useCallback(() => {\n setTypeFilter(null);\n setStatusFilter(null);\n setSearchQuery('');\n }, [setTypeFilter, setStatusFilter, setSearchQuery]);\n\n const handleExportParser = useCallback(\n async (id: string) => {\n const parser = parsers.find((p: Parser) => p.id === id);\n if (parser) {\n await downloadParser(id, parser.name);\n }\n },\n [parsers, downloadParser]\n );\n\n const handleImportParser = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_parserId: string, _parserName: string, _wasUpdate: boolean) => {\n // Refetch parsers to show the newly imported one\n refetch();\n },\n [refetch]\n );\n\n const hasActiveFilters = typeFilter !== null || statusFilter !== null || (searchQuery?.length ?? 0) > 0;\n\n // Error state\n if (error) {\n return (\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Card\n style={{\n borderColor: 'var(--color-status-error-border)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n }}\n >\n <CardContent style={{ padding: 'var(--space-pagePadding)' }}>\n <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>\n <div\n style={{\n padding: 'var(--space-controlGap)',\n borderRadius: '50%',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n <AlertCircle\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n color: 'var(--color-status-error-text)',\n }}\n />\n </div>\n <h3\n style={{\n fontSize: 'var(--font-size-body-lg)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-status-error-text)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n Failed to load Parsers\n </h3>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n {error.message}\n </p>\n <Button size=\"sm\" onClick={() => refetch()}>\n Try Again\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Header Section */}\n <div className=\"relative overflow-hidden border-b border-border-seam bg-gloss-card\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-formGap)' }}>\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <FileCode2\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>\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>Data Parsers</GradientText>\n </h1>\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-secondary)', margin: 0 }}>\n Transform and process data with JQ or JSONata expressions\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={{\n label: 'Create Parser',\n onSelect: handleCreateParser,\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: 'Import',\n onSelect: handleImportParser,\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 {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Parsers define how raw input — log lines, CSVs, JSON, or arbitrary text — is read and\n mapped into structured fields. Build a parser once, test it against a sample, then reuse\n it across pipelines so every source lands in a consistent, queryable shape.\n </PagePurpose>\n\n {/* Search and Filters Row */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginBottom: 'var(--space-sectionGap)',\n flexWrap: 'wrap',\n }}\n >\n {/* Search Bar */}\n <div style={{ position: 'relative', maxWidth: '400px', flex: 1 }}>\n <Search\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=\"text\"\n placeholder=\"Search parsers...\"\n value={searchQuery || ''}\n onChange={handleSearchChange}\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Type Filter */}\n <Select value={typeFilter || 'all'} onValueChange={handleTypeFilterChange}>\n <SelectTrigger style={{ width: '150px' }}>\n <SelectValue placeholder=\"All Types\" />\n </SelectTrigger>\n <SelectContent>\n {TYPE_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Status Filter */}\n <Select value={statusFilter || 'all'} onValueChange={handleStatusFilterChange}>\n <SelectTrigger style={{ width: '150px' }}>\n <SelectValue placeholder=\"All Status\" />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Clear Filters */}\n {hasActiveFilters && (\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n <X\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n Clear Filters\n </Button>\n )}\n\n {/* Results Count */}\n <Badge variant=\"secondary\" style={{ marginLeft: 'auto' }}>\n {filteredParsers.length} {filteredParsers.length === 1 ? 'Parser' : 'Parsers'}\n </Badge>\n </div>\n\n {/* Loading state */}\n {loading && (\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 Loading parsers...\n </p>\n </div>\n </div>\n )}\n\n {/* Empty state */}\n {!loading && filteredParsers.length === 0 && !hasActiveFilters && (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No Parsers yet\"\n description=\"Create your first parser to start transforming data with JQ or JSONata.\"\n action={\n <Button onClick={handleCreateParser}>\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n Create Parser\n </Button>\n }\n />\n </GlassCard>\n )}\n\n {/* No results state */}\n {!loading && filteredParsers.length === 0 && hasActiveFilters && (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No matching parsers\"\n description=\"No parsers match your filters\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n Clear Filters\n </Button>\n }\n />\n </GlassCard>\n )}\n\n {/* Parser Grid */}\n {!loading && filteredParsers.length > 0 && (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {filteredParsers.map((parser: Parser) => (\n <ParserCard\n key={parser.id}\n parser={parser}\n onView={handleViewParser}\n onEdit={handleEditParser}\n onExport={handleExportParser}\n onDelete={handleDeleteParser}\n isDeleting={isDeleting && deleteTargetId === parser.id}\n />\n ))}\n </div>\n )}\n </div>\n\n {/* Import Parser Dialog */}\n <ImportParserDialog\n isOpen={isImportDialogOpen}\n onClose={() => setIsImportDialogOpen(false)}\n consoleId={consoleId}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DataParsersPage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DataParsersPage.js","names":[],"sources":["../../../src/bigconsole/pages/DataParsersPage.tsx"],"sourcesContent":["/**\n * Data Parsers Page\n *\n * Page for listing and managing parsers with filtering and search.\n * Uses fe-libs shared UI components and semantic tokens for consistent styling.\n */\n\nimport { memo, useState, useMemo, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeConfirm, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport { useParserStore } from '../store/parserStore';\nimport { useParserOperations } from '../hooks/useParserOperations';\nimport { useExportParser } from '../hooks/useExportParser';\nimport { ParserCard } from '../components/parser/ParserCard';\nimport { ImportParserDialog } from '../components/parser/ImportParserDialog';\nimport type { Parser, ParserType, ParserStatus } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } 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';\n\n// Lucide icons\nimport { Plus, Search, AlertCircle, FileCode2, X, Upload } from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface ParserFilterOption<T> {\n value: T | 'all';\n label: string;\n}\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst TYPE_OPTIONS: ParserFilterOption<ParserType>[] = [\n { value: 'all', label: 'All Types' },\n { value: 'jq', label: 'JQ' },\n { value: 'jsonata', label: 'JSONata' },\n];\n\nconst STATUS_OPTIONS: ParserFilterOption<ParserStatus>[] = [\n { value: 'all', label: 'All Status' },\n { value: 'active', label: 'Active' },\n { value: 'inactive', label: 'Inactive' },\n];\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\n// Default console ID for standalone parser listing\nconst DEFAULT_CONSOLE_ID = 'default';\n\nexport const DataParsersPage = memo(function DataParsersPage() {\n const navigate = useAppNavigate();\n const { consoleId: urlConsoleId } = useParams<{ consoleId?: string }>();\n\n // Use URL consoleId or fall back to default\n const consoleId = urlConsoleId || DEFAULT_CONSOLE_ID;\n\n // Store state\n const typeFilter = useParserStore((state) => state.typeFilter);\n const statusFilter = useParserStore((state) => state.statusFilter);\n const searchQuery = useParserStore((state) => state.searchQuery);\n const setTypeFilter = useParserStore((state) => state.setTypeFilter);\n const setStatusFilter = useParserStore((state) => state.setStatusFilter);\n const setSearchQuery = useParserStore((state) => state.setSearchQuery);\n\n // Local state\n const [isDeleting, setIsDeleting] = useState(false);\n const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null);\n const [isImportDialogOpen, setIsImportDialogOpen] = useState(false);\n\n // Operations - use consoleId from URL or default\n const { loading, error, parsers, deleteParser, refetch } = useParserOperations({\n consoleId,\n skipFetch: false,\n });\n\n // Export hook\n const { downloadParser, loading: exportLoading } = useExportParser();\n\n // Filter parsers\n const filteredParsers = useMemo(() => {\n if (!parsers) return [];\n\n return parsers.filter((parser: Parser) => {\n if (typeFilter && parser.type !== typeFilter) {\n return false;\n }\n if (statusFilter && parser.status !== statusFilter) {\n return false;\n }\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const nameMatch = parser.name?.toLowerCase().includes(query);\n const descMatch = parser.description?.toLowerCase().includes(query);\n const inputKeyMatch = parser.inputSinkKey?.toLowerCase().includes(query);\n const outputKeyMatch = parser.outputKey?.toLowerCase().includes(query);\n if (!nameMatch && !descMatch && !inputKeyMatch && !outputKeyMatch) {\n return false;\n }\n }\n return true;\n });\n }, [parsers, typeFilter, statusFilter, searchQuery]);\n\n // Handlers\n const handleTypeFilterChange = useCallback(\n (value: string) => {\n setTypeFilter(value === 'all' ? null : (value as ParserType));\n },\n [setTypeFilter]\n );\n\n const handleStatusFilterChange = useCallback(\n (value: string) => {\n setStatusFilter(value === 'all' ? null : (value as ParserStatus));\n },\n [setStatusFilter]\n );\n\n const handleSearchChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchQuery(e.target.value);\n },\n [setSearchQuery]\n );\n\n const handleCreateParser = useCallback(() => {\n // Navigate to dashboard-scoped parser creation if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/new`);\n } else {\n navigate('/parsers/new');\n }\n }, [navigate, urlConsoleId]);\n\n const handleViewParser = useCallback(\n (id: string) => {\n // Navigate to dashboard-scoped parser view if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/${id}`);\n } else {\n navigate(`/parsers/${id}`);\n }\n },\n [navigate, urlConsoleId]\n );\n\n const handleEditParser = useCallback(\n (id: string) => {\n // Navigate to dashboard-scoped parser edit if consoleId is provided\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers/${id}/edit`);\n } else {\n navigate(`/parsers/${id}/edit`);\n }\n },\n [navigate, urlConsoleId]\n );\n\n const handleDeleteParser = useCallback(\n async (id: string) => {\n const native = await nativeConfirm({\n title: 'Delete parser?',\n message: 'Are you sure you want to delete this parser?',\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok = native === null ? window.confirm('Are you sure you want to delete this parser?') : native;\n if (!ok) return;\n nativeImpact('medium');\n setIsDeleting(true);\n setDeleteTargetId(id);\n try {\n await deleteParser(id);\n if (isNative()) void nativeNotify('success');\n } catch (err) {\n if (isNative()) void nativeNotify('error');\n } finally {\n setIsDeleting(false);\n setDeleteTargetId(null);\n }\n },\n [deleteParser]\n );\n\n const handleClearFilters = useCallback(() => {\n setTypeFilter(null);\n setStatusFilter(null);\n setSearchQuery('');\n }, [setTypeFilter, setStatusFilter, setSearchQuery]);\n\n const handleExportParser = useCallback(\n async (id: string) => {\n const parser = parsers.find((p: Parser) => p.id === id);\n if (parser) {\n await downloadParser(id, parser.name);\n }\n },\n [parsers, downloadParser]\n );\n\n const handleImportParser = useCallback(() => {\n setIsImportDialogOpen(true);\n }, []);\n\n const handleImportSuccess = useCallback(\n (_parserId: string, _parserName: string, _wasUpdate: boolean) => {\n // Refetch parsers to show the newly imported one\n refetch();\n },\n [refetch]\n );\n\n const hasActiveFilters = typeFilter !== null || statusFilter !== null || (searchQuery?.length ?? 0) > 0;\n\n // Error state\n if (error) {\n return (\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <Card\n style={{\n borderColor: 'var(--color-status-error-border)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n }}\n >\n <CardContent style={{ padding: 'var(--space-pagePadding)' }}>\n <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center' }}>\n <div\n style={{\n padding: 'var(--space-controlGap)',\n borderRadius: '50%',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n <AlertCircle\n style={{\n height: 'var(--size-icon-xl)',\n width: 'var(--size-icon-xl)',\n color: 'var(--color-status-error-text)',\n }}\n />\n </div>\n <h3\n style={{\n fontSize: 'var(--font-size-body-lg)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-status-error-text)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n Failed to load Parsers\n </h3>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n marginBottom: 'var(--space-formGap)',\n }}\n >\n {error.message}\n </p>\n <Button size=\"sm\" onClick={() => refetch()}>\n Try Again\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n }\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n {/* Header Section */}\n <div className=\"relative overflow-hidden border-b border-border-seam bg-gloss-card\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-formGap)' }}>\n <div\n style={{\n display: 'flex',\n height: 'var(--size-controlHeight-lg)',\n width: 'var(--size-controlHeight-lg)',\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 'var(--radius-card)',\n backgroundColor: 'var(--color-action-primary-bg)',\n }}\n >\n <FileCode2\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>\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>Data Parsers</GradientText>\n </h1>\n <p style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-secondary)', margin: 0 }}>\n Transform and process data with JQ or JSONata expressions\n </p>\n </div>\n </div>\n\n <CTAOverflowMenu\n primary={{\n label: 'Create Parser',\n onSelect: handleCreateParser,\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: 'Import',\n onSelect: handleImportParser,\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 {/* Main Content */}\n <div style={{ padding: 'var(--space-pagePadding)' }}>\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n Parsers define how raw input — log lines, CSVs, JSON, or arbitrary text — is read and mapped into structured\n fields. Build a parser once, test it against a sample, then reuse it across pipelines so every source lands in\n a consistent, queryable shape.\n </PagePurpose>\n\n {/* Search and Filters Row */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginBottom: 'var(--space-sectionGap)',\n flexWrap: 'wrap',\n }}\n >\n {/* Search Bar */}\n <div style={{ position: 'relative', maxWidth: '400px', flex: 1 }}>\n <Search\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=\"text\"\n placeholder=\"Search parsers...\"\n value={searchQuery || ''}\n onChange={handleSearchChange}\n style={{ paddingLeft: 'var(--size-controlHeight-md)', height: 'var(--size-controlHeight-md)' }}\n />\n </div>\n\n {/* Type Filter */}\n <Select value={typeFilter || 'all'} onValueChange={handleTypeFilterChange}>\n <SelectTrigger style={{ width: '150px' }}>\n <SelectValue placeholder=\"All Types\" />\n </SelectTrigger>\n <SelectContent>\n {TYPE_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Status Filter */}\n <Select value={statusFilter || 'all'} onValueChange={handleStatusFilterChange}>\n <SelectTrigger style={{ width: '150px' }}>\n <SelectValue placeholder=\"All Status\" />\n </SelectTrigger>\n <SelectContent>\n {STATUS_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {opt.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {/* Clear Filters */}\n {hasActiveFilters && (\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n <X\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n Clear Filters\n </Button>\n )}\n\n {/* Results Count */}\n <Badge variant=\"secondary\" style={{ marginLeft: 'auto' }}>\n {filteredParsers.length} {filteredParsers.length === 1 ? 'Parser' : 'Parsers'}\n </Badge>\n </div>\n\n {/* Loading state */}\n {loading && (\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 Loading parsers...\n </p>\n </div>\n </div>\n )}\n\n {/* Empty state */}\n {!loading && filteredParsers.length === 0 && !hasActiveFilters && (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No Parsers yet\"\n description=\"Create your first parser to start transforming data with JQ or JSONata.\"\n action={\n <Button onClick={handleCreateParser}>\n <Plus\n style={{\n marginRight: 'var(--space-iconGap)',\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n }}\n />\n Create Parser\n </Button>\n }\n />\n </GlassCard>\n )}\n\n {/* No results state */}\n {!loading && filteredParsers.length === 0 && hasActiveFilters && (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No matching parsers\"\n description=\"No parsers match your filters\"\n action={\n <Button variant=\"ghost\" size=\"sm\" onClick={handleClearFilters}>\n Clear Filters\n </Button>\n }\n />\n </GlassCard>\n )}\n\n {/* Parser Grid */}\n {!loading && filteredParsers.length > 0 && (\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))',\n gap: 'var(--space-formGap)',\n }}\n >\n {filteredParsers.map((parser: Parser) => (\n <ParserCard\n key={parser.id}\n parser={parser}\n onView={handleViewParser}\n onEdit={handleEditParser}\n onExport={handleExportParser}\n onDelete={handleDeleteParser}\n isDeleting={isDeleting && deleteTargetId === parser.id}\n />\n ))}\n </div>\n )}\n </div>\n\n {/* Import Parser Dialog */}\n <ImportParserDialog\n isOpen={isImportDialogOpen}\n onClose={() => setIsImportDialogOpen(false)}\n consoleId={consoleId}\n onSuccess={handleImportSuccess}\n />\n </div>\n );\n});\n\nexport default DataParsersPage;\n"],"mappings":"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"}
|