@burdenoff/microfe-bigconsole 2026.812.1 → 2026.812.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/components/widgets/PropertiesPanel.js +480 -473
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetRegistry.js +4 -4
- package/dist/bigconsole/components/widgets/WidgetRegistry.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/CircularGauge.js +36 -32
- package/dist/bigconsole/components/widgets/gauge/CircularGauge.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js +60 -50
- package/dist/bigconsole/components/widgets/gauge/GaugeConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js +63 -34
- package/dist/bigconsole/components/widgets/gauge/GaugeWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/gauge/LinearGauge.js +46 -45
- package/dist/bigconsole/components/widgets/gauge/LinearGauge.js.map +1 -1
- package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js +152 -89
- package/dist/bigconsole/components/widgets/kpi-comparison/KPIComparisonWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js +5 -1
- package/dist/bigconsole/components/widgets/metric-card/MetricCardConfig.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js +112 -89
- package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js +21 -13
- package/dist/bigconsole/components/widgets/metric-card/SparklineChart.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/index.js +4 -4
- package/dist/bigconsole/components/widgets/progress/ProgressWidget.js +129 -114
- package/dist/bigconsole/components/widgets/progress/ProgressWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/utils/targetProgress.js +13 -0
- package/dist/bigconsole/components/widgets/utils/targetProgress.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PropertiesPanel.js","names":[],"sources":["../../../../src/bigconsole/components/widgets/PropertiesPanel.tsx"],"sourcesContent":["/**\n * PropertiesPanel Component\n *\n * Right-side panel for widget configuration.\n * v2.0: Added Actions tab for managing widget actions.\n * Uses fe-libs shared components for consistent styling.\n */\n\nimport React, { type FC, memo, useCallback, useState, useEffect, useRef, useMemo } from 'react';\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@burdenoff/fe-libs/ui';\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@burdenoff/fe-libs/ui';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Textarea } from '@burdenoff/fe-libs/ui';\nimport { Label } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent } from '@burdenoff/fe-libs/ui';\nimport { Plus, ChevronUp, ChevronDown, Pencil, Trash2, ExternalLink, Check, X, Info } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { Widget, DataSourceType, RendererType } from '../../types';\nimport { useWidgetStore } from '../../store';\nimport { useWidgetOperations, useDrilldownOperations } from '../../hooks';\nimport {\n useWidgetActions,\n type WidgetAction,\n type CreateWidgetActionInput,\n type UpdateWidgetActionInput,\n} from '../../hooks/useWidgetActions';\nimport { getWidgetDefinition } from './WidgetRegistry';\nimport { ActionConfigDialog, type ActionSaveData, type ActionWebhookDraft } from './ActionConfigDialog';\nimport { useEventWebhooks, type EventWebhookMapping } from '../../hooks/useEventWebhooks';\nimport { WebhookConfigPanel } from './webhook-config';\nimport { EventLogViewer } from './event-logs';\nimport { DrilldownConfigPanel, type DrilldownConfig } from './DrilldownConfigPanel';\n// Data Source selectors\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { useParserOperations } from '../../hooks/useParserOperations';\nimport { AdvancedSection, AppearanceSection, DataBindingSection, GeneralSection } from '../builder/inspector';\nimport { Section, SectionStack } from '../common';\nimport {\n getWidgetBinding,\n getWidgetBindingStatus,\n reconcilePanelConfig,\n serializeWidgetBinding,\n} from '../../utils/widgetBinding';\n\n// ============================================================================\n// v2.0: Renderer Options\n// ============================================================================\n\nconst RENDERER_OPTIONS: {\n value: RendererType;\n labelKey: string;\n label: string;\n descriptionKey: string;\n description: string;\n}[] = [\n {\n value: 'BIGCONSOLE',\n labelKey: 'bigconsole.widget.properties.renderer.bigconsole.label',\n label: 'BigConsole Native',\n descriptionKey: 'bigconsole.widget.properties.renderer.bigconsole.description',\n description: 'Full-featured native widgets',\n },\n {\n value: 'ADAPTIVE_CARD',\n labelKey: 'bigconsole.widget.properties.renderer.adaptiveCard.label',\n label: 'Adaptive Cards',\n descriptionKey: 'bigconsole.widget.properties.renderer.adaptiveCard.description',\n description: 'Microsoft Teams/Outlook format',\n },\n {\n value: 'BLOCKKIT',\n labelKey: 'bigconsole.widget.properties.renderer.blockKit.label',\n label: 'Block Kit',\n descriptionKey: 'bigconsole.widget.properties.renderer.blockKit.description',\n description: 'Slack format',\n },\n {\n value: 'CUSTOM',\n labelKey: 'bigconsole.widget.properties.renderer.custom.label',\n label: 'Custom',\n descriptionKey: 'bigconsole.widget.properties.renderer.custom.description',\n description: 'Custom React component',\n },\n];\n\n// ============================================================================\n// Data Source Constants\n// ============================================================================\n\nconst DATA_SOURCE_TYPES: {\n value: DataSourceType;\n labelKey: string;\n label: string;\n descriptionKey: string;\n description: string;\n}[] = [\n {\n value: 'DATASINK',\n labelKey: 'bigconsole.widget.properties.dataSourceType.datasink.label',\n label: 'DataSink',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.datasink.description',\n description: 'Use data from BigConsole DataSinks',\n },\n {\n value: 'REST_API',\n labelKey: 'bigconsole.widget.properties.dataSourceType.restApi.label',\n label: 'REST API',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.restApi.description',\n description: 'Fetch data from REST endpoints',\n },\n {\n value: 'GRAPHQL',\n labelKey: 'bigconsole.widget.properties.dataSourceType.graphql.label',\n label: 'GraphQL',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.graphql.description',\n description: 'Query data using GraphQL',\n },\n {\n value: 'SQL',\n labelKey: 'bigconsole.widget.properties.dataSourceType.sql.label',\n label: 'SQL Database',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.sql.description',\n description: 'Query SQL databases directly',\n },\n {\n value: 'WEBSOCKET',\n labelKey: 'bigconsole.widget.properties.dataSourceType.websocket.label',\n label: 'WebSocket',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.websocket.description',\n description: 'Real-time data streaming',\n },\n {\n value: 'MOCK',\n labelKey: 'bigconsole.widget.properties.dataSourceType.mock.label',\n label: 'Sample JSON',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.mock.description',\n description: 'Manually enter sample JSON data',\n },\n];\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PropertiesPanelProps {\n isOpen: boolean;\n onClose: () => void;\n}\n\ntype TabId = 'general' | 'data' | 'appearance' | 'advanced';\n\n// The event a button emits when clicked. MUST match DashboardGrid.handleWidgetAction\n// (`widget:${name.toLowerCase().replace(/\\s+/g,'_')}:clicked`) so a webhook mapping\n// created here actually fires for the click.\nfunction eventPatternForAction(actionName: string): string {\n return `widget:${actionName.toLowerCase().replace(/\\s+/g, '_')}:clicked`;\n}\n\n// Action style colours. `bg-action-primary` / `bg-muted-foreground` are not\n// registered in the BigConsole shell theme, so they emitted no CSS at all and\n// the status dot rendered invisible — see sectionStyles.ts for the failure mode.\nconst ACTION_STYLE_COLORS: Record<string, string> = {\n PRIMARY: 'bg-action-primary-bg',\n SECONDARY: 'bg-[var(--color-text-muted)]',\n SUCCESS: 'bg-status-success-bg',\n WARNING: 'bg-status-warning-bg',\n DANGER: 'bg-status-error-bg',\n};\n\nconst ACTION_STYLE_FALLBACK_COLOR = 'bg-[var(--color-text-muted)]';\n\n/** Tab triggers: body-sm type and a 40px touch target on every device. */\nconst TAB_TRIGGER_STYLE: React.CSSProperties = {\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: 'var(--font-size-body-sm)',\n padding: '6px 10px',\n minHeight: 'var(--size-controlHeight-md)',\n flexShrink: 0,\n};\n\n// ============================================================================\n// Config Component Map\n// ============================================================================\n\n/**\n * Detects field names from parser output and returns suggested labelField and valueField\n * @param output - The parser execution output\n * @returns Object with suggested labelField and valueField\n */\nfunction detectFieldsFromParserOutput(output: unknown): {\n labelField?: string;\n valueField?: string;\n xAxisField?: string;\n} {\n if (!output) return {};\n\n // If output is an array, look at the first item\n const sampleItem = Array.isArray(output) ? output[0] : output;\n\n if (!sampleItem || typeof sampleItem !== 'object') return {};\n\n const keys = Object.keys(sampleItem as Record<string, unknown>);\n\n // Common label field names (in order of priority)\n const labelFieldCandidates = [\n 'name',\n 'label',\n 'category',\n 'title',\n 'key',\n 'id',\n 'month',\n 'date',\n 'region',\n 'product',\n ];\n // Common value field names (in order of priority)\n const valueFieldCandidates = ['value', 'amount', 'count', 'total', 'revenue', 'sales', 'quantity', 'orders', 'sum'];\n\n let labelField: string | undefined;\n let valueField: string | undefined;\n\n // Find the best label field\n for (const candidate of labelFieldCandidates) {\n if (keys.includes(candidate)) {\n labelField = candidate;\n break;\n }\n }\n\n // Find the best value field\n for (const candidate of valueFieldCandidates) {\n if (keys.includes(candidate)) {\n valueField = candidate;\n break;\n }\n }\n\n // If no standard names found, use heuristics:\n // - First string field as label\n // - First number field as value\n if (!labelField || !valueField) {\n for (const key of keys) {\n const val = (sampleItem as Record<string, unknown>)[key];\n if (!labelField && typeof val === 'string') {\n labelField = key;\n }\n if (!valueField && typeof val === 'number') {\n valueField = key;\n }\n if (labelField && valueField) break;\n }\n }\n\n return {\n labelField,\n valueField,\n xAxisField: labelField, // For line/bar charts, xAxisField is usually the same as labelField\n };\n}\n\n// ============================================================================\n// Template Editor Component\n// ============================================================================\n\ninterface TemplateEditorProps {\n widget: Widget;\n onSave: (template: Record<string, unknown>) => void;\n}\n\nconst TemplateEditor: FC<TemplateEditorProps> = memo(function TemplateEditor({ widget, onSave }) {\n const { t } = useI18n();\n const [localValue, setLocalValue] = useState<string>('');\n const [jsonError, setJsonError] = useState<string | null>(null);\n const initializedRef = useRef(false);\n\n // Initialize from widget.template only once per widget\n useEffect(() => {\n try {\n const templateStr =\n widget.template && typeof widget.template === 'object' ? JSON.stringify(widget.template, null, 2) : '';\n setLocalValue(templateStr);\n setJsonError(null);\n initializedRef.current = true;\n } catch {\n setLocalValue('');\n }\n }, [widget.id, widget.template]);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const value = e.target.value;\n setLocalValue(value);\n\n if (value.trim() === '') {\n setJsonError(null);\n // Empty template is valid - notify parent\n onSave({});\n } else {\n try {\n const parsed = JSON.parse(value);\n setJsonError(null);\n // Valid JSON - immediately notify parent so main Save button becomes active\n onSave(parsed);\n } catch (err) {\n setJsonError((err as Error).message);\n // Don't call onSave for invalid JSON\n }\n }\n },\n [onSave]\n );\n\n const handleFormat = useCallback(() => {\n try {\n const parsed = JSON.parse(localValue);\n const formatted = JSON.stringify(parsed, null, 2);\n setLocalValue(formatted);\n setJsonError(null);\n onSave(parsed);\n } catch (err) {\n setJsonError((err as Error).message);\n }\n }, [localValue, onSave]);\n\n return (\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={4}\n title={t('bigconsole.widget.properties.template.label', 'Template (JSON)')}\n description={`The ${widget.renderer} payload this widget renders.`}\n tone={jsonError ? 'danger' : 'neutral'}\n meta={\n jsonError ? (\n <span className=\"text-xs font-medium text-[var(--color-status-error-text)]\">\n {t('bigconsole.widget.properties.template.invalidJson', 'Invalid JSON')}\n </span>\n ) : undefined\n }\n className=\"mt-form-gap\"\n data-testid=\"widget-inspector-template\"\n >\n <Textarea\n value={localValue}\n onChange={handleChange}\n spellCheck={false}\n placeholder={t('bigconsole.widget.properties.template.placeholder', 'Enter {{renderer}} template JSON...', {\n renderer: widget.renderer ?? '',\n })}\n style={{\n fontFamily: 'var(--font-family-mono)',\n fontSize: 'var(--font-size-body-sm)',\n minHeight: '180px',\n borderColor: jsonError ? 'var(--color-status-error-border)' : undefined,\n resize: 'vertical',\n }}\n />\n\n {jsonError && (\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n padding: 'var(--space-controlGap)',\n borderRadius: 'var(--radius-button)',\n marginTop: 'var(--space-controlGap)',\n }}\n >\n {jsonError}\n </p>\n )}\n\n <div className=\"mt-control-gap flex flex-wrap items-center gap-control-gap\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleFormat}\n disabled={!!jsonError || localValue.trim() === ''}\n style={{ height: 'var(--size-controlHeight-sm)', fontWeight: 'var(--font-weight-semibold)' }}\n >\n {t('bigconsole.widget.properties.template.format', 'Format')}\n </Button>\n {!jsonError && localValue.trim() !== '' && (\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-success-text)',\n display: 'flex',\n alignItems: 'center',\n gap: '4px',\n }}\n >\n <Check style={{ width: '12px', height: '12px' }} />\n {t('bigconsole.widget.properties.template.validJson', 'Valid JSON')}\n </span>\n )}\n </div>\n\n <div className=\"mt-control-gap flex flex-wrap items-center justify-between gap-x-4 gap-y-1 border-t border-[var(--color-border-subtle)] pt-control-gap text-xs text-text-muted\">\n <span className=\"font-medium\">{t('bigconsole.widget.properties.template.ctrlSToSave', 'Ctrl+S to save')}</span>\n {widget.renderer === 'ADAPTIVE_CARD' && (\n <a\n href=\"https://adaptivecards.io/designer/\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n color: 'var(--color-text-link)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n textDecoration: 'none',\n }}\n >\n {t('bigconsole.widget.properties.template.designer', 'Designer')}\n <ExternalLink style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </a>\n )}\n {widget.renderer === 'BLOCKKIT' && (\n <a\n href=\"https://app.slack.com/block-kit-builder\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n color: 'var(--color-text-link)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n textDecoration: 'none',\n }}\n >\n {t('bigconsole.widget.properties.template.blockKitBuilder', 'Block Kit Builder')}\n <ExternalLink style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </a>\n )}\n </div>\n </Section>\n );\n});\n\n// ============================================================================\n// Local Form State Type\n// ============================================================================\n\ninterface LocalFormState {\n title: string;\n description: string;\n refreshInterval: number | undefined;\n renderer: RendererType;\n template: Record<string, unknown> | undefined;\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: string | undefined;\n datasetId: string | undefined;\n parserId: string | undefined;\n parserRules: string | undefined;\n config: Record<string, unknown>;\n // Drilldown configuration\n drilldownConfig: DrilldownConfig | undefined;\n}\n\nfunction createLocalFormStateFromWidget(widget: Widget): LocalFormState {\n const widgetFields = widget as unknown as Record<string, string | undefined>;\n const binding = getWidgetBinding({\n dataSinkId: widgetFields.dataSinkId,\n parserId: widgetFields.parserId,\n config: (widget.config as Record<string, unknown>) || {},\n });\n\n return {\n title: widget.title || '',\n description: widget.description || '',\n refreshInterval: widget.refreshInterval ?? undefined,\n renderer: (widget.renderer as RendererType) || 'BIGCONSOLE',\n template: widget.template as Record<string, unknown> | undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: binding.source.kind === 'DATASINK' ? binding.source.ref : undefined,\n datasetId: widgetFields.datasetId ?? undefined,\n parserId: binding.parserId,\n parserRules: widgetFields.parserRules ?? undefined,\n config: (widget.config as Record<string, unknown>) || {},\n // Drilldown configuration\n drilldownConfig: widget.drilldown\n ? {\n enabled: widget.drilldown.enabled ?? false,\n targetDashboardId: widget.drilldown.targetDashboardId ?? '',\n navigationMode: widget.drilldown.navigationMode ?? 'MODAL',\n parameterMapping: (widget.drilldown.parameterMapping as Record<string, string>) ?? {},\n passGlobalFilters: widget.drilldown.passGlobalFilters ?? true,\n maxDepth: widget.drilldown.maxDepth ?? 3,\n openIn: widget.drilldown.openIn ?? 'same',\n }\n : undefined,\n };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const PropertiesPanel: FC<PropertiesPanelProps> = memo(function PropertiesPanel({ isOpen, onClose }) {\n const { t } = useI18n();\n const [activeTab, setActiveTab] = useState<TabId>('general');\n const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');\n const [localForm, setLocalForm] = useState<LocalFormState | null>(null);\n const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);\n\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const widgets = useWidgetStore((state) => state.widgets);\n const updateWidgetInStore = useWidgetStore((state) => state.updateWidget);\n\n const selectedWidget = selectedWidgetId ? widgets.get(selectedWidgetId) : undefined;\n const previousSelectedWidgetIdRef = useRef<string | null>(null);\n // The widget config as it was when `localForm` was last seeded. Used on save to\n // apply only the panel's own config changes without clobbering keys another\n // editor (e.g. the pivot canvas) persisted while the panel held unsaved edits.\n const seededConfigRef = useRef<Record<string, unknown>>({});\n\n useEffect(() => {\n if (!selectedWidgetId) {\n previousSelectedWidgetIdRef.current = null;\n setLocalForm(null);\n setHasUnsavedChanges(false);\n setSaveStatus('idle');\n return;\n }\n\n const widgetSwitched = previousSelectedWidgetIdRef.current !== selectedWidgetId;\n previousSelectedWidgetIdRef.current = selectedWidgetId;\n\n if (selectedWidget) {\n if (hasUnsavedChanges && !widgetSwitched) return;\n\n setLocalForm(createLocalFormStateFromWidget(selectedWidget));\n seededConfigRef.current = (selectedWidget.config as Record<string, unknown>) || {};\n setHasUnsavedChanges(false);\n setSaveStatus('idle');\n } else {\n setLocalForm(null);\n setHasUnsavedChanges(false);\n setSaveStatus('idle');\n }\n }, [selectedWidgetId, selectedWidget, hasUnsavedChanges]);\n\n const { updateWidget: updateWidgetApi } = useWidgetOperations(selectedWidget?.pageId, selectedWidget?.dashboardId, {\n skipFetch: true,\n });\n\n // Drilldown operations\n const { createDrilldown, updateDrilldown, deleteDrilldown } = useDrilldownOperations();\n\n // DataSink operations - for looking up DataSink by key\n const { dataSinks } = useDataSinkOperations({ skipFetch: false });\n\n // Track selected DataSink key for filtering parsers\n const [selectedDataSinkKey, setSelectedDataSinkKey] = useState<string | null>(null);\n\n // Parser operations - for executing parser to detect fields\n const { executeParser } = useParserOperations({\n consoleId: selectedWidget?.dashboardId,\n skipFetch: true,\n });\n\n // Track if we're auto-detecting fields\n const [isDetectingFields, setIsDetectingFields] = useState(false);\n const [bindingPreview, setBindingPreview] = useState<unknown>(undefined);\n const [bindingPreviewComplete, setBindingPreviewComplete] = useState(false);\n const [bindingPreviewError, setBindingPreviewError] = useState<string | null>(null);\n\n useEffect(() => {\n setBindingPreview(undefined);\n setBindingPreviewComplete(false);\n setBindingPreviewError(null);\n setIsDetectingFields(false);\n }, [selectedWidgetId]);\n\n const {\n actions,\n loading: actionsLoading,\n createAction,\n updateAction,\n deleteAction,\n reorderActions,\n } = useWidgetActions(selectedWidgetId || undefined);\n\n // Webhook mappings for this widget — used to pair a button with an HTTP call\n // inline (\"When clicked → call a webhook\") without leaving the Add-Button flow.\n const {\n webhooks,\n loading: webhooksLoading,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n refetch: refetchWebhooks,\n } = useEventWebhooks({\n widgetId: selectedWidgetId || undefined,\n skipFetch: !selectedWidgetId,\n });\n\n // Match a webhook mapping to an action (by its emitted event) within a given list.\n const findWebhookIn = useCallback(\n (list: EventWebhookMapping[], actionName: string) => {\n const pattern = eventPatternForAction(actionName);\n return list.find((w) => w.widgetId === selectedWidgetId && w.eventPattern === pattern) ?? null;\n },\n [selectedWidgetId]\n );\n\n // Find the webhook mapping paired to an action from the currently-loaded list.\n const webhookForAction = useCallback(\n (actionName: string) => findWebhookIn(webhooks, actionName),\n [findWebhookIn, webhooks]\n );\n\n // Create/update/remove the paired webhook mapping to match the dialog's outcome.\n // Returns false if a required webhook mutation failed so the caller can surface\n // it instead of silently leaving a button that looks wired but isn't (#5).\n const reconcileActionWebhook = useCallback(\n async (actionName: string, draft: ActionWebhookDraft | null | undefined): Promise<boolean> => {\n if (!selectedWidgetId || draft === undefined) return true;\n const pattern = eventPatternForAction(actionName);\n let existing = webhookForAction(actionName);\n // If the mapping list is still loading, a null lookup is unreliable and a\n // create here would duplicate an existing mapping (two endpoints firing per\n // click). Force a fresh fetch and re-check before deciding there is none (#7).\n if (!existing && webhooksLoading) {\n const res = await refetchWebhooks();\n const fresh = (res?.data?.listEventWebhookMappings ?? []) as EventWebhookMapping[];\n existing = findWebhookIn(fresh, actionName);\n }\n if (draft && draft.url) {\n if (existing) {\n // Re-activate on save — a mapping disabled in the Webhooks panel would\n // otherwise show as wired while clicks are still ignored (#4).\n const res = await updateWebhook({\n id: existing.id,\n eventPattern: pattern,\n webhookUrl: draft.url,\n method: draft.method,\n isActive: true,\n });\n return res !== null;\n }\n const res = await createWebhook({\n widgetId: selectedWidgetId,\n eventPattern: pattern,\n webhookUrl: draft.url,\n method: draft.method,\n });\n return res !== null;\n }\n if (draft === null && existing) {\n // Outcome switched away from \"call a webhook\" — drop the binding.\n return deleteWebhook(existing.id);\n }\n return true;\n },\n [\n selectedWidgetId,\n webhookForAction,\n webhooksLoading,\n refetchWebhooks,\n findWebhookIn,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n ]\n );\n\n const [isActionDialogOpen, setIsActionDialogOpen] = useState(false);\n const [editingAction, setEditingAction] = useState<WidgetAction | undefined>(undefined);\n const [actionSaving, setActionSaving] = useState(false);\n // When a webhook mutation fails after the action itself saved, we keep the\n // dialog open on the persisted action so the user can retry. That reassigns\n // the dialog's `action` prop and re-runs its reset effect, which would wipe the\n // URL the user just typed (the mapping never persisted, so `editingWebhook` is\n // null). Stash the attempted draft here and feed it back as `initialWebhook` so\n // the dialog re-seeds it instead of clearing it.\n //\n // Tri-state: `undefined` = no retry draft (use the persisted mapping);\n // an object = a failed webhook create/update to re-seed; `null` = a failed\n // \"switch to Emit / remove webhook\" so the retry keeps the Emit selection\n // instead of falling back to the still-present old mapping (#8).\n const [pendingWebhookDraft, setPendingWebhookDraft] = useState<ActionWebhookDraft | null | undefined>(undefined);\n // Surfaces a save failure inside the dialog so the user isn't left with a\n // silently-editable form and no idea what went wrong (#9).\n const [pendingActionError, setPendingActionError] = useState<string | null>(null);\n\n // Prefill the dialog's webhook field when editing an action already paired with one.\n const editingWebhook = useMemo<ActionWebhookDraft | null>(() => {\n if (!editingAction) return null;\n const paired = webhookForAction(editingAction.name);\n return paired ? { url: paired.webhookUrl, method: paired.method } : null;\n }, [editingAction, webhookForAction]);\n\n // A retry draft (from a failed save) wins over the persisted mapping — including\n // an explicit `null` removal retry, which must NOT fall back to `editingWebhook`.\n const dialogInitialWebhook = pendingWebhookDraft !== undefined ? pendingWebhookDraft : editingWebhook;\n\n // Pre-compute each action's paired webhook once per render instead of running a\n // `.find()` inside the action-list JSX for every row on every render.\n const webhookByActionName = useMemo(() => {\n const map = new Map<string, EventWebhookMapping>();\n for (const a of actions) {\n const paired = webhookForAction(a.name);\n if (paired) map.set(a.name, paired);\n }\n return map;\n }, [actions, webhookForAction]);\n\n const handleAddAction = useCallback(() => {\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setEditingAction(undefined);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleEditAction = useCallback((action: WidgetAction) => {\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setEditingAction(action);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleSaveAction = useCallback(\n async (data: ActionSaveData, webhook: ActionWebhookDraft | null) => {\n if (!selectedWidgetId) return;\n\n setActionSaving(true);\n setPendingActionError(null);\n try {\n // `data` carries only action fields; the webhook binding arrives as a\n // separate arg so it can never leak into the GraphQL mutation input.\n const isUpdate = 'id' in data && !!data.id;\n\n let newAction: WidgetAction | null = null;\n if (isUpdate) {\n newAction = await updateAction(data as UpdateWidgetActionInput);\n } else {\n newAction = await createAction(data as Omit<CreateWidgetActionInput, 'widgetId'>);\n }\n\n // Immediately sync to widget store after successful save\n // This ensures the widget displays the action right away\n if (newAction) {\n const currentActions = [...actions];\n if (isUpdate) {\n // Update existing action\n const idx = currentActions.findIndex((a) => a.id === newAction?.id);\n if (idx >= 0) {\n currentActions[idx] = newAction;\n }\n } else {\n // Add new action\n currentActions.push(newAction);\n }\n // Sort by order and update store\n const sortedActions = currentActions.sort((a, b) => a.order - b.order);\n updateWidgetInStore(selectedWidgetId, { actions: sortedActions });\n\n // Pair (or unpair) the webhook that actually gives the button its behaviour.\n const webhookOk = await reconcileActionWebhook(newAction.name, webhook);\n if (!webhookOk) {\n // The action saved but its webhook did not. Keep the dialog open on\n // the now-persisted action so the user can retry (as an update, not a\n // duplicate). Stash the typed draft (which may be `null` for a failed\n // switch-to-Emit) so re-seeding the dialog keeps the user's choice (#8),\n // and surface why the save didn't fully land (#9).\n setPendingWebhookDraft(webhook);\n setPendingActionError(\n webhook\n ? t(\n 'bigconsole.widget.properties.action.webhookSaveFailed',\n 'The button was saved, but its webhook could not be saved. Check the URL and try again.'\n )\n : t(\n 'bigconsole.widget.properties.action.webhookRemoveFailed',\n 'The button was saved, but its old webhook could not be removed. Try again.'\n )\n );\n setEditingAction(newAction);\n return;\n }\n\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n }\n } catch (err) {\n // Keep the dialog open (we never closed it in the throw path) and surface\n // the failure to the user — console.log is globally no-op'd here, so warn (#9).\n console.warn('Failed to save widget action:', err);\n setPendingActionError(\n err instanceof Error\n ? err.message\n : t('bigconsole.widget.properties.action.saveFailed', 'Could not save the button. Please try again.')\n );\n } finally {\n setActionSaving(false);\n }\n },\n [selectedWidgetId, actions, createAction, updateAction, updateWidgetInStore, reconcileActionWebhook, t]\n );\n\n const handleDeleteAction = useCallback(\n async (actionId: string) => {\n if (!selectedWidgetId) return;\n\n if (\n window.confirm(\n t('bigconsole.widget.properties.action.deleteConfirm', 'Are you sure you want to delete this action?')\n )\n ) {\n const target = actions.find((a) => a.id === actionId);\n const success = await deleteAction(actionId);\n if (success) {\n // Immediately sync to widget store after deletion\n const updatedActions = actions.filter((a) => a.id !== actionId);\n updateWidgetInStore(selectedWidgetId, { actions: updatedActions });\n\n // Best-effort: remove the webhook mapping this button was paired with.\n if (target) {\n const paired = webhookForAction(target.name);\n if (paired) await deleteWebhook(paired.id);\n }\n }\n }\n },\n [selectedWidgetId, actions, deleteAction, updateWidgetInStore, webhookForAction, deleteWebhook, t]\n );\n\n const handleMoveActionUp = useCallback(\n async (index: number) => {\n if (!selectedWidgetId || index === 0) return;\n const sortedActions = [...actions].sort((a, b) => a.order - b.order);\n const newOrder = sortedActions.map((a) => a.id);\n [newOrder[index - 1], newOrder[index]] = [newOrder[index], newOrder[index - 1]];\n const success = await reorderActions(newOrder);\n if (success) {\n // Immediately update local order for UI responsiveness\n const reordered = sortedActions\n .map((a, _i) => ({\n ...a,\n order: newOrder.indexOf(a.id),\n }))\n .sort((a, b) => a.order - b.order);\n updateWidgetInStore(selectedWidgetId, { actions: reordered });\n }\n },\n [selectedWidgetId, actions, reorderActions, updateWidgetInStore]\n );\n\n const handleMoveActionDown = useCallback(\n async (index: number) => {\n if (!selectedWidgetId) return;\n const sortedActions = [...actions].sort((a, b) => a.order - b.order);\n if (index === sortedActions.length - 1) return;\n const newOrder = sortedActions.map((a) => a.id);\n [newOrder[index], newOrder[index + 1]] = [newOrder[index + 1], newOrder[index]];\n const success = await reorderActions(newOrder);\n if (success) {\n // Immediately update local order for UI responsiveness\n const reordered = sortedActions\n .map((a, _i) => ({\n ...a,\n order: newOrder.indexOf(a.id),\n }))\n .sort((a, b) => a.order - b.order);\n updateWidgetInStore(selectedWidgetId, { actions: reordered });\n }\n },\n [selectedWidgetId, actions, reorderActions, updateWidgetInStore]\n );\n\n useEffect(() => {\n if (selectedWidgetId && actions.length >= 0 && !actionsLoading) {\n updateWidgetInStore(selectedWidgetId, { actions });\n }\n }, [selectedWidgetId, actions, actionsLoading, updateWidgetInStore]);\n\n const updateLocalForm = useCallback(<K extends keyof LocalFormState>(key: K, value: LocalFormState[K]) => {\n setLocalForm((prev) => (prev ? { ...prev, [key]: value } : prev));\n setHasUnsavedChanges(true);\n }, []);\n\n const updateLocalConfig = useCallback((newConfig: Record<string, unknown>) => {\n setLocalForm((prev) => (prev ? { ...prev, config: newConfig } : prev));\n setHasUnsavedChanges(true);\n }, []);\n\n // Initialize selectedDataSinkKey from widget's dataSinkId\n useEffect(() => {\n if (localForm?.dataSinkId && dataSinks.length > 0) {\n const ds = dataSinks.find((d) => d.id === localForm.dataSinkId);\n if (ds) {\n setSelectedDataSinkKey(ds.key);\n }\n } else {\n setSelectedDataSinkKey(null);\n }\n }, [localForm?.dataSinkId, dataSinks]);\n\n // Handle DataSink selection - looks up ID from key and updates form\n const handleDataSinkChange = useCallback(\n (key: string | null) => {\n setBindingPreview(undefined);\n setBindingPreviewComplete(false);\n setBindingPreviewError(null);\n setSelectedDataSinkKey(key);\n if (key) {\n const ds = dataSinks.find((d) => d.key === key);\n if (ds) {\n updateLocalForm('dataSinkId', ds.id);\n // Also update config.dataSinkKey for backward compatibility\n setLocalForm((prev) =>\n prev\n ? {\n ...prev,\n config: { ...prev.config, dataSinkKey: key },\n }\n : prev\n );\n setHasUnsavedChanges(true);\n }\n } else {\n updateLocalForm('dataSinkId', undefined);\n setLocalForm((prev) =>\n prev\n ? {\n ...prev,\n config: { ...prev.config, dataSinkKey: undefined },\n parserId: undefined, // Clear parser when DataSink is cleared\n }\n : prev\n );\n setHasUnsavedChanges(true);\n }\n },\n [dataSinks, updateLocalForm]\n );\n\n // Handle Parser selection - with auto-detection of field mappings\n const handleParserChange = useCallback(\n async (parserId: string | null) => {\n updateLocalForm('parserId', parserId || undefined);\n setBindingPreview(undefined);\n setBindingPreviewComplete(false);\n setBindingPreviewError(null);\n\n // If a parser is selected, execute it to detect field structure\n if (parserId && selectedWidget) {\n setIsDetectingFields(true);\n try {\n const result = await executeParser(parserId);\n\n if (result?.success) {\n setBindingPreview(result.output);\n setBindingPreviewComplete(true);\n const detectedFields = detectFieldsFromParserOutput(result.output);\n\n // Only auto-populate if fields were detected and not already set\n if (detectedFields.labelField || detectedFields.valueField) {\n setLocalForm((prev) => {\n if (!prev) return prev;\n\n const currentConfig = prev.config || {};\n const newConfig = { ...currentConfig };\n\n // Auto-populate labelField if not already set\n if (detectedFields.labelField && !currentConfig.labelField) {\n newConfig.labelField = detectedFields.labelField;\n }\n\n // Auto-populate valueField if not already set\n if (detectedFields.valueField && !currentConfig.valueField) {\n newConfig.valueField = detectedFields.valueField;\n }\n\n // Auto-populate xAxisField for line/bar/area charts if not already set\n if (detectedFields.xAxisField && !currentConfig.xAxisField) {\n newConfig.xAxisField = detectedFields.xAxisField;\n }\n\n return { ...prev, config: newConfig };\n });\n setHasUnsavedChanges(true);\n }\n } else {\n setBindingPreviewComplete(true);\n setBindingPreviewError(\n result?.error ||\n t('bigconsole.widget.properties.parser.previewFailed', 'The parser could not produce a preview.')\n );\n }\n } catch (err) {\n setBindingPreviewComplete(true);\n setBindingPreviewError(\n err instanceof Error\n ? err.message\n : t('bigconsole.widget.properties.parser.previewFailed', 'The parser could not produce a preview.')\n );\n console.warn('Failed to auto-detect parser fields:', err);\n } finally {\n setIsDetectingFields(false);\n }\n }\n },\n [updateLocalForm, executeParser, selectedWidget, t]\n );\n\n const handleSaveAll = useCallback(async () => {\n if (!selectedWidget || !localForm) return;\n\n setSaveStatus('saving');\n\n try {\n const metadata = { ...(selectedWidget.metadata || {}), description: localForm.description || '' };\n // Reconcile against the latest persisted config so we don't wipe keys another\n // editor changed while the panel held unsaved edits (e.g. pivot rows/columns/\n // values dragged on the canvas). See reconcilePanelConfig.\n const latestConfig =\n (useWidgetStore.getState().widgets.get(selectedWidget.id)?.config as Record<string, unknown>) ?? {};\n const mergedConfig = reconcilePanelConfig(latestConfig, seededConfigRef.current, localForm.config);\n const bindingDraft = getWidgetBinding({\n dataSinkId: localForm.dataSinkId,\n parserId: localForm.parserId,\n config: { ...mergedConfig, binding: undefined },\n });\n const bindingWrite = serializeWidgetBinding(bindingDraft, mergedConfig);\n\n const result = await updateWidgetApi({\n id: selectedWidget.id,\n title: localForm.title,\n renderer: localForm.renderer,\n template: localForm.template,\n config: bindingWrite.config,\n refreshInterval: localForm.refreshInterval,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: bindingWrite.dataSinkId,\n datasetId: localForm.datasetId,\n parserId: bindingWrite.parserId,\n parserRules: localForm.parserRules,\n metadata,\n });\n\n // Save drilldown configuration to backend\n let savedDrilldown = selectedWidget.drilldown;\n if (localForm.drilldownConfig) {\n const { enabled, targetDashboardId, navigationMode, parameterMapping, passGlobalFilters, maxDepth, openIn } =\n localForm.drilldownConfig;\n\n if (selectedWidget.drilldown?.id) {\n // Update existing drilldown\n const drilldownResult = await updateDrilldown({\n id: selectedWidget.drilldown.id,\n targetDashboardId,\n parameterMapping,\n navigationMode,\n enabled,\n passGlobalFilters,\n maxDepth,\n openIn,\n });\n if (drilldownResult) {\n savedDrilldown = drilldownResult;\n }\n } else if (targetDashboardId) {\n // Create new drilldown (only if targetDashboardId is set)\n const drilldownResult = await createDrilldown({\n sourceWidgetId: selectedWidget.id,\n targetDashboardId,\n parameterMapping: parameterMapping || {},\n navigationMode,\n passGlobalFilters,\n maxDepth,\n openIn,\n });\n if (drilldownResult) {\n savedDrilldown = { ...drilldownResult, enabled };\n }\n }\n } else if (selectedWidget.drilldown?.id && !localForm.drilldownConfig) {\n // Delete drilldown if it existed but was removed\n await deleteDrilldown(selectedWidget.drilldown.id);\n savedDrilldown = undefined;\n }\n\n if (result) {\n updateWidgetInStore(selectedWidget.id, {\n title: localForm.title,\n description: localForm.description,\n renderer: localForm.renderer,\n template: localForm.template,\n config: bindingWrite.config,\n refreshInterval: localForm.refreshInterval,\n // v2.0: Data source fields\n dataSinkId: bindingWrite.dataSinkId,\n datasetId: localForm.datasetId,\n parserId: bindingWrite.parserId,\n parserRules: localForm.parserRules,\n // Drilldown configuration from backend\n drilldown: savedDrilldown,\n // Update timestamp to trigger WidgetWrapper re-render (fixes drilldown mode change bug)\n updatedAt: new Date().toISOString(),\n } as Partial<Widget>);\n\n setSaveStatus('saved');\n setHasUnsavedChanges(false);\n setTimeout(() => setSaveStatus('idle'), 2000);\n } else {\n setSaveStatus('error');\n }\n } catch (err) {\n setSaveStatus('error');\n }\n }, [\n selectedWidget,\n localForm,\n updateWidgetApi,\n updateWidgetInStore,\n createDrilldown,\n updateDrilldown,\n deleteDrilldown,\n ]);\n\n const handleDiscardAll = useCallback(() => {\n if (!selectedWidget) return;\n\n setLocalForm(createLocalFormStateFromWidget(selectedWidget));\n seededConfigRef.current = (selectedWidget.config as Record<string, unknown>) || {};\n setHasUnsavedChanges(false);\n setSaveStatus('idle');\n }, [selectedWidget]);\n\n const handleConfigChange = useCallback(\n (newConfig: Record<string, unknown>) => {\n updateLocalConfig(newConfig);\n },\n [updateLocalConfig]\n );\n\n const handleDrilldownChange = useCallback((config: DrilldownConfig) => {\n setLocalForm((prev) => (prev ? { ...prev, drilldownConfig: config } : prev));\n setHasUnsavedChanges(true);\n }, []);\n\n const definition = selectedWidget ? getWidgetDefinition(selectedWidget.type) : null;\n\n const rendererOptions = useMemo(\n () =>\n RENDERER_OPTIONS.map((opt) => ({\n value: opt.value,\n label: t(opt.labelKey, opt.label),\n description: t(opt.descriptionKey, opt.description),\n })),\n [t]\n );\n\n const dataSourceTypes = useMemo(\n () =>\n DATA_SOURCE_TYPES.map((type) => ({\n value: type.value,\n label: t(type.labelKey, type.label),\n })),\n [t]\n );\n\n return (\n <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()} modal={false}>\n {/*\n `min(420px, 100vw)` instead of a hard 420px + 90vw cap: on a 390px\n viewport the old sizing left a 39px sliver of unusable canvas beside a\n full-bleed sheet. Now the sheet simply takes the viewport (BOFF-5636).\n */}\n <SheetContent\n style={{\n width: 'min(420px, 100vw)',\n maxWidth: '100vw',\n padding: 0,\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n overflow: 'hidden',\n }}\n >\n <SheetHeader\n style={{\n padding: 'var(--space-cardPadding)',\n borderBottom: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n flexShrink: 0,\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-controlGap)' }}>\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 boxShadow: 'var(--shadow-elevation-2)',\n flexShrink: 0,\n }}\n >\n <Info\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 <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <SheetTitle\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-bold)',\n color: 'var(--color-text-primary)',\n margin: 0,\n }}\n >\n {selectedWidget\n ? t('bigconsole.widget.properties.header.widgetProperties', 'Widget Properties')\n : t('bigconsole.widget.properties.header.properties', 'Properties')}\n </SheetTitle>\n {saveStatus === 'saving' && (\n <Spinner style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n )}\n {saveStatus === 'saved' && (\n <Check\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-status-success-bg)',\n }}\n />\n )}\n {saveStatus === 'error' && (\n <X\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-status-error-bg)',\n }}\n />\n )}\n </div>\n {definition && (\n <SheetDescription\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n margin: 'var(--space-controlGap) 0 0 0',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n {definition.name}\n </SheetDescription>\n )}\n </div>\n </div>\n </SheetHeader>\n\n {selectedWidget ? (\n <Tabs\n value={activeTab}\n onValueChange={(v) => setActiveTab(v as TabId)}\n style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}\n >\n {/*\n The tab strip is the panel's navigation, so it no longer renders in\n type smaller than the content it switches (was a hard-coded 12px),\n and it wraps to a second row instead of becoming a horizontal\n scroller that can hide the fourth tab off-screen (BOFF-5636).\n */}\n <TabsList\n style={{\n width: '100%',\n display: 'flex',\n flexWrap: 'wrap',\n justifyContent: 'flex-start',\n borderRadius: 0,\n borderBottom: '1px solid var(--color-border-subtle)',\n padding: '2px 8px',\n backgroundColor: 'var(--color-bg-sunken)',\n minHeight: 'var(--size-controlHeight-lg)',\n height: 'auto',\n flexShrink: 0,\n gap: '2px',\n }}\n >\n <TabsTrigger value=\"general\" style={TAB_TRIGGER_STYLE}>\n {t('bigconsole.widget.properties.tab.general', 'General')}\n </TabsTrigger>\n <TabsTrigger value=\"data\" style={TAB_TRIGGER_STYLE}>\n {t('bigconsole.widget.properties.tab.data', 'Data')}\n </TabsTrigger>\n <TabsTrigger value=\"advanced\" style={TAB_TRIGGER_STYLE}>\n {t('bigconsole.widget.properties.tab.advanced', 'Advanced')}\n </TabsTrigger>\n <TabsTrigger value=\"appearance\" style={TAB_TRIGGER_STYLE}>\n {t('bigconsole.widget.properties.tab.style', 'Style')}\n </TabsTrigger>\n </TabsList>\n\n <div style={{ flex: 1, overflow: 'auto', padding: 'var(--space-cardPadding)', minHeight: 0 }}>\n <TabsContent value=\"general\" className=\"mt-0\">\n {localForm && (\n <SectionStack>\n <GeneralSection\n title={localForm.title}\n description={localForm.description}\n refreshInterval={localForm.refreshInterval}\n onTitleChange={(title) => updateLocalForm('title', title)}\n onDescriptionChange={(description) => updateLocalForm('description', description)}\n onRefreshIntervalChange={(refreshInterval) => updateLocalForm('refreshInterval', refreshInterval)}\n />\n </SectionStack>\n )}\n </TabsContent>\n\n <TabsContent value=\"data\" className=\"mt-0\">\n {localForm && (\n <SectionStack>\n {(() => {\n const binding = getWidgetBinding({\n dataSinkId: localForm.dataSinkId,\n parserId: localForm.parserId,\n config: { ...localForm.config, binding: undefined },\n });\n const validation = getWidgetBindingStatus({\n binding,\n loading: isDetectingFields,\n error: bindingPreviewError,\n preview: bindingPreview,\n previewComplete: bindingPreviewComplete,\n });\n\n return (\n <DataBindingSection\n consoleId={selectedWidget?.dashboardId || ''}\n binding={binding}\n dataSinkKey={selectedDataSinkKey}\n dataPath={\n typeof localForm.config.dataPath === 'string' ? localForm.config.dataPath : undefined\n }\n status={validation.status}\n missingFields={validation.missingFields}\n preview={bindingPreview}\n error={bindingPreviewError}\n onDataSinkChange={handleDataSinkChange}\n onParserChange={handleParserChange}\n onDataPathChange={(dataPath) => updateLocalConfig({ ...localForm.config, dataPath })}\n />\n );\n })()}\n\n {/*\n Escape hatch for widgets that bypass the DataSink pipeline.\n It used to be an uppercase micro-label over up to seven\n ungrouped fields shaped exactly like the primary binding\n above it; a recessed section says plainly that this is the\n alternative path (BOFF-5636).\n */}\n <Section\n emphasis=\"muted\"\n title={t('bigconsole.widget.properties.data.alternativeSources', 'Alternative Data Sources')}\n description=\"Skip the DataSink pipeline and fetch straight from an API, a GraphQL endpoint or sample JSON.\"\n headingLevel={3}\n data-testid=\"widget-inspector-alt-sources\"\n >\n <div className=\"flex flex-col gap-form-gap\">\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.data.sourceTypeLabel', 'Data Source Type')}</Label>\n <Select\n value={(localForm.config?.dataSourceType as string) || ''}\n onValueChange={(value) =>\n updateLocalConfig({ ...localForm.config, dataSourceType: value || undefined })\n }\n >\n <SelectTrigger\n style={{ height: 'var(--size-controlHeight-md)', fontSize: 'var(--font-size-body-sm)' }}\n >\n <SelectValue\n placeholder={t(\n 'bigconsole.widget.properties.data.selectTypePlaceholder',\n 'Select type...'\n )}\n />\n </SelectTrigger>\n <SelectContent\n position=\"popper\"\n side=\"bottom\"\n sideOffset={4}\n align=\"start\"\n style={{\n zIndex: 9999,\n maxHeight: '200px',\n backgroundColor: 'var(--color-overlay-popoverBg, var(--color-bg-surface))',\n border: '1px solid var(--color-border-subtle)',\n boxShadow: 'var(--shadow-elevation-3)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n {dataSourceTypes.map((type) => (\n <SelectItem key={type.value} value={type.value}>\n {type.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {localForm.config?.dataSourceType === 'REST_API' && (\n <div className=\"space-y-3\">\n {/* Endpoint URL */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.rest.endpointUrlLabel', 'Endpoint URL *')}</Label>\n <Input\n value={(localForm.config?.endpointUrl as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, endpointUrl: e.target.value || undefined })\n }\n placeholder=\"https://api.example.com/data\"\n />\n </div>\n\n {/* HTTP Method */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.rest.httpMethodLabel', 'HTTP Method')}</Label>\n <Select\n value={(localForm.config?.httpMethod as string) || 'GET'}\n onValueChange={(value) => updateLocalConfig({ ...localForm.config, httpMethod: value })}\n >\n <SelectTrigger style={{ height: 'var(--size-controlHeight-md)' }}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" side=\"bottom\" sideOffset={4}>\n <SelectItem value=\"GET\">GET</SelectItem>\n <SelectItem value=\"POST\">POST</SelectItem>\n <SelectItem value=\"PUT\">PUT</SelectItem>\n <SelectItem value=\"DELETE\">DELETE</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n {/* Headers */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.rest.headersLabel', 'Headers (JSON)')}</Label>\n <Textarea\n value={(localForm.config?.headers as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, headers: e.target.value || undefined })\n }\n rows={3}\n placeholder='{\"Authorization\": \"Bearer token\", \"Content-Type\": \"application/json\"}'\n className=\"font-mono text-xs\"\n />\n </div>\n\n {/* Authentication Type */}\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.rest.authenticationLabel', 'Authentication')}\n </Label>\n <Select\n value={(localForm.config?.authType as string) || 'none'}\n onValueChange={(value) => updateLocalConfig({ ...localForm.config, authType: value })}\n >\n <SelectTrigger style={{ height: 'var(--size-controlHeight-md)' }}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" side=\"bottom\" sideOffset={4}>\n <SelectItem value=\"none\">\n {t('bigconsole.widget.properties.rest.authNone', 'None')}\n </SelectItem>\n <SelectItem value=\"api_key\">\n {t('bigconsole.widget.properties.rest.authApiKey', 'API Key')}\n </SelectItem>\n <SelectItem value=\"bearer\">\n {t('bigconsole.widget.properties.rest.authBearer', 'Bearer Token')}\n </SelectItem>\n <SelectItem value=\"basic\">\n {t('bigconsole.widget.properties.rest.authBasic', 'Basic Auth')}\n </SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n {/* Auth Value (conditional) */}\n {!!(localForm.config?.authType && localForm.config.authType !== 'none') && (\n <div className=\"space-y-2\">\n <Label>\n {String(localForm.config.authType) === 'basic'\n ? t('bigconsole.widget.properties.rest.credentialsLabel', 'Credentials (user:pass)')\n : t('bigconsole.widget.properties.rest.tokenKeyLabel', 'Token/Key')}\n </Label>\n <Input\n type=\"password\"\n value={(localForm.config?.authValue as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, authValue: e.target.value || undefined })\n }\n placeholder={\n localForm.config.authType === 'basic'\n ? t(\n 'bigconsole.widget.properties.rest.credentialsPlaceholder',\n 'username:password'\n )\n : t(\n 'bigconsole.widget.properties.rest.tokenKeyPlaceholder',\n 'Enter token or API key'\n )\n }\n />\n </div>\n )}\n\n {/* Request Body (for POST/PUT) */}\n {(localForm.config?.httpMethod === 'POST' || localForm.config?.httpMethod === 'PUT') && (\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.rest.requestBodyLabel', 'Request Body (JSON)')}\n </Label>\n <Textarea\n value={(localForm.config?.requestBody as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, requestBody: e.target.value || undefined })\n }\n rows={4}\n placeholder='{\"key\": \"value\"}'\n className=\"font-mono text-xs\"\n />\n </div>\n )}\n\n {/* Data Path */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.dataPath.label', 'Data Path (optional)')}</Label>\n <Input\n value={(localForm.config?.dataPath as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, dataPath: e.target.value || undefined })\n }\n placeholder=\"$.data.items or .data.items\"\n />\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'bigconsole.widget.properties.dataPath.restHelp',\n 'JSONPath or dot notation to extract data from response'\n )}\n </p>\n </div>\n </div>\n )}\n\n {localForm.config?.dataSourceType === 'GRAPHQL' && (\n <div className=\"space-y-3\">\n {/* GraphQL Endpoint */}\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.graphql.endpointLabel', 'GraphQL Endpoint *')}\n </Label>\n <Input\n value={(localForm.config?.graphqlEndpoint as string) || ''}\n onChange={(e) =>\n updateLocalConfig({\n ...localForm.config,\n graphqlEndpoint: e.target.value || undefined,\n })\n }\n placeholder=\"https://api.example.com/graphql\"\n />\n </div>\n\n {/* GraphQL Query */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.graphql.queryLabel', 'Query *')}</Label>\n <Textarea\n value={(localForm.config?.graphqlQuery as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, graphqlQuery: e.target.value || undefined })\n }\n rows={6}\n placeholder={`query GetData($limit: Int) {\n items(limit: $limit) {\n id\n name\n value\n }\n}`}\n className=\"font-mono text-xs\"\n />\n </div>\n\n {/* GraphQL Variables */}\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.graphql.variablesLabel', 'Variables (JSON, optional)')}\n </Label>\n <Textarea\n value={(localForm.config?.graphqlVariables as string) || ''}\n onChange={(e) =>\n updateLocalConfig({\n ...localForm.config,\n graphqlVariables: e.target.value || undefined,\n })\n }\n rows={3}\n placeholder='{\"limit\": 10, \"offset\": 0}'\n className=\"font-mono text-xs\"\n />\n </div>\n\n {/* Headers */}\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.graphql.headersLabel', 'Headers (JSON, optional)')}\n </Label>\n <Textarea\n value={(localForm.config?.headers as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, headers: e.target.value || undefined })\n }\n rows={2}\n placeholder='{\"Authorization\": \"Bearer token\"}'\n className=\"font-mono text-xs\"\n />\n </div>\n\n {/* Data Path */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.dataPath.label', 'Data Path (optional)')}</Label>\n <Input\n value={(localForm.config?.dataPath as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, dataPath: e.target.value || undefined })\n }\n placeholder=\".data.items\"\n />\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'bigconsole.widget.properties.dataPath.graphqlHelp',\n 'Path to extract data from GraphQL response'\n )}\n </p>\n </div>\n </div>\n )}\n\n {localForm.config?.dataSourceType === 'MOCK' && (\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.mock.sampleDataLabel', 'Sample Data (JSON)')}\n </Label>\n <Textarea\n value={(localForm.config?.mockData as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, mockData: e.target.value || undefined })\n }\n rows={5}\n placeholder='{\"value\": 100}'\n className=\"font-mono text-sm\"\n />\n </div>\n )}\n </div>\n </Section>\n </SectionStack>\n )}\n </TabsContent>\n\n <TabsContent value=\"advanced\" className=\"mt-0\">\n <SectionStack>\n {localForm && (\n <AdvancedSection\n renderer={localForm.renderer}\n rendererOptions={rendererOptions}\n onRendererChange={(renderer) => {\n updateLocalForm('renderer', renderer);\n if (renderer === 'BIGCONSOLE') updateLocalForm('template', undefined);\n }}\n templateEditor={\n localForm.renderer !== 'BIGCONSOLE' ? (\n <TemplateEditor\n widget={{ ...selectedWidget, template: localForm.template, renderer: localForm.renderer }}\n onSave={(template) => updateLocalForm('template', template)}\n />\n ) : undefined\n }\n />\n )}\n\n <Section\n title={t('bigconsole.widget.properties.actions.heading', 'Widget Actions')}\n description={t(\n 'bigconsole.widget.properties.actions.subheading',\n 'Configure buttons on this widget'\n )}\n headingLevel={3}\n meta={\n actions.length > 0 ? (\n <span className=\"rounded-pill bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {actions.length}\n </span>\n ) : undefined\n }\n actions={\n <Button size=\"sm\" onClick={handleAddAction}>\n <Plus className=\"size-4\" />\n {t('bigconsole.widget.properties.actions.add', 'Add')}\n </Button>\n }\n data-testid=\"widget-inspector-actions\"\n >\n {actionsLoading ? (\n <div className=\"flex justify-center py-8\">\n <Spinner className=\"size-6\" />\n </div>\n ) : actions.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t('bigconsole.widget.properties.actions.empty', 'No actions configured')}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {[...actions]\n .sort((a, b) => a.order - b.order)\n .map((action, index) => (\n <Card key={action.id} className={action.disabled ? 'opacity-60' : ''}>\n <CardContent className=\"p-3 flex items-center gap-2\">\n <div className=\"flex flex-col gap-0.5\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleMoveActionUp(index)}\n disabled={index === 0}\n className=\"h-5 w-5\"\n >\n <ChevronUp className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleMoveActionDown(index)}\n disabled={index === actions.length - 1}\n className=\"h-5 w-5\"\n >\n <ChevronDown className=\"size-3\" />\n </Button>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span\n aria-hidden=\"true\"\n className={`size-2 shrink-0 rounded-full ${ACTION_STYLE_COLORS[action.style] || ACTION_STYLE_FALLBACK_COLOR}`}\n />\n <span className=\"text-sm font-medium truncate\">{action.label}</span>\n </div>\n <p className=\"text-xs text-text-secondary truncate\">\n {webhookByActionName.has(action.name)\n ? t(\n 'bigconsole.widget.properties.actions.webhookSummary',\n '→ {{method}} {{url}}',\n {\n method: webhookByActionName.get(action.name)?.method ?? '',\n url: webhookByActionName.get(action.name)?.webhookUrl ?? '',\n }\n )\n : t('bigconsole.widget.properties.actions.emitsEvent', 'Emits an event')}\n </p>\n </div>\n\n <div className=\"flex shrink-0 gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Edit ${action.label}`}\n onClick={() => handleEditAction(action)}\n >\n <Pencil className=\"size-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Delete ${action.label}`}\n onClick={() => handleDeleteAction(action.id)}\n >\n <Trash2 className=\"size-4\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n </Section>\n\n <Section\n title={t('bigconsole.widget.properties.section.drilldown', 'Drilldown')}\n description=\"Where a click on this widget's data takes the viewer.\"\n headingLevel={3}\n data-testid=\"widget-inspector-drilldown\"\n >\n <DrilldownConfigPanel widget={selectedWidget} onChange={handleDrilldownChange} />\n </Section>\n\n <Section\n title={t('bigconsole.widget.properties.section.webhooks', 'Webhooks')}\n description=\"HTTP endpoints called when this widget emits an event.\"\n headingLevel={3}\n data-testid=\"widget-inspector-webhooks\"\n >\n <WebhookConfigPanel dashboardId={selectedWidget.dashboardId} widgetId={selectedWidget.id} />\n </Section>\n\n <Section\n emphasis=\"muted\"\n title={t('bigconsole.widget.properties.section.events', 'Events')}\n description=\"Recent deliveries for this widget, newest first. Supporting detail — nothing here changes the widget.\"\n headingLevel={3}\n data-testid=\"widget-inspector-events\"\n >\n <EventLogViewer\n dashboardId={selectedWidget.dashboardId}\n widgetId={selectedWidget.id}\n maxHeight=\"min(50vh, 360px)\"\n />\n </Section>\n </SectionStack>\n </TabsContent>\n\n <TabsContent value=\"appearance\" className=\"mt-0\">\n {localForm && (\n <AppearanceSection\n widgetType={selectedWidget.type}\n config={localForm.config || {}}\n onChange={handleConfigChange}\n />\n )}\n </TabsContent>\n </div>\n </Tabs>\n ) : (\n <div className=\"flex min-h-0 flex-1 items-center justify-center p-card-padding\">\n <Section\n tone=\"info\"\n title={t('bigconsole.widget.properties.emptyState', 'Select a widget to edit')}\n headingLevel={3}\n icon={<Info className=\"h-icon-md w-icon-md\" />}\n className=\"w-full max-w-sm\"\n data-testid=\"widget-inspector-empty\"\n >\n <p className=\"text-sm text-text-secondary\">\n Pick a widget on the canvas and its identity, data binding, actions and style options appear here.\n </p>\n </Section>\n </div>\n )}\n\n {/* Global Save/Discard Footer */}\n {selectedWidget && (\n <div\n style={{\n padding: 'var(--space-cardPadding)',\n borderTop: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-sunken)',\n flexShrink: 0,\n }}\n >\n {/*\n Save state used to be three ad-hoc inline chips with raw rgba()\n fallbacks baked into the var() calls. They are now one toned\n section, so \"unsaved / saved / failed\" reads the same here as every\n other status surface in the module (BOFF-5636).\n */}\n {hasUnsavedChanges && (\n <Section\n tone=\"warning\"\n padding=\"compact\"\n title={t('bigconsole.widget.properties.footer.unsavedChanges', 'You have unsaved changes')}\n headingLevel={3}\n className=\"mb-control-gap\"\n data-testid=\"widget-inspector-save-unsaved\"\n />\n )}\n\n {saveStatus === 'saved' && !hasUnsavedChanges && (\n <Section\n tone=\"success\"\n padding=\"compact\"\n title={t('bigconsole.widget.properties.footer.allSaved', 'All changes saved')}\n headingLevel={3}\n icon={<Check className=\"h-icon-sm w-icon-sm\" />}\n className=\"mb-control-gap\"\n data-testid=\"widget-inspector-save-saved\"\n />\n )}\n\n {saveStatus === 'error' && (\n <Section\n tone=\"danger\"\n padding=\"compact\"\n title={t('bigconsole.widget.properties.footer.saveFailed', 'Failed to save changes')}\n headingLevel={3}\n icon={<X className=\"h-icon-sm w-icon-sm\" />}\n className=\"mb-control-gap\"\n data-testid=\"widget-inspector-save-error\"\n />\n )}\n\n {/* Action buttons */}\n <div className=\"flex flex-wrap gap-control-gap\">\n <Button\n onClick={handleSaveAll}\n disabled={saveStatus === 'saving' || !hasUnsavedChanges}\n style={{\n flex: 1,\n height: 'var(--size-controlHeight-md)',\n backgroundColor: hasUnsavedChanges ? 'var(--color-action-primary-bg)' : undefined,\n opacity: hasUnsavedChanges ? 1 : 0.5,\n }}\n >\n {saveStatus === 'saving' ? (\n <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Spinner style={{ width: 'var(--size-icon-sm)', height: 'var(--size-icon-sm)' }} />\n {t('bigconsole.widget.properties.footer.saving', 'Saving...')}\n </span>\n ) : (\n <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Check style={{ width: 'var(--size-icon-sm)', height: 'var(--size-icon-sm)' }} />\n {t('bigconsole.widget.properties.footer.saveChanges', 'Save Changes')}\n </span>\n )}\n </Button>\n <Button\n variant=\"outline\"\n onClick={handleDiscardAll}\n disabled={saveStatus === 'saving' || !hasUnsavedChanges}\n style={{\n height: 'var(--size-controlHeight-md)',\n opacity: hasUnsavedChanges ? 1 : 0.5,\n }}\n >\n <X style={{ width: 'var(--size-icon-sm)', height: 'var(--size-icon-sm)' }} />\n {t('bigconsole.widget.properties.footer.discard', 'Discard')}\n </Button>\n </div>\n </div>\n )}\n\n <ActionConfigDialog\n isOpen={isActionDialogOpen}\n action={editingAction}\n initialWebhook={dialogInitialWebhook}\n isLoading={actionSaving}\n saveError={pendingActionError}\n onClose={() => {\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n }}\n onSave={handleSaveAction}\n />\n </SheetContent>\n </Sheet>\n );\n});\n\nexport default PropertiesPanel;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAM,KAMA;CACJ;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACD;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACD;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACD;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACF,EAMK,KAMA;CACJ;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACD;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACD;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACD;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACD;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACD;EACE,OAAO;EACP,UAAU;EACV,OAAO;EACP,gBAAgB;EAChB,aAAa;EACd;CACF;AAgBD,SAAS,GAAsB,GAA4B;AACzD,QAAO,UAAU,EAAW,aAAa,CAAC,QAAQ,QAAQ,IAAI,CAAC;;AAMjE,IAAM,KAA8C;CAClD,SAAS;CACT,WAAW;CACX,SAAS;CACT,SAAS;CACT,QAAQ;CACT,EAEK,KAA8B,gCAG9B,IAAyC;CAC7C,YAAY;CACZ,UAAU;CACV,SAAS;CACT,WAAW;CACX,YAAY;CACb;AAWD,SAAS,GAA6B,GAIpC;AACA,KAAI,CAAC,EAAQ,QAAO,EAAE;CAGtB,IAAM,IAAa,MAAM,QAAQ,EAAO,GAAG,EAAO,KAAK;AAEvD,KAAI,CAAC,KAAc,OAAO,KAAe,SAAU,QAAO,EAAE;CAE5D,IAAM,IAAO,OAAO,KAAK,EAAsC,EAGzD,IAAuB;EAC3B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EAEK,IAAuB;EAAC;EAAS;EAAU;EAAS;EAAS;EAAW;EAAS;EAAY;EAAU;EAAM,EAE/G,GACA;AAGJ,MAAK,IAAM,KAAa,EACtB,KAAI,EAAK,SAAS,EAAU,EAAE;AAC5B,MAAa;AACb;;AAKJ,MAAK,IAAM,KAAa,EACtB,KAAI,EAAK,SAAS,EAAU,EAAE;AAC5B,MAAa;AACb;;AAOJ,KAAI,CAAC,KAAc,CAAC,EAClB,MAAK,IAAM,KAAO,GAAM;EACtB,IAAM,IAAO,EAAuC;AAOpD,MANI,CAAC,KAAc,OAAO,KAAQ,aAChC,IAAa,IAEX,CAAC,KAAc,OAAO,KAAQ,aAChC,IAAa,IAEX,KAAc,EAAY;;AAIlC,QAAO;EACL;EACA;EACA,YAAY;EACb;;AAYH,IAAM,KAA0C,GAAK,SAAwB,EAAE,WAAQ,aAAU;CAC/F,IAAM,EAAE,SAAM,IAAS,EACjB,CAAC,GAAY,KAAiB,EAAiB,GAAG,EAClD,CAAC,GAAW,KAAgB,EAAwB,KAAK,EACzD,IAAiB,GAAO,GAAM;AAGpC,SAAgB;AACd,MAAI;AAKF,GAFA,EADE,EAAO,YAAY,OAAO,EAAO,YAAa,WAAW,KAAK,UAAU,EAAO,UAAU,MAAM,EAAE,GAAG,GAC5E,EAC1B,EAAa,KAAK,EAClB,EAAe,UAAU;UACnB;AACN,KAAc,GAAG;;IAElB,CAAC,EAAO,IAAI,EAAO,SAAS,CAAC;CAEhC,IAAM,KAAe,GAClB,MAA8C;EAC7C,IAAM,IAAQ,EAAE,OAAO;AAGvB,MAFA,EAAc,EAAM,EAEhB,EAAM,MAAM,KAAK,GAGnB,CAFA,EAAa,KAAK,EAElB,EAAO,EAAE,CAAC;MAEV,KAAI;GACF,IAAM,IAAS,KAAK,MAAM,EAAM;AAGhC,GAFA,EAAa,KAAK,EAElB,EAAO,EAAO;WACP,GAAK;AACZ,KAAc,EAAc,QAAQ;;IAK1C,CAAC,EAAO,CACT,EAEK,IAAe,QAAkB;AACrC,MAAI;GACF,IAAM,IAAS,KAAK,MAAM,EAAW;AAIrC,GAFA,EADkB,KAAK,UAAU,GAAQ,MAAM,EAAE,CACzB,EACxB,EAAa,KAAK,EAClB,EAAO,EAAO;WACP,GAAK;AACZ,KAAc,EAAc,QAAQ;;IAErC,CAAC,GAAY,EAAO,CAAC;AAExB,QACE,kBAAC,GAAD;EACE,UAAS;EACT,SAAQ;EACR,cAAc;EACd,OAAO,EAAE,+CAA+C,kBAAkB;EAC1E,aAAa,OAAO,EAAO,SAAS;EACpC,MAAM,IAAY,WAAW;EAC7B,MACE,IACE,kBAAC,QAAD;GAAM,WAAU;aACb,EAAE,qDAAqD,eAAe;GAClE,CAAA,GACL,KAAA;EAEN,WAAU;EACV,eAAY;YAfd;GAiBE,kBAAC,GAAD;IACE,OAAO;IACP,UAAU;IACV,YAAY;IACZ,aAAa,EAAE,qDAAqD,uCAAuC,EACzG,UAAU,EAAO,YAAY,IAC9B,CAAC;IACF,OAAO;KACL,YAAY;KACZ,UAAU;KACV,WAAW;KACX,aAAa,IAAY,qCAAqC,KAAA;KAC9D,QAAQ;KACT;IACD,CAAA;GAED,KACC,kBAAC,KAAD;IACE,OAAO;KACL,UAAU;KACV,OAAO;KACP,iBAAiB;KACjB,SAAS;KACT,cAAc;KACd,WAAW;KACZ;cAEA;IACC,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,SAAQ;KACR,MAAK;KACL,SAAS;KACT,UAAU,CAAC,CAAC,KAAa,EAAW,MAAM,KAAK;KAC/C,OAAO;MAAE,QAAQ;MAAgC,YAAY;MAA+B;eAE3F,EAAE,gDAAgD,SAAS;KACrD,CAAA,EACR,CAAC,KAAa,EAAW,MAAM,KAAK,MACnC,kBAAC,QAAD;KACE,OAAO;MACL,UAAU;MACV,OAAO;MACP,SAAS;MACT,YAAY;MACZ,KAAK;MACN;eAPH,CASE,kBAAC,GAAD,EAAO,OAAO;MAAE,OAAO;MAAQ,QAAQ;MAAQ,EAAI,CAAA,EAClD,EAAE,mDAAmD,aAAa,CAC9D;OAEL;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD;MAAM,WAAU;gBAAe,EAAE,qDAAqD,iBAAiB;MAAQ,CAAA;KAC9G,EAAO,aAAa,mBACnB,kBAAC,KAAD;MACE,MAAK;MACL,QAAO;MACP,KAAI;MACJ,OAAO;OACL,SAAS;OACT,YAAY;OACZ,KAAK;OACL,OAAO;OACP,UAAU;OACV,YAAY;OACZ,gBAAgB;OACjB;gBAZH,CAcG,EAAE,kDAAkD,WAAW,EAChE,kBAAC,IAAD,EAAc,OAAO;OAAE,QAAQ;OAAuB,OAAO;OAAuB,EAAI,CAAA,CACtF;;KAEL,EAAO,aAAa,cACnB,kBAAC,KAAD;MACE,MAAK;MACL,QAAO;MACP,KAAI;MACJ,OAAO;OACL,SAAS;OACT,YAAY;OACZ,KAAK;OACL,OAAO;OACP,UAAU;OACV,YAAY;OACZ,gBAAgB;OACjB;gBAZH,CAcG,EAAE,yDAAyD,oBAAoB,EAChF,kBAAC,IAAD,EAAc,OAAO;OAAE,QAAQ;OAAuB,OAAO;OAAuB,EAAI,CAAA,CACtF;;KAEF;;GACE;;EAEZ;AAsBF,SAAS,GAA+B,GAAgC;CACtE,IAAM,IAAe,GACf,IAAU,EAAiB;EAC/B,YAAY,EAAa;EACzB,UAAU,EAAa;EACvB,QAAS,EAAO,UAAsC,EAAE;EACzD,CAAC;AAEF,QAAO;EACL,OAAO,EAAO,SAAS;EACvB,aAAa,EAAO,eAAe;EACnC,iBAAiB,EAAO,mBAAmB,KAAA;EAC3C,UAAW,EAAO,YAA6B;EAC/C,UAAU,EAAO;EAEjB,YAAY,EAAQ,OAAO,SAAS,aAAa,EAAQ,OAAO,MAAM,KAAA;EACtE,WAAW,EAAa,aAAa,KAAA;EACrC,UAAU,EAAQ;EAClB,aAAa,EAAa,eAAe,KAAA;EACzC,QAAS,EAAO,UAAsC,EAAE;EAExD,iBAAiB,EAAO,YACpB;GACE,SAAS,EAAO,UAAU,WAAW;GACrC,mBAAmB,EAAO,UAAU,qBAAqB;GACzD,gBAAgB,EAAO,UAAU,kBAAkB;GACnD,kBAAmB,EAAO,UAAU,oBAA+C,EAAE;GACrF,mBAAmB,EAAO,UAAU,qBAAqB;GACzD,UAAU,EAAO,UAAU,YAAY;GACvC,QAAQ,EAAO,UAAU,UAAU;GACpC,GACD,KAAA;EACL;;AAOH,IAAa,IAA4C,GAAK,SAAyB,EAAE,YAAQ,eAAW;CAC1G,IAAM,EAAE,SAAM,IAAS,EACjB,CAAC,IAAW,MAAgB,EAAgB,UAAU,EACtD,CAAC,GAAY,KAAiB,EAAgD,OAAO,EACrF,CAAC,GAAW,KAAgB,EAAgC,KAAK,EACjE,CAAC,GAAmB,KAAwB,EAAS,GAAM,EAE3D,IAAmB,GAAgB,MAAU,EAAM,iBAAiB,EACpE,KAAU,GAAgB,MAAU,EAAM,QAAQ,EAClD,IAAsB,GAAgB,MAAU,EAAM,aAAa,EAEnE,IAAiB,IAAmB,GAAQ,IAAI,EAAiB,GAAG,KAAA,GACpE,IAA8B,GAAsB,KAAK,EAIzD,IAAkB,GAAgC,EAAE,CAAC;AAE3D,SAAgB;AACd,MAAI,CAAC,GAAkB;AAIrB,GAHA,EAA4B,UAAU,MACtC,EAAa,KAAK,EAClB,EAAqB,GAAM,EAC3B,EAAc,OAAO;AACrB;;EAGF,IAAM,IAAiB,EAA4B,YAAY;AAG/D,MAFA,EAA4B,UAAU,GAElC,GAAgB;AAClB,OAAI,KAAqB,CAAC,EAAgB;AAK1C,GAHA,EAAa,GAA+B,EAAe,CAAC,EAC5D,EAAgB,UAAW,EAAe,UAAsC,EAAE,EAClF,EAAqB,GAAM,EAC3B,EAAc,OAAO;QAIrB,CAFA,EAAa,KAAK,EAClB,EAAqB,GAAM,EAC3B,EAAc,OAAO;IAEtB;EAAC;EAAkB;EAAgB;EAAkB,CAAC;CAEzD,IAAM,EAAE,cAAc,OAAoB,EAAoB,GAAgB,QAAQ,GAAgB,aAAa,EACjH,WAAW,IACZ,CAAC,EAGI,EAAE,qBAAiB,qBAAiB,wBAAoB,GAAwB,EAGhF,EAAE,iBAAc,EAAsB,EAAE,WAAW,IAAO,CAAC,EAG3D,CAAC,IAAqB,KAA0B,EAAwB,KAAK,EAG7E,EAAE,sBAAkB,GAAoB;EAC5C,WAAW,GAAgB;EAC3B,WAAW;EACZ,CAAC,EAGI,CAAC,IAAmB,MAAwB,EAAS,GAAM,EAC3D,CAAC,IAAgB,KAAqB,EAAkB,KAAA,EAAU,EAClE,CAAC,IAAwB,KAA6B,EAAS,GAAM,EACrE,CAAC,IAAqB,KAA0B,EAAwB,KAAK;AAEnF,SAAgB;AAId,EAHA,EAAkB,KAAA,EAAU,EAC5B,EAA0B,GAAM,EAChC,EAAuB,KAAK,EAC5B,GAAqB,GAAM;IAC1B,CAAC,EAAiB,CAAC;CAEtB,IAAM,EACJ,YACA,SAAS,IACT,kBACA,kBACA,kBACA,sBACE,EAAiB,KAAoB,KAAA,EAAU,EAI7C,EACJ,cACA,SAAS,IACT,mBACA,mBACA,kBACA,SAAS,OACP,EAAiB;EACnB,UAAU,KAAoB,KAAA;EAC9B,WAAW,CAAC;EACb,CAAC,EAGI,IAAgB,GACnB,GAA6B,MAAuB;EACnD,IAAM,IAAU,GAAsB,EAAW;AACjD,SAAO,EAAK,MAAM,MAAM,EAAE,aAAa,KAAoB,EAAE,iBAAiB,EAAQ,IAAI;IAE5F,CAAC,EAAiB,CACnB,EAGK,IAAmB,GACtB,MAAuB,EAAc,IAAU,EAAW,EAC3D,CAAC,GAAe,GAAS,CAC1B,EAKK,KAAyB,EAC7B,OAAO,GAAoB,MAAmE;AAC5F,MAAI,CAAC,KAAoB,MAAU,KAAA,EAAW,QAAO;EACrD,IAAM,IAAU,GAAsB,EAAW,EAC7C,IAAW,EAAiB,EAAW;AAkC3C,SA9BI,CAAC,KAAY,OAGf,IAAW,GAFC,MAAM,IAAiB,GACf,MAAM,4BAA4B,EAAE,EACxB,EAAW,GAEzC,KAAS,EAAM,MACb,IAGU,MAAM,GAAc;GAC9B,IAAI,EAAS;GACb,cAAc;GACd,YAAY,EAAM;GAClB,QAAQ,EAAM;GACd,UAAU;GACX,CAAC,KACa,OAEL,MAAM,GAAc;GAC9B,UAAU;GACV,cAAc;GACd,YAAY,EAAM;GAClB,QAAQ,EAAM;GACf,CAAC,KACa,OAEb,MAAU,QAAQ,IAEb,EAAc,EAAS,GAAG,GAE5B;IAET;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CACF,EAEK,CAAC,IAAoB,KAAyB,EAAS,GAAM,EAC7D,CAAC,GAAe,KAAoB,EAAmC,KAAA,EAAU,EACjF,CAAC,IAAc,MAAmB,EAAS,GAAM,EAYjD,CAAC,IAAqB,KAA0B,EAAgD,KAAA,EAAU,EAG1G,CAAC,IAAoB,KAAyB,EAAwB,KAAK,EAG3E,KAAiB,QAAyC;AAC9D,MAAI,CAAC,EAAe,QAAO;EAC3B,IAAM,IAAS,EAAiB,EAAc,KAAK;AACnD,SAAO,IAAS;GAAE,KAAK,EAAO;GAAY,QAAQ,EAAO;GAAQ,GAAG;IACnE,CAAC,GAAe,EAAiB,CAAC,EAI/B,KAAuB,OAAwB,KAAA,IAAkC,KAAtB,IAI3D,KAAsB,QAAc;EACxC,IAAM,oBAAM,IAAI,KAAkC;AAClD,OAAK,IAAM,KAAK,GAAS;GACvB,IAAM,IAAS,EAAiB,EAAE,KAAK;AACvC,GAAI,KAAQ,EAAI,IAAI,EAAE,MAAM,EAAO;;AAErC,SAAO;IACN,CAAC,GAAS,EAAiB,CAAC,EAEzB,KAAkB,QAAkB;AAIxC,EAHA,EAAuB,KAAA,EAAU,EACjC,EAAsB,KAAK,EAC3B,EAAiB,KAAA,EAAU,EAC3B,EAAsB,GAAK;IAC1B,EAAE,CAAC,EAEA,KAAmB,GAAa,MAAyB;AAI7D,EAHA,EAAuB,KAAA,EAAU,EACjC,EAAsB,KAAK,EAC3B,EAAiB,EAAO,EACxB,EAAsB,GAAK;IAC1B,EAAE,CAAC,EAEA,KAAmB,EACvB,OAAO,GAAsB,MAAuC;AAC7D,SAGL;GADA,GAAgB,GAAK,EACrB,EAAsB,KAAK;AAC3B,OAAI;IAGF,IAAM,IAAW,QAAQ,KAAQ,CAAC,CAAC,EAAK,IAEpC,IAAiC;AASrC,QARA,AAGE,IAHE,IACU,MAAM,GAAa,EAAgC,GAEnD,MAAM,GAAa,EAAkD,EAK/E,GAAW;KACb,IAAM,IAAiB,CAAC,GAAG,EAAQ;AACnC,SAAI,GAAU;MAEZ,IAAM,IAAM,EAAe,WAAW,MAAM,EAAE,OAAO,GAAW,GAAG;AACnE,MAAI,KAAO,MACT,EAAe,KAAO;WAIxB,GAAe,KAAK,EAAU;AAQhC,SAJA,EAAoB,GAAkB,EAAE,SADlB,EAAe,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EACN,CAAC,EAI7D,CADc,MAAM,GAAuB,EAAU,MAAM,EAAQ,EACvD;AAkBd,MAZA,EAAuB,EAAQ,EAC/B,EACE,IACI,EACE,yDACA,yFACD,GACD,EACE,2DACA,6EACD,CACN,EACD,EAAiB,EAAU;AAC3B;;AAMF,KAHA,EAAuB,KAAA,EAAU,EACjC,EAAsB,KAAK,EAC3B,EAAsB,GAAM,EAC5B,EAAiB,KAAA,EAAU;;YAEtB,GAAK;AAIZ,IADA,QAAQ,KAAK,iCAAiC,EAAI,EAClD,EACE,aAAe,QACX,EAAI,UACJ,EAAE,kDAAkD,+CAA+C,CACxG;aACO;AACR,OAAgB,GAAM;;;IAG1B;EAAC;EAAkB;EAAS;EAAc;EAAc;EAAqB;EAAwB;EAAE,CACxG,EAEK,KAAqB,EACzB,OAAO,MAAqB;AACrB,WAGH,OAAO,QACL,EAAE,qDAAqD,+CAA+C,CACvG,EACD;GACA,IAAM,IAAS,EAAQ,MAAM,MAAM,EAAE,OAAO,EAAS;AAErD,OADgB,MAAM,GAAa,EAAS,KAI1C,EAAoB,GAAkB,EAAE,SADjB,EAAQ,QAAQ,MAAM,EAAE,OAAO,EAAS,EACE,CAAC,EAG9D,IAAQ;IACV,IAAM,IAAS,EAAiB,EAAO,KAAK;AAC5C,IAAI,KAAQ,MAAM,EAAc,EAAO,GAAG;;;IAKlD;EAAC;EAAkB;EAAS;EAAc;EAAqB;EAAkB;EAAe;EAAE,CACnG,EAEK,KAAqB,EACzB,OAAO,MAAkB;AACvB,MAAI,CAAC,KAAoB,MAAU,EAAG;EACtC,IAAM,IAAgB,CAAC,GAAG,EAAQ,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EAC9D,IAAW,EAAc,KAAK,MAAM,EAAE,GAAG;AAG/C,EAFA,CAAC,EAAS,IAAQ,IAAI,EAAS,MAAU,CAAC,EAAS,IAAQ,EAAS,IAAQ,GAAG,EAC/D,MAAM,EAAe,EAAS,IAS5C,EAAoB,GAAkB,EAAE,SANtB,EACf,KAAK,GAAG,OAAQ;GACf,GAAG;GACH,OAAO,EAAS,QAAQ,EAAE,GAAG;GAC9B,EAAE,CACF,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EACwB,CAAC;IAGjE;EAAC;EAAkB;EAAS;EAAgB;EAAoB,CACjE,EAEK,KAAuB,EAC3B,OAAO,MAAkB;AACvB,MAAI,CAAC,EAAkB;EACvB,IAAM,IAAgB,CAAC,GAAG,EAAQ,CAAC,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;AACpE,MAAI,MAAU,EAAc,SAAS,EAAG;EACxC,IAAM,IAAW,EAAc,KAAK,MAAM,EAAE,GAAG;AAG/C,EAFA,CAAC,EAAS,IAAQ,EAAS,IAAQ,MAAM,CAAC,EAAS,IAAQ,IAAI,EAAS,GAAO,EAC/D,MAAM,EAAe,EAAS,IAS5C,EAAoB,GAAkB,EAAE,SANtB,EACf,KAAK,GAAG,OAAQ;GACf,GAAG;GACH,OAAO,EAAS,QAAQ,EAAE,GAAG;GAC9B,EAAE,CACF,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,EACwB,CAAC;IAGjE;EAAC;EAAkB;EAAS;EAAgB;EAAoB,CACjE;AAED,SAAgB;AACd,EAAI,KAAoB,EAAQ,UAAU,KAAK,CAAC,MAC9C,EAAoB,GAAkB,EAAE,YAAS,CAAC;IAEnD;EAAC;EAAkB;EAAS;EAAgB;EAAoB,CAAC;CAEpE,IAAM,IAAkB,GAA6C,GAAQ,MAA6B;AAExG,EADA,GAAc,MAAU,KAAO;GAAE,GAAG;IAAO,IAAM;GAAO,CAAS,EACjE,EAAqB,GAAK;IACzB,EAAE,CAAC,EAEA,IAAoB,GAAa,MAAuC;AAE5E,EADA,GAAc,MAAU,KAAO;GAAE,GAAG;GAAM,QAAQ;GAAW,CAAS,EACtE,EAAqB,GAAK;IACzB,EAAE,CAAC;AAGN,SAAgB;AACd,MAAI,GAAW,cAAc,EAAU,SAAS,GAAG;GACjD,IAAM,IAAK,EAAU,MAAM,MAAM,EAAE,OAAO,EAAU,WAAW;AAC/D,GAAI,KACF,EAAuB,EAAG,IAAI;QAGhC,GAAuB,KAAK;IAE7B,CAAC,GAAW,YAAY,EAAU,CAAC;CAGtC,IAAM,KAAuB,GAC1B,MAAuB;AAKtB,MAJA,EAAkB,KAAA,EAAU,EAC5B,EAA0B,GAAM,EAChC,EAAuB,KAAK,EAC5B,EAAuB,EAAI,EACvB,GAAK;GACP,IAAM,IAAK,EAAU,MAAM,MAAM,EAAE,QAAQ,EAAI;AAC/C,GAAI,MACF,EAAgB,cAAc,EAAG,GAAG,EAEpC,GAAc,MACZ,KACI;IACE,GAAG;IACH,QAAQ;KAAE,GAAG,EAAK;KAAQ,aAAa;KAAK;IAC7C,CAEN,EACD,EAAqB,GAAK;QAa5B,CAVA,EAAgB,cAAc,KAAA,EAAU,EACxC,GAAc,MACZ,KACI;GACE,GAAG;GACH,QAAQ;IAAE,GAAG,EAAK;IAAQ,aAAa,KAAA;IAAW;GAClD,UAAU,KAAA;GACX,CAEN,EACD,EAAqB,GAAK;IAG9B,CAAC,GAAW,EAAgB,CAC7B,EAGK,KAAqB,EACzB,OAAO,MAA4B;AAOjC,MANA,EAAgB,YAAY,KAAY,KAAA,EAAU,EAClD,EAAkB,KAAA,EAAU,EAC5B,EAA0B,GAAM,EAChC,EAAuB,KAAK,EAGxB,KAAY,GAAgB;AAC9B,MAAqB,GAAK;AAC1B,OAAI;IACF,IAAM,IAAS,MAAM,GAAc,EAAS;AAE5C,QAAI,GAAQ,SAAS;AAEnB,KADA,EAAkB,EAAO,OAAO,EAChC,EAA0B,GAAK;KAC/B,IAAM,IAAiB,GAA6B,EAAO,OAAO;AAGlE,MAAI,EAAe,cAAc,EAAe,gBAC9C,GAAc,MAAS;AACrB,UAAI,CAAC,EAAM,QAAO;MAElB,IAAM,IAAgB,EAAK,UAAU,EAAE,EACjC,IAAY,EAAE,GAAG,GAAe;AAiBtC,aAdI,EAAe,cAAc,CAAC,EAAc,eAC9C,EAAU,aAAa,EAAe,aAIpC,EAAe,cAAc,CAAC,EAAc,eAC9C,EAAU,aAAa,EAAe,aAIpC,EAAe,cAAc,CAAC,EAAc,eAC9C,EAAU,aAAa,EAAe,aAGjC;OAAE,GAAG;OAAM,QAAQ;OAAW;OACrC,EACF,EAAqB,GAAK;UAI5B,CADA,EAA0B,GAAK,EAC/B,EACE,GAAQ,SACN,EAAE,qDAAqD,0CAA0C,CACpG;YAEI,GAAK;AAOZ,IANA,EAA0B,GAAK,EAC/B,EACE,aAAe,QACX,EAAI,UACJ,EAAE,qDAAqD,0CAA0C,CACtG,EACD,QAAQ,KAAK,wCAAwC,EAAI;aACjD;AACR,OAAqB,GAAM;;;IAIjC;EAAC;EAAiB;EAAe;EAAgB;EAAE,CACpD,EAEK,KAAgB,EAAY,YAAY;AACxC,SAAC,KAAkB,CAAC,IAExB;KAAc,SAAS;AAEvB,OAAI;IACF,IAAM,IAAW;KAAE,GAAI,EAAe,YAAY,EAAE;KAAG,aAAa,EAAU,eAAe;KAAI,EAM3F,IAAe,GADlB,EAAe,UAAU,CAAC,QAAQ,IAAI,EAAe,GAAG,EAAE,UAAsC,EAAE,EAC7C,EAAgB,SAAS,EAAU,OAAO,EAM5F,IAAe,GALA,EAAiB;KACpC,YAAY,EAAU;KACtB,UAAU,EAAU;KACpB,QAAQ;MAAE,GAAG;MAAc,SAAS,KAAA;MAAW;KAChD,CAAC,EACwD,EAAa,EAEjE,IAAS,MAAM,GAAgB;KACnC,IAAI,EAAe;KACnB,OAAO,EAAU;KACjB,UAAU,EAAU;KACpB,UAAU,EAAU;KACpB,QAAQ,EAAa;KACrB,iBAAiB,EAAU;KAE3B,YAAY,EAAa;KACzB,WAAW,EAAU;KACrB,UAAU,EAAa;KACvB,aAAa,EAAU;KACvB;KACD,CAAC,EAGE,IAAiB,EAAe;AACpC,QAAI,EAAU,iBAAiB;KAC7B,IAAM,EAAE,YAAS,sBAAmB,mBAAgB,qBAAkB,sBAAmB,aAAU,cACjG,EAAU;AAEZ,SAAI,EAAe,WAAW,IAAI;MAEhC,IAAM,IAAkB,MAAM,GAAgB;OAC5C,IAAI,EAAe,UAAU;OAC7B;OACA;OACA;OACA;OACA;OACA;OACA;OACD,CAAC;AACF,MAAI,MACF,IAAiB;gBAEV,GAAmB;MAE5B,IAAM,IAAkB,MAAM,GAAgB;OAC5C,gBAAgB,EAAe;OAC/B;OACA,kBAAkB,KAAoB,EAAE;OACxC;OACA;OACA;OACA;OACD,CAAC;AACF,MAAI,MACF,IAAiB;OAAE,GAAG;OAAiB;OAAS;;WAG3C,EAAe,WAAW,MAAM,CAAC,EAAU,oBAEpD,MAAM,GAAgB,EAAe,UAAU,GAAG,EAClD,IAAiB,KAAA;AAGnB,IAAI,KACF,EAAoB,EAAe,IAAI;KACrC,OAAO,EAAU;KACjB,aAAa,EAAU;KACvB,UAAU,EAAU;KACpB,UAAU,EAAU;KACpB,QAAQ,EAAa;KACrB,iBAAiB,EAAU;KAE3B,YAAY,EAAa;KACzB,WAAW,EAAU;KACrB,UAAU,EAAa;KACvB,aAAa,EAAU;KAEvB,WAAW;KAEX,4BAAW,IAAI,MAAM,EAAC,aAAa;KACpC,CAAoB,EAErB,EAAc,QAAQ,EACtB,EAAqB,GAAM,EAC3B,iBAAiB,EAAc,OAAO,EAAE,IAAK,IAE7C,EAAc,QAAQ;WAEZ;AACZ,MAAc,QAAQ;;;IAEvB;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,EAEI,KAAmB,QAAkB;AACpC,QAEL,EAAa,GAA+B,EAAe,CAAC,EAC5D,EAAgB,UAAW,EAAe,UAAsC,EAAE,EAClF,EAAqB,GAAM,EAC3B,EAAc,OAAO;IACpB,CAAC,EAAe,CAAC,EAEd,KAAqB,GACxB,MAAuC;AACtC,IAAkB,EAAU;IAE9B,CAAC,EAAkB,CACpB,EAEK,KAAwB,GAAa,MAA4B;AAErE,EADA,GAAc,MAAU,KAAO;GAAE,GAAG;GAAM,iBAAiB;GAAQ,CAAS,EAC5E,EAAqB,GAAK;IACzB,EAAE,CAAC,EAEA,KAAa,IAAiB,EAAoB,EAAe,KAAK,GAAG,MAEzE,KAAkB,QAEpB,GAAiB,KAAK,OAAS;EAC7B,OAAO,EAAI;EACX,OAAO,EAAE,EAAI,UAAU,EAAI,MAAM;EACjC,aAAa,EAAE,EAAI,gBAAgB,EAAI,YAAY;EACpD,EAAE,EACL,CAAC,EAAE,CACJ,EAEK,KAAkB,QAEpB,GAAkB,KAAK,OAAU;EAC/B,OAAO,EAAK;EACZ,OAAO,EAAE,EAAK,UAAU,EAAK,MAAM;EACpC,EAAE,EACL,CAAC,EAAE,CACJ;AAED,QACE,kBAAC,IAAD;EAAO,MAAM;EAAQ,eAAe,MAAS,CAAC,KAAQ,IAAS;EAAE,OAAO;YAMtE,kBAAC,IAAD;GACE,OAAO;IACL,OAAO;IACP,UAAU;IACV,SAAS;IACT,SAAS;IACT,eAAe;IACf,QAAQ;IACR,UAAU;IACX;aATH;IAWE,kBAAC,IAAD;KACE,OAAO;MACL,SAAS;MACT,cAAc;MACd,iBAAiB;MACjB,YAAY;MACb;eAED,kBAAC,OAAD;MAAK,OAAO;OAAE,SAAS;OAAQ,YAAY;OAAU,KAAK;OAA2B;gBAArF,CACE,kBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,QAAQ;QACR,OAAO;QACP,YAAY;QACZ,gBAAgB;QAChB,cAAc;QACd,iBAAiB;QACjB,WAAW;QACX,YAAY;QACb;iBAED,kBAAC,IAAD,EACE,OAAO;QACL,QAAQ;QACR,OAAO;QACP,OAAO;QACR,EACD,CAAA;OACE,CAAA,EACN,kBAAC,OAAD;OAAK,OAAO;QAAE,MAAM;QAAG,UAAU;QAAG;iBAApC,CACE,kBAAC,OAAD;QAAK,OAAO;SAAE,SAAS;SAAQ,YAAY;SAAU,KAAK;SAAwB;kBAAlF;SACE,kBAAC,IAAD;UACE,OAAO;WACL,UAAU;WACV,YAAY;WACZ,OAAO;WACP,QAAQ;WACT;oBAEA,IACG,EAAE,wDAAwD,oBAAoB,GAC9E,EAAE,kDAAkD,aAAa;UAC1D,CAAA;SACZ,MAAe,YACd,kBAAC,IAAD,EAAS,OAAO;UAAE,QAAQ;UAAuB,OAAO;UAAuB,EAAI,CAAA;SAEpF,MAAe,WACd,kBAAC,GAAD,EACE,OAAO;UACL,QAAQ;UACR,OAAO;UACP,OAAO;UACR,EACD,CAAA;SAEH,MAAe,WACd,kBAAC,IAAD,EACE,OAAO;UACL,QAAQ;UACR,OAAO;UACP,OAAO;UACR,EACD,CAAA;SAEA;WACL,MACC,kBAAC,IAAD;QACE,OAAO;SACL,UAAU;SACV,OAAO;SACP,QAAQ;SACR,YAAY;SACb;kBAEA,GAAW;QACK,CAAA,CAEjB;SACF;;KACM,CAAA;IAEb,IACC,kBAAC,IAAD;KACE,OAAO;KACP,gBAAgB,MAAM,GAAa,EAAW;KAC9C,OAAO;MAAE,MAAM;MAAG,SAAS;MAAQ,eAAe;MAAU,WAAW;MAAG,UAAU;MAAU;eAHhG,CAWE,kBAAC,IAAD;MACE,OAAO;OACL,OAAO;OACP,SAAS;OACT,UAAU;OACV,gBAAgB;OAChB,cAAc;OACd,cAAc;OACd,SAAS;OACT,iBAAiB;OACjB,WAAW;OACX,QAAQ;OACR,YAAY;OACZ,KAAK;OACN;gBAdH;OAgBE,kBAAC,GAAD;QAAa,OAAM;QAAU,OAAO;kBACjC,EAAE,4CAA4C,UAAU;QAC7C,CAAA;OACd,kBAAC,GAAD;QAAa,OAAM;QAAO,OAAO;kBAC9B,EAAE,yCAAyC,OAAO;QACvC,CAAA;OACd,kBAAC,GAAD;QAAa,OAAM;QAAW,OAAO;kBAClC,EAAE,6CAA6C,WAAW;QAC/C,CAAA;OACd,kBAAC,GAAD;QAAa,OAAM;QAAa,OAAO;kBACpC,EAAE,0CAA0C,QAAQ;QACzC,CAAA;OACL;SAEX,kBAAC,OAAD;MAAK,OAAO;OAAE,MAAM;OAAG,UAAU;OAAQ,SAAS;OAA4B,WAAW;OAAG;gBAA5F;OACE,kBAAC,GAAD;QAAa,OAAM;QAAU,WAAU;kBACpC,KACC,kBAAC,GAAD,EAAA,UACE,kBAAC,IAAD;SACE,OAAO,EAAU;SACjB,aAAa,EAAU;SACvB,iBAAiB,EAAU;SAC3B,gBAAgB,MAAU,EAAgB,SAAS,EAAM;SACzD,sBAAsB,MAAgB,EAAgB,eAAe,EAAY;SACjF,0BAA0B,MAAoB,EAAgB,mBAAmB,EAAgB;SACjG,CAAA,EACW,CAAA;QAEL,CAAA;OAEd,kBAAC,GAAD;QAAa,OAAM;QAAO,WAAU;kBACjC,KACC,kBAAC,GAAD,EAAA,UAAA,QACU;SACN,IAAM,IAAU,EAAiB;UAC/B,YAAY,EAAU;UACtB,UAAU,EAAU;UACpB,QAAQ;WAAE,GAAG,EAAU;WAAQ,SAAS,KAAA;WAAW;UACpD,CAAC,EACI,IAAa,GAAuB;UACxC;UACA,SAAS;UACT,OAAO;UACP,SAAS;UACT,iBAAiB;UAClB,CAAC;AAEF,gBACE,kBAAC,IAAD;UACE,WAAW,GAAgB,eAAe;UACjC;UACT,aAAa;UACb,UACE,OAAO,EAAU,OAAO,YAAa,WAAW,EAAU,OAAO,WAAW,KAAA;UAE9E,QAAQ,EAAW;UACnB,eAAe,EAAW;UAC1B,SAAS;UACT,OAAO;UACP,kBAAkB;UAClB,gBAAgB;UAChB,mBAAmB,MAAa,EAAkB;WAAE,GAAG,EAAU;WAAQ;WAAU,CAAC;UACpF,CAAA;YAEF,EASJ,kBAAC,GAAD;SACE,UAAS;SACT,OAAO,EAAE,wDAAwD,2BAA2B;SAC5F,aAAY;SACZ,cAAc;SACd,eAAY;mBAEZ,kBAAC,OAAD;UAAK,WAAU;oBAAf;WACE,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,qDAAqD,mBAAmB,EAAS,CAAA,EAC3F,kBAAC,IAAD;aACE,OAAQ,EAAU,QAAQ,kBAA6B;aACvD,gBAAgB,MACd,EAAkB;cAAE,GAAG,EAAU;cAAQ,gBAAgB,KAAS,KAAA;cAAW,CAAC;uBAHlF,CAME,kBAAC,IAAD;cACE,OAAO;eAAE,QAAQ;eAAgC,UAAU;eAA4B;wBAEvF,kBAAC,IAAD,EACE,aAAa,EACX,2DACA,iBACD,EACD,CAAA;cACY,CAAA,EAChB,kBAAC,IAAD;cACE,UAAS;cACT,MAAK;cACL,YAAY;cACZ,OAAM;cACN,OAAO;eACL,QAAQ;eACR,WAAW;eACX,iBAAiB;eACjB,QAAQ;eACR,WAAW;eACX,cAAc;eACf;wBAEA,GAAgB,KAAK,MACpB,kBAAC,GAAD;eAA6B,OAAO,EAAK;yBACtC,EAAK;eACK,EAFI,EAAK,MAET,CACb;cACY,CAAA,CACT;eACL;;WAEL,EAAU,QAAQ,mBAAmB,cACpC,kBAAC,OAAD;YAAK,WAAU;sBAAf;aAEE,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,sDAAsD,iBAAiB,EAAS,CAAA,EAC1F,kBAAC,GAAD;eACE,OAAQ,EAAU,QAAQ,eAA0B;eACpD,WAAW,MACT,EAAkB;gBAAE,GAAG,EAAU;gBAAQ,aAAa,EAAE,OAAO,SAAS,KAAA;gBAAW,CAAC;eAEtF,aAAY;eACZ,CAAA,CACE;;aAGN,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,qDAAqD,cAAc,EAAS,CAAA,EACtF,kBAAC,IAAD;eACE,OAAQ,EAAU,QAAQ,cAAyB;eACnD,gBAAgB,MAAU,EAAkB;gBAAE,GAAG,EAAU;gBAAQ,YAAY;gBAAO,CAAC;yBAFzF,CAIE,kBAAC,IAAD;gBAAe,OAAO,EAAE,QAAQ,gCAAgC;0BAC9D,kBAAC,IAAD,EAAe,CAAA;gBACD,CAAA,EAChB,kBAAC,IAAD;gBAAe,UAAS;gBAAS,MAAK;gBAAS,YAAY;0BAA3D;iBACE,kBAAC,GAAD;kBAAY,OAAM;4BAAM;kBAAgB,CAAA;iBACxC,kBAAC,GAAD;kBAAY,OAAM;4BAAO;kBAAiB,CAAA;iBAC1C,kBAAC,GAAD;kBAAY,OAAM;4BAAM;kBAAgB,CAAA;iBACxC,kBAAC,GAAD;kBAAY,OAAM;4BAAS;kBAAmB,CAAA;iBAChC;kBACT;iBACL;;aAGN,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,kDAAkD,iBAAiB,EAAS,CAAA,EACtF,kBAAC,GAAD;eACE,OAAQ,EAAU,QAAQ,WAAsB;eAChD,WAAW,MACT,EAAkB;gBAAE,GAAG,EAAU;gBAAQ,SAAS,EAAE,OAAO,SAAS,KAAA;gBAAW,CAAC;eAElF,MAAM;eACN,aAAY;eACZ,WAAU;eACV,CAAA,CACE;;aAGN,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UACG,EAAE,yDAAyD,iBAAiB,EACvE,CAAA,EACR,kBAAC,IAAD;eACE,OAAQ,EAAU,QAAQ,YAAuB;eACjD,gBAAgB,MAAU,EAAkB;gBAAE,GAAG,EAAU;gBAAQ,UAAU;gBAAO,CAAC;yBAFvF,CAIE,kBAAC,IAAD;gBAAe,OAAO,EAAE,QAAQ,gCAAgC;0BAC9D,kBAAC,IAAD,EAAe,CAAA;gBACD,CAAA,EAChB,kBAAC,IAAD;gBAAe,UAAS;gBAAS,MAAK;gBAAS,YAAY;0BAA3D;iBACE,kBAAC,GAAD;kBAAY,OAAM;4BACf,EAAE,8CAA8C,OAAO;kBAC7C,CAAA;iBACb,kBAAC,GAAD;kBAAY,OAAM;4BACf,EAAE,gDAAgD,UAAU;kBAClD,CAAA;iBACb,kBAAC,GAAD;kBAAY,OAAM;4BACf,EAAE,gDAAgD,eAAe;kBACvD,CAAA;iBACb,kBAAC,GAAD;kBAAY,OAAM;4BACf,EAAE,+CAA+C,aAAa;kBACpD,CAAA;iBACC;kBACT;iBACL;;aAGL,CAAC,EAAE,EAAU,QAAQ,YAAY,EAAU,OAAO,aAAa,WAC9D,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UACG,OAAO,EAAU,OAAO,SAAS,KAAK,UACnC,EAAE,sDAAsD,0BAA0B,GAClF,EAAE,mDAAmD,YAAY,EAC/D,CAAA,EACR,kBAAC,GAAD;eACE,MAAK;eACL,OAAQ,EAAU,QAAQ,aAAwB;eAClD,WAAW,MACT,EAAkB;gBAAE,GAAG,EAAU;gBAAQ,WAAW,EAAE,OAAO,SAAS,KAAA;gBAAW,CAAC;eAEpF,aACE,EAAU,OAAO,aAAa,UAC1B,EACE,4DACA,oBACD,GACD,EACE,yDACA,yBACD;eAEP,CAAA,CACE;;cAIN,EAAU,QAAQ,eAAe,UAAU,EAAU,QAAQ,eAAe,UAC5E,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UACG,EAAE,sDAAsD,sBAAsB,EACzE,CAAA,EACR,kBAAC,GAAD;eACE,OAAQ,EAAU,QAAQ,eAA0B;eACpD,WAAW,MACT,EAAkB;gBAAE,GAAG,EAAU;gBAAQ,aAAa,EAAE,OAAO,SAAS,KAAA;gBAAW,CAAC;eAEtF,MAAM;eACN,aAAY;eACZ,WAAU;eACV,CAAA,CACE;;aAIR,kBAAC,OAAD;cAAK,WAAU;wBAAf;eACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,+CAA+C,uBAAuB,EAAS,CAAA;eACzF,kBAAC,GAAD;gBACE,OAAQ,EAAU,QAAQ,YAAuB;gBACjD,WAAW,MACT,EAAkB;iBAAE,GAAG,EAAU;iBAAQ,UAAU,EAAE,OAAO,SAAS,KAAA;iBAAW,CAAC;gBAEnF,aAAY;gBACZ,CAAA;eACF,kBAAC,KAAD;gBAAG,WAAU;0BACV,EACC,kDACA,yDACD;gBACC,CAAA;eACA;;aACF;;WAGP,EAAU,QAAQ,mBAAmB,aACpC,kBAAC,OAAD;YAAK,WAAU;sBAAf;aAEE,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UACG,EAAE,sDAAsD,qBAAqB,EACxE,CAAA,EACR,kBAAC,GAAD;eACE,OAAQ,EAAU,QAAQ,mBAA8B;eACxD,WAAW,MACT,EAAkB;gBAChB,GAAG,EAAU;gBACb,iBAAiB,EAAE,OAAO,SAAS,KAAA;gBACpC,CAAC;eAEJ,aAAY;eACZ,CAAA,CACE;;aAGN,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,mDAAmD,UAAU,EAAS,CAAA,EAChF,kBAAC,GAAD;eACE,OAAQ,EAAU,QAAQ,gBAA2B;eACrD,WAAW,MACT,EAAkB;gBAAE,GAAG,EAAU;gBAAQ,cAAc,EAAE,OAAO,SAAS,KAAA;gBAAW,CAAC;eAEvF,MAAM;eACN,aAAa;eAOb,WAAU;eACV,CAAA,CACE;;aAGN,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UACG,EAAE,uDAAuD,6BAA6B,EACjF,CAAA,EACR,kBAAC,GAAD;eACE,OAAQ,EAAU,QAAQ,oBAA+B;eACzD,WAAW,MACT,EAAkB;gBAChB,GAAG,EAAU;gBACb,kBAAkB,EAAE,OAAO,SAAS,KAAA;gBACrC,CAAC;eAEJ,MAAM;eACN,aAAY;eACZ,WAAU;eACV,CAAA,CACE;;aAGN,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,GAAD,EAAA,UACG,EAAE,qDAAqD,2BAA2B,EAC7E,CAAA,EACR,kBAAC,GAAD;eACE,OAAQ,EAAU,QAAQ,WAAsB;eAChD,WAAW,MACT,EAAkB;gBAAE,GAAG,EAAU;gBAAQ,SAAS,EAAE,OAAO,SAAS,KAAA;gBAAW,CAAC;eAElF,MAAM;eACN,aAAY;eACZ,WAAU;eACV,CAAA,CACE;;aAGN,kBAAC,OAAD;cAAK,WAAU;wBAAf;eACE,kBAAC,GAAD,EAAA,UAAQ,EAAE,+CAA+C,uBAAuB,EAAS,CAAA;eACzF,kBAAC,GAAD;gBACE,OAAQ,EAAU,QAAQ,YAAuB;gBACjD,WAAW,MACT,EAAkB;iBAAE,GAAG,EAAU;iBAAQ,UAAU,EAAE,OAAO,SAAS,KAAA;iBAAW,CAAC;gBAEnF,aAAY;gBACZ,CAAA;eACF,kBAAC,KAAD;gBAAG,WAAU;0BACV,EACC,qDACA,6CACD;gBACC,CAAA;eACA;;aACF;;WAGP,EAAU,QAAQ,mBAAmB,UACpC,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,GAAD,EAAA,UACG,EAAE,qDAAqD,qBAAqB,EACvE,CAAA,EACR,kBAAC,GAAD;aACE,OAAQ,EAAU,QAAQ,YAAuB;aACjD,WAAW,MACT,EAAkB;cAAE,GAAG,EAAU;cAAQ,UAAU,EAAE,OAAO,SAAS,KAAA;cAAW,CAAC;aAEnF,MAAM;aACN,aAAY;aACZ,WAAU;aACV,CAAA,CACE;;WAEJ;;SACE,CAAA,CACG,EAAA,CAAA;QAEL,CAAA;OAEd,kBAAC,GAAD;QAAa,OAAM;QAAW,WAAU;kBACtC,kBAAC,GAAD,EAAA,UAAA;SACG,KACC,kBAAC,IAAD;UACE,UAAU,EAAU;UACH;UACjB,mBAAmB,MAAa;AAE9B,WADA,EAAgB,YAAY,EAAS,EACjC,MAAa,gBAAc,EAAgB,YAAY,KAAA,EAAU;;UAEvE,gBACE,EAAU,aAAa,eAKnB,KAAA,IAJF,kBAAC,IAAD;WACE,QAAQ;YAAE,GAAG;YAAgB,UAAU,EAAU;YAAU,UAAU,EAAU;YAAU;WACzF,SAAS,MAAa,EAAgB,YAAY,EAAS;WAC3D,CAAA;UAGN,CAAA;SAGJ,kBAAC,GAAD;UACE,OAAO,EAAE,gDAAgD,iBAAiB;UAC1E,aAAa,EACX,mDACA,mCACD;UACD,cAAc;UACd,MACE,EAAQ,SAAS,IACf,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAQ;WACJ,CAAA,GACL,KAAA;UAEN,SACE,kBAAC,GAAD;WAAQ,MAAK;WAAK,SAAS;qBAA3B,CACE,kBAAC,IAAD,EAAM,WAAU,UAAW,CAAA,EAC1B,EAAE,4CAA4C,MAAM,CAC9C;;UAEX,eAAY;oBAEX,KACC,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,IAAD,EAAS,WAAU,UAAW,CAAA;WAC1B,CAAA,GACJ,EAAQ,WAAW,IACrB,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAE,8CAA8C,wBAAwB;WACvE,CAAA,GAEJ,kBAAC,OAAD;WAAK,WAAU;qBACZ,CAAC,GAAG,EAAQ,CACV,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CACjC,KAAK,GAAQ,MACZ,kBAAC,IAAD;YAAsB,WAAW,EAAO,WAAW,eAAe;sBAChE,kBAAC,IAAD;aAAa,WAAU;uBAAvB;cACE,kBAAC,OAAD;eAAK,WAAU;yBAAf,CACE,kBAAC,GAAD;gBACE,SAAQ;gBACR,MAAK;gBACL,eAAe,GAAmB,EAAM;gBACxC,UAAU,MAAU;gBACpB,WAAU;0BAEV,kBAAC,IAAD,EAAW,WAAU,UAAW,CAAA;gBACzB,CAAA,EACT,kBAAC,GAAD;gBACE,SAAQ;gBACR,MAAK;gBACL,eAAe,GAAqB,EAAM;gBAC1C,UAAU,MAAU,EAAQ,SAAS;gBACrC,WAAU;0BAEV,kBAAC,IAAD,EAAa,WAAU,UAAW,CAAA;gBAC3B,CAAA,CACL;;cAEN,kBAAC,OAAD;eAAK,WAAU;yBAAf,CACE,kBAAC,OAAD;gBAAK,WAAU;0BAAf,CACE,kBAAC,QAAD;iBACE,eAAY;iBACZ,WAAW,gCAAgC,GAAoB,EAAO,UAAU;iBAChF,CAAA,EACF,kBAAC,QAAD;iBAAM,WAAU;2BAAgC,EAAO;iBAAa,CAAA,CAChE;mBACN,kBAAC,KAAD;gBAAG,WAAU;0BACV,GAAoB,IAAI,EAAO,KAAK,GACjC,EACE,uDACA,wBACA;iBACE,QAAQ,GAAoB,IAAI,EAAO,KAAK,EAAE,UAAU;iBACxD,KAAK,GAAoB,IAAI,EAAO,KAAK,EAAE,cAAc;iBAC1D,CACF,GACD,EAAE,mDAAmD,iBAAiB;gBACxE,CAAA,CACA;;cAEN,kBAAC,OAAD;eAAK,WAAU;yBAAf,CACE,kBAAC,GAAD;gBACE,SAAQ;gBACR,MAAK;gBACL,cAAY,QAAQ,EAAO;gBAC3B,eAAe,GAAiB,EAAO;0BAEvC,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;gBACtB,CAAA,EACT,kBAAC,GAAD;gBACE,SAAQ;gBACR,MAAK;gBACL,cAAY,UAAU,EAAO;gBAC7B,eAAe,GAAmB,EAAO,GAAG;0BAE5C,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;gBACtB,CAAA,CACL;;cACM;;YACT,EAhEI,EAAO,GAgEX,CACP;WACA,CAAA;UAEA,CAAA;SAEV,kBAAC,GAAD;UACE,OAAO,EAAE,kDAAkD,YAAY;UACvE,aAAY;UACZ,cAAc;UACd,eAAY;oBAEZ,kBAAC,IAAD;WAAsB,QAAQ;WAAgB,UAAU;WAAyB,CAAA;UACzE,CAAA;SAEV,kBAAC,GAAD;UACE,OAAO,EAAE,iDAAiD,WAAW;UACrE,aAAY;UACZ,cAAc;UACd,eAAY;oBAEZ,kBAAC,IAAD;WAAoB,aAAa,EAAe;WAAa,UAAU,EAAe;WAAM,CAAA;UACpF,CAAA;SAEV,kBAAC,GAAD;UACE,UAAS;UACT,OAAO,EAAE,+CAA+C,SAAS;UACjE,aAAY;UACZ,cAAc;UACd,eAAY;oBAEZ,kBAAC,IAAD;WACE,aAAa,EAAe;WAC5B,UAAU,EAAe;WACzB,WAAU;WACV,CAAA;UACM,CAAA;SACG,EAAA,CAAA;QACH,CAAA;OAEd,kBAAC,GAAD;QAAa,OAAM;QAAa,WAAU;kBACvC,KACC,kBAAC,IAAD;SACE,YAAY,EAAe;SAC3B,QAAQ,EAAU,UAAU,EAAE;SAC9B,UAAU;SACV,CAAA;QAEQ,CAAA;OACV;QACD;SAEP,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,MAAK;MACL,OAAO,EAAE,2CAA2C,0BAA0B;MAC9E,cAAc;MACd,MAAM,kBAAC,IAAD,EAAM,WAAU,uBAAwB,CAAA;MAC9C,WAAU;MACV,eAAY;gBAEZ,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAEvC,CAAA;MACI,CAAA;KACN,CAAA;IAIP,KACC,kBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,WAAW;MACX,iBAAiB;MACjB,YAAY;MACb;eANH;MAcG,KACC,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,OAAO,EAAE,sDAAsD,2BAA2B;OAC1F,cAAc;OACd,WAAU;OACV,eAAY;OACZ,CAAA;MAGH,MAAe,WAAW,CAAC,KAC1B,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,OAAO,EAAE,gDAAgD,oBAAoB;OAC7E,cAAc;OACd,MAAM,kBAAC,GAAD,EAAO,WAAU,uBAAwB,CAAA;OAC/C,WAAU;OACV,eAAY;OACZ,CAAA;MAGH,MAAe,WACd,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,OAAO,EAAE,kDAAkD,yBAAyB;OACpF,cAAc;OACd,MAAM,kBAAC,IAAD,EAAG,WAAU,uBAAwB,CAAA;OAC3C,WAAU;OACV,eAAY;OACZ,CAAA;MAIJ,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,SAAS;QACT,UAAU,MAAe,YAAY,CAAC;QACtC,OAAO;SACL,MAAM;SACN,QAAQ;SACR,iBAAiB,IAAoB,mCAAmC,KAAA;SACxE,SAAS,IAAoB,IAAI;SAClC;kBAEA,MAAe,WACd,kBAAC,QAAD;SAAM,OAAO;UAAE,SAAS;UAAQ,YAAY;UAAU,KAAK;UAAwB;mBAAnF,CACE,kBAAC,IAAD,EAAS,OAAO;UAAE,OAAO;UAAuB,QAAQ;UAAuB,EAAI,CAAA,EAClF,EAAE,8CAA8C,YAAY,CACxD;aAEP,kBAAC,QAAD;SAAM,OAAO;UAAE,SAAS;UAAQ,YAAY;UAAU,KAAK;UAAwB;mBAAnF,CACE,kBAAC,GAAD,EAAO,OAAO;UAAE,OAAO;UAAuB,QAAQ;UAAuB,EAAI,CAAA,EAChF,EAAE,mDAAmD,eAAe,CAChE;;QAEF,CAAA,EACT,kBAAC,GAAD;QACE,SAAQ;QACR,SAAS;QACT,UAAU,MAAe,YAAY,CAAC;QACtC,OAAO;SACL,QAAQ;SACR,SAAS,IAAoB,IAAI;SAClC;kBAPH,CASE,kBAAC,IAAD,EAAG,OAAO;SAAE,OAAO;SAAuB,QAAQ;SAAuB,EAAI,CAAA,EAC5E,EAAE,+CAA+C,UAAU,CACrD;UACL;;MACF;;IAGR,kBAAC,IAAD;KACE,QAAQ;KACR,QAAQ;KACR,gBAAgB;KAChB,WAAW;KACX,WAAW;KACX,eAAe;AAIb,MAHA,EAAsB,GAAM,EAC5B,EAAiB,KAAA,EAAU,EAC3B,EAAuB,KAAA,EAAU,EACjC,EAAsB,KAAK;;KAE7B,QAAQ;KACR,CAAA;IACW;;EACT,CAAA;EAEV"}
|
|
1
|
+
{"version":3,"file":"PropertiesPanel.js","names":[],"sources":["../../../../src/bigconsole/components/widgets/PropertiesPanel.tsx"],"sourcesContent":["/**\n * PropertiesPanel Component\n *\n * Right-side panel for widget configuration.\n * v2.0: Added Actions tab for managing widget actions.\n * Uses fe-libs shared components for consistent styling.\n */\n\nimport React, { type FC, memo, useCallback, useState, useEffect, useRef, useMemo } from 'react';\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@burdenoff/fe-libs/ui';\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@burdenoff/fe-libs/ui';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Textarea } from '@burdenoff/fe-libs/ui';\nimport { Label } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent } from '@burdenoff/fe-libs/ui';\nimport { Plus, ChevronUp, ChevronDown, Pencil, Trash2, ExternalLink, Check, X, Info } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport type { Widget, DataSourceType, RendererType } from '../../types';\nimport { useWidgetStore } from '../../store';\nimport { useWidgetOperations, useDrilldownOperations } from '../../hooks';\nimport {\n useWidgetActions,\n type WidgetAction,\n type CreateWidgetActionInput,\n type UpdateWidgetActionInput,\n} from '../../hooks/useWidgetActions';\nimport { getWidgetDefinition } from './WidgetRegistry';\nimport { ActionConfigDialog, type ActionSaveData, type ActionWebhookDraft } from './ActionConfigDialog';\nimport { useEventWebhooks, type EventWebhookMapping } from '../../hooks/useEventWebhooks';\nimport { WebhookConfigPanel } from './webhook-config';\nimport { EventLogViewer } from './event-logs';\nimport { DrilldownConfigPanel, type DrilldownConfig } from './DrilldownConfigPanel';\n// Data Source selectors\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { useParserOperations } from '../../hooks/useParserOperations';\nimport { AdvancedSection, AppearanceSection, DataBindingSection, GeneralSection } from '../builder/inspector';\nimport { Section, SectionStack } from '../common';\nimport {\n getWidgetBinding,\n getWidgetBindingStatus,\n reconcilePanelConfig,\n serializeWidgetBinding,\n} from '../../utils/widgetBinding';\n\n// ============================================================================\n// v2.0: Renderer Options\n// ============================================================================\n\nconst RENDERER_OPTIONS: {\n value: RendererType;\n labelKey: string;\n label: string;\n descriptionKey: string;\n description: string;\n}[] = [\n {\n value: 'BIGCONSOLE',\n labelKey: 'bigconsole.widget.properties.renderer.bigconsole.label',\n label: 'BigConsole Native',\n descriptionKey: 'bigconsole.widget.properties.renderer.bigconsole.description',\n description: 'Full-featured native widgets',\n },\n {\n value: 'ADAPTIVE_CARD',\n labelKey: 'bigconsole.widget.properties.renderer.adaptiveCard.label',\n label: 'Adaptive Cards',\n descriptionKey: 'bigconsole.widget.properties.renderer.adaptiveCard.description',\n description: 'Microsoft Teams/Outlook format',\n },\n {\n value: 'BLOCKKIT',\n labelKey: 'bigconsole.widget.properties.renderer.blockKit.label',\n label: 'Block Kit',\n descriptionKey: 'bigconsole.widget.properties.renderer.blockKit.description',\n description: 'Slack format',\n },\n {\n value: 'CUSTOM',\n labelKey: 'bigconsole.widget.properties.renderer.custom.label',\n label: 'Custom',\n descriptionKey: 'bigconsole.widget.properties.renderer.custom.description',\n description: 'Custom React component',\n },\n];\n\n// ============================================================================\n// Data Source Constants\n// ============================================================================\n\nconst DATA_SOURCE_TYPES: {\n value: DataSourceType;\n labelKey: string;\n label: string;\n descriptionKey: string;\n description: string;\n}[] = [\n {\n value: 'DATASINK',\n labelKey: 'bigconsole.widget.properties.dataSourceType.datasink.label',\n label: 'DataSink',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.datasink.description',\n description: 'Use data from BigConsole DataSinks',\n },\n {\n value: 'REST_API',\n labelKey: 'bigconsole.widget.properties.dataSourceType.restApi.label',\n label: 'REST API',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.restApi.description',\n description: 'Fetch data from REST endpoints',\n },\n {\n value: 'GRAPHQL',\n labelKey: 'bigconsole.widget.properties.dataSourceType.graphql.label',\n label: 'GraphQL',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.graphql.description',\n description: 'Query data using GraphQL',\n },\n {\n value: 'SQL',\n labelKey: 'bigconsole.widget.properties.dataSourceType.sql.label',\n label: 'SQL Database',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.sql.description',\n description: 'Query SQL databases directly',\n },\n {\n value: 'WEBSOCKET',\n labelKey: 'bigconsole.widget.properties.dataSourceType.websocket.label',\n label: 'WebSocket',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.websocket.description',\n description: 'Real-time data streaming',\n },\n {\n value: 'MOCK',\n labelKey: 'bigconsole.widget.properties.dataSourceType.mock.label',\n label: 'Sample JSON',\n descriptionKey: 'bigconsole.widget.properties.dataSourceType.mock.description',\n description: 'Manually enter sample JSON data',\n },\n];\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface PropertiesPanelProps {\n isOpen: boolean;\n onClose: () => void;\n}\n\ntype TabId = 'general' | 'data' | 'appearance' | 'advanced';\n\n// The event a button emits when clicked. MUST match DashboardGrid.handleWidgetAction\n// (`widget:${name.toLowerCase().replace(/\\s+/g,'_')}:clicked`) so a webhook mapping\n// created here actually fires for the click.\nfunction eventPatternForAction(actionName: string): string {\n return `widget:${actionName.toLowerCase().replace(/\\s+/g, '_')}:clicked`;\n}\n\n// Action style colours. `bg-action-primary` / `bg-muted-foreground` are not\n// registered in the BigConsole shell theme, so they emitted no CSS at all and\n// the status dot rendered invisible — see sectionStyles.ts for the failure mode.\nconst ACTION_STYLE_COLORS: Record<string, string> = {\n PRIMARY: 'bg-action-primary-bg',\n SECONDARY: 'bg-[var(--color-text-muted)]',\n SUCCESS: 'bg-status-success-bg',\n WARNING: 'bg-status-warning-bg',\n DANGER: 'bg-status-error-bg',\n};\n\nconst ACTION_STYLE_FALLBACK_COLOR = 'bg-[var(--color-text-muted)]';\n\n/** Tab triggers: body-sm type and a 40px touch target on every device. */\nconst TAB_TRIGGER_STYLE: React.CSSProperties = {\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: 'var(--font-size-body-sm)',\n padding: '6px 10px',\n minHeight: 'var(--size-controlHeight-md)',\n flexShrink: 0,\n};\n\n// ============================================================================\n// Config Component Map\n// ============================================================================\n\n/**\n * Detects field names from parser output and returns suggested labelField and valueField\n * @param output - The parser execution output\n * @returns Object with suggested labelField and valueField\n */\nfunction detectFieldsFromParserOutput(output: unknown): {\n labelField?: string;\n valueField?: string;\n xAxisField?: string;\n} {\n if (!output) return {};\n\n // If output is an array, look at the first item\n const sampleItem = Array.isArray(output) ? output[0] : output;\n\n if (!sampleItem || typeof sampleItem !== 'object') return {};\n\n const keys = Object.keys(sampleItem as Record<string, unknown>);\n\n // Common label field names (in order of priority)\n const labelFieldCandidates = [\n 'name',\n 'label',\n 'category',\n 'title',\n 'key',\n 'id',\n 'month',\n 'date',\n 'region',\n 'product',\n ];\n // Common value field names (in order of priority)\n const valueFieldCandidates = ['value', 'amount', 'count', 'total', 'revenue', 'sales', 'quantity', 'orders', 'sum'];\n\n let labelField: string | undefined;\n let valueField: string | undefined;\n\n // Find the best label field\n for (const candidate of labelFieldCandidates) {\n if (keys.includes(candidate)) {\n labelField = candidate;\n break;\n }\n }\n\n // Find the best value field\n for (const candidate of valueFieldCandidates) {\n if (keys.includes(candidate)) {\n valueField = candidate;\n break;\n }\n }\n\n // If no standard names found, use heuristics:\n // - First string field as label\n // - First number field as value\n if (!labelField || !valueField) {\n for (const key of keys) {\n const val = (sampleItem as Record<string, unknown>)[key];\n if (!labelField && typeof val === 'string') {\n labelField = key;\n }\n if (!valueField && typeof val === 'number') {\n valueField = key;\n }\n if (labelField && valueField) break;\n }\n }\n\n return {\n labelField,\n valueField,\n xAxisField: labelField, // For line/bar charts, xAxisField is usually the same as labelField\n };\n}\n\n// ============================================================================\n// Template Editor Component\n// ============================================================================\n\ninterface TemplateEditorProps {\n widget: Widget;\n onSave: (template: Record<string, unknown>) => void;\n}\n\nconst TemplateEditor: FC<TemplateEditorProps> = memo(function TemplateEditor({ widget, onSave }) {\n const { t } = useI18n();\n const [localValue, setLocalValue] = useState<string>('');\n const [jsonError, setJsonError] = useState<string | null>(null);\n const initializedRef = useRef(false);\n\n // Initialize from widget.template only once per widget\n useEffect(() => {\n try {\n const templateStr =\n widget.template && typeof widget.template === 'object' ? JSON.stringify(widget.template, null, 2) : '';\n setLocalValue(templateStr);\n setJsonError(null);\n initializedRef.current = true;\n } catch {\n setLocalValue('');\n }\n }, [widget.id, widget.template]);\n\n const handleChange = useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const value = e.target.value;\n setLocalValue(value);\n\n if (value.trim() === '') {\n setJsonError(null);\n // Empty template is valid - notify parent\n onSave({});\n } else {\n try {\n const parsed = JSON.parse(value);\n setJsonError(null);\n // Valid JSON - immediately notify parent so main Save button becomes active\n onSave(parsed);\n } catch (err) {\n setJsonError((err as Error).message);\n // Don't call onSave for invalid JSON\n }\n }\n },\n [onSave]\n );\n\n const handleFormat = useCallback(() => {\n try {\n const parsed = JSON.parse(localValue);\n const formatted = JSON.stringify(parsed, null, 2);\n setLocalValue(formatted);\n setJsonError(null);\n onSave(parsed);\n } catch (err) {\n setJsonError((err as Error).message);\n }\n }, [localValue, onSave]);\n\n return (\n <Section\n emphasis=\"muted\"\n padding=\"compact\"\n headingLevel={4}\n title={t('bigconsole.widget.properties.template.label', 'Template (JSON)')}\n description={`The ${widget.renderer} payload this widget renders.`}\n tone={jsonError ? 'danger' : 'neutral'}\n meta={\n jsonError ? (\n <span className=\"text-xs font-medium text-[var(--color-status-error-text)]\">\n {t('bigconsole.widget.properties.template.invalidJson', 'Invalid JSON')}\n </span>\n ) : undefined\n }\n className=\"mt-form-gap\"\n data-testid=\"widget-inspector-template\"\n >\n <Textarea\n value={localValue}\n onChange={handleChange}\n spellCheck={false}\n placeholder={t('bigconsole.widget.properties.template.placeholder', 'Enter {{renderer}} template JSON...', {\n renderer: widget.renderer ?? '',\n })}\n style={{\n fontFamily: 'var(--font-family-mono)',\n fontSize: 'var(--font-size-body-sm)',\n minHeight: '180px',\n borderColor: jsonError ? 'var(--color-status-error-border)' : undefined,\n resize: 'vertical',\n }}\n />\n\n {jsonError && (\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n padding: 'var(--space-controlGap)',\n borderRadius: 'var(--radius-button)',\n marginTop: 'var(--space-controlGap)',\n }}\n >\n {jsonError}\n </p>\n )}\n\n <div className=\"mt-control-gap flex flex-wrap items-center gap-control-gap\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleFormat}\n disabled={!!jsonError || localValue.trim() === ''}\n style={{ height: 'var(--size-controlHeight-sm)', fontWeight: 'var(--font-weight-semibold)' }}\n >\n {t('bigconsole.widget.properties.template.format', 'Format')}\n </Button>\n {!jsonError && localValue.trim() !== '' && (\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-success-text)',\n display: 'flex',\n alignItems: 'center',\n gap: '4px',\n }}\n >\n <Check style={{ width: '12px', height: '12px' }} />\n {t('bigconsole.widget.properties.template.validJson', 'Valid JSON')}\n </span>\n )}\n </div>\n\n <div className=\"mt-control-gap flex flex-wrap items-center justify-between gap-x-4 gap-y-1 border-t border-[var(--color-border-subtle)] pt-control-gap text-xs text-text-muted\">\n <span className=\"font-medium\">{t('bigconsole.widget.properties.template.ctrlSToSave', 'Ctrl+S to save')}</span>\n {widget.renderer === 'ADAPTIVE_CARD' && (\n <a\n href=\"https://adaptivecards.io/designer/\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n color: 'var(--color-text-link)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n textDecoration: 'none',\n }}\n >\n {t('bigconsole.widget.properties.template.designer', 'Designer')}\n <ExternalLink style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </a>\n )}\n {widget.renderer === 'BLOCKKIT' && (\n <a\n href=\"https://app.slack.com/block-kit-builder\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n color: 'var(--color-text-link)',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n textDecoration: 'none',\n }}\n >\n {t('bigconsole.widget.properties.template.blockKitBuilder', 'Block Kit Builder')}\n <ExternalLink style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </a>\n )}\n </div>\n </Section>\n );\n});\n\n// ============================================================================\n// Local Form State Type\n// ============================================================================\n\ninterface LocalFormState {\n title: string;\n description: string;\n refreshInterval: number | undefined;\n renderer: RendererType;\n template: Record<string, unknown> | undefined;\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: string | undefined;\n datasetId: string | undefined;\n parserId: string | undefined;\n parserRules: string | undefined;\n config: Record<string, unknown>;\n // Drilldown configuration\n drilldownConfig: DrilldownConfig | undefined;\n}\n\nfunction createLocalFormStateFromWidget(widget: Widget): LocalFormState {\n const widgetFields = widget as unknown as Record<string, string | undefined>;\n const binding = getWidgetBinding({\n dataSinkId: widgetFields.dataSinkId,\n parserId: widgetFields.parserId,\n config: (widget.config as Record<string, unknown>) || {},\n });\n\n return {\n title: widget.title || '',\n description: widget.description || '',\n refreshInterval: widget.refreshInterval ?? undefined,\n renderer: (widget.renderer as RendererType) || 'BIGCONSOLE',\n template: widget.template as Record<string, unknown> | undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: binding.source.kind === 'DATASINK' ? binding.source.ref : undefined,\n datasetId: widgetFields.datasetId ?? undefined,\n parserId: binding.parserId,\n parserRules: widgetFields.parserRules ?? undefined,\n config: (widget.config as Record<string, unknown>) || {},\n // Drilldown configuration\n drilldownConfig: widget.drilldown\n ? {\n enabled: widget.drilldown.enabled ?? false,\n targetDashboardId: widget.drilldown.targetDashboardId ?? '',\n navigationMode: widget.drilldown.navigationMode ?? 'MODAL',\n parameterMapping: (widget.drilldown.parameterMapping as Record<string, string>) ?? {},\n passGlobalFilters: widget.drilldown.passGlobalFilters ?? true,\n maxDepth: widget.drilldown.maxDepth ?? 3,\n openIn: widget.drilldown.openIn ?? 'same',\n }\n : undefined,\n };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const PropertiesPanel: FC<PropertiesPanelProps> = memo(function PropertiesPanel({ isOpen, onClose }) {\n const { t } = useI18n();\n const [activeTab, setActiveTab] = useState<TabId>('general');\n const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved' | 'error'>('idle');\n const [localForm, setLocalForm] = useState<LocalFormState | null>(null);\n const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);\n\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const widgets = useWidgetStore((state) => state.widgets);\n const updateWidgetInStore = useWidgetStore((state) => state.updateWidget);\n\n const selectedWidget = selectedWidgetId ? widgets.get(selectedWidgetId) : undefined;\n const previousSelectedWidgetIdRef = useRef<string | null>(null);\n // The widget config as it was when `localForm` was last seeded. Used on save to\n // apply only the panel's own config changes without clobbering keys another\n // editor (e.g. the pivot canvas) persisted while the panel held unsaved edits.\n const seededConfigRef = useRef<Record<string, unknown>>({});\n\n /**\n * Monotonic counter bumped on every local edit.\n *\n * `handleSaveAll` snapshots it before awaiting the mutation. Any edit made\n * while that request is in flight bumps it, which tells the completion\n * handler NOT to clear `hasUnsavedChanges` — otherwise the re-seed effect\n * below (which only bails out `if (hasUnsavedChanges)`) would immediately\n * overwrite `localForm` with the just-saved server state and the user's\n * in-flight edit would vanish with no error.\n */\n const formRevisionRef = useRef(0);\n const markFormDirty = useCallback(() => {\n formRevisionRef.current += 1;\n setHasUnsavedChanges(true);\n }, []);\n\n useEffect(() => {\n if (!selectedWidgetId) {\n previousSelectedWidgetIdRef.current = null;\n setLocalForm(null);\n setHasUnsavedChanges(false);\n setSaveStatus('idle');\n return;\n }\n\n const widgetSwitched = previousSelectedWidgetIdRef.current !== selectedWidgetId;\n previousSelectedWidgetIdRef.current = selectedWidgetId;\n\n if (selectedWidget) {\n if (hasUnsavedChanges && !widgetSwitched) return;\n\n setLocalForm(createLocalFormStateFromWidget(selectedWidget));\n seededConfigRef.current = (selectedWidget.config as Record<string, unknown>) || {};\n setHasUnsavedChanges(false);\n setSaveStatus('idle');\n } else {\n setLocalForm(null);\n setHasUnsavedChanges(false);\n setSaveStatus('idle');\n }\n }, [selectedWidgetId, selectedWidget, hasUnsavedChanges]);\n\n const { updateWidget: updateWidgetApi } = useWidgetOperations(selectedWidget?.pageId, selectedWidget?.dashboardId, {\n skipFetch: true,\n });\n\n // Drilldown operations\n const { createDrilldown, updateDrilldown, deleteDrilldown } = useDrilldownOperations();\n\n // DataSink operations - for looking up DataSink by key\n const { dataSinks } = useDataSinkOperations({ skipFetch: false });\n\n // Track selected DataSink key for filtering parsers\n const [selectedDataSinkKey, setSelectedDataSinkKey] = useState<string | null>(null);\n\n // Parser operations - for executing parser to detect fields\n const { executeParser } = useParserOperations({\n consoleId: selectedWidget?.dashboardId,\n skipFetch: true,\n });\n\n // Track if we're auto-detecting fields\n const [isDetectingFields, setIsDetectingFields] = useState(false);\n const [bindingPreview, setBindingPreview] = useState<unknown>(undefined);\n const [bindingPreviewComplete, setBindingPreviewComplete] = useState(false);\n const [bindingPreviewError, setBindingPreviewError] = useState<string | null>(null);\n\n useEffect(() => {\n setBindingPreview(undefined);\n setBindingPreviewComplete(false);\n setBindingPreviewError(null);\n setIsDetectingFields(false);\n }, [selectedWidgetId]);\n\n const {\n actions,\n loading: actionsLoading,\n createAction,\n updateAction,\n deleteAction,\n reorderActions,\n } = useWidgetActions(selectedWidgetId || undefined);\n\n // Webhook mappings for this widget — used to pair a button with an HTTP call\n // inline (\"When clicked → call a webhook\") without leaving the Add-Button flow.\n const {\n webhooks,\n loading: webhooksLoading,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n refetch: refetchWebhooks,\n } = useEventWebhooks({\n widgetId: selectedWidgetId || undefined,\n skipFetch: !selectedWidgetId,\n });\n\n // Match a webhook mapping to an action (by its emitted event) within a given list.\n const findWebhookIn = useCallback(\n (list: EventWebhookMapping[], actionName: string) => {\n const pattern = eventPatternForAction(actionName);\n return list.find((w) => w.widgetId === selectedWidgetId && w.eventPattern === pattern) ?? null;\n },\n [selectedWidgetId]\n );\n\n // Find the webhook mapping paired to an action from the currently-loaded list.\n const webhookForAction = useCallback(\n (actionName: string) => findWebhookIn(webhooks, actionName),\n [findWebhookIn, webhooks]\n );\n\n // Create/update/remove the paired webhook mapping to match the dialog's outcome.\n // Returns false if a required webhook mutation failed so the caller can surface\n // it instead of silently leaving a button that looks wired but isn't (#5).\n const reconcileActionWebhook = useCallback(\n async (actionName: string, draft: ActionWebhookDraft | null | undefined): Promise<boolean> => {\n if (!selectedWidgetId || draft === undefined) return true;\n const pattern = eventPatternForAction(actionName);\n let existing = webhookForAction(actionName);\n // If the mapping list is still loading, a null lookup is unreliable and a\n // create here would duplicate an existing mapping (two endpoints firing per\n // click). Force a fresh fetch and re-check before deciding there is none (#7).\n if (!existing && webhooksLoading) {\n const res = await refetchWebhooks();\n const fresh = (res?.data?.listEventWebhookMappings ?? []) as EventWebhookMapping[];\n existing = findWebhookIn(fresh, actionName);\n }\n if (draft && draft.url) {\n if (existing) {\n // Re-activate on save — a mapping disabled in the Webhooks panel would\n // otherwise show as wired while clicks are still ignored (#4).\n const res = await updateWebhook({\n id: existing.id,\n eventPattern: pattern,\n webhookUrl: draft.url,\n method: draft.method,\n isActive: true,\n });\n return res !== null;\n }\n const res = await createWebhook({\n widgetId: selectedWidgetId,\n eventPattern: pattern,\n webhookUrl: draft.url,\n method: draft.method,\n });\n return res !== null;\n }\n if (draft === null && existing) {\n // Outcome switched away from \"call a webhook\" — drop the binding.\n return deleteWebhook(existing.id);\n }\n return true;\n },\n [\n selectedWidgetId,\n webhookForAction,\n webhooksLoading,\n refetchWebhooks,\n findWebhookIn,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n ]\n );\n\n const [isActionDialogOpen, setIsActionDialogOpen] = useState(false);\n const [editingAction, setEditingAction] = useState<WidgetAction | undefined>(undefined);\n const [actionSaving, setActionSaving] = useState(false);\n // When a webhook mutation fails after the action itself saved, we keep the\n // dialog open on the persisted action so the user can retry. That reassigns\n // the dialog's `action` prop and re-runs its reset effect, which would wipe the\n // URL the user just typed (the mapping never persisted, so `editingWebhook` is\n // null). Stash the attempted draft here and feed it back as `initialWebhook` so\n // the dialog re-seeds it instead of clearing it.\n //\n // Tri-state: `undefined` = no retry draft (use the persisted mapping);\n // an object = a failed webhook create/update to re-seed; `null` = a failed\n // \"switch to Emit / remove webhook\" so the retry keeps the Emit selection\n // instead of falling back to the still-present old mapping (#8).\n const [pendingWebhookDraft, setPendingWebhookDraft] = useState<ActionWebhookDraft | null | undefined>(undefined);\n // Surfaces a save failure inside the dialog so the user isn't left with a\n // silently-editable form and no idea what went wrong (#9).\n const [pendingActionError, setPendingActionError] = useState<string | null>(null);\n\n // Prefill the dialog's webhook field when editing an action already paired with one.\n const editingWebhook = useMemo<ActionWebhookDraft | null>(() => {\n if (!editingAction) return null;\n const paired = webhookForAction(editingAction.name);\n return paired ? { url: paired.webhookUrl, method: paired.method } : null;\n }, [editingAction, webhookForAction]);\n\n // A retry draft (from a failed save) wins over the persisted mapping — including\n // an explicit `null` removal retry, which must NOT fall back to `editingWebhook`.\n const dialogInitialWebhook = pendingWebhookDraft !== undefined ? pendingWebhookDraft : editingWebhook;\n\n // Pre-compute each action's paired webhook once per render instead of running a\n // `.find()` inside the action-list JSX for every row on every render.\n const webhookByActionName = useMemo(() => {\n const map = new Map<string, EventWebhookMapping>();\n for (const a of actions) {\n const paired = webhookForAction(a.name);\n if (paired) map.set(a.name, paired);\n }\n return map;\n }, [actions, webhookForAction]);\n\n const handleAddAction = useCallback(() => {\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setEditingAction(undefined);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleEditAction = useCallback((action: WidgetAction) => {\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setEditingAction(action);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleSaveAction = useCallback(\n async (data: ActionSaveData, webhook: ActionWebhookDraft | null) => {\n if (!selectedWidgetId) return;\n\n setActionSaving(true);\n setPendingActionError(null);\n try {\n // `data` carries only action fields; the webhook binding arrives as a\n // separate arg so it can never leak into the GraphQL mutation input.\n const isUpdate = 'id' in data && !!data.id;\n\n let newAction: WidgetAction | null = null;\n if (isUpdate) {\n newAction = await updateAction(data as UpdateWidgetActionInput);\n } else {\n newAction = await createAction(data as Omit<CreateWidgetActionInput, 'widgetId'>);\n }\n\n // Immediately sync to widget store after successful save\n // This ensures the widget displays the action right away\n if (newAction) {\n const currentActions = [...actions];\n if (isUpdate) {\n // Update existing action\n const idx = currentActions.findIndex((a) => a.id === newAction?.id);\n if (idx >= 0) {\n currentActions[idx] = newAction;\n }\n } else {\n // Add new action\n currentActions.push(newAction);\n }\n // Sort by order and update store\n const sortedActions = currentActions.sort((a, b) => a.order - b.order);\n updateWidgetInStore(selectedWidgetId, { actions: sortedActions });\n\n // Pair (or unpair) the webhook that actually gives the button its behaviour.\n const webhookOk = await reconcileActionWebhook(newAction.name, webhook);\n if (!webhookOk) {\n // The action saved but its webhook did not. Keep the dialog open on\n // the now-persisted action so the user can retry (as an update, not a\n // duplicate). Stash the typed draft (which may be `null` for a failed\n // switch-to-Emit) so re-seeding the dialog keeps the user's choice (#8),\n // and surface why the save didn't fully land (#9).\n setPendingWebhookDraft(webhook);\n setPendingActionError(\n webhook\n ? t(\n 'bigconsole.widget.properties.action.webhookSaveFailed',\n 'The button was saved, but its webhook could not be saved. Check the URL and try again.'\n )\n : t(\n 'bigconsole.widget.properties.action.webhookRemoveFailed',\n 'The button was saved, but its old webhook could not be removed. Try again.'\n )\n );\n setEditingAction(newAction);\n return;\n }\n\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n }\n } catch (err) {\n // Keep the dialog open (we never closed it in the throw path) and surface\n // the failure to the user — console.log is globally no-op'd here, so warn (#9).\n console.warn('Failed to save widget action:', err);\n setPendingActionError(\n err instanceof Error\n ? err.message\n : t('bigconsole.widget.properties.action.saveFailed', 'Could not save the button. Please try again.')\n );\n } finally {\n setActionSaving(false);\n }\n },\n [selectedWidgetId, actions, createAction, updateAction, updateWidgetInStore, reconcileActionWebhook, t]\n );\n\n const handleDeleteAction = useCallback(\n async (actionId: string) => {\n if (!selectedWidgetId) return;\n\n if (\n window.confirm(\n t('bigconsole.widget.properties.action.deleteConfirm', 'Are you sure you want to delete this action?')\n )\n ) {\n const target = actions.find((a) => a.id === actionId);\n const success = await deleteAction(actionId);\n if (success) {\n // Immediately sync to widget store after deletion\n const updatedActions = actions.filter((a) => a.id !== actionId);\n updateWidgetInStore(selectedWidgetId, { actions: updatedActions });\n\n // Best-effort: remove the webhook mapping this button was paired with.\n if (target) {\n const paired = webhookForAction(target.name);\n if (paired) await deleteWebhook(paired.id);\n }\n }\n }\n },\n [selectedWidgetId, actions, deleteAction, updateWidgetInStore, webhookForAction, deleteWebhook, t]\n );\n\n const handleMoveActionUp = useCallback(\n async (index: number) => {\n if (!selectedWidgetId || index === 0) return;\n const sortedActions = [...actions].sort((a, b) => a.order - b.order);\n const newOrder = sortedActions.map((a) => a.id);\n [newOrder[index - 1], newOrder[index]] = [newOrder[index], newOrder[index - 1]];\n const success = await reorderActions(newOrder);\n if (success) {\n // Immediately update local order for UI responsiveness\n const reordered = sortedActions\n .map((a, _i) => ({\n ...a,\n order: newOrder.indexOf(a.id),\n }))\n .sort((a, b) => a.order - b.order);\n updateWidgetInStore(selectedWidgetId, { actions: reordered });\n }\n },\n [selectedWidgetId, actions, reorderActions, updateWidgetInStore]\n );\n\n const handleMoveActionDown = useCallback(\n async (index: number) => {\n if (!selectedWidgetId) return;\n const sortedActions = [...actions].sort((a, b) => a.order - b.order);\n if (index === sortedActions.length - 1) return;\n const newOrder = sortedActions.map((a) => a.id);\n [newOrder[index], newOrder[index + 1]] = [newOrder[index + 1], newOrder[index]];\n const success = await reorderActions(newOrder);\n if (success) {\n // Immediately update local order for UI responsiveness\n const reordered = sortedActions\n .map((a, _i) => ({\n ...a,\n order: newOrder.indexOf(a.id),\n }))\n .sort((a, b) => a.order - b.order);\n updateWidgetInStore(selectedWidgetId, { actions: reordered });\n }\n },\n [selectedWidgetId, actions, reorderActions, updateWidgetInStore]\n );\n\n useEffect(() => {\n if (selectedWidgetId && actions.length >= 0 && !actionsLoading) {\n updateWidgetInStore(selectedWidgetId, { actions });\n }\n }, [selectedWidgetId, actions, actionsLoading, updateWidgetInStore]);\n\n const updateLocalForm = useCallback(\n <K extends keyof LocalFormState>(key: K, value: LocalFormState[K]) => {\n setLocalForm((prev) => (prev ? { ...prev, [key]: value } : prev));\n markFormDirty();\n },\n [markFormDirty]\n );\n\n const updateLocalConfig = useCallback(\n (newConfig: Record<string, unknown>) => {\n setLocalForm((prev) => (prev ? { ...prev, config: newConfig } : prev));\n markFormDirty();\n },\n [markFormDirty]\n );\n\n // Initialize selectedDataSinkKey from widget's dataSinkId\n useEffect(() => {\n if (localForm?.dataSinkId && dataSinks.length > 0) {\n const ds = dataSinks.find((d) => d.id === localForm.dataSinkId);\n if (ds) {\n setSelectedDataSinkKey(ds.key);\n }\n } else {\n setSelectedDataSinkKey(null);\n }\n }, [localForm?.dataSinkId, dataSinks]);\n\n // Handle DataSink selection - looks up ID from key and updates form\n const handleDataSinkChange = useCallback(\n (key: string | null) => {\n setBindingPreview(undefined);\n setBindingPreviewComplete(false);\n setBindingPreviewError(null);\n setSelectedDataSinkKey(key);\n if (key) {\n const ds = dataSinks.find((d) => d.key === key);\n if (ds) {\n updateLocalForm('dataSinkId', ds.id);\n // Also update config.dataSinkKey for backward compatibility\n setLocalForm((prev) =>\n prev\n ? {\n ...prev,\n config: { ...prev.config, dataSinkKey: key },\n }\n : prev\n );\n markFormDirty();\n }\n } else {\n updateLocalForm('dataSinkId', undefined);\n setLocalForm((prev) =>\n prev\n ? {\n ...prev,\n config: { ...prev.config, dataSinkKey: undefined },\n parserId: undefined, // Clear parser when DataSink is cleared\n }\n : prev\n );\n markFormDirty();\n }\n },\n [dataSinks, updateLocalForm, markFormDirty]\n );\n\n // Handle Parser selection - with auto-detection of field mappings\n const handleParserChange = useCallback(\n async (parserId: string | null) => {\n updateLocalForm('parserId', parserId || undefined);\n setBindingPreview(undefined);\n setBindingPreviewComplete(false);\n setBindingPreviewError(null);\n\n // If a parser is selected, execute it to detect field structure\n if (parserId && selectedWidget) {\n setIsDetectingFields(true);\n try {\n const result = await executeParser(parserId);\n\n if (result?.success) {\n setBindingPreview(result.output);\n setBindingPreviewComplete(true);\n const detectedFields = detectFieldsFromParserOutput(result.output);\n\n // Only auto-populate if fields were detected and not already set\n if (detectedFields.labelField || detectedFields.valueField) {\n setLocalForm((prev) => {\n if (!prev) return prev;\n\n const currentConfig = prev.config || {};\n const newConfig = { ...currentConfig };\n\n // Auto-populate labelField if not already set\n if (detectedFields.labelField && !currentConfig.labelField) {\n newConfig.labelField = detectedFields.labelField;\n }\n\n // Auto-populate valueField if not already set\n if (detectedFields.valueField && !currentConfig.valueField) {\n newConfig.valueField = detectedFields.valueField;\n }\n\n // Auto-populate xAxisField for line/bar/area charts if not already set\n if (detectedFields.xAxisField && !currentConfig.xAxisField) {\n newConfig.xAxisField = detectedFields.xAxisField;\n }\n\n return { ...prev, config: newConfig };\n });\n markFormDirty();\n }\n } else {\n setBindingPreviewComplete(true);\n setBindingPreviewError(\n result?.error ||\n t('bigconsole.widget.properties.parser.previewFailed', 'The parser could not produce a preview.')\n );\n }\n } catch (err) {\n setBindingPreviewComplete(true);\n setBindingPreviewError(\n err instanceof Error\n ? err.message\n : t('bigconsole.widget.properties.parser.previewFailed', 'The parser could not produce a preview.')\n );\n console.warn('Failed to auto-detect parser fields:', err);\n } finally {\n setIsDetectingFields(false);\n }\n }\n },\n [updateLocalForm, executeParser, selectedWidget, markFormDirty, t]\n );\n\n const handleSaveAll = useCallback(async () => {\n if (!selectedWidget || !localForm) return;\n\n // Snapshot the form revision before awaiting: any edit made while the\n // mutation is in flight bumps it, and must survive the completion handler.\n const revisionAtSave = formRevisionRef.current;\n\n setSaveStatus('saving');\n\n try {\n const metadata = { ...(selectedWidget.metadata || {}), description: localForm.description || '' };\n // Reconcile against the latest persisted config so we don't wipe keys another\n // editor changed while the panel held unsaved edits (e.g. pivot rows/columns/\n // values dragged on the canvas). See reconcilePanelConfig.\n const latestConfig =\n (useWidgetStore.getState().widgets.get(selectedWidget.id)?.config as Record<string, unknown>) ?? {};\n const mergedConfig = reconcilePanelConfig(latestConfig, seededConfigRef.current, localForm.config);\n const bindingDraft = getWidgetBinding({\n dataSinkId: localForm.dataSinkId,\n parserId: localForm.parserId,\n config: { ...mergedConfig, binding: undefined },\n });\n const bindingWrite = serializeWidgetBinding(bindingDraft, mergedConfig);\n\n const result = await updateWidgetApi({\n id: selectedWidget.id,\n title: localForm.title,\n renderer: localForm.renderer,\n template: localForm.template,\n config: bindingWrite.config,\n refreshInterval: localForm.refreshInterval,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: bindingWrite.dataSinkId,\n datasetId: localForm.datasetId,\n parserId: bindingWrite.parserId,\n parserRules: localForm.parserRules,\n metadata,\n });\n\n // Save drilldown configuration to backend\n let savedDrilldown = selectedWidget.drilldown;\n if (localForm.drilldownConfig) {\n const { enabled, targetDashboardId, navigationMode, parameterMapping, passGlobalFilters, maxDepth, openIn } =\n localForm.drilldownConfig;\n\n if (selectedWidget.drilldown?.id) {\n // Update existing drilldown\n const drilldownResult = await updateDrilldown({\n id: selectedWidget.drilldown.id,\n targetDashboardId,\n parameterMapping,\n navigationMode,\n enabled,\n passGlobalFilters,\n maxDepth,\n openIn,\n });\n if (drilldownResult) {\n savedDrilldown = drilldownResult;\n }\n } else if (targetDashboardId) {\n // Create new drilldown (only if targetDashboardId is set)\n const drilldownResult = await createDrilldown({\n sourceWidgetId: selectedWidget.id,\n targetDashboardId,\n parameterMapping: parameterMapping || {},\n navigationMode,\n passGlobalFilters,\n maxDepth,\n openIn,\n });\n if (drilldownResult) {\n savedDrilldown = { ...drilldownResult, enabled };\n }\n }\n } else if (selectedWidget.drilldown?.id && !localForm.drilldownConfig) {\n // Delete drilldown if it existed but was removed\n await deleteDrilldown(selectedWidget.drilldown.id);\n savedDrilldown = undefined;\n }\n\n if (result) {\n updateWidgetInStore(selectedWidget.id, {\n title: localForm.title,\n description: localForm.description,\n renderer: localForm.renderer,\n template: localForm.template,\n config: bindingWrite.config,\n refreshInterval: localForm.refreshInterval,\n // v2.0: Data source fields\n dataSinkId: bindingWrite.dataSinkId,\n datasetId: localForm.datasetId,\n parserId: bindingWrite.parserId,\n parserRules: localForm.parserRules,\n // Drilldown configuration from backend\n drilldown: savedDrilldown,\n // Update timestamp to trigger WidgetWrapper re-render (fixes drilldown mode change bug)\n updatedAt: new Date().toISOString(),\n } as Partial<Widget>);\n\n setSaveStatus('saved');\n // Only mark clean if nothing was edited during the round-trip.\n // Clearing unconditionally let the re-seed effect overwrite localForm\n // with the just-saved server state, silently discarding that edit.\n if (formRevisionRef.current === revisionAtSave) {\n setHasUnsavedChanges(false);\n }\n setTimeout(() => setSaveStatus('idle'), 2000);\n } else {\n setSaveStatus('error');\n }\n } catch (err) {\n setSaveStatus('error');\n }\n }, [\n selectedWidget,\n localForm,\n updateWidgetApi,\n updateWidgetInStore,\n createDrilldown,\n updateDrilldown,\n deleteDrilldown,\n ]);\n\n const handleDiscardAll = useCallback(() => {\n if (!selectedWidget) return;\n\n setLocalForm(createLocalFormStateFromWidget(selectedWidget));\n seededConfigRef.current = (selectedWidget.config as Record<string, unknown>) || {};\n setHasUnsavedChanges(false);\n setSaveStatus('idle');\n }, [selectedWidget]);\n\n const handleConfigChange = useCallback(\n (newConfig: Record<string, unknown>) => {\n updateLocalConfig(newConfig);\n },\n [updateLocalConfig]\n );\n\n const handleDrilldownChange = useCallback(\n (config: DrilldownConfig) => {\n setLocalForm((prev) => (prev ? { ...prev, drilldownConfig: config } : prev));\n markFormDirty();\n },\n [markFormDirty]\n );\n\n const definition = selectedWidget ? getWidgetDefinition(selectedWidget.type) : null;\n\n const rendererOptions = useMemo(\n () =>\n RENDERER_OPTIONS.map((opt) => ({\n value: opt.value,\n label: t(opt.labelKey, opt.label),\n description: t(opt.descriptionKey, opt.description),\n })),\n [t]\n );\n\n const dataSourceTypes = useMemo(\n () =>\n DATA_SOURCE_TYPES.map((type) => ({\n value: type.value,\n label: t(type.labelKey, type.label),\n })),\n [t]\n );\n\n return (\n <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()} modal={false}>\n {/*\n `min(420px, 100vw)` instead of a hard 420px + 90vw cap: on a 390px\n viewport the old sizing left a 39px sliver of unusable canvas beside a\n full-bleed sheet. Now the sheet simply takes the viewport (BOFF-5636).\n */}\n <SheetContent\n style={{\n width: 'min(420px, 100vw)',\n maxWidth: '100vw',\n padding: 0,\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n overflow: 'hidden',\n }}\n >\n <SheetHeader\n style={{\n padding: 'var(--space-cardPadding)',\n borderBottom: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-surface)',\n flexShrink: 0,\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-controlGap)' }}>\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 boxShadow: 'var(--shadow-elevation-2)',\n flexShrink: 0,\n }}\n >\n <Info\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 <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <SheetTitle\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-bold)',\n color: 'var(--color-text-primary)',\n margin: 0,\n }}\n >\n {selectedWidget\n ? t('bigconsole.widget.properties.header.widgetProperties', 'Widget Properties')\n : t('bigconsole.widget.properties.header.properties', 'Properties')}\n </SheetTitle>\n {saveStatus === 'saving' && (\n <Spinner style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n )}\n {saveStatus === 'saved' && (\n <Check\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-status-success-bg)',\n }}\n />\n )}\n {saveStatus === 'error' && (\n <X\n style={{\n height: 'var(--size-icon-sm)',\n width: 'var(--size-icon-sm)',\n color: 'var(--color-status-error-bg)',\n }}\n />\n )}\n </div>\n {definition && (\n <SheetDescription\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n margin: 'var(--space-controlGap) 0 0 0',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n {definition.name}\n </SheetDescription>\n )}\n </div>\n </div>\n </SheetHeader>\n\n {selectedWidget ? (\n <Tabs\n value={activeTab}\n onValueChange={(v) => setActiveTab(v as TabId)}\n style={{ flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0, overflow: 'hidden' }}\n >\n {/*\n The tab strip is the panel's navigation, so it no longer renders in\n type smaller than the content it switches (was a hard-coded 12px),\n and it wraps to a second row instead of becoming a horizontal\n scroller that can hide the fourth tab off-screen (BOFF-5636).\n */}\n <TabsList\n style={{\n width: '100%',\n display: 'flex',\n flexWrap: 'wrap',\n justifyContent: 'flex-start',\n borderRadius: 0,\n borderBottom: '1px solid var(--color-border-subtle)',\n padding: '2px 8px',\n backgroundColor: 'var(--color-bg-sunken)',\n minHeight: 'var(--size-controlHeight-lg)',\n height: 'auto',\n flexShrink: 0,\n gap: '2px',\n }}\n >\n <TabsTrigger value=\"general\" style={TAB_TRIGGER_STYLE}>\n {t('bigconsole.widget.properties.tab.general', 'General')}\n </TabsTrigger>\n <TabsTrigger value=\"data\" style={TAB_TRIGGER_STYLE}>\n {t('bigconsole.widget.properties.tab.data', 'Data')}\n </TabsTrigger>\n <TabsTrigger value=\"advanced\" style={TAB_TRIGGER_STYLE}>\n {t('bigconsole.widget.properties.tab.advanced', 'Advanced')}\n </TabsTrigger>\n <TabsTrigger value=\"appearance\" style={TAB_TRIGGER_STYLE}>\n {t('bigconsole.widget.properties.tab.style', 'Style')}\n </TabsTrigger>\n </TabsList>\n\n <div style={{ flex: 1, overflow: 'auto', padding: 'var(--space-cardPadding)', minHeight: 0 }}>\n <TabsContent value=\"general\" className=\"mt-0\">\n {localForm && (\n <SectionStack>\n <GeneralSection\n title={localForm.title}\n description={localForm.description}\n refreshInterval={localForm.refreshInterval}\n onTitleChange={(title) => updateLocalForm('title', title)}\n onDescriptionChange={(description) => updateLocalForm('description', description)}\n onRefreshIntervalChange={(refreshInterval) => updateLocalForm('refreshInterval', refreshInterval)}\n />\n </SectionStack>\n )}\n </TabsContent>\n\n <TabsContent value=\"data\" className=\"mt-0\">\n {localForm && (\n <SectionStack>\n {(() => {\n const binding = getWidgetBinding({\n dataSinkId: localForm.dataSinkId,\n parserId: localForm.parserId,\n config: { ...localForm.config, binding: undefined },\n });\n const validation = getWidgetBindingStatus({\n binding,\n loading: isDetectingFields,\n error: bindingPreviewError,\n preview: bindingPreview,\n previewComplete: bindingPreviewComplete,\n });\n\n return (\n <DataBindingSection\n consoleId={selectedWidget?.dashboardId || ''}\n binding={binding}\n dataSinkKey={selectedDataSinkKey}\n dataPath={\n typeof localForm.config.dataPath === 'string' ? localForm.config.dataPath : undefined\n }\n status={validation.status}\n missingFields={validation.missingFields}\n preview={bindingPreview}\n error={bindingPreviewError}\n onDataSinkChange={handleDataSinkChange}\n onParserChange={handleParserChange}\n onDataPathChange={(dataPath) => updateLocalConfig({ ...localForm.config, dataPath })}\n />\n );\n })()}\n\n {/*\n Escape hatch for widgets that bypass the DataSink pipeline.\n It used to be an uppercase micro-label over up to seven\n ungrouped fields shaped exactly like the primary binding\n above it; a recessed section says plainly that this is the\n alternative path (BOFF-5636).\n */}\n <Section\n emphasis=\"muted\"\n title={t('bigconsole.widget.properties.data.alternativeSources', 'Alternative Data Sources')}\n description=\"Skip the DataSink pipeline and fetch straight from an API, a GraphQL endpoint or sample JSON.\"\n headingLevel={3}\n data-testid=\"widget-inspector-alt-sources\"\n >\n <div className=\"flex flex-col gap-form-gap\">\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.data.sourceTypeLabel', 'Data Source Type')}</Label>\n <Select\n value={(localForm.config?.dataSourceType as string) || ''}\n onValueChange={(value) =>\n updateLocalConfig({ ...localForm.config, dataSourceType: value || undefined })\n }\n >\n <SelectTrigger\n style={{ height: 'var(--size-controlHeight-md)', fontSize: 'var(--font-size-body-sm)' }}\n >\n <SelectValue\n placeholder={t(\n 'bigconsole.widget.properties.data.selectTypePlaceholder',\n 'Select type...'\n )}\n />\n </SelectTrigger>\n <SelectContent\n position=\"popper\"\n side=\"bottom\"\n sideOffset={4}\n align=\"start\"\n style={{\n zIndex: 9999,\n maxHeight: '200px',\n backgroundColor: 'var(--color-overlay-popoverBg, var(--color-bg-surface))',\n border: '1px solid var(--color-border-subtle)',\n boxShadow: 'var(--shadow-elevation-3)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n {dataSourceTypes.map((type) => (\n <SelectItem key={type.value} value={type.value}>\n {type.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {localForm.config?.dataSourceType === 'REST_API' && (\n <div className=\"space-y-3\">\n {/* Endpoint URL */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.rest.endpointUrlLabel', 'Endpoint URL *')}</Label>\n <Input\n value={(localForm.config?.endpointUrl as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, endpointUrl: e.target.value || undefined })\n }\n placeholder=\"https://api.example.com/data\"\n />\n </div>\n\n {/* HTTP Method */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.rest.httpMethodLabel', 'HTTP Method')}</Label>\n <Select\n value={(localForm.config?.httpMethod as string) || 'GET'}\n onValueChange={(value) => updateLocalConfig({ ...localForm.config, httpMethod: value })}\n >\n <SelectTrigger style={{ height: 'var(--size-controlHeight-md)' }}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" side=\"bottom\" sideOffset={4}>\n <SelectItem value=\"GET\">GET</SelectItem>\n <SelectItem value=\"POST\">POST</SelectItem>\n <SelectItem value=\"PUT\">PUT</SelectItem>\n <SelectItem value=\"DELETE\">DELETE</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n {/* Headers */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.rest.headersLabel', 'Headers (JSON)')}</Label>\n <Textarea\n value={(localForm.config?.headers as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, headers: e.target.value || undefined })\n }\n rows={3}\n placeholder='{\"Authorization\": \"Bearer token\", \"Content-Type\": \"application/json\"}'\n className=\"font-mono text-xs\"\n />\n </div>\n\n {/* Authentication Type */}\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.rest.authenticationLabel', 'Authentication')}\n </Label>\n <Select\n value={(localForm.config?.authType as string) || 'none'}\n onValueChange={(value) => updateLocalConfig({ ...localForm.config, authType: value })}\n >\n <SelectTrigger style={{ height: 'var(--size-controlHeight-md)' }}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent position=\"popper\" side=\"bottom\" sideOffset={4}>\n <SelectItem value=\"none\">\n {t('bigconsole.widget.properties.rest.authNone', 'None')}\n </SelectItem>\n <SelectItem value=\"api_key\">\n {t('bigconsole.widget.properties.rest.authApiKey', 'API Key')}\n </SelectItem>\n <SelectItem value=\"bearer\">\n {t('bigconsole.widget.properties.rest.authBearer', 'Bearer Token')}\n </SelectItem>\n <SelectItem value=\"basic\">\n {t('bigconsole.widget.properties.rest.authBasic', 'Basic Auth')}\n </SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n {/* Auth Value (conditional) */}\n {!!(localForm.config?.authType && localForm.config.authType !== 'none') && (\n <div className=\"space-y-2\">\n <Label>\n {String(localForm.config.authType) === 'basic'\n ? t('bigconsole.widget.properties.rest.credentialsLabel', 'Credentials (user:pass)')\n : t('bigconsole.widget.properties.rest.tokenKeyLabel', 'Token/Key')}\n </Label>\n <Input\n type=\"password\"\n value={(localForm.config?.authValue as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, authValue: e.target.value || undefined })\n }\n placeholder={\n localForm.config.authType === 'basic'\n ? t(\n 'bigconsole.widget.properties.rest.credentialsPlaceholder',\n 'username:password'\n )\n : t(\n 'bigconsole.widget.properties.rest.tokenKeyPlaceholder',\n 'Enter token or API key'\n )\n }\n />\n </div>\n )}\n\n {/* Request Body (for POST/PUT) */}\n {(localForm.config?.httpMethod === 'POST' || localForm.config?.httpMethod === 'PUT') && (\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.rest.requestBodyLabel', 'Request Body (JSON)')}\n </Label>\n <Textarea\n value={(localForm.config?.requestBody as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, requestBody: e.target.value || undefined })\n }\n rows={4}\n placeholder='{\"key\": \"value\"}'\n className=\"font-mono text-xs\"\n />\n </div>\n )}\n\n {/* Data Path */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.dataPath.label', 'Data Path (optional)')}</Label>\n <Input\n value={(localForm.config?.dataPath as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, dataPath: e.target.value || undefined })\n }\n placeholder=\"$.data.items or .data.items\"\n />\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'bigconsole.widget.properties.dataPath.restHelp',\n 'JSONPath or dot notation to extract data from response'\n )}\n </p>\n </div>\n </div>\n )}\n\n {localForm.config?.dataSourceType === 'GRAPHQL' && (\n <div className=\"space-y-3\">\n {/* GraphQL Endpoint */}\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.graphql.endpointLabel', 'GraphQL Endpoint *')}\n </Label>\n <Input\n value={(localForm.config?.graphqlEndpoint as string) || ''}\n onChange={(e) =>\n updateLocalConfig({\n ...localForm.config,\n graphqlEndpoint: e.target.value || undefined,\n })\n }\n placeholder=\"https://api.example.com/graphql\"\n />\n </div>\n\n {/* GraphQL Query */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.graphql.queryLabel', 'Query *')}</Label>\n <Textarea\n value={(localForm.config?.graphqlQuery as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, graphqlQuery: e.target.value || undefined })\n }\n rows={6}\n placeholder={`query GetData($limit: Int) {\n items(limit: $limit) {\n id\n name\n value\n }\n}`}\n className=\"font-mono text-xs\"\n />\n </div>\n\n {/* GraphQL Variables */}\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.graphql.variablesLabel', 'Variables (JSON, optional)')}\n </Label>\n <Textarea\n value={(localForm.config?.graphqlVariables as string) || ''}\n onChange={(e) =>\n updateLocalConfig({\n ...localForm.config,\n graphqlVariables: e.target.value || undefined,\n })\n }\n rows={3}\n placeholder='{\"limit\": 10, \"offset\": 0}'\n className=\"font-mono text-xs\"\n />\n </div>\n\n {/* Headers */}\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.graphql.headersLabel', 'Headers (JSON, optional)')}\n </Label>\n <Textarea\n value={(localForm.config?.headers as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, headers: e.target.value || undefined })\n }\n rows={2}\n placeholder='{\"Authorization\": \"Bearer token\"}'\n className=\"font-mono text-xs\"\n />\n </div>\n\n {/* Data Path */}\n <div className=\"space-y-2\">\n <Label>{t('bigconsole.widget.properties.dataPath.label', 'Data Path (optional)')}</Label>\n <Input\n value={(localForm.config?.dataPath as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, dataPath: e.target.value || undefined })\n }\n placeholder=\".data.items\"\n />\n <p className=\"text-xs text-text-secondary\">\n {t(\n 'bigconsole.widget.properties.dataPath.graphqlHelp',\n 'Path to extract data from GraphQL response'\n )}\n </p>\n </div>\n </div>\n )}\n\n {localForm.config?.dataSourceType === 'MOCK' && (\n <div className=\"space-y-2\">\n <Label>\n {t('bigconsole.widget.properties.mock.sampleDataLabel', 'Sample Data (JSON)')}\n </Label>\n <Textarea\n value={(localForm.config?.mockData as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, mockData: e.target.value || undefined })\n }\n rows={5}\n placeholder='{\"value\": 100}'\n className=\"font-mono text-sm\"\n />\n </div>\n )}\n </div>\n </Section>\n </SectionStack>\n )}\n </TabsContent>\n\n <TabsContent value=\"advanced\" className=\"mt-0\">\n <SectionStack>\n {localForm && (\n <AdvancedSection\n renderer={localForm.renderer}\n rendererOptions={rendererOptions}\n onRendererChange={(renderer) => {\n updateLocalForm('renderer', renderer);\n if (renderer === 'BIGCONSOLE') updateLocalForm('template', undefined);\n }}\n templateEditor={\n localForm.renderer !== 'BIGCONSOLE' ? (\n <TemplateEditor\n widget={{ ...selectedWidget, template: localForm.template, renderer: localForm.renderer }}\n onSave={(template) => updateLocalForm('template', template)}\n />\n ) : undefined\n }\n />\n )}\n\n <Section\n title={t('bigconsole.widget.properties.actions.heading', 'Widget Actions')}\n description={t(\n 'bigconsole.widget.properties.actions.subheading',\n 'Configure buttons on this widget'\n )}\n headingLevel={3}\n meta={\n actions.length > 0 ? (\n <span className=\"rounded-pill bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {actions.length}\n </span>\n ) : undefined\n }\n actions={\n <Button size=\"sm\" onClick={handleAddAction}>\n <Plus className=\"size-4\" />\n {t('bigconsole.widget.properties.actions.add', 'Add')}\n </Button>\n }\n data-testid=\"widget-inspector-actions\"\n >\n {actionsLoading ? (\n <div className=\"flex justify-center py-8\">\n <Spinner className=\"size-6\" />\n </div>\n ) : actions.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {t('bigconsole.widget.properties.actions.empty', 'No actions configured')}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {[...actions]\n .sort((a, b) => a.order - b.order)\n .map((action, index) => (\n <Card key={action.id} className={action.disabled ? 'opacity-60' : ''}>\n <CardContent className=\"p-3 flex items-center gap-2\">\n <div className=\"flex flex-col gap-0.5\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleMoveActionUp(index)}\n disabled={index === 0}\n className=\"h-5 w-5\"\n >\n <ChevronUp className=\"size-3\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleMoveActionDown(index)}\n disabled={index === actions.length - 1}\n className=\"h-5 w-5\"\n >\n <ChevronDown className=\"size-3\" />\n </Button>\n </div>\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span\n aria-hidden=\"true\"\n className={`size-2 shrink-0 rounded-full ${ACTION_STYLE_COLORS[action.style] || ACTION_STYLE_FALLBACK_COLOR}`}\n />\n <span className=\"text-sm font-medium truncate\">{action.label}</span>\n </div>\n <p className=\"text-xs text-text-secondary truncate\">\n {webhookByActionName.has(action.name)\n ? t(\n 'bigconsole.widget.properties.actions.webhookSummary',\n '→ {{method}} {{url}}',\n {\n method: webhookByActionName.get(action.name)?.method ?? '',\n url: webhookByActionName.get(action.name)?.webhookUrl ?? '',\n }\n )\n : t('bigconsole.widget.properties.actions.emitsEvent', 'Emits an event')}\n </p>\n </div>\n\n <div className=\"flex shrink-0 gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Edit ${action.label}`}\n onClick={() => handleEditAction(action)}\n >\n <Pencil className=\"size-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={`Delete ${action.label}`}\n onClick={() => handleDeleteAction(action.id)}\n >\n <Trash2 className=\"size-4\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n </Section>\n\n <Section\n title={t('bigconsole.widget.properties.section.drilldown', 'Drilldown')}\n description=\"Where a click on this widget's data takes the viewer.\"\n headingLevel={3}\n data-testid=\"widget-inspector-drilldown\"\n >\n <DrilldownConfigPanel widget={selectedWidget} onChange={handleDrilldownChange} />\n </Section>\n\n <Section\n title={t('bigconsole.widget.properties.section.webhooks', 'Webhooks')}\n description=\"HTTP endpoints called when this widget emits an event.\"\n headingLevel={3}\n data-testid=\"widget-inspector-webhooks\"\n >\n <WebhookConfigPanel dashboardId={selectedWidget.dashboardId} widgetId={selectedWidget.id} />\n </Section>\n\n <Section\n emphasis=\"muted\"\n title={t('bigconsole.widget.properties.section.events', 'Events')}\n description=\"Recent deliveries for this widget, newest first. Supporting detail — nothing here changes the widget.\"\n headingLevel={3}\n data-testid=\"widget-inspector-events\"\n >\n <EventLogViewer\n dashboardId={selectedWidget.dashboardId}\n widgetId={selectedWidget.id}\n maxHeight=\"min(50vh, 360px)\"\n />\n </Section>\n </SectionStack>\n </TabsContent>\n\n <TabsContent value=\"appearance\" className=\"mt-0\">\n {localForm && (\n <AppearanceSection\n widgetType={selectedWidget.type}\n config={localForm.config || {}}\n onChange={handleConfigChange}\n />\n )}\n </TabsContent>\n </div>\n </Tabs>\n ) : (\n <div className=\"flex min-h-0 flex-1 items-center justify-center p-card-padding\">\n <Section\n tone=\"info\"\n title={t('bigconsole.widget.properties.emptyState', 'Select a widget to edit')}\n headingLevel={3}\n icon={<Info className=\"h-icon-md w-icon-md\" />}\n className=\"w-full max-w-sm\"\n data-testid=\"widget-inspector-empty\"\n >\n <p className=\"text-sm text-text-secondary\">\n Pick a widget on the canvas and its identity, data binding, actions and style options appear here.\n </p>\n </Section>\n </div>\n )}\n\n {/* Global Save/Discard Footer */}\n {selectedWidget && (\n <div\n style={{\n padding: 'var(--space-cardPadding)',\n borderTop: '1px solid var(--color-border-subtle)',\n backgroundColor: 'var(--color-bg-sunken)',\n flexShrink: 0,\n }}\n >\n {/*\n Save state used to be three ad-hoc inline chips with raw rgba()\n fallbacks baked into the var() calls. They are now one toned\n section, so \"unsaved / saved / failed\" reads the same here as every\n other status surface in the module (BOFF-5636).\n */}\n {hasUnsavedChanges && (\n <Section\n tone=\"warning\"\n padding=\"compact\"\n title={t('bigconsole.widget.properties.footer.unsavedChanges', 'You have unsaved changes')}\n headingLevel={3}\n className=\"mb-control-gap\"\n data-testid=\"widget-inspector-save-unsaved\"\n />\n )}\n\n {saveStatus === 'saved' && !hasUnsavedChanges && (\n <Section\n tone=\"success\"\n padding=\"compact\"\n title={t('bigconsole.widget.properties.footer.allSaved', 'All changes saved')}\n headingLevel={3}\n icon={<Check className=\"h-icon-sm w-icon-sm\" />}\n className=\"mb-control-gap\"\n data-testid=\"widget-inspector-save-saved\"\n />\n )}\n\n {saveStatus === 'error' && (\n <Section\n tone=\"danger\"\n padding=\"compact\"\n title={t('bigconsole.widget.properties.footer.saveFailed', 'Failed to save changes')}\n headingLevel={3}\n icon={<X className=\"h-icon-sm w-icon-sm\" />}\n className=\"mb-control-gap\"\n data-testid=\"widget-inspector-save-error\"\n />\n )}\n\n {/* Action buttons */}\n <div className=\"flex flex-wrap gap-control-gap\">\n <Button\n onClick={handleSaveAll}\n disabled={saveStatus === 'saving' || !hasUnsavedChanges}\n style={{\n flex: 1,\n height: 'var(--size-controlHeight-md)',\n backgroundColor: hasUnsavedChanges ? 'var(--color-action-primary-bg)' : undefined,\n opacity: hasUnsavedChanges ? 1 : 0.5,\n }}\n >\n {saveStatus === 'saving' ? (\n <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Spinner style={{ width: 'var(--size-icon-sm)', height: 'var(--size-icon-sm)' }} />\n {t('bigconsole.widget.properties.footer.saving', 'Saving...')}\n </span>\n ) : (\n <span style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Check style={{ width: 'var(--size-icon-sm)', height: 'var(--size-icon-sm)' }} />\n {t('bigconsole.widget.properties.footer.saveChanges', 'Save Changes')}\n </span>\n )}\n </Button>\n <Button\n variant=\"outline\"\n onClick={handleDiscardAll}\n disabled={saveStatus === 'saving' || !hasUnsavedChanges}\n style={{\n height: 'var(--size-controlHeight-md)',\n opacity: hasUnsavedChanges ? 1 : 0.5,\n }}\n >\n <X style={{ width: 'var(--size-icon-sm)', height: 'var(--size-icon-sm)' }} />\n {t('bigconsole.widget.properties.footer.discard', 'Discard')}\n </Button>\n </div>\n </div>\n )}\n\n <ActionConfigDialog\n isOpen={isActionDialogOpen}\n action={editingAction}\n initialWebhook={dialogInitialWebhook}\n isLoading={actionSaving}\n saveError={pendingActionError}\n onClose={() => {\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n }}\n onSave={handleSaveAction}\n />\n </SheetContent>\n </Sheet>\n );\n});\n\nexport default PropertiesPanel;\n"],"mappings":"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"}
|
|
@@ -50,7 +50,7 @@ var e = {
|
|
|
50
50
|
category: "KPI",
|
|
51
51
|
component: null,
|
|
52
52
|
defaultConfig: {
|
|
53
|
-
format: "
|
|
53
|
+
format: "auto",
|
|
54
54
|
decimalPlaces: 0,
|
|
55
55
|
showTrend: !0,
|
|
56
56
|
showSparkline: !1
|
|
@@ -278,15 +278,15 @@ var e = {
|
|
|
278
278
|
thresholds: [
|
|
279
279
|
{
|
|
280
280
|
value: 33,
|
|
281
|
-
color: "
|
|
281
|
+
color: "error"
|
|
282
282
|
},
|
|
283
283
|
{
|
|
284
284
|
value: 66,
|
|
285
|
-
color: "
|
|
285
|
+
color: "warning"
|
|
286
286
|
},
|
|
287
287
|
{
|
|
288
288
|
value: 100,
|
|
289
|
-
color: "
|
|
289
|
+
color: "success"
|
|
290
290
|
}
|
|
291
291
|
]
|
|
292
292
|
},
|