@burdenoff/microfe-bigconsole 2026.1004.3 → 2026.1004.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/bigconsole/BigConsoleRoutes.d.ts +1 -1
  2. package/dist/bigconsole/BigConsoleRoutes.d.ts.map +1 -1
  3. package/dist/bigconsole/BigConsoleRoutes.js +15 -1
  4. package/dist/bigconsole/BigConsoleRoutes.js.map +1 -1
  5. package/dist/bigconsole/components/DrilldownModal.d.ts.map +1 -1
  6. package/dist/bigconsole/components/DrilldownModal.js +54 -53
  7. package/dist/bigconsole/components/DrilldownModal.js.map +1 -1
  8. package/dist/bigconsole/components/builder/inspector/DataBindingSection.d.ts +3 -1
  9. package/dist/bigconsole/components/builder/inspector/DataBindingSection.d.ts.map +1 -1
  10. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js +40 -39
  11. package/dist/bigconsole/components/builder/inspector/DataBindingSection.js.map +1 -1
  12. package/dist/bigconsole/components/dashboard/DashboardCanvas.d.ts.map +1 -1
  13. package/dist/bigconsole/components/dashboard/DashboardCanvas.js +184 -177
  14. package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
  15. package/dist/bigconsole/components/dashboard/DashboardGrid.d.ts.map +1 -1
  16. package/dist/bigconsole/components/dashboard/DashboardGrid.js +40 -22
  17. package/dist/bigconsole/components/dashboard/DashboardGrid.js.map +1 -1
  18. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.d.ts.map +1 -1
  19. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js +31 -25
  20. package/dist/bigconsole/components/dashboard/ExportDashboardDialog.js.map +1 -1
  21. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.d.ts +1 -1
  22. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.d.ts.map +1 -1
  23. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js +115 -80
  24. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/FilterEditor.js.map +1 -1
  25. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.d.ts.map +1 -1
  26. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js +177 -146
  27. package/dist/bigconsole/components/dashboard/GlobalFiltersBar/GlobalFiltersBar.js.map +1 -1
  28. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.d.ts +1 -1
  29. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.d.ts.map +1 -1
  30. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js +375 -325
  31. package/dist/bigconsole/components/dashboard/ShareDialog/ShareDialog.js.map +1 -1
  32. package/dist/bigconsole/components/datasink/CacheTTLSelector.d.ts.map +1 -1
  33. package/dist/bigconsole/components/datasink/CacheTTLSelector.js +7 -1
  34. package/dist/bigconsole/components/datasink/CacheTTLSelector.js.map +1 -1
  35. package/dist/bigconsole/components/parser/ParserSelector.d.ts +3 -1
  36. package/dist/bigconsole/components/parser/ParserSelector.d.ts.map +1 -1
  37. package/dist/bigconsole/components/parser/ParserSelector.js +94 -94
  38. package/dist/bigconsole/components/parser/ParserSelector.js.map +1 -1
  39. package/dist/bigconsole/components/pipeline/PipelineCard.d.ts.map +1 -1
  40. package/dist/bigconsole/components/pipeline/PipelineCard.js +111 -89
  41. package/dist/bigconsole/components/pipeline/PipelineCard.js.map +1 -1
  42. package/dist/bigconsole/components/pipeline/PipelineScopePicker.d.ts +8 -0
  43. package/dist/bigconsole/components/pipeline/PipelineScopePicker.d.ts.map +1 -0
  44. package/dist/bigconsole/components/pipeline/PipelineScopePicker.js +52 -0
  45. package/dist/bigconsole/components/pipeline/PipelineScopePicker.js.map +1 -0
  46. package/dist/bigconsole/components/widgets/PropertiesPanel.d.ts.map +1 -1
  47. package/dist/bigconsole/components/widgets/PropertiesPanel.js +257 -221
  48. package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
  49. package/dist/bigconsole/components/widgets/WidgetHeader.d.ts +1 -0
  50. package/dist/bigconsole/components/widgets/WidgetHeader.d.ts.map +1 -1
  51. package/dist/bigconsole/components/widgets/WidgetHeader.js +29 -29
  52. package/dist/bigconsole/components/widgets/WidgetHeader.js.map +1 -1
  53. package/dist/bigconsole/components/widgets/WidgetWrapper.d.ts +2 -0
  54. package/dist/bigconsole/components/widgets/WidgetWrapper.d.ts.map +1 -1
  55. package/dist/bigconsole/components/widgets/WidgetWrapper.js +247 -239
  56. package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
  57. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.d.ts.map +1 -1
  58. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js +6 -4
  59. package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js.map +1 -1
  60. package/dist/bigconsole/components/widgets/states/EmptyState.d.ts +1 -0
  61. package/dist/bigconsole/components/widgets/states/EmptyState.d.ts.map +1 -1
  62. package/dist/bigconsole/components/widgets/states/EmptyState.js +4 -4
  63. package/dist/bigconsole/components/widgets/states/EmptyState.js.map +1 -1
  64. package/dist/bigconsole/components/workflow/WorkflowDesignPreview.d.ts +7 -0
  65. package/dist/bigconsole/components/workflow/WorkflowDesignPreview.d.ts.map +1 -0
  66. package/dist/bigconsole/components/workflow/WorkflowDesignPreview.js +94 -0
  67. package/dist/bigconsole/components/workflow/WorkflowDesignPreview.js.map +1 -0
  68. package/dist/bigconsole/components/workflow/WorkflowSinkLinkForm.d.ts +7 -0
  69. package/dist/bigconsole/components/workflow/WorkflowSinkLinkForm.d.ts.map +1 -0
  70. package/dist/bigconsole/components/workflow/WorkflowSinkLinkForm.js +101 -0
  71. package/dist/bigconsole/components/workflow/WorkflowSinkLinkForm.js.map +1 -0
  72. package/dist/bigconsole/hooks/useBigConsoleSettings.d.ts +16 -0
  73. package/dist/bigconsole/hooks/useBigConsoleSettings.d.ts.map +1 -0
  74. package/dist/bigconsole/hooks/useBigConsoleSettings.js +57 -0
  75. package/dist/bigconsole/hooks/useBigConsoleSettings.js.map +1 -0
  76. package/dist/bigconsole/hooks/useDashboardOperations.d.ts.map +1 -1
  77. package/dist/bigconsole/hooks/useDashboardOperations.js +102 -79
  78. package/dist/bigconsole/hooks/useDashboardOperations.js.map +1 -1
  79. package/dist/bigconsole/hooks/useExportDashboard.d.ts +3 -1
  80. package/dist/bigconsole/hooks/useExportDashboard.d.ts.map +1 -1
  81. package/dist/bigconsole/hooks/useExportDashboard.js +23 -13
  82. package/dist/bigconsole/hooks/useExportDashboard.js.map +1 -1
  83. package/dist/bigconsole/hooks/useFilterUrlSync.d.ts +18 -0
  84. package/dist/bigconsole/hooks/useFilterUrlSync.d.ts.map +1 -1
  85. package/dist/bigconsole/hooks/useFilterUrlSync.js +87 -57
  86. package/dist/bigconsole/hooks/useFilterUrlSync.js.map +1 -1
  87. package/dist/bigconsole/hooks/useGlobalFilters.d.ts.map +1 -1
  88. package/dist/bigconsole/hooks/useGlobalFilters.js +39 -29
  89. package/dist/bigconsole/hooks/useGlobalFilters.js.map +1 -1
  90. package/dist/bigconsole/hooks/usePageOperations.d.ts.map +1 -1
  91. package/dist/bigconsole/hooks/usePageOperations.js +1 -0
  92. package/dist/bigconsole/hooks/usePageOperations.js.map +1 -1
  93. package/dist/bigconsole/hooks/usePipelineOperations.d.ts.map +1 -1
  94. package/dist/bigconsole/hooks/usePipelineOperations.js +114 -112
  95. package/dist/bigconsole/hooks/usePipelineOperations.js.map +1 -1
  96. package/dist/bigconsole/hooks/useShareRecipients.d.ts +22 -0
  97. package/dist/bigconsole/hooks/useShareRecipients.d.ts.map +1 -0
  98. package/dist/bigconsole/hooks/useShareRecipients.js +198 -0
  99. package/dist/bigconsole/hooks/useShareRecipients.js.map +1 -0
  100. package/dist/bigconsole/pages/DashboardBuilderPage.d.ts.map +1 -1
  101. package/dist/bigconsole/pages/DashboardBuilderPage.js +198 -193
  102. package/dist/bigconsole/pages/DashboardBuilderPage.js.map +1 -1
  103. package/dist/bigconsole/pages/DashboardViewPage.d.ts.map +1 -1
  104. package/dist/bigconsole/pages/DashboardViewPage.js +171 -167
  105. package/dist/bigconsole/pages/DashboardViewPage.js.map +1 -1
  106. package/dist/bigconsole/pages/DashboardsPage.d.ts.map +1 -1
  107. package/dist/bigconsole/pages/DashboardsPage.js +313 -266
  108. package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
  109. package/dist/bigconsole/pages/DataSinkBuilderPage.d.ts.map +1 -1
  110. package/dist/bigconsole/pages/DataSinkBuilderPage.js +84 -79
  111. package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
  112. package/dist/bigconsole/pages/DataSinkViewPage.d.ts.map +1 -1
  113. package/dist/bigconsole/pages/DataSinkViewPage.js +267 -257
  114. package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
  115. package/dist/bigconsole/pages/InsightsPage.d.ts.map +1 -1
  116. package/dist/bigconsole/pages/InsightsPage.js +465 -402
  117. package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
  118. package/dist/bigconsole/pages/PipelineBuilderPage.d.ts.map +1 -1
  119. package/dist/bigconsole/pages/PipelineBuilderPage.js +169 -157
  120. package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
  121. package/dist/bigconsole/pages/PipelineViewPage.d.ts.map +1 -1
  122. package/dist/bigconsole/pages/PipelineViewPage.js +370 -230
  123. package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
  124. package/dist/bigconsole/pages/PipelinesPage.d.ts.map +1 -1
  125. package/dist/bigconsole/pages/PipelinesPage.js +181 -151
  126. package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
  127. package/dist/bigconsole/pages/SettingsPage.d.ts.map +1 -1
  128. package/dist/bigconsole/pages/SettingsPage.js +188 -208
  129. package/dist/bigconsole/pages/SettingsPage.js.map +1 -1
  130. package/dist/bigconsole/pages/WidgetDemoPage.d.ts.map +1 -1
  131. package/dist/bigconsole/pages/WidgetDemoPage.js +134 -117
  132. package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
  133. package/dist/bigconsole/pages/WorkflowViewPage.d.ts.map +1 -1
  134. package/dist/bigconsole/pages/WorkflowViewPage.js +315 -267
  135. package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
  136. package/dist/bigconsole/pages/WorkflowsPage.d.ts.map +1 -1
  137. package/dist/bigconsole/pages/WorkflowsPage.js +4 -3
  138. package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
  139. package/dist/bigconsole/types/index.d.ts +4 -0
  140. package/dist/bigconsole/types/index.d.ts.map +1 -1
  141. package/dist/generated/wspace-operations.d.ts +148 -0
  142. package/dist/generated/wspace-operations.d.ts.map +1 -1
  143. package/dist/generated/wspace-operations.js +172 -133
  144. package/dist/generated/wspace-operations.js.map +1 -1
  145. package/dist/generated/wspace-types.d.ts +21 -0
  146. package/dist/generated/wspace-types.d.ts.map +1 -1
  147. package/dist/generated/wspace-types.js.map +1 -1
  148. package/dist/microfe-bigconsole.css +1 -1
  149. package/dist/translations/en.d.ts.map +1 -1
  150. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ShareDialog.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/ShareDialog/ShareDialog.tsx"],"sourcesContent":["/**\n * ShareDialog Component\n *\n * Dialog for sharing dashboards with users and roles.\n */\n\nimport React, { useState, useEffect, useCallback, useMemo } from 'react';\nimport { createPortal } from 'react-dom';\nimport { formatDistanceToNow } from 'date-fns';\nimport {\n useDashboardSharing,\n type DashboardShare,\n type SharePermission,\n type ShareRecipientType,\n} from '../../../hooks';\nimport type { DashboardVisibility } from '../../../types';\nimport { useBigConsoleAnalytics } from '../../../hooks/useBigConsoleAnalytics';\nimport { EmbedTab } from './EmbedTab';\n\n// Dark mode aware colors\nconst getColors = (isDark: boolean) => ({\n bgPrimary: isDark ? '#1a1a2e' : '#ffffff',\n bgSecondary: isDark ? '#252542' : '#f4f4f5',\n bgTertiary: isDark ? '#2d2d4a' : '#e4e4e7',\n textPrimary: isDark ? '#f4f4f5' : '#18181b',\n textSecondary: isDark ? '#a1a1aa' : '#71717a',\n textTertiary: isDark ? '#71717a' : '#a1a1aa',\n borderDefault: isDark ? '#3f3f5c' : '#e4e4e7',\n actionPrimaryBg: '#6366f1',\n actionPrimaryBgHover: '#4f46e5',\n statusSuccessBg: '#22c55e',\n statusErrorText: '#ef4444',\n});\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ShareDialogProps {\n dashboardId: string;\n dashboardName: string;\n isOpen: boolean;\n onClose: () => void;\n /**\n * Current general-access level (`Dashboard.visibility`). The General access\n * control renders ONLY when this and `onVisibilityChange` are both supplied —\n * a selector with no known current value would be a guess, and one with no\n * handler would be a switch wired to nothing.\n */\n visibility?: DashboardVisibility;\n /** Persist a new general-access level. Resolves once the write completes. */\n onVisibilityChange?: (visibility: DashboardVisibility) => Promise<void>;\n /** False when the actor lacks `bigconsole-dashboard:update`; the control is then read-only. */\n canChangeVisibility?: boolean;\n}\n\n/** General-access levels, in widening order. */\nconst VISIBILITY_OPTIONS: { value: DashboardVisibility; label: string; description: string }[] = [\n { value: 'PRIVATE', label: 'Private', description: 'Only people added below' },\n { value: 'WORKSPACE', label: 'Workspace', description: 'Anyone in this workspace' },\n { value: 'PUBLIC', label: 'Public', description: 'Anyone with the link' },\n];\n\ninterface ShareRecipient {\n id: string;\n name: string;\n email?: string;\n type: ShareRecipientType;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Permission options as per spec (03-graphql-contract.md)\n * Spec values: view, edit\n */\nconst PERMISSION_OPTIONS: { value: SharePermission; label: string; description: string }[] = [\n { value: 'VIEW', label: 'View', description: 'Can view the dashboard' },\n { value: 'EDIT', label: 'Edit', description: 'Can edit widgets, layout, and share' },\n];\n\n// Use shared semantic tokens for portal rendering\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ShareDialog({\n dashboardId,\n dashboardName,\n isOpen,\n onClose,\n visibility,\n onVisibilityChange,\n canChangeVisibility = true,\n}: ShareDialogProps) {\n const { shares, loading, fetchShares, shareWithUser, shareWithGroup, updateShare, revokeShare, revokeAllShares } =\n useDashboardSharing();\n const emitBigConsoleEvent = useBigConsoleAnalytics();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedRecipient, setSelectedRecipient] = useState<ShareRecipient | null>(null);\n const [selectedPermission, setSelectedPermission] = useState<SharePermission>('VIEW');\n const [shareLink, setShareLink] = useState('');\n const [copySuccess, setCopySuccess] = useState(false);\n const [activeTab, setActiveTab] = useState<'people' | 'embed'>('people');\n const [visibilitySaving, setVisibilitySaving] = useState(false);\n const [visibilityError, setVisibilityError] = useState<string | null>(null);\n\n // Fetch shares when dialog opens\n useEffect(() => {\n if (isOpen && dashboardId) {\n fetchShares(dashboardId);\n // Generate share link\n setShareLink(`${window.location.origin}/dashboard/${dashboardId}?shared=true`);\n }\n }, [isOpen, dashboardId, fetchShares]);\n\n // Handle share - per spec: only USER and GROUP recipient types\n const handleShare = useCallback(async () => {\n if (!selectedRecipient) return;\n\n emitBigConsoleEvent('bigconsole.dashboard.share_clicked', {\n dashboardId,\n permission: selectedPermission,\n recipientType: selectedRecipient.type,\n });\n\n let share: DashboardShare | null = null;\n if (selectedRecipient.type === 'USER') {\n share = await shareWithUser(dashboardId, selectedRecipient.id, selectedPermission);\n } else if (selectedRecipient.type === 'GROUP') {\n share = await shareWithGroup(dashboardId, selectedRecipient.id, selectedPermission);\n }\n\n if (share) {\n emitBigConsoleEvent('bigconsole.dashboard.shared', {\n dashboardId,\n permission: selectedPermission,\n recipientType: selectedRecipient.type,\n });\n }\n\n setSelectedRecipient(null);\n setSearchQuery('');\n }, [dashboardId, emitBigConsoleEvent, selectedRecipient, selectedPermission, shareWithGroup, shareWithUser]);\n\n // Handle update permission\n const handleUpdatePermission = useCallback(\n async (shareId: string, newPermission: SharePermission) => {\n await updateShare({ id: shareId, permission: newPermission });\n },\n [updateShare]\n );\n\n // Handle revoke\n const handleRevoke = useCallback(\n async (shareId: string) => {\n if (window.confirm('Are you sure you want to revoke this access?')) {\n await revokeShare(shareId);\n }\n },\n [revokeShare]\n );\n\n // Handle revoke all\n const handleRevokeAll = useCallback(async () => {\n if (window.confirm('Are you sure you want to revoke all shared access?')) {\n await revokeAllShares(dashboardId);\n }\n }, [dashboardId, revokeAllShares]);\n\n const handleVisibilityChange = useCallback(\n async (next: DashboardVisibility) => {\n if (!onVisibilityChange || next === visibility) return;\n setVisibilityError(null);\n setVisibilitySaving(true);\n try {\n await onVisibilityChange(next);\n } catch (err) {\n // Surface the failure: the select snaps back to `visibility`, which the\n // parent has not changed, so a silent catch would look like a success.\n setVisibilityError(err instanceof Error ? err.message : 'Could not update general access.');\n } finally {\n setVisibilitySaving(false);\n }\n },\n [onVisibilityChange, visibility]\n );\n\n // Copy link to clipboard\n const handleCopyLink = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(shareLink);\n setCopySuccess(true);\n setTimeout(() => setCopySuccess(false), 2000);\n } catch (err) {}\n }, [shareLink]);\n\n // Mock search results (in real app, this would come from an API)\n // Per spec: ShareRecipientType is 'user' or 'group' only\n const searchResults: ShareRecipient[] = searchQuery\n ? (\n [\n { id: 'user-1', name: 'John Doe', email: 'john@example.com', type: 'USER' as ShareRecipientType },\n { id: 'user-2', name: 'Jane Smith', email: 'jane@example.com', type: 'USER' as ShareRecipientType },\n { id: 'group-admin', name: 'Administrators', type: 'GROUP' as ShareRecipientType },\n { id: 'group-editor', name: 'Editors', type: 'GROUP' as ShareRecipientType },\n ] as ShareRecipient[]\n ).filter(\n (r) =>\n r.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n r.email?.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : [];\n\n // Check if dark mode is enabled\n const isDarkMode = typeof document !== 'undefined' && document.documentElement.classList.contains('dark');\n const colors = useMemo(() => getColors(isDarkMode), [isDarkMode]);\n\n if (!isOpen) return null;\n\n // Use portal to render at document.body level to avoid z-index/stacking context issues\n const dialogContent = (\n <div\n style={{\n position: 'fixed',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundColor: 'var(--color-overlay-scrim)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 'var(--space-formGap)',\n zIndex: 99999,\n isolation: 'isolate',\n }}\n onClick={onClose}\n role=\"presentation\"\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"share-dialog-title\"\n data-testid=\"share-dialog\"\n style={{\n // was a hard 520px, which overflowed every phone viewport\n width: '100%',\n maxWidth: '520px',\n maxHeight: '85vh',\n display: 'flex',\n flexDirection: 'column',\n backgroundColor: colors.bgPrimary,\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-3)',\n overflow: 'hidden',\n position: 'relative',\n zIndex: 100000,\n color: colors.textPrimary,\n }}\n onClick={(e) => e.stopPropagation()}\n >\n {/* Header */}\n <div\n style={{\n flexShrink: 0,\n padding: '20px 24px',\n borderBottom: `1px solid ${colors.borderDefault}`,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n backgroundColor: colors.bgPrimary,\n }}\n >\n <div>\n <h2\n id=\"share-dialog-title\"\n style={{ margin: 0, fontSize: '18px', fontWeight: 600, color: colors.textPrimary }}\n >\n Share Dashboard\n </h2>\n <p style={{ margin: '4px 0 0', fontSize: '13px', color: colors.textSecondary }}>{dashboardName}</p>\n </div>\n <button\n onClick={onClose}\n style={{\n background: 'none',\n border: 'none',\n fontSize: '24px',\n cursor: 'pointer',\n color: colors.textSecondary,\n padding: '4px',\n lineHeight: 1,\n }}\n >\n ×\n </button>\n </div>\n\n {/* Tabs */}\n <div\n role=\"tablist\"\n aria-label=\"Share options\"\n style={{\n flexShrink: 0,\n display: 'flex',\n gap: '4px',\n padding: '0 24px',\n borderBottom: `1px solid ${colors.borderDefault}`,\n backgroundColor: colors.bgPrimary,\n }}\n >\n {(['people', 'embed'] as const).map((tab) => (\n <button\n key={tab}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === tab}\n onClick={() => setActiveTab(tab)}\n style={{\n padding: '10px 12px',\n background: 'none',\n border: 'none',\n borderBottom: `2px solid ${activeTab === tab ? colors.actionPrimaryBg : 'transparent'}`,\n color: activeTab === tab ? colors.textPrimary : colors.textSecondary,\n fontSize: '13px',\n fontWeight: 600,\n cursor: 'pointer',\n }}\n >\n {tab === 'people' ? 'People' : 'Publish to web'}\n </button>\n ))}\n </div>\n\n {/* Publish-to-web tab */}\n {activeTab === 'embed' && (\n <div className=\"min-h-0 flex-1 overflow-y-auto\" style={{ backgroundColor: colors.bgPrimary }}>\n <EmbedTab dashboardId={dashboardId} colors={colors} />\n </div>\n )}\n\n {/* People tab (existing content) */}\n {activeTab === 'people' && (\n <div\n className=\"flex min-h-0 flex-1 flex-col gap-section-gap overflow-y-auto p-card-padding\"\n style={{ backgroundColor: colors.bgPrimary }}\n >\n {/* General access — WHO the link works for. It leads because the\n link below is only as useful as this setting allows: copying a\n URL from a PRIVATE board hands over something the recipient\n cannot open. Backed by `Dashboard.visibility` +\n `BigConsoleUpdateDashboardInput.visibility`. */}\n {visibility && onVisibilityChange && (\n <section aria-labelledby=\"share-visibility-heading\">\n <h3\n id=\"share-visibility-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n General access\n </h3>\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>\n <select\n aria-label=\"General access\"\n data-testid=\"share-visibility\"\n value={visibility}\n disabled={!canChangeVisibility || visibilitySaving}\n onChange={(e) => void handleVisibilityChange(e.target.value as DashboardVisibility)}\n style={{\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n cursor: !canChangeVisibility || visibilitySaving ? 'not-allowed' : 'pointer',\n opacity: !canChangeVisibility || visibilitySaving ? 0.6 : 1,\n }}\n >\n {VISIBILITY_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {VISIBILITY_OPTIONS.find((opt) => opt.value === visibility)?.description}\n </span>\n </div>\n {!canChangeVisibility && (\n <p style={{ margin: '6px 0 0', fontSize: '11px', color: colors.textSecondary }}>\n You don&apos;t have permission to change general access.\n </p>\n )}\n {visibilityError && (\n <p style={{ margin: '6px 0 0', fontSize: '11px', color: colors.statusErrorText }}>\n {visibilityError}\n </p>\n )}\n </section>\n )}\n\n {/* Share Link Section — the fastest way to share, so it leads */}\n <section aria-labelledby=\"share-link-heading\">\n <h3\n id=\"share-link-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n Share link\n </h3>\n <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>\n <input\n type=\"text\"\n value={shareLink}\n readOnly\n style={{\n flex: 1,\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgSecondary,\n color: colors.textPrimary,\n }}\n />\n <button\n onClick={handleCopyLink}\n style={{\n padding: '10px 16px',\n backgroundColor: copySuccess ? colors.statusSuccessBg : colors.actionPrimaryBg,\n color: 'var(--color-action-primary-text)',\n border: 'none',\n borderRadius: '6px',\n cursor: 'pointer',\n fontSize: '13px',\n fontWeight: 500,\n minWidth: '80px',\n }}\n >\n {copySuccess ? 'Copied!' : 'Copy'}\n </button>\n </div>\n </section>\n\n {/* Add People Section */}\n <section aria-labelledby=\"share-invite-heading\">\n <h3\n id=\"share-invite-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n Invite people\n </h3>\n <div style={{ display: 'flex', gap: '8px', marginBottom: '8px', flexWrap: 'wrap' }}>\n <div style={{ flex: 1, position: 'relative' }}>\n <input\n type=\"text\"\n value={selectedRecipient ? selectedRecipient.name : searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setSelectedRecipient(null);\n }}\n placeholder=\"Search by name or email...\"\n style={{\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n />\n {/* Search Results Dropdown */}\n {searchQuery && !selectedRecipient && searchResults.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 0,\n right: 0,\n marginTop: '4px',\n backgroundColor: colors.bgPrimary,\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n boxShadow: 'var(--shadow-elevation-3)',\n zIndex: 100001,\n maxHeight: '200px',\n overflowY: 'auto',\n }}\n >\n {searchResults.map((result) => (\n <button\n key={result.id}\n onClick={() => setSelectedRecipient(result)}\n style={{\n display: 'block',\n width: '100%',\n padding: '10px 12px',\n textAlign: 'left',\n backgroundColor: 'transparent',\n border: 'none',\n cursor: 'pointer',\n borderBottom: `1px solid ${colors.borderDefault}`,\n }}\n >\n <div style={{ fontSize: '13px', fontWeight: 500, color: colors.textPrimary }}>\n {result.name}\n </div>\n {result.email && (\n <div style={{ fontSize: '11px', color: colors.textSecondary }}>{result.email}</div>\n )}\n <span\n style={{\n fontSize: '10px',\n color: colors.textTertiary,\n textTransform: 'uppercase',\n }}\n >\n {result.type}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n <select\n value={selectedPermission}\n onChange={(e) => setSelectedPermission(e.target.value as SharePermission)}\n style={{\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n >\n {PERMISSION_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <button\n onClick={handleShare}\n disabled={!selectedRecipient || loading}\n style={{\n padding: '10px 16px',\n backgroundColor: selectedRecipient ? colors.actionPrimaryBg : colors.bgTertiary,\n color: selectedRecipient ? 'var(--color-action-primary-text)' : colors.textSecondary,\n border: 'none',\n borderRadius: '6px',\n cursor: selectedRecipient ? 'pointer' : 'not-allowed',\n fontSize: '13px',\n fontWeight: 500,\n }}\n >\n Share\n </button>\n </div>\n </section>\n\n {/* Current Shares */}\n <section aria-labelledby=\"share-access-heading\">\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: '8px',\n marginBottom: '12px',\n }}\n >\n <h3 id=\"share-access-heading\" className=\"text-sm font-semibold\" style={{ color: colors.textPrimary }}>\n People with access ({shares.length})\n </h3>\n {shares.length > 0 && (\n <button\n onClick={handleRevokeAll}\n style={{\n padding: '4px 8px',\n backgroundColor: 'transparent',\n color: colors.statusErrorText,\n border: 'none',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n Revoke All\n </button>\n )}\n </div>\n\n {loading && shares.length === 0 ? (\n <p style={{ color: colors.textSecondary, fontSize: '13px' }}>Loading...</p>\n ) : shares.length === 0 ? (\n <p style={{ color: colors.textSecondary, fontSize: '13px' }}>\n No one has access to this dashboard yet.\n </p>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>\n {shares.map((share) => (\n <ShareItem\n key={share.id}\n share={share}\n onUpdatePermission={handleUpdatePermission}\n onRevoke={handleRevoke}\n colors={colors}\n />\n ))}\n </div>\n )}\n </section>\n </div>\n )}\n </div>\n </div>\n );\n\n // Render via portal to document.body to escape any stacking context issues\n return createPortal(dialogContent, document.body);\n}\n\n// ============================================================================\n// ShareItem Sub-component\n// ============================================================================\n\ninterface ShareItemProps {\n share: DashboardShare;\n onUpdatePermission: (shareId: string, permission: SharePermission) => void;\n onRevoke: (shareId: string) => void;\n colors: ReturnType<typeof getColors>;\n}\n\n/**\n * Access window for one share.\n *\n * `expiresAt` is selected by DashboardShareFields on every share this dialog\n * loads, but the row never said whether the access it lists ends — so a share\n * that lapsed yesterday looked identical to a permanent one. Expired shares are\n * called out explicitly: the backend leaves the row in place, so \"expired\" is a\n * state the reader has to be told about, not one they can infer from absence.\n */\nfunction describeExpiry(expiresAt: string | undefined): { label: string; expired: boolean } {\n if (!expiresAt) return { label: 'No expiry', expired: false };\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) return { label: 'No expiry', expired: false };\n if (date.getTime() <= Date.now()) {\n return { label: `Expired ${formatDistanceToNow(date, { addSuffix: true })}`, expired: true };\n }\n return { label: `Expires ${formatDistanceToNow(date, { addSuffix: true })}`, expired: false };\n}\n\nfunction ShareItem({ share, onUpdatePermission, onRevoke, colors }: ShareItemProps) {\n const expiry = describeExpiry(share.expiresAt);\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '10px 12px',\n backgroundColor: colors.bgSecondary,\n borderRadius: '6px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>\n <div\n style={{\n width: '36px',\n height: '36px',\n borderRadius: '50%',\n backgroundColor: colors.actionPrimaryBg,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n color: 'var(--color-action-primary-text)',\n fontWeight: 600,\n fontSize: '14px',\n }}\n >\n {share.recipientType === 'USER' ? 'U' : 'R'}\n </div>\n <div>\n <div style={{ fontSize: '13px', fontWeight: 500, color: colors.textPrimary }}>{share.recipientId}</div>\n <div style={{ fontSize: '11px', color: colors.textSecondary }}>\n {share.recipientType} - Shared {new Date(share.createdAt).toLocaleDateString()}\n </div>\n </div>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>\n <span\n data-testid=\"share-expiry\"\n title={share.expiresAt ? new Date(share.expiresAt).toLocaleString() : 'Access does not expire'}\n style={{\n fontSize: '11px',\n padding: '4px 8px',\n borderRadius: '999px',\n whiteSpace: 'nowrap',\n backgroundColor: colors.bgTertiary,\n color: expiry.expired ? colors.statusErrorText : colors.textSecondary,\n fontWeight: expiry.expired ? 600 : 400,\n }}\n >\n {expiry.label}\n </span>\n <select\n value={share.permission}\n onChange={(e) => onUpdatePermission(share.id, e.target.value as SharePermission)}\n style={{\n padding: '6px 10px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '4px',\n fontSize: '12px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n >\n {PERMISSION_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <button\n onClick={() => onRevoke(share.id)}\n style={{\n padding: '6px 10px',\n backgroundColor: 'transparent',\n color: colors.statusErrorText,\n border: `1px solid ${colors.statusErrorText}`,\n borderRadius: '4px',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n Revoke\n </button>\n </div>\n </div>\n );\n}\n\nexport default ShareDialog;\n"],"mappings":"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"}
1
+ {"version":3,"file":"ShareDialog.js","names":[],"sources":["../../../../../src/bigconsole/components/dashboard/ShareDialog/ShareDialog.tsx"],"sourcesContent":["/**\n * ShareDialog Component\n *\n * Dialog for sharing dashboards with users and roles.\n */\n\nimport React, { useState, useEffect, useCallback, useRef } from 'react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n} from '@burdenoff/fe-libs/ui';\nimport { formatDistanceToNow } from 'date-fns';\nimport { useDashboardSharing, type DashboardShare, type SharePermission } from '../../../hooks';\nimport type { DashboardVisibility } from '../../../types';\nimport { useBigConsoleAnalytics } from '../../../hooks/useBigConsoleAnalytics';\nimport { EmbedTab } from './EmbedTab';\nimport { useShareRecipients, type ShareRecipient } from '../../../hooks/useShareRecipients';\nimport { useBigConsole, useBigConsoleBasePath } from '../../../context/BigConsoleContext';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// Semantic variables follow shell themes even though the dialog is portalled.\nconst colors = {\n bgPrimary: 'var(--color-bg-surface)',\n bgSecondary: 'var(--color-bg-sunken)',\n bgTertiary: 'var(--color-bg-canvas)',\n textPrimary: 'var(--color-text-primary)',\n textSecondary: 'var(--color-text-secondary)',\n textTertiary: 'var(--color-text-muted)',\n borderDefault: 'var(--color-border-default)',\n actionPrimaryBg: 'var(--color-action-primary-bg)',\n actionPrimaryBgHover: 'var(--color-action-primary-bgHover)',\n statusSuccessBg: 'var(--color-status-success-bg)',\n statusErrorText: 'var(--color-status-error-text)',\n};\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ShareDialogProps {\n dashboardId: string;\n dashboardName: string;\n isOpen: boolean;\n onClose: () => void;\n /**\n * Current general-access level (`Dashboard.visibility`). The General access\n * control renders ONLY when this and `onVisibilityChange` are both supplied —\n * a selector with no known current value would be a guess, and one with no\n * handler would be a switch wired to nothing.\n */\n visibility?: DashboardVisibility;\n /** Persist a new general-access level. Resolves once the write completes. */\n onVisibilityChange?: (visibility: DashboardVisibility) => Promise<void>;\n /** False when the actor lacks `bigconsole-dashboard:update`; the control is then read-only. */\n canChangeVisibility?: boolean;\n}\n\n/** General-access levels, in widening order. */\nconst VISIBILITY_OPTIONS: { value: DashboardVisibility; label: string; description: string }[] = [\n { value: 'PRIVATE', label: 'Private', description: 'Only people added below' },\n { value: 'WORKSPACE', label: 'Workspace', description: 'Anyone in this workspace' },\n { value: 'PUBLIC', label: 'Public', description: 'Anyone with the link' },\n];\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Permission options as per spec (03-graphql-contract.md)\n * Spec values: view, edit\n */\nconst PERMISSION_OPTIONS: { value: SharePermission; label: string; description: string }[] = [\n { value: 'VIEW', label: 'View', description: 'Can view the dashboard' },\n { value: 'EDIT', label: 'Edit', description: 'Can edit widgets, layout, and share' },\n];\n\n// Use shared semantic tokens for portal rendering\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport function ShareDialog(props: ShareDialogProps) {\n const { workspaceId, currentUser } = useBigConsole();\n const returnFocusRef = useRef<HTMLElement | null>(null);\n // Remount sharing state at the authorization boundary, including in-flight ACL reads.\n // The existing sharing hook belongs to a single actor/workspace/dashboard.\n return (\n <ShareDialogContent\n key={`${currentUser?.id ?? ''}|${workspaceId ?? ''}|${props.dashboardId}`}\n {...props}\n returnFocusRef={returnFocusRef}\n />\n );\n}\n\nfunction ShareDialogContent({\n dashboardId,\n dashboardName,\n isOpen,\n onClose,\n visibility,\n onVisibilityChange,\n canChangeVisibility = true,\n returnFocusRef,\n}: ShareDialogProps & { returnFocusRef: React.RefObject<HTMLElement | null> }) {\n const { t } = useI18n();\n const {\n shares,\n loading,\n error: sharingError,\n fetchShares,\n shareWithUser,\n shareWithGroup,\n updateShare,\n revokeShare,\n revokeAllShares,\n } = useDashboardSharing();\n const emitBigConsoleEvent = useBigConsoleAnalytics();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedRecipient, setSelectedRecipient] = useState<ShareRecipient | null>(null);\n const [selectedPermission, setSelectedPermission] = useState<SharePermission>('VIEW');\n const [shareLink, setShareLink] = useState('');\n const [copySuccess, setCopySuccess] = useState(false);\n const [activeTab, setActiveTab] = useState<'people' | 'embed'>('people');\n const [visibilitySaving, setVisibilitySaving] = useState(false);\n const [visibilityError, setVisibilityError] = useState<string | null>(null);\n const [shareError, setShareError] = useState<string | null>(null);\n const directory = useShareRecipients(isOpen, searchQuery);\n const basePath = useBigConsoleBasePath();\n useEffect(() => {\n let cancelled = false;\n void Promise.resolve().then(() => {\n if (cancelled) return;\n setSelectedRecipient(null);\n setSearchQuery('');\n setShareError(null);\n });\n return () => {\n cancelled = true;\n };\n }, [directory.owner, dashboardId, isOpen]);\n\n // Fetch shares when dialog opens\n useEffect(() => {\n if (isOpen && dashboardId) {\n fetchShares(dashboardId);\n // Generate share link\n const url = new URL(`${basePath}/dashboards/${encodeURIComponent(dashboardId)}`, window.location.origin);\n const context = new URLSearchParams(window.location.search);\n for (const key of ['org', 'workspace', 'project', 'tenant']) {\n const value = context.get(key);\n if (value) url.searchParams.set(key, value);\n }\n // The selected workspace remains authoritative when host navigation omits context parameters.\n if (directory.workspaceId) url.searchParams.set('workspace', directory.workspaceId);\n setShareLink(url.toString());\n }\n }, [isOpen, dashboardId, fetchShares, basePath, directory.workspaceId]);\n\n // Handle share - per spec: only USER and GROUP recipient types\n const handleShare = useCallback(async () => {\n if (!selectedRecipient || loading || selectedRecipient.workspaceId !== directory.workspaceId) return;\n setShareError(null);\n\n emitBigConsoleEvent('bigconsole.dashboard.share_clicked', {\n dashboardId,\n permission: selectedPermission,\n recipientType: selectedRecipient.type,\n });\n\n let share: DashboardShare | null = null;\n if (selectedRecipient.type === 'USER') {\n share = await shareWithUser(dashboardId, selectedRecipient.id, selectedPermission);\n } else if (selectedRecipient.type === 'GROUP') {\n share = await shareWithGroup(dashboardId, selectedRecipient.id, selectedPermission);\n }\n\n if (share) {\n emitBigConsoleEvent('bigconsole.dashboard.shared', {\n dashboardId,\n permission: selectedPermission,\n recipientType: selectedRecipient.type,\n });\n }\n\n if (!share) {\n setShareError(\n t('bigconsole.share.error.grantFailed', 'Access could not be granted. Review the recipient and try again.')\n );\n return;\n }\n setSelectedRecipient(null);\n setSearchQuery('');\n }, [\n directory.workspaceId,\n t,\n loading,\n dashboardId,\n emitBigConsoleEvent,\n selectedRecipient,\n selectedPermission,\n shareWithGroup,\n shareWithUser,\n ]);\n\n // Handle update permission\n const handleUpdatePermission = useCallback(\n async (shareId: string, newPermission: SharePermission) => {\n await updateShare({ id: shareId, permission: newPermission });\n },\n [updateShare]\n );\n\n // Handle revoke\n const handleRevoke = useCallback(\n async (shareId: string) => {\n if (window.confirm('Are you sure you want to revoke this access?')) {\n await revokeShare(shareId);\n }\n },\n [revokeShare]\n );\n\n // Handle revoke all\n const handleRevokeAll = useCallback(async () => {\n if (window.confirm('Are you sure you want to revoke all shared access?')) {\n await revokeAllShares(dashboardId);\n }\n }, [dashboardId, revokeAllShares]);\n\n const handleVisibilityChange = useCallback(\n async (next: DashboardVisibility) => {\n if (!onVisibilityChange || next === visibility) return;\n setVisibilityError(null);\n setVisibilitySaving(true);\n try {\n await onVisibilityChange(next);\n } catch (err) {\n // Surface the failure: the select snaps back to `visibility`, which the\n // parent has not changed, so a silent catch would look like a success.\n setVisibilityError(err instanceof Error ? err.message : 'Could not update general access.');\n } finally {\n setVisibilitySaving(false);\n }\n },\n [onVisibilityChange, visibility]\n );\n\n // Copy link to clipboard\n const handleCopyLink = useCallback(async () => {\n try {\n await navigator.clipboard.writeText(shareLink);\n setCopySuccess(true);\n setTimeout(() => setCopySuccess(false), 2000);\n } catch {\n setShareError(\n t('bigconsole.share.error.copyFailed', 'The link could not be copied. Select the link and copy it manually.')\n );\n }\n }, [shareLink, t]);\n\n const searchResults = directory.results;\n\n if (!isOpen) return null;\n\n return (\n <ResponsiveDialog\n open={isOpen}\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <ResponsiveDialogContent\n showCloseButton={false}\n data-testid=\"share-dialog\"\n className=\"flex w-full flex-col gap-0 overflow-hidden rounded-card p-0 sm:w-[calc(100%-2rem)] sm:max-w-[520px]\"\n onOpenAutoFocus={() => {\n const active = document.activeElement;\n // ResponsiveDialog swaps implementations at the breakpoint while open.\n // Keep the original external trigger rather than a control in the old dialog.\n if (active instanceof HTMLElement && active !== document.body && !active.closest('[role=dialog]')) {\n returnFocusRef.current = active;\n }\n }}\n onCloseAutoFocus={(event) => {\n event.preventDefault();\n if (returnFocusRef.current?.isConnected) returnFocusRef.current.focus();\n }}\n style={{ maxWidth: '520px', maxHeight: '85dvh', backgroundColor: colors.bgPrimary, color: colors.textPrimary }}\n >\n {/* Header */}\n <div\n style={{\n flexShrink: 0,\n padding: '20px 24px',\n borderBottom: `1px solid ${colors.borderDefault}`,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n backgroundColor: colors.bgPrimary,\n }}\n >\n <div>\n <ResponsiveDialogTitle style={{ margin: 0, fontSize: '18px', fontWeight: 600, color: colors.textPrimary }}>\n Share Dashboard\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription style={{ margin: '4px 0 0', fontSize: '13px', color: colors.textSecondary }}>\n {dashboardName}\n </ResponsiveDialogDescription>\n </div>\n <button\n type=\"button\"\n aria-label={t('bigconsole.share.closeLabel', 'Close share dialog')}\n onClick={onClose}\n style={{\n background: 'none',\n border: 'none',\n fontSize: '24px',\n cursor: 'pointer',\n color: colors.textSecondary,\n padding: '4px',\n lineHeight: 1,\n }}\n >\n ×\n </button>\n </div>\n\n {/* Tabs */}\n <div\n role=\"tablist\"\n aria-label=\"Share options\"\n style={{\n flexShrink: 0,\n display: 'flex',\n gap: '4px',\n padding: '0 24px',\n borderBottom: `1px solid ${colors.borderDefault}`,\n backgroundColor: colors.bgPrimary,\n }}\n >\n {(['people', 'embed'] as const).map((tab) => (\n <button\n key={tab}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === tab}\n onClick={() => setActiveTab(tab)}\n style={{\n padding: '10px 12px',\n background: 'none',\n border: 'none',\n borderBottom: `2px solid ${activeTab === tab ? colors.actionPrimaryBg : 'transparent'}`,\n color: activeTab === tab ? colors.textPrimary : colors.textSecondary,\n fontSize: '13px',\n fontWeight: 600,\n cursor: 'pointer',\n }}\n >\n {tab === 'people' ? 'People' : 'Publish to web'}\n </button>\n ))}\n </div>\n\n {/* Publish-to-web tab */}\n {activeTab === 'embed' && (\n <div className=\"min-h-0 flex-1 overflow-y-auto\" style={{ backgroundColor: colors.bgPrimary }}>\n <EmbedTab dashboardId={dashboardId} colors={colors} />\n </div>\n )}\n\n {/* People tab (existing content) */}\n {activeTab === 'people' && (\n <div\n className=\"flex min-h-0 flex-1 flex-col gap-section-gap overflow-y-auto p-card-padding\"\n style={{ backgroundColor: colors.bgPrimary }}\n >\n {/* General access — WHO the link works for. It leads because the\n link below is only as useful as this setting allows: copying a\n URL from a PRIVATE board hands over something the recipient\n cannot open. Backed by `Dashboard.visibility` +\n `BigConsoleUpdateDashboardInput.visibility`. */}\n {visibility && onVisibilityChange && (\n <section aria-labelledby=\"share-visibility-heading\">\n <h3\n id=\"share-visibility-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n General access\n </h3>\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>\n <select\n aria-label=\"General access\"\n data-testid=\"share-visibility\"\n value={visibility}\n disabled={!canChangeVisibility || visibilitySaving}\n onChange={(e) => void handleVisibilityChange(e.target.value as DashboardVisibility)}\n style={{\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n cursor: !canChangeVisibility || visibilitySaving ? 'not-allowed' : 'pointer',\n opacity: !canChangeVisibility || visibilitySaving ? 0.6 : 1,\n }}\n >\n {VISIBILITY_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <span style={{ fontSize: '12px', color: colors.textSecondary }}>\n {VISIBILITY_OPTIONS.find((opt) => opt.value === visibility)?.description}\n </span>\n </div>\n {!canChangeVisibility && (\n <p style={{ margin: '6px 0 0', fontSize: '11px', color: colors.textSecondary }}>\n You don&apos;t have permission to change general access.\n </p>\n )}\n {visibilityError && (\n <p style={{ margin: '6px 0 0', fontSize: '11px', color: colors.statusErrorText }}>\n {visibilityError}\n </p>\n )}\n </section>\n )}\n\n {/* Share Link Section — the fastest way to share, so it leads */}\n <section aria-labelledby=\"share-link-heading\">\n <h3\n id=\"share-link-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n Share link\n </h3>\n <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>\n <input\n type=\"text\"\n value={shareLink}\n readOnly\n style={{\n flex: 1,\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgSecondary,\n color: colors.textPrimary,\n }}\n />\n <button\n onClick={handleCopyLink}\n style={{\n padding: '10px 16px',\n backgroundColor: copySuccess ? colors.statusSuccessBg : colors.actionPrimaryBg,\n color: 'var(--color-action-primary-text)',\n border: 'none',\n borderRadius: '6px',\n cursor: 'pointer',\n fontSize: '13px',\n fontWeight: 500,\n minWidth: '80px',\n }}\n >\n {copySuccess ? 'Copied!' : 'Copy'}\n </button>\n </div>\n </section>\n\n {(sharingError || shareError) && (\n <p role=\"alert\" style={{ color: colors.statusErrorText }}>\n {shareError ||\n t('bigconsole.share.error.loadFailed', 'Dashboard access could not be loaded or updated. Try again.')}\n </p>\n )}\n {/* Add People Section */}\n <section aria-labelledby=\"share-invite-heading\">\n <h3\n id=\"share-invite-heading\"\n className=\"mb-form-gap text-sm font-semibold\"\n style={{ color: colors.textPrimary }}\n >\n Invite people\n </h3>\n <p style={{ color: colors.textSecondary, fontSize: '13px', marginBottom: '8px' }}>\n {t(\n 'bigconsole.share.directory.scopeHint',\n 'Only members and groups from the current workspace are listed.'\n )}\n </p>\n {directory.loading && (\n <p role=\"status\">{t('bigconsole.share.directory.loading', 'Loading workspace recipients…')}</p>\n )}\n {directory.error && (\n <div role=\"alert\">\n <p>\n {t(\n 'bigconsole.share.directory.loadFailed',\n 'The workspace directory could not be loaded. Your access may not allow directory lookup.'\n )}\n </p>\n <button type=\"button\" onClick={directory.retry}>\n {t('bigconsole.share.directory.retry', 'Retry directory')}\n </button>\n </div>\n )}\n {searchQuery &&\n !selectedRecipient &&\n !directory.loading &&\n !directory.error &&\n searchResults.length === 0 && (\n <p role=\"status\">\n {t('bigconsole.share.directory.empty', 'No matching workspace members or groups.')}\n </p>\n )}\n {directory.hasMoreMembers && (\n <button type=\"button\" disabled={directory.loading} onClick={directory.loadMoreMembers}>\n {t('bigconsole.share.directory.loadMore', 'Load more workspace members')}\n </button>\n )}\n {directory.hasMoreGroups && (\n <p>\n {t(\n 'bigconsole.share.directory.moreGroups',\n 'More groups match. Narrow your search to find a specific group.'\n )}\n </p>\n )}\n <div style={{ display: 'flex', gap: '8px', marginBottom: '8px', flexWrap: 'wrap' }}>\n <div style={{ flex: 1, position: 'relative' }}>\n <input\n type=\"text\"\n value={selectedRecipient ? selectedRecipient.name : searchQuery}\n onChange={(e) => {\n setSearchQuery(e.target.value);\n setSelectedRecipient(null);\n }}\n aria-label={t('bigconsole.share.directory.searchLabel', 'Search workspace members and groups')}\n placeholder={t(\n 'bigconsole.share.directory.searchPlaceholder',\n 'Search workspace members or groups...'\n )}\n style={{\n width: '100%',\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n />\n {/* Search Results Dropdown */}\n {searchQuery && !selectedRecipient && searchResults.length > 0 && (\n <div\n style={{\n position: 'absolute',\n top: '100%',\n left: 0,\n right: 0,\n marginTop: '4px',\n backgroundColor: colors.bgPrimary,\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n boxShadow: 'var(--shadow-elevation-3)',\n zIndex: 100001,\n maxHeight: '200px',\n overflowY: 'auto',\n }}\n >\n {searchResults.map((result) => (\n <button\n key={`${result.type}:${result.id}`}\n type=\"button\"\n onClick={() => setSelectedRecipient(result)}\n style={{\n display: 'block',\n width: '100%',\n padding: '10px 12px',\n textAlign: 'left',\n backgroundColor: 'transparent',\n border: 'none',\n cursor: 'pointer',\n borderBottom: `1px solid ${colors.borderDefault}`,\n }}\n >\n <div style={{ fontSize: '13px', fontWeight: 500, color: colors.textPrimary }}>\n {result.name}\n </div>\n {result.email && (\n <div style={{ fontSize: '11px', color: colors.textSecondary }}>{result.email}</div>\n )}\n <span\n style={{\n fontSize: '10px',\n color: colors.textTertiary,\n textTransform: 'uppercase',\n }}\n >\n {result.type}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n <select\n aria-label={t('bigconsole.share.permissionLabel', 'Access permission')}\n value={selectedPermission}\n onChange={(e) => setSelectedPermission(e.target.value as SharePermission)}\n style={{\n padding: '10px 12px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '6px',\n fontSize: '13px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n >\n {PERMISSION_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <button\n onClick={handleShare}\n disabled={!selectedRecipient || loading || selectedRecipient.workspaceId !== directory.workspaceId}\n style={{\n padding: '10px 16px',\n backgroundColor: selectedRecipient ? colors.actionPrimaryBg : colors.bgTertiary,\n color: selectedRecipient ? 'var(--color-action-primary-text)' : colors.textSecondary,\n border: 'none',\n borderRadius: '6px',\n cursor: selectedRecipient ? 'pointer' : 'not-allowed',\n fontSize: '13px',\n fontWeight: 500,\n }}\n >\n Share\n </button>\n </div>\n </section>\n\n {/* Current Shares */}\n <section aria-labelledby=\"share-access-heading\">\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n flexWrap: 'wrap',\n gap: '8px',\n marginBottom: '12px',\n }}\n >\n <h3 id=\"share-access-heading\" className=\"text-sm font-semibold\" style={{ color: colors.textPrimary }}>\n People with access ({shares.length})\n </h3>\n {shares.length > 0 && (\n <button\n onClick={handleRevokeAll}\n style={{\n padding: '4px 8px',\n backgroundColor: 'transparent',\n color: colors.statusErrorText,\n border: 'none',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n Revoke All\n </button>\n )}\n </div>\n\n {loading && shares.length === 0 ? (\n <p style={{ color: colors.textSecondary, fontSize: '13px' }}>Loading...</p>\n ) : shares.length === 0 ? (\n <p style={{ color: colors.textSecondary, fontSize: '13px' }}>\n No one has access to this dashboard yet.\n </p>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>\n {shares.map((share) => (\n <ShareItem\n key={share.id}\n share={share}\n onUpdatePermission={handleUpdatePermission}\n onRevoke={handleRevoke}\n colors={colors}\n />\n ))}\n </div>\n )}\n </section>\n </div>\n )}\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ============================================================================\n// ShareItem Sub-component\n// ============================================================================\n\ninterface ShareItemProps {\n share: DashboardShare;\n onUpdatePermission: (shareId: string, permission: SharePermission) => void;\n onRevoke: (shareId: string) => void;\n colors: typeof colors;\n}\n\n/**\n * Access window for one share.\n *\n * `expiresAt` is selected by DashboardShareFields on every share this dialog\n * loads, but the row never said whether the access it lists ends — so a share\n * that lapsed yesterday looked identical to a permanent one. Expired shares are\n * called out explicitly: the backend leaves the row in place, so \"expired\" is a\n * state the reader has to be told about, not one they can infer from absence.\n */\nfunction describeExpiry(expiresAt: string | undefined): { label: string; expired: boolean } {\n if (!expiresAt) return { label: 'No expiry', expired: false };\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) return { label: 'No expiry', expired: false };\n if (date.getTime() <= Date.now()) {\n return { label: `Expired ${formatDistanceToNow(date, { addSuffix: true })}`, expired: true };\n }\n return { label: `Expires ${formatDistanceToNow(date, { addSuffix: true })}`, expired: false };\n}\n\nfunction ShareItem({ share, onUpdatePermission, onRevoke, colors }: ShareItemProps) {\n const expiry = describeExpiry(share.expiresAt);\n\n return (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n padding: '10px 12px',\n backgroundColor: colors.bgSecondary,\n borderRadius: '6px',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>\n <div\n style={{\n width: '36px',\n height: '36px',\n borderRadius: '50%',\n backgroundColor: colors.actionPrimaryBg,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n color: 'var(--color-action-primary-text)',\n fontWeight: 600,\n fontSize: '14px',\n }}\n >\n {share.recipientType === 'USER' ? 'U' : 'R'}\n </div>\n <div>\n <div style={{ fontSize: '13px', fontWeight: 500, color: colors.textPrimary }}>{share.recipientId}</div>\n <div style={{ fontSize: '11px', color: colors.textSecondary }}>\n {share.recipientType} - Shared {new Date(share.createdAt).toLocaleDateString()}\n </div>\n </div>\n </div>\n <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' }}>\n <span\n data-testid=\"share-expiry\"\n title={share.expiresAt ? new Date(share.expiresAt).toLocaleString() : 'Access does not expire'}\n style={{\n fontSize: '11px',\n padding: '4px 8px',\n borderRadius: '999px',\n whiteSpace: 'nowrap',\n backgroundColor: colors.bgTertiary,\n color: expiry.expired ? colors.statusErrorText : colors.textSecondary,\n fontWeight: expiry.expired ? 600 : 400,\n }}\n >\n {expiry.label}\n </span>\n <select\n value={share.permission}\n onChange={(e) => onUpdatePermission(share.id, e.target.value as SharePermission)}\n style={{\n padding: '6px 10px',\n border: `1px solid ${colors.borderDefault}`,\n borderRadius: '4px',\n fontSize: '12px',\n backgroundColor: colors.bgPrimary,\n color: colors.textPrimary,\n }}\n >\n {PERMISSION_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n <button\n onClick={() => onRevoke(share.id)}\n style={{\n padding: '6px 10px',\n backgroundColor: 'transparent',\n color: colors.statusErrorText,\n border: `1px solid ${colors.statusErrorText}`,\n borderRadius: '4px',\n cursor: 'pointer',\n fontSize: '12px',\n }}\n >\n Revoke\n </button>\n </div>\n </div>\n );\n}\n\nexport default ShareDialog;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CacheTTLSelector.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/datasink/CacheTTLSelector.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,UAAU,qBAAqB;IAC7B,uDAAuD;IACvD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,kCAAkC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAwBD,eAAO,MAAM,gBAAgB,6DAyV3B,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"CacheTTLSelector.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/datasink/CacheTTLSelector.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,UAAU,qBAAqB;IAC7B,uDAAuD;IACvD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,kCAAkC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAwBD,eAAO,MAAM,gBAAgB,6DAsW3B,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
@@ -3,6 +3,7 @@ import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
3
3
  import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
4
4
  //#region src/bigconsole/components/datasink/CacheTTLSelector.tsx
5
5
  var u = [
6
+ 0,
6
7
  300,
7
8
  900,
8
9
  3600,
@@ -13,6 +14,11 @@ function d(e) {
13
14
  }
14
15
  var f = e(function({ value: e, onChange: u, disabled: f = !1, error: p, hideLabel: m = !1 }) {
15
16
  let { t: h } = l(), g = r(() => [
17
+ {
18
+ value: 0,
19
+ label: h("bigconsole.dataSinks.cacheTtl.presetNoCacheLabel", "No cache"),
20
+ description: h("bigconsole.dataSinks.cacheTtl.presetNoCacheDescription", "Read current stored data without caching results")
21
+ },
16
22
  {
17
23
  value: 300,
18
24
  label: h("bigconsole.dataSinks.cacheTtl.preset5MinutesLabel", "5 minutes"),
@@ -235,7 +241,7 @@ var f = e(function({ value: e, onChange: u, disabled: f = !1, error: p, hideLabe
235
241
  strokeWidth: 2,
236
242
  d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
237
243
  })
238
- }), /* @__PURE__ */ s("span", { children: h("bigconsole.dataSinks.cacheTtl.infoCachedFor", "Data will be cached for {{duration}} before being refreshed", { duration: _(e) }) })]
244
+ }), /* @__PURE__ */ s("span", { children: e === 0 ? h("bigconsole.dataSinks.cacheTtl.presetNoCacheDescription", "Read current stored data without caching results") : h("bigconsole.dataSinks.cacheTtl.infoCachedFor", "Data will be cached for {{duration}} before being refreshed", { duration: _(e) }) })]
239
245
  })
240
246
  ]
241
247
  });
@@ -1 +1 @@
1
- {"version":3,"file":"CacheTTLSelector.js","names":[],"sources":["../../../../src/bigconsole/components/datasink/CacheTTLSelector.tsx"],"sourcesContent":["/**\n * Cache TTL Selector\n *\n * Component for selecting cache duration with preset options and custom input.\n */\n\nimport { memo, useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface CacheTTLOption {\n value: number;\n label: string;\n description: string;\n recommended?: boolean;\n}\n\ninterface CacheTTLSelectorProps {\n /** Current TTL value in seconds (null for no cache) */\n value: number | null;\n /** Callback when value changes */\n onChange: (value: number) => void;\n /** Whether selector is disabled */\n disabled?: boolean;\n /** Error message */\n error?: string;\n /**\n * Hide the built-in \"Cache Duration\" heading + description. Use when the\n * surrounding form already renders its own label for this control, to avoid a\n * duplicated heading.\n */\n hideLabel?: boolean;\n}\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst PRESET_VALUES: number[] = [300, 900, 3600, 86400];\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Check if value is a preset option\n */\nfunction isPresetValue(value: number | null): boolean {\n if (value === null) return false;\n return PRESET_VALUES.includes(value);\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const CacheTTLSelector = memo(function CacheTTLSelector({\n value,\n onChange,\n disabled = false,\n error,\n hideLabel = false,\n}: CacheTTLSelectorProps) {\n const { t } = useI18n();\n\n const PRESET_OPTIONS = useMemo<CacheTTLOption[]>(\n () => [\n {\n value: 300, // 5 minutes\n label: t('bigconsole.dataSinks.cacheTtl.preset5MinutesLabel', '5 minutes'),\n description: t('bigconsole.dataSinks.cacheTtl.preset5MinutesDescription', 'Best for real-time dashboards'),\n },\n {\n value: 900, // 15 minutes\n label: t('bigconsole.dataSinks.cacheTtl.preset15MinutesLabel', '15 minutes'),\n description: t('bigconsole.dataSinks.cacheTtl.preset15MinutesDescription', 'Balanced performance'),\n recommended: true,\n },\n {\n value: 3600, // 1 hour\n label: t('bigconsole.dataSinks.cacheTtl.preset1HourLabel', '1 hour'),\n description: t('bigconsole.dataSinks.cacheTtl.preset1HourDescription', 'For data that changes hourly'),\n },\n {\n value: 86400, // 24 hours\n label: t('bigconsole.dataSinks.cacheTtl.preset24HoursLabel', '24 hours'),\n description: t('bigconsole.dataSinks.cacheTtl.preset24HoursDescription', 'For daily reports'),\n },\n ],\n [t]\n );\n\n // Format seconds to human-readable string\n const formatDuration = useCallback(\n (seconds: number): string => {\n if (seconds < 60) {\n return t('bigconsole.dataSinks.cacheTtl.durationSeconds', '{{count}} seconds', { count: seconds });\n }\n if (seconds < 3600) {\n return t('bigconsole.dataSinks.cacheTtl.durationMinutes', '{{count}} minutes', {\n count: Math.floor(seconds / 60),\n });\n }\n if (seconds < 86400) {\n return t('bigconsole.dataSinks.cacheTtl.durationHours', '{{count}} hours', {\n count: Math.floor(seconds / 3600),\n });\n }\n return t('bigconsole.dataSinks.cacheTtl.durationDays', '{{count}} days', {\n count: Math.floor(seconds / 86400),\n });\n },\n [t]\n );\n\n // Track whether custom mode is active\n const [isCustom, setIsCustom] = useState(!isPresetValue(value) && value !== null);\n const [customValue, setCustomValue] = useState(value?.toString() || '');\n const [validationError, setValidationError] = useState<string | null>(null);\n const prevValueRef = useRef(value);\n\n // Sync custom value when external value changes (using deferred update to avoid cascading renders)\n useEffect(() => {\n if (prevValueRef.current !== value) {\n prevValueRef.current = value;\n if (value !== null && !isPresetValue(value)) {\n // Defer state update to avoid synchronous setState in effect\n queueMicrotask(() => {\n setCustomValue(value.toString());\n setIsCustom(true);\n });\n }\n }\n }, [value]);\n\n // Handle preset selection\n const handlePresetChange = useCallback(\n (presetValue: number) => {\n if (disabled) return;\n setIsCustom(false);\n onChange(presetValue);\n },\n [disabled, onChange]\n );\n\n // Handle custom mode activation\n const handleCustomSelect = useCallback(() => {\n if (disabled) return;\n setIsCustom(true);\n const parsed = parseInt(customValue);\n if (!isNaN(parsed) && parsed >= 30 && parsed <= 86400) {\n onChange(parsed);\n }\n }, [disabled, customValue, onChange]);\n\n // Handle custom value change with validation\n const handleCustomChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const inputValue = e.target.value;\n setCustomValue(inputValue);\n\n // Validate input\n if (inputValue === '') {\n setValidationError(null);\n return;\n }\n\n const parsed = parseInt(inputValue);\n\n if (isNaN(parsed)) {\n setValidationError(t('bigconsole.dataSinks.cacheTtl.errorInvalidNumber', 'Please enter a valid number'));\n return;\n }\n\n if (parsed < 30) {\n setValidationError(t('bigconsole.dataSinks.cacheTtl.errorMinDuration', 'Minimum cache duration is 30 seconds'));\n return;\n }\n\n if (parsed > 86400) {\n setValidationError(\n t('bigconsole.dataSinks.cacheTtl.errorMaxDuration', 'Maximum cache duration is 86400 seconds (24 hours)')\n );\n return;\n }\n\n // Valid value\n setValidationError(null);\n onChange(parsed);\n },\n [onChange, t]\n );\n\n return (\n <div className=\"space-y-form-gap\">\n {/* Label — suppressed with `hideLabel` when the surrounding form already\n renders one for this control, so the section never shows a duplicated\n heading. The `role=\"group\"` name below keeps the control set announced\n to assistive tech either way. */}\n {!hideLabel && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.dataSinks.cacheTtl.heading', 'Cache Duration')}\n </label>\n <p className=\"text-xs text-text-secondary\">\n {t('bigconsole.dataSinks.cacheTtl.headingDescription', 'How long should data be cached before refreshing?')}\n </p>\n </div>\n )}\n\n {/* Options */}\n <div\n role=\"group\"\n aria-label={t('bigconsole.dataSinks.cacheTtl.heading', 'Cache Duration')}\n className=\"space-y-list-gap\"\n >\n {/* Preset options */}\n {PRESET_OPTIONS.map((option) => (\n <label\n key={option.value}\n className={`\n flex cursor-pointer items-start gap-3\n rounded-input\n border p-3\n motion-safe:transition-colors\n ${disabled ? 'cursor-not-allowed opacity-50' : ''}\n ${\n !isCustom && value === option.value\n ? 'border-[var(--color-action-primary-bg)] bg-bg-accentSubtle'\n : 'border-[var(--color-border-subtle)] hover:border-[var(--color-border-strong)]'\n }\n `}\n >\n <input\n type=\"radio\"\n name=\"cache-ttl\"\n checked={!isCustom && value === option.value}\n onChange={() => handlePresetChange(option.value)}\n disabled={disabled}\n className={`\n mt-0.5\n h-4 w-4\n border-[var(--color-border-default)]\n text-action-primary-bg\n focus-visible:ring-2\n focus-visible:ring-ring\n `}\n />\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">{option.label}</span>\n {option.recommended && (\n <span className=\"px-1.5 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-action-primary-bg rounded\">\n {t('bigconsole.dataSinks.cacheTtl.recommendedBadge', 'Recommended')}\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary mt-0.5\">{option.description}</p>\n </div>\n </label>\n ))}\n\n {/* Custom option */}\n <label\n className={`\n flex cursor-pointer items-start gap-3\n rounded-input\n border p-3\n motion-safe:transition-colors\n ${disabled ? 'cursor-not-allowed opacity-50' : ''}\n ${\n isCustom\n ? 'border-[var(--color-action-primary-bg)] bg-bg-accentSubtle'\n : 'border-[var(--color-border-subtle)] hover:border-[var(--color-border-strong)]'\n }\n `}\n >\n <input\n type=\"radio\"\n name=\"cache-ttl\"\n checked={isCustom}\n onChange={handleCustomSelect}\n disabled={disabled}\n className={`\n mt-0.5\n h-4 w-4\n border-[var(--color-border-default)]\n text-action-primary-bg\n focus-visible:ring-2\n focus-visible:ring-ring\n `}\n />\n <div className=\"flex-1\">\n <span className=\"text-sm font-medium text-text-primary\">\n {t('bigconsole.dataSinks.cacheTtl.customLabel', 'Custom')}\n </span>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {t(\n 'bigconsole.dataSinks.cacheTtl.customDescription',\n 'Enter a custom cache duration (30 - 86400 seconds)'\n )}\n </p>\n {isCustom && (\n <>\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <input\n type=\"number\"\n aria-label=\"Custom cache duration in seconds\"\n value={customValue}\n onChange={handleCustomChange}\n disabled={disabled}\n min={30}\n max={86400}\n placeholder=\"300\"\n className={`\n h-control-md w-full min-w-0 rounded-input\n border bg-bg-sunken\n px-3\n text-sm text-text-primary\n focus-visible:outline-none\n focus-visible:ring-2\n focus-visible:ring-ring\n sm:w-32\n ${error || validationError ? 'border-[var(--color-status-error-border)]' : 'border-[var(--color-border-default)]'}\n `}\n />\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.cacheTtl.secondsUnit', 'seconds')}\n </span>\n {customValue && !isNaN(parseInt(customValue)) && !validationError && (\n <span className=\"text-xs text-text-muted\">({formatDuration(parseInt(customValue))})</span>\n )}\n </div>\n {validationError && (\n <div className=\"flex items-center gap-1 mt-1\">\n <svg\n className=\"w-3.5 h-3.5 text-status-error-text flex-shrink-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <span className=\"text-xs text-status-error-text\">{validationError}</span>\n </div>\n )}\n </>\n )}\n </div>\n </label>\n </div>\n\n {/* Error message */}\n {error && (\n <div className=\"flex items-start gap-2\">\n <svg\n className=\"w-4 h-4 text-status-error-text flex-shrink-0 mt-0.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <p className=\"text-xs text-status-error-text\">{error}</p>\n </div>\n )}\n\n {/* Info message */}\n {value !== null && !error && (\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <span>\n {t(\n 'bigconsole.dataSinks.cacheTtl.infoCachedFor',\n 'Data will be cached for {{duration}} before being refreshed',\n {\n duration: formatDuration(value),\n }\n )}\n </span>\n </div>\n )}\n </div>\n );\n});\n\nexport default CacheTTLSelector;\n"],"mappings":"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"}
1
+ {"version":3,"file":"CacheTTLSelector.js","names":[],"sources":["../../../../src/bigconsole/components/datasink/CacheTTLSelector.tsx"],"sourcesContent":["/**\n * Cache TTL Selector\n *\n * Component for selecting cache duration with preset options and custom input.\n */\n\nimport { memo, useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface CacheTTLOption {\n value: number;\n label: string;\n description: string;\n recommended?: boolean;\n}\n\ninterface CacheTTLSelectorProps {\n /** Current TTL value in seconds (null for no cache) */\n value: number | null;\n /** Callback when value changes */\n onChange: (value: number) => void;\n /** Whether selector is disabled */\n disabled?: boolean;\n /** Error message */\n error?: string;\n /**\n * Hide the built-in \"Cache Duration\" heading + description. Use when the\n * surrounding form already renders its own label for this control, to avoid a\n * duplicated heading.\n */\n hideLabel?: boolean;\n}\n\n// ============================================================================\n// CONSTANTS\n// ============================================================================\n\nconst PRESET_VALUES: number[] = [0, 300, 900, 3600, 86400];\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Check if value is a preset option\n */\nfunction isPresetValue(value: number | null): boolean {\n if (value === null) return false;\n return PRESET_VALUES.includes(value);\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const CacheTTLSelector = memo(function CacheTTLSelector({\n value,\n onChange,\n disabled = false,\n error,\n hideLabel = false,\n}: CacheTTLSelectorProps) {\n const { t } = useI18n();\n\n const PRESET_OPTIONS = useMemo<CacheTTLOption[]>(\n () => [\n {\n value: 0,\n label: t('bigconsole.dataSinks.cacheTtl.presetNoCacheLabel', 'No cache'),\n description: t(\n 'bigconsole.dataSinks.cacheTtl.presetNoCacheDescription',\n 'Read current stored data without caching results'\n ),\n },\n {\n value: 300, // 5 minutes\n label: t('bigconsole.dataSinks.cacheTtl.preset5MinutesLabel', '5 minutes'),\n description: t('bigconsole.dataSinks.cacheTtl.preset5MinutesDescription', 'Best for real-time dashboards'),\n },\n {\n value: 900, // 15 minutes\n label: t('bigconsole.dataSinks.cacheTtl.preset15MinutesLabel', '15 minutes'),\n description: t('bigconsole.dataSinks.cacheTtl.preset15MinutesDescription', 'Balanced performance'),\n recommended: true,\n },\n {\n value: 3600, // 1 hour\n label: t('bigconsole.dataSinks.cacheTtl.preset1HourLabel', '1 hour'),\n description: t('bigconsole.dataSinks.cacheTtl.preset1HourDescription', 'For data that changes hourly'),\n },\n {\n value: 86400, // 24 hours\n label: t('bigconsole.dataSinks.cacheTtl.preset24HoursLabel', '24 hours'),\n description: t('bigconsole.dataSinks.cacheTtl.preset24HoursDescription', 'For daily reports'),\n },\n ],\n [t]\n );\n\n // Format seconds to human-readable string\n const formatDuration = useCallback(\n (seconds: number): string => {\n if (seconds < 60) {\n return t('bigconsole.dataSinks.cacheTtl.durationSeconds', '{{count}} seconds', { count: seconds });\n }\n if (seconds < 3600) {\n return t('bigconsole.dataSinks.cacheTtl.durationMinutes', '{{count}} minutes', {\n count: Math.floor(seconds / 60),\n });\n }\n if (seconds < 86400) {\n return t('bigconsole.dataSinks.cacheTtl.durationHours', '{{count}} hours', {\n count: Math.floor(seconds / 3600),\n });\n }\n return t('bigconsole.dataSinks.cacheTtl.durationDays', '{{count}} days', {\n count: Math.floor(seconds / 86400),\n });\n },\n [t]\n );\n\n // Track whether custom mode is active\n const [isCustom, setIsCustom] = useState(!isPresetValue(value) && value !== null);\n const [customValue, setCustomValue] = useState(value?.toString() || '');\n const [validationError, setValidationError] = useState<string | null>(null);\n const prevValueRef = useRef(value);\n\n // Sync custom value when external value changes (using deferred update to avoid cascading renders)\n useEffect(() => {\n if (prevValueRef.current !== value) {\n prevValueRef.current = value;\n if (value !== null && !isPresetValue(value)) {\n // Defer state update to avoid synchronous setState in effect\n queueMicrotask(() => {\n setCustomValue(value.toString());\n setIsCustom(true);\n });\n }\n }\n }, [value]);\n\n // Handle preset selection\n const handlePresetChange = useCallback(\n (presetValue: number) => {\n if (disabled) return;\n setIsCustom(false);\n onChange(presetValue);\n },\n [disabled, onChange]\n );\n\n // Handle custom mode activation\n const handleCustomSelect = useCallback(() => {\n if (disabled) return;\n setIsCustom(true);\n const parsed = parseInt(customValue);\n if (!isNaN(parsed) && parsed >= 30 && parsed <= 86400) {\n onChange(parsed);\n }\n }, [disabled, customValue, onChange]);\n\n // Handle custom value change with validation\n const handleCustomChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const inputValue = e.target.value;\n setCustomValue(inputValue);\n\n // Validate input\n if (inputValue === '') {\n setValidationError(null);\n return;\n }\n\n const parsed = parseInt(inputValue);\n\n if (isNaN(parsed)) {\n setValidationError(t('bigconsole.dataSinks.cacheTtl.errorInvalidNumber', 'Please enter a valid number'));\n return;\n }\n\n if (parsed < 30) {\n setValidationError(t('bigconsole.dataSinks.cacheTtl.errorMinDuration', 'Minimum cache duration is 30 seconds'));\n return;\n }\n\n if (parsed > 86400) {\n setValidationError(\n t('bigconsole.dataSinks.cacheTtl.errorMaxDuration', 'Maximum cache duration is 86400 seconds (24 hours)')\n );\n return;\n }\n\n // Valid value\n setValidationError(null);\n onChange(parsed);\n },\n [onChange, t]\n );\n\n return (\n <div className=\"space-y-form-gap\">\n {/* Label — suppressed with `hideLabel` when the surrounding form already\n renders one for this control, so the section never shows a duplicated\n heading. The `role=\"group\"` name below keeps the control set announced\n to assistive tech either way. */}\n {!hideLabel && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {t('bigconsole.dataSinks.cacheTtl.heading', 'Cache Duration')}\n </label>\n <p className=\"text-xs text-text-secondary\">\n {t('bigconsole.dataSinks.cacheTtl.headingDescription', 'How long should data be cached before refreshing?')}\n </p>\n </div>\n )}\n\n {/* Options */}\n <div\n role=\"group\"\n aria-label={t('bigconsole.dataSinks.cacheTtl.heading', 'Cache Duration')}\n className=\"space-y-list-gap\"\n >\n {/* Preset options */}\n {PRESET_OPTIONS.map((option) => (\n <label\n key={option.value}\n className={`\n flex cursor-pointer items-start gap-3\n rounded-input\n border p-3\n motion-safe:transition-colors\n ${disabled ? 'cursor-not-allowed opacity-50' : ''}\n ${\n !isCustom && value === option.value\n ? 'border-[var(--color-action-primary-bg)] bg-bg-accentSubtle'\n : 'border-[var(--color-border-subtle)] hover:border-[var(--color-border-strong)]'\n }\n `}\n >\n <input\n type=\"radio\"\n name=\"cache-ttl\"\n checked={!isCustom && value === option.value}\n onChange={() => handlePresetChange(option.value)}\n disabled={disabled}\n className={`\n mt-0.5\n h-4 w-4\n border-[var(--color-border-default)]\n text-action-primary-bg\n focus-visible:ring-2\n focus-visible:ring-ring\n `}\n />\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">{option.label}</span>\n {option.recommended && (\n <span className=\"px-1.5 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-action-primary-bg rounded\">\n {t('bigconsole.dataSinks.cacheTtl.recommendedBadge', 'Recommended')}\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary mt-0.5\">{option.description}</p>\n </div>\n </label>\n ))}\n\n {/* Custom option */}\n <label\n className={`\n flex cursor-pointer items-start gap-3\n rounded-input\n border p-3\n motion-safe:transition-colors\n ${disabled ? 'cursor-not-allowed opacity-50' : ''}\n ${\n isCustom\n ? 'border-[var(--color-action-primary-bg)] bg-bg-accentSubtle'\n : 'border-[var(--color-border-subtle)] hover:border-[var(--color-border-strong)]'\n }\n `}\n >\n <input\n type=\"radio\"\n name=\"cache-ttl\"\n checked={isCustom}\n onChange={handleCustomSelect}\n disabled={disabled}\n className={`\n mt-0.5\n h-4 w-4\n border-[var(--color-border-default)]\n text-action-primary-bg\n focus-visible:ring-2\n focus-visible:ring-ring\n `}\n />\n <div className=\"flex-1\">\n <span className=\"text-sm font-medium text-text-primary\">\n {t('bigconsole.dataSinks.cacheTtl.customLabel', 'Custom')}\n </span>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {t(\n 'bigconsole.dataSinks.cacheTtl.customDescription',\n 'Enter a custom cache duration (30 - 86400 seconds)'\n )}\n </p>\n {isCustom && (\n <>\n <div className=\"mt-3 flex flex-wrap items-center gap-2\">\n <input\n type=\"number\"\n aria-label=\"Custom cache duration in seconds\"\n value={customValue}\n onChange={handleCustomChange}\n disabled={disabled}\n min={30}\n max={86400}\n placeholder=\"300\"\n className={`\n h-control-md w-full min-w-0 rounded-input\n border bg-bg-sunken\n px-3\n text-sm text-text-primary\n focus-visible:outline-none\n focus-visible:ring-2\n focus-visible:ring-ring\n sm:w-32\n ${error || validationError ? 'border-[var(--color-status-error-border)]' : 'border-[var(--color-border-default)]'}\n `}\n />\n <span className=\"text-sm text-text-secondary\">\n {t('bigconsole.dataSinks.cacheTtl.secondsUnit', 'seconds')}\n </span>\n {customValue && !isNaN(parseInt(customValue)) && !validationError && (\n <span className=\"text-xs text-text-muted\">({formatDuration(parseInt(customValue))})</span>\n )}\n </div>\n {validationError && (\n <div className=\"flex items-center gap-1 mt-1\">\n <svg\n className=\"w-3.5 h-3.5 text-status-error-text flex-shrink-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <span className=\"text-xs text-status-error-text\">{validationError}</span>\n </div>\n )}\n </>\n )}\n </div>\n </label>\n </div>\n\n {/* Error message */}\n {error && (\n <div className=\"flex items-start gap-2\">\n <svg\n className=\"w-4 h-4 text-status-error-text flex-shrink-0 mt-0.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <p className=\"text-xs text-status-error-text\">{error}</p>\n </div>\n )}\n\n {/* Info message */}\n {value !== null && !error && (\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <span>\n {value === 0\n ? t(\n 'bigconsole.dataSinks.cacheTtl.presetNoCacheDescription',\n 'Read current stored data without caching results'\n )\n : t(\n 'bigconsole.dataSinks.cacheTtl.infoCachedFor',\n 'Data will be cached for {{duration}} before being refreshed',\n {\n duration: formatDuration(value),\n }\n )}\n </span>\n </div>\n )}\n </div>\n );\n});\n\nexport default CacheTTLSelector;\n"],"mappings":"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"}
@@ -1,10 +1,12 @@
1
- import { ParserType, ParserStatus } from '../../types';
1
+ import { ParserType, ParserStatus, Parser } from '../../types';
2
2
 
3
3
  interface ParserSelectorProps {
4
4
  /** Console ID */
5
5
  consoleId: string;
6
6
  /** Currently selected parser ID */
7
7
  value: string | null;
8
+ /** Actual bound parser fetched by ID, even when list requests are still loading. */
9
+ resolvedParser?: Parser;
8
10
  /** Callback when selection changes */
9
11
  onChange: (id: string | null) => void;
10
12
  /** Filter by type */
@@ -1 +1 @@
1
- {"version":3,"file":"ParserSelector.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/parser/ParserSelector.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiB5D,UAAU,mBAAmB;IAC3B,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sCAAsC;IACtC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,qBAAqB;IACrB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,uBAAuB;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,mCAAmC;IACnC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kCAAkC;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAuGD,eAAO,MAAM,cAAc,2DAoUzB,CAAC;AAEH,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"ParserSelector.d.ts","sourceRoot":"","sources":["../../../../src/bigconsole/components/parser/ParserSelector.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAiBpE,UAAU,mBAAmB;IAC3B,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,oFAAoF;IACpF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sCAAsC;IACtC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,qBAAqB;IACrB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,uBAAuB;IACvB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,mCAAmC;IACnC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kCAAkC;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAuGD,eAAO,MAAM,cAAc,2DAsUzB,CAAC;AAEH,eAAe,cAAc,CAAC"}