@burdenoff/microfe-bigconsole 2026.727.4 → 2026.728.1
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.
|
@@ -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 } 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 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 } from './ActionConfigDialog';\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 { getWidgetBinding, getWidgetBindingStatus, serializeWidgetBinding } from '../../utils/widgetBinding';\n\n// ============================================================================\n// v2.0: Renderer Options\n// ============================================================================\n\nconst RENDERER_OPTIONS: { value: RendererType; label: string; description: string }[] = [\n { value: 'BIGCONSOLE', label: 'BigConsole Native', description: 'Full-featured native widgets' },\n { value: 'ADAPTIVE_CARD', label: 'Adaptive Cards', description: 'Microsoft Teams/Outlook format' },\n { value: 'BLOCKKIT', label: 'Block Kit', description: 'Slack format' },\n { value: 'CUSTOM', label: 'Custom', description: 'Custom React component' },\n];\n\n// ============================================================================\n// Data Source Constants\n// ============================================================================\n\nconst DATA_SOURCE_TYPES: { value: DataSourceType; label: string; description: string }[] = [\n { value: 'DATASINK', label: 'DataSink', description: 'Use data from BigConsole DataSinks' },\n { value: 'REST_API', label: 'REST API', description: 'Fetch data from REST endpoints' },\n { value: 'GRAPHQL', label: 'GraphQL', description: 'Query data using GraphQL' },\n { value: 'SQL', label: 'SQL Database', description: 'Query SQL databases directly' },\n { value: 'WEBSOCKET', label: 'WebSocket', description: 'Real-time data streaming' },\n { value: 'MOCK', label: 'Sample JSON', description: 'Manually enter sample JSON data' },\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// Action style colors\nconst ACTION_STYLE_COLORS: Record<string, string> = {\n PRIMARY: 'bg-action-primary',\n SECONDARY: 'bg-muted-foreground',\n SUCCESS: 'bg-state-success',\n WARNING: 'bg-state-warning',\n DANGER: 'bg-state-error',\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 [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 <div\n style={{\n paddingTop: 'var(--space-formGap)',\n borderTop: '1px solid var(--color-border)',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n <label\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n Template (JSON)\n </label>\n {jsonError && (\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n Invalid JSON\n </span>\n )}\n </div>\n\n <Textarea\n value={localValue}\n onChange={handleChange}\n spellCheck={false}\n placeholder={`Enter ${widget.renderer} template JSON...`}\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\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginTop: 'var(--space-controlGap)',\n }}\n >\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 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 Valid JSON\n </span>\n )}\n </div>\n\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n marginTop: 'var(--space-controlGap)',\n paddingTop: 'var(--space-controlGap)',\n borderTop: '1px solid var(--color-border)',\n }}\n >\n <span style={{ fontWeight: 'var(--font-weight-medium)' }}>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 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 Block Kit Builder\n <ExternalLink style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </a>\n )}\n </div>\n </div>\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 [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\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 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 const [isActionDialogOpen, setIsActionDialogOpen] = useState(false);\n const [editingAction, setEditingAction] = useState<WidgetAction | undefined>(undefined);\n const [actionSaving, setActionSaving] = useState(false);\n\n const handleAddAction = useCallback(() => {\n setEditingAction(undefined);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleEditAction = useCallback((action: WidgetAction) => {\n setEditingAction(action);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleSaveAction = useCallback(\n async (data: Omit<CreateWidgetActionInput, 'widgetId'> | UpdateWidgetActionInput) => {\n if (!selectedWidgetId) return;\n\n setActionSaving(true);\n try {\n let newAction: WidgetAction | null = null;\n if ('id' in data) {\n newAction = await updateAction(data);\n } else {\n newAction = await createAction(data);\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 ('id' in data) {\n // Update existing action\n const idx = currentActions.findIndex((a) => a.id === data.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\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n } catch (err) {\n } finally {\n setActionSaving(false);\n }\n },\n [selectedWidgetId, actions, createAction, updateAction, updateWidgetInStore]\n );\n\n const handleDeleteAction = useCallback(\n async (actionId: string) => {\n if (!selectedWidgetId) return;\n\n if (window.confirm('Are you sure you want to delete this action?')) {\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 }\n },\n [selectedWidgetId, actions, deleteAction, updateWidgetInStore]\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(result?.error || 'The parser could not produce a preview.');\n }\n } catch (err) {\n setBindingPreviewComplete(true);\n setBindingPreviewError(err instanceof Error ? err.message : 'The parser could not produce a preview.');\n console.warn('Failed to auto-detect parser fields:', err);\n } finally {\n setIsDetectingFields(false);\n }\n }\n },\n [updateLocalForm, executeParser, selectedWidget]\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 const bindingDraft = getWidgetBinding({\n dataSinkId: localForm.dataSinkId,\n parserId: localForm.parserId,\n config: { ...localForm.config, binding: undefined },\n });\n const bindingWrite = serializeWidgetBinding(bindingDraft, localForm.config);\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 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 return (\n <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()} modal={false}>\n <SheetContent\n style={{\n width: '420px',\n maxWidth: '90vw',\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)',\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 ? 'Widget 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 <TabsList\n style={{\n width: '100%',\n display: 'flex',\n justifyContent: 'flex-start',\n borderRadius: 0,\n borderBottom: '1px solid var(--color-border)',\n padding: '0 8px',\n backgroundColor: 'var(--color-bg-sunken)',\n height: 'var(--size-controlHeight-lg)',\n flexShrink: 0,\n gap: '2px',\n overflowX: 'auto',\n }}\n >\n <TabsTrigger\n value=\"general\"\n style={{\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: '12px',\n padding: '6px 8px',\n flexShrink: 0,\n }}\n >\n General\n </TabsTrigger>\n <TabsTrigger\n value=\"data\"\n style={{\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: '12px',\n padding: '6px 8px',\n flexShrink: 0,\n }}\n >\n Data\n </TabsTrigger>\n <TabsTrigger\n value=\"advanced\"\n style={{\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: '12px',\n padding: '6px 8px',\n flexShrink: 0,\n }}\n >\n Advanced\n </TabsTrigger>\n <TabsTrigger\n value=\"appearance\"\n style={{\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: '12px',\n padding: '6px 8px',\n flexShrink: 0,\n }}\n >\n 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 <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-formGap)' }}>\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 </div>\n )}\n </TabsContent>\n\n <TabsContent value=\"data\" className=\"mt-0 space-y-4\">\n {localForm && (\n <>\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 {/* Divider */}\n <div className=\"border-t border-border-subtle pt-4\">\n <Label className=\"text-xs text-text-secondary uppercase tracking-wide\">\n Alternative Data Sources\n </Label>\n </div>\n\n {/* Data Source Type Selector */}\n <div className=\"space-y-2\">\n <Label>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 placeholder=\"Select type...\" />\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, #ffffff))',\n border: '1px solid var(--color-border)',\n boxShadow: 'var(--shadow-elevation-3)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n {DATA_SOURCE_TYPES.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>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>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>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>Authentication</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\">None</SelectItem>\n <SelectItem value=\"api_key\">API Key</SelectItem>\n <SelectItem value=\"bearer\">Bearer Token</SelectItem>\n <SelectItem value=\"basic\">Basic Auth</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' ? 'Credentials (user:pass)' : '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' ? 'username:password' : 'Enter token or API key'\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>Request Body (JSON)</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>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 JSONPath or dot notation to extract data from response\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>GraphQL Endpoint *</Label>\n <Input\n value={(localForm.config?.graphqlEndpoint as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, graphqlEndpoint: e.target.value || undefined })\n }\n placeholder=\"https://api.example.com/graphql\"\n />\n </div>\n\n {/* GraphQL Query */}\n <div className=\"space-y-2\">\n <Label>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>Variables (JSON, optional)</Label>\n <Textarea\n value={(localForm.config?.graphqlVariables as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, graphqlVariables: e.target.value || undefined })\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>Headers (JSON, optional)</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>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\">Path to extract data from GraphQL response</p>\n </div>\n </div>\n )}\n\n {localForm.config?.dataSourceType === 'MOCK' && (\n <div className=\"space-y-2\">\n <Label>Sample Data (JSON)</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 </>\n )}\n </TabsContent>\n\n <TabsContent value=\"advanced\" className=\"mt-0 space-y-6\">\n {localForm && (\n <AdvancedSection\n renderer={localForm.renderer}\n rendererOptions={RENDERER_OPTIONS}\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 className=\"space-y-4 border-t border-border-default pt-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-sm font-medium\">Widget Actions</h3>\n <p className=\"text-xs text-text-secondary\">Configure buttons on this widget</p>\n </div>\n <Button size=\"sm\" onClick={handleAddAction}>\n <Plus className=\"size-4\" />\n Add\n </Button>\n </div>\n\n {actionsLoading ? (\n <div className=\"flex justify-center py-8\">\n <Spinner className=\"size-6\" />\n </div>\n ) : actions.length === 0 ? (\n <div className=\"py-8 text-center\">\n <Info className=\"size-10 mx-auto mb-2 text-text-secondary/50\" />\n <p className=\"text-sm text-text-secondary\">No actions configured</p>\n </div>\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 className={`size-2 rounded-full ${ACTION_STYLE_COLORS[action.style] || 'bg-muted-foreground'}`}\n />\n <span className=\"text-sm font-medium truncate\">{action.label}</span>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {action.name} • {action.type}\n </p>\n </div>\n\n <div className=\"flex gap-1\">\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={() => handleEditAction(action)}>\n <Pencil className=\"size-4\" />\n </Button>\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={() => handleDeleteAction(action.id)}>\n <Trash2 className=\"size-4\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n </section>\n\n <section className=\"space-y-3 border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Drilldown</h3>\n <DrilldownConfigPanel widget={selectedWidget} onChange={handleDrilldownChange} />\n </section>\n\n <section className=\"space-y-3 border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Webhooks</h3>\n <WebhookConfigPanel dashboardId={selectedWidget.dashboardId} widgetId={selectedWidget.id} />\n </section>\n\n <section className=\"space-y-3 border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Events</h3>\n <EventLogViewer\n dashboardId={selectedWidget.dashboardId}\n widgetId={selectedWidget.id}\n maxHeight=\"calc(100vh - 350px)\"\n />\n </section>\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-1 flex items-center justify-center p-4\">\n <div className=\"text-center\">\n <Info className=\"size-12 mx-auto mb-3 text-text-secondary\" />\n <p className=\"text-sm text-text-secondary\">Select a widget to edit</p>\n </div>\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)',\n backgroundColor: 'var(--color-bg-sunken)',\n flexShrink: 0,\n }}\n >\n {/* Status indicator */}\n {hasUnsavedChanges && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n marginBottom: 'var(--space-controlGap)',\n padding: 'var(--space-controlGap)',\n backgroundColor: 'var(--color-status-warning-bgSubtle, rgba(251, 191, 36, 0.1))',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-status-warning-border, rgba(251, 191, 36, 0.3))',\n }}\n >\n <div\n style={{\n width: '8px',\n height: '8px',\n borderRadius: '50%',\n backgroundColor: 'var(--color-status-warning-bg)',\n flexShrink: 0,\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-warning-text, #92400e)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n You have unsaved changes\n </span>\n </div>\n )}\n\n {saveStatus === 'saved' && !hasUnsavedChanges && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n marginBottom: 'var(--space-controlGap)',\n padding: 'var(--space-controlGap)',\n backgroundColor: 'var(--color-status-success-bgSubtle, rgba(34, 197, 94, 0.1))',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-status-success-border, rgba(34, 197, 94, 0.3))',\n }}\n >\n <Check\n style={{\n width: 'var(--size-icon-sm)',\n height: 'var(--size-icon-sm)',\n color: 'var(--color-status-success-text)',\n flexShrink: 0,\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-success-text)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n All changes saved\n </span>\n </div>\n )}\n\n {saveStatus === 'error' && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n marginBottom: 'var(--space-controlGap)',\n padding: 'var(--space-controlGap)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-status-error-border)',\n }}\n >\n <X\n style={{\n width: 'var(--size-icon-sm)',\n height: 'var(--size-icon-sm)',\n color: 'var(--color-status-error-text)',\n flexShrink: 0,\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n Failed to save changes\n </span>\n </div>\n )}\n\n {/* Action buttons */}\n <div style={{ display: 'flex', gap: 'var(--space-controlGap)' }}>\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 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 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 Discard\n </Button>\n </div>\n </div>\n )}\n\n <ActionConfigDialog\n isOpen={isActionDialogOpen}\n action={editingAction}\n isLoading={actionSaving}\n onClose={() => {\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n }}\n onSave={handleSaveAction}\n />\n </SheetContent>\n </Sheet>\n );\n});\n\nexport default PropertiesPanel;\n"],"mappings":"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"}
|
|
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 } 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 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 } from './ActionConfigDialog';\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 {\n getWidgetBinding,\n getWidgetBindingStatus,\n reconcilePanelConfig,\n serializeWidgetBinding,\n} from '../../utils/widgetBinding';\n\n// ============================================================================\n// v2.0: Renderer Options\n// ============================================================================\n\nconst RENDERER_OPTIONS: { value: RendererType; label: string; description: string }[] = [\n { value: 'BIGCONSOLE', label: 'BigConsole Native', description: 'Full-featured native widgets' },\n { value: 'ADAPTIVE_CARD', label: 'Adaptive Cards', description: 'Microsoft Teams/Outlook format' },\n { value: 'BLOCKKIT', label: 'Block Kit', description: 'Slack format' },\n { value: 'CUSTOM', label: 'Custom', description: 'Custom React component' },\n];\n\n// ============================================================================\n// Data Source Constants\n// ============================================================================\n\nconst DATA_SOURCE_TYPES: { value: DataSourceType; label: string; description: string }[] = [\n { value: 'DATASINK', label: 'DataSink', description: 'Use data from BigConsole DataSinks' },\n { value: 'REST_API', label: 'REST API', description: 'Fetch data from REST endpoints' },\n { value: 'GRAPHQL', label: 'GraphQL', description: 'Query data using GraphQL' },\n { value: 'SQL', label: 'SQL Database', description: 'Query SQL databases directly' },\n { value: 'WEBSOCKET', label: 'WebSocket', description: 'Real-time data streaming' },\n { value: 'MOCK', label: 'Sample JSON', description: 'Manually enter sample JSON data' },\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// Action style colors\nconst ACTION_STYLE_COLORS: Record<string, string> = {\n PRIMARY: 'bg-action-primary',\n SECONDARY: 'bg-muted-foreground',\n SUCCESS: 'bg-state-success',\n WARNING: 'bg-state-warning',\n DANGER: 'bg-state-error',\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 [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 <div\n style={{\n paddingTop: 'var(--space-formGap)',\n borderTop: '1px solid var(--color-border)',\n }}\n >\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n <label\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n }}\n >\n Template (JSON)\n </label>\n {jsonError && (\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n Invalid JSON\n </span>\n )}\n </div>\n\n <Textarea\n value={localValue}\n onChange={handleChange}\n spellCheck={false}\n placeholder={`Enter ${widget.renderer} template JSON...`}\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\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-controlGap)',\n marginTop: 'var(--space-controlGap)',\n }}\n >\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 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 Valid JSON\n </span>\n )}\n </div>\n\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n marginTop: 'var(--space-controlGap)',\n paddingTop: 'var(--space-controlGap)',\n borderTop: '1px solid var(--color-border)',\n }}\n >\n <span style={{ fontWeight: 'var(--font-weight-medium)' }}>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 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 Block Kit Builder\n <ExternalLink style={{ height: 'var(--size-icon-sm)', width: 'var(--size-icon-sm)' }} />\n </a>\n )}\n </div>\n </div>\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 [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 const [isActionDialogOpen, setIsActionDialogOpen] = useState(false);\n const [editingAction, setEditingAction] = useState<WidgetAction | undefined>(undefined);\n const [actionSaving, setActionSaving] = useState(false);\n\n const handleAddAction = useCallback(() => {\n setEditingAction(undefined);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleEditAction = useCallback((action: WidgetAction) => {\n setEditingAction(action);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleSaveAction = useCallback(\n async (data: Omit<CreateWidgetActionInput, 'widgetId'> | UpdateWidgetActionInput) => {\n if (!selectedWidgetId) return;\n\n setActionSaving(true);\n try {\n let newAction: WidgetAction | null = null;\n if ('id' in data) {\n newAction = await updateAction(data);\n } else {\n newAction = await createAction(data);\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 ('id' in data) {\n // Update existing action\n const idx = currentActions.findIndex((a) => a.id === data.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\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n } catch (err) {\n } finally {\n setActionSaving(false);\n }\n },\n [selectedWidgetId, actions, createAction, updateAction, updateWidgetInStore]\n );\n\n const handleDeleteAction = useCallback(\n async (actionId: string) => {\n if (!selectedWidgetId) return;\n\n if (window.confirm('Are you sure you want to delete this action?')) {\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 }\n },\n [selectedWidgetId, actions, deleteAction, updateWidgetInStore]\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(result?.error || 'The parser could not produce a preview.');\n }\n } catch (err) {\n setBindingPreviewComplete(true);\n setBindingPreviewError(err instanceof Error ? err.message : 'The parser could not produce a preview.');\n console.warn('Failed to auto-detect parser fields:', err);\n } finally {\n setIsDetectingFields(false);\n }\n }\n },\n [updateLocalForm, executeParser, selectedWidget]\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 return (\n <Sheet open={isOpen} onOpenChange={(open) => !open && onClose()} modal={false}>\n <SheetContent\n style={{\n width: '420px',\n maxWidth: '90vw',\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)',\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 ? 'Widget 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 <TabsList\n style={{\n width: '100%',\n display: 'flex',\n justifyContent: 'flex-start',\n borderRadius: 0,\n borderBottom: '1px solid var(--color-border)',\n padding: '0 8px',\n backgroundColor: 'var(--color-bg-sunken)',\n height: 'var(--size-controlHeight-lg)',\n flexShrink: 0,\n gap: '2px',\n overflowX: 'auto',\n }}\n >\n <TabsTrigger\n value=\"general\"\n style={{\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: '12px',\n padding: '6px 8px',\n flexShrink: 0,\n }}\n >\n General\n </TabsTrigger>\n <TabsTrigger\n value=\"data\"\n style={{\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: '12px',\n padding: '6px 8px',\n flexShrink: 0,\n }}\n >\n Data\n </TabsTrigger>\n <TabsTrigger\n value=\"advanced\"\n style={{\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: '12px',\n padding: '6px 8px',\n flexShrink: 0,\n }}\n >\n Advanced\n </TabsTrigger>\n <TabsTrigger\n value=\"appearance\"\n style={{\n fontWeight: 'var(--font-weight-semibold)',\n fontSize: '12px',\n padding: '6px 8px',\n flexShrink: 0,\n }}\n >\n 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 <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-formGap)' }}>\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 </div>\n )}\n </TabsContent>\n\n <TabsContent value=\"data\" className=\"mt-0 space-y-4\">\n {localForm && (\n <>\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 {/* Divider */}\n <div className=\"border-t border-border-subtle pt-4\">\n <Label className=\"text-xs text-text-secondary uppercase tracking-wide\">\n Alternative Data Sources\n </Label>\n </div>\n\n {/* Data Source Type Selector */}\n <div className=\"space-y-2\">\n <Label>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 placeholder=\"Select type...\" />\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, #ffffff))',\n border: '1px solid var(--color-border)',\n boxShadow: 'var(--shadow-elevation-3)',\n borderRadius: 'var(--radius-button)',\n }}\n >\n {DATA_SOURCE_TYPES.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>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>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>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>Authentication</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\">None</SelectItem>\n <SelectItem value=\"api_key\">API Key</SelectItem>\n <SelectItem value=\"bearer\">Bearer Token</SelectItem>\n <SelectItem value=\"basic\">Basic Auth</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' ? 'Credentials (user:pass)' : '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' ? 'username:password' : 'Enter token or API key'\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>Request Body (JSON)</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>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 JSONPath or dot notation to extract data from response\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>GraphQL Endpoint *</Label>\n <Input\n value={(localForm.config?.graphqlEndpoint as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, graphqlEndpoint: e.target.value || undefined })\n }\n placeholder=\"https://api.example.com/graphql\"\n />\n </div>\n\n {/* GraphQL Query */}\n <div className=\"space-y-2\">\n <Label>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>Variables (JSON, optional)</Label>\n <Textarea\n value={(localForm.config?.graphqlVariables as string) || ''}\n onChange={(e) =>\n updateLocalConfig({ ...localForm.config, graphqlVariables: e.target.value || undefined })\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>Headers (JSON, optional)</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>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\">Path to extract data from GraphQL response</p>\n </div>\n </div>\n )}\n\n {localForm.config?.dataSourceType === 'MOCK' && (\n <div className=\"space-y-2\">\n <Label>Sample Data (JSON)</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 </>\n )}\n </TabsContent>\n\n <TabsContent value=\"advanced\" className=\"mt-0 space-y-6\">\n {localForm && (\n <AdvancedSection\n renderer={localForm.renderer}\n rendererOptions={RENDERER_OPTIONS}\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 className=\"space-y-4 border-t border-border-default pt-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-sm font-medium\">Widget Actions</h3>\n <p className=\"text-xs text-text-secondary\">Configure buttons on this widget</p>\n </div>\n <Button size=\"sm\" onClick={handleAddAction}>\n <Plus className=\"size-4\" />\n Add\n </Button>\n </div>\n\n {actionsLoading ? (\n <div className=\"flex justify-center py-8\">\n <Spinner className=\"size-6\" />\n </div>\n ) : actions.length === 0 ? (\n <div className=\"py-8 text-center\">\n <Info className=\"size-10 mx-auto mb-2 text-text-secondary/50\" />\n <p className=\"text-sm text-text-secondary\">No actions configured</p>\n </div>\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 className={`size-2 rounded-full ${ACTION_STYLE_COLORS[action.style] || 'bg-muted-foreground'}`}\n />\n <span className=\"text-sm font-medium truncate\">{action.label}</span>\n </div>\n <p className=\"text-xs text-text-secondary\">\n {action.name} • {action.type}\n </p>\n </div>\n\n <div className=\"flex gap-1\">\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={() => handleEditAction(action)}>\n <Pencil className=\"size-4\" />\n </Button>\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={() => handleDeleteAction(action.id)}>\n <Trash2 className=\"size-4\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n </section>\n\n <section className=\"space-y-3 border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Drilldown</h3>\n <DrilldownConfigPanel widget={selectedWidget} onChange={handleDrilldownChange} />\n </section>\n\n <section className=\"space-y-3 border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Webhooks</h3>\n <WebhookConfigPanel dashboardId={selectedWidget.dashboardId} widgetId={selectedWidget.id} />\n </section>\n\n <section className=\"space-y-3 border-t border-border-default pt-4\">\n <h3 className=\"text-sm font-semibold text-text-primary\">Events</h3>\n <EventLogViewer\n dashboardId={selectedWidget.dashboardId}\n widgetId={selectedWidget.id}\n maxHeight=\"calc(100vh - 350px)\"\n />\n </section>\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-1 flex items-center justify-center p-4\">\n <div className=\"text-center\">\n <Info className=\"size-12 mx-auto mb-3 text-text-secondary\" />\n <p className=\"text-sm text-text-secondary\">Select a widget to edit</p>\n </div>\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)',\n backgroundColor: 'var(--color-bg-sunken)',\n flexShrink: 0,\n }}\n >\n {/* Status indicator */}\n {hasUnsavedChanges && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n marginBottom: 'var(--space-controlGap)',\n padding: 'var(--space-controlGap)',\n backgroundColor: 'var(--color-status-warning-bgSubtle, rgba(251, 191, 36, 0.1))',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-status-warning-border, rgba(251, 191, 36, 0.3))',\n }}\n >\n <div\n style={{\n width: '8px',\n height: '8px',\n borderRadius: '50%',\n backgroundColor: 'var(--color-status-warning-bg)',\n flexShrink: 0,\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-warning-text, #92400e)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n You have unsaved changes\n </span>\n </div>\n )}\n\n {saveStatus === 'saved' && !hasUnsavedChanges && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n marginBottom: 'var(--space-controlGap)',\n padding: 'var(--space-controlGap)',\n backgroundColor: 'var(--color-status-success-bgSubtle, rgba(34, 197, 94, 0.1))',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-status-success-border, rgba(34, 197, 94, 0.3))',\n }}\n >\n <Check\n style={{\n width: 'var(--size-icon-sm)',\n height: 'var(--size-icon-sm)',\n color: 'var(--color-status-success-text)',\n flexShrink: 0,\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-success-text)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n All changes saved\n </span>\n </div>\n )}\n\n {saveStatus === 'error' && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--space-iconGap)',\n marginBottom: 'var(--space-controlGap)',\n padding: 'var(--space-controlGap)',\n backgroundColor: 'var(--color-status-error-bgSubtle)',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-status-error-border)',\n }}\n >\n <X\n style={{\n width: 'var(--size-icon-sm)',\n height: 'var(--size-icon-sm)',\n color: 'var(--color-status-error-text)',\n flexShrink: 0,\n }}\n />\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n fontWeight: 'var(--font-weight-medium)',\n }}\n >\n Failed to save changes\n </span>\n </div>\n )}\n\n {/* Action buttons */}\n <div style={{ display: 'flex', gap: 'var(--space-controlGap)' }}>\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 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 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 Discard\n </Button>\n </div>\n </div>\n )}\n\n <ActionConfigDialog\n isOpen={isActionDialogOpen}\n action={editingAction}\n isLoading={actionSaving}\n onClose={() => {\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n }}\n onSave={handleSaveAction}\n />\n </SheetContent>\n </Sheet>\n );\n});\n\nexport default PropertiesPanel;\n"],"mappings":"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"}
|
|
@@ -56,7 +56,13 @@ function o(e) {
|
|
|
56
56
|
config: e.config ?? {}
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
-
function s(e, t) {
|
|
59
|
+
function s(e, t, n) {
|
|
60
|
+
let r = { ...e }, i = (e, t) => JSON.stringify(e) === JSON.stringify(t);
|
|
61
|
+
for (let e of Object.keys(n)) i(n[e], t[e]) || (r[e] = n[e]);
|
|
62
|
+
for (let e of Object.keys(t)) e in n || delete r[e];
|
|
63
|
+
return r;
|
|
64
|
+
}
|
|
65
|
+
function c(e, t) {
|
|
60
66
|
let n = {
|
|
61
67
|
source: {
|
|
62
68
|
kind: e.source.kind,
|
|
@@ -76,10 +82,10 @@ function s(e, t) {
|
|
|
76
82
|
parserId: n.parserId
|
|
77
83
|
};
|
|
78
84
|
}
|
|
79
|
-
function
|
|
85
|
+
function l(e, t) {
|
|
80
86
|
return t.replace(/^\$?\.?/, "").split(".").filter(Boolean).reduce((e, t) => n(e) ? e[t] : void 0, e);
|
|
81
87
|
}
|
|
82
|
-
function
|
|
88
|
+
function u(e, t) {
|
|
83
89
|
if (e.source.kind === "DATASINK" && !e.source.ref) return {
|
|
84
90
|
status: "unbound",
|
|
85
91
|
missingFields: []
|
|
@@ -88,13 +94,13 @@ function l(e, t) {
|
|
|
88
94
|
status: "bound",
|
|
89
95
|
missingFields: []
|
|
90
96
|
};
|
|
91
|
-
let n = Object.entries(e.fields).filter(([, e]) =>
|
|
97
|
+
let n = Object.entries(e.fields).filter(([, e]) => l(t, e) === void 0).map(([e]) => e);
|
|
92
98
|
return {
|
|
93
99
|
status: n.length > 0 ? "field_mismatch" : "bound",
|
|
94
100
|
missingFields: n
|
|
95
101
|
};
|
|
96
102
|
}
|
|
97
|
-
function
|
|
103
|
+
function d({ binding: e, loading: t = !1, error: n, preview: r, previewComplete: i = !1 }) {
|
|
98
104
|
if (t) return {
|
|
99
105
|
status: "loading",
|
|
100
106
|
missingFields: []
|
|
@@ -103,13 +109,13 @@ function u({ binding: e, loading: t = !1, error: n, preview: r, previewComplete:
|
|
|
103
109
|
status: "parser_error",
|
|
104
110
|
missingFields: []
|
|
105
111
|
};
|
|
106
|
-
let a =
|
|
112
|
+
let a = u(e);
|
|
107
113
|
return a.status === "unbound" || !i ? a : r == null || Array.isArray(r) && r.length === 0 ? {
|
|
108
114
|
status: "no_data",
|
|
109
115
|
missingFields: []
|
|
110
|
-
} :
|
|
116
|
+
} : u(e, Array.isArray(r) ? r[0] : r);
|
|
111
117
|
}
|
|
112
118
|
//#endregion
|
|
113
|
-
export { o as getBindingForWidget, a as getWidgetBinding,
|
|
119
|
+
export { o as getBindingForWidget, a as getWidgetBinding, d as getWidgetBindingStatus, s as reconcilePanelConfig, c as serializeWidgetBinding };
|
|
114
120
|
|
|
115
121
|
//# sourceMappingURL=widgetBinding.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgetBinding.js","names":[],"sources":["../../../src/bigconsole/utils/widgetBinding.ts"],"sourcesContent":["import type { DataSourceType, Widget, WidgetBinding, WidgetBindingStatus, WidgetBindingValidation } from '../types';\n\nconst SOURCE_KINDS: ReadonlySet<string> = new Set<DataSourceType>([\n 'DATASINK',\n 'REST_API',\n 'GRAPHQL',\n 'SQL',\n 'WEBSOCKET',\n 'MOCK',\n]);\n\nconst FIELD_CONFIG_KEYS = [\n 'labelField',\n 'valueField',\n 'xAxisField',\n 'yAxisField',\n 'categoryField',\n 'seriesField',\n] as const;\n\ninterface LegacyBindingInput {\n dataSinkId?: string;\n parserId?: string;\n config?: Record<string, unknown>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isSourceKind(value: unknown): value is DataSourceType {\n return typeof value === 'string' && SOURCE_KINDS.has(value);\n}\n\nfunction readCanonicalBinding(value: unknown): WidgetBinding | null {\n if (!isRecord(value) || !isRecord(value.source) || !isSourceKind(value.source.kind)) return null;\n\n const fields = isRecord(value.fields)\n ? Object.fromEntries(\n Object.entries(value.fields).filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n )\n : {};\n\n return {\n source: {\n kind: value.source.kind,\n ref: typeof value.source.ref === 'string' ? value.source.ref : undefined,\n },\n parserId: typeof value.parserId === 'string' ? value.parserId : undefined,\n fields,\n };\n}\n\n/** Read canonical binding first, then adapt legacy widget fields without mutating persisted data. */\nexport function getWidgetBinding(input: LegacyBindingInput): WidgetBinding {\n const config = input.config ?? {};\n const canonical = readCanonicalBinding(config.binding);\n if (canonical) return canonical;\n\n const configuredKind = isSourceKind(config.dataSourceType) ? config.dataSourceType : undefined;\n const kind: DataSourceType = input.dataSinkId ? 'DATASINK' : (configuredKind ?? 'DATASINK');\n const fields: Record<string, string> = {};\n\n for (const key of FIELD_CONFIG_KEYS) {\n const value = config[key];\n if (typeof value === 'string' && value.length > 0) fields[key] = value;\n }\n\n return {\n source: { kind, ref: kind === 'DATASINK' ? input.dataSinkId : undefined },\n parserId: input.parserId,\n fields,\n };\n}\n\nexport function getBindingForWidget(widget: Widget): WidgetBinding {\n return getWidgetBinding({\n dataSinkId: widget.dataSinkId ?? undefined,\n parserId: widget.parserId ?? undefined,\n config: (widget.config as Record<string, unknown>) ?? {},\n });\n}\n\n/** Dual-write canonical binding and renderer compatibility fields for one migration release. */\nexport function serializeWidgetBinding(binding: WidgetBinding, config: Record<string, unknown>) {\n const canonical: WidgetBinding = {\n source: { kind: binding.source.kind, ref: binding.source.ref },\n parserId: binding.parserId,\n fields: { ...binding.fields },\n };\n\n return {\n config: {\n ...config,\n binding: canonical,\n dataSourceType: canonical.source.kind,\n ...canonical.fields,\n },\n dataSinkId: canonical.source.kind === 'DATASINK' ? canonical.source.ref : undefined,\n parserId: canonical.parserId,\n };\n}\n\nexport function resolveBindingPath(value: unknown, path: string): unknown {\n return path\n .replace(/^\\$?\\.?/, '')\n .split('.')\n .filter(Boolean)\n .reduce<unknown>((current, segment) => (isRecord(current) ? current[segment] : undefined), value);\n}\n\nexport function validateWidgetBinding(binding: WidgetBinding, sample?: unknown): WidgetBindingValidation {\n const isUnbound = binding.source.kind === 'DATASINK' && !binding.source.ref;\n if (isUnbound) return { status: 'unbound', missingFields: [] };\n if (sample === undefined) return { status: 'bound', missingFields: [] };\n\n const missingFields = Object.entries(binding.fields)\n .filter(([, path]) => resolveBindingPath(sample, path) === undefined)\n .map(([field]) => field);\n\n return { status: missingFields.length > 0 ? 'field_mismatch' : 'bound', missingFields };\n}\n\ninterface BindingStatusInput {\n binding: WidgetBinding;\n loading?: boolean;\n error?: string | null;\n preview?: unknown;\n previewComplete?: boolean;\n}\n\nexport function getWidgetBindingStatus({\n binding,\n loading = false,\n error,\n preview,\n previewComplete = false,\n}: BindingStatusInput): { status: WidgetBindingStatus; missingFields: string[] } {\n if (loading) return { status: 'loading', missingFields: [] };\n if (error) return { status: 'parser_error', missingFields: [] };\n\n const initialValidation = validateWidgetBinding(binding);\n if (initialValidation.status === 'unbound') return initialValidation;\n if (!previewComplete) return initialValidation;\n if (preview == null || (Array.isArray(preview) && preview.length === 0)) {\n return { status: 'no_data', missingFields: [] };\n }\n\n return validateWidgetBinding(binding, Array.isArray(preview) ? preview[0] : preview);\n}\n"],"mappings":";AAEA,IAAM,IAAoC,IAAI,IAAoB;CAChE;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,EAEI,IAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACD;AAQD,SAAS,EAAS,GAAkD;AAClE,QAAO,OAAO,KAAU,cAAY,KAAkB,CAAC,MAAM,QAAQ,EAAM;;AAG7E,SAAS,EAAa,GAAyC;AAC7D,QAAO,OAAO,KAAU,YAAY,EAAa,IAAI,EAAM;;AAG7D,SAAS,EAAqB,GAAsC;AAClE,KAAI,CAAC,EAAS,EAAM,IAAI,CAAC,EAAS,EAAM,OAAO,IAAI,CAAC,EAAa,EAAM,OAAO,KAAK,CAAE,QAAO;CAE5F,IAAM,IAAS,EAAS,EAAM,OAAO,GACjC,OAAO,YACL,OAAO,QAAQ,EAAM,OAAO,CAAC,QAAQ,MAAqC,OAAO,EAAM,MAAO,SAAS,CACxG,GACD,EAAE;AAEN,QAAO;EACL,QAAQ;GACN,MAAM,EAAM,OAAO;GACnB,KAAK,OAAO,EAAM,OAAO,OAAQ,WAAW,EAAM,OAAO,MAAM,KAAA;GAChE;EACD,UAAU,OAAO,EAAM,YAAa,WAAW,EAAM,WAAW,KAAA;EAChE;EACD;;AAIH,SAAgB,EAAiB,GAA0C;CACzE,IAAM,IAAS,EAAM,UAAU,EAAE,EAC3B,IAAY,EAAqB,EAAO,QAAQ;AACtD,KAAI,EAAW,QAAO;CAEtB,IAAM,IAAiB,EAAa,EAAO,eAAe,GAAG,EAAO,iBAAiB,KAAA,GAC/E,IAAuB,EAAM,aAAa,aAAc,KAAkB,YAC1E,IAAiC,EAAE;AAEzC,MAAK,IAAM,KAAO,GAAmB;EACnC,IAAM,IAAQ,EAAO;AACrB,EAAI,OAAO,KAAU,YAAY,EAAM,SAAS,MAAG,EAAO,KAAO;;AAGnE,QAAO;EACL,QAAQ;GAAE;GAAM,KAAK,MAAS,aAAa,EAAM,aAAa,KAAA;GAAW;EACzE,UAAU,EAAM;EAChB;EACD;;AAGH,SAAgB,EAAoB,GAA+B;AACjE,QAAO,EAAiB;EACtB,YAAY,EAAO,cAAc,KAAA;EACjC,UAAU,EAAO,YAAY,KAAA;EAC7B,QAAS,EAAO,UAAsC,EAAE;EACzD,CAAC;;
|
|
1
|
+
{"version":3,"file":"widgetBinding.js","names":[],"sources":["../../../src/bigconsole/utils/widgetBinding.ts"],"sourcesContent":["import type { DataSourceType, Widget, WidgetBinding, WidgetBindingStatus, WidgetBindingValidation } from '../types';\n\nconst SOURCE_KINDS: ReadonlySet<string> = new Set<DataSourceType>([\n 'DATASINK',\n 'REST_API',\n 'GRAPHQL',\n 'SQL',\n 'WEBSOCKET',\n 'MOCK',\n]);\n\nconst FIELD_CONFIG_KEYS = [\n 'labelField',\n 'valueField',\n 'xAxisField',\n 'yAxisField',\n 'categoryField',\n 'seriesField',\n] as const;\n\ninterface LegacyBindingInput {\n dataSinkId?: string;\n parserId?: string;\n config?: Record<string, unknown>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isSourceKind(value: unknown): value is DataSourceType {\n return typeof value === 'string' && SOURCE_KINDS.has(value);\n}\n\nfunction readCanonicalBinding(value: unknown): WidgetBinding | null {\n if (!isRecord(value) || !isRecord(value.source) || !isSourceKind(value.source.kind)) return null;\n\n const fields = isRecord(value.fields)\n ? Object.fromEntries(\n Object.entries(value.fields).filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n )\n : {};\n\n return {\n source: {\n kind: value.source.kind,\n ref: typeof value.source.ref === 'string' ? value.source.ref : undefined,\n },\n parserId: typeof value.parserId === 'string' ? value.parserId : undefined,\n fields,\n };\n}\n\n/** Read canonical binding first, then adapt legacy widget fields without mutating persisted data. */\nexport function getWidgetBinding(input: LegacyBindingInput): WidgetBinding {\n const config = input.config ?? {};\n const canonical = readCanonicalBinding(config.binding);\n if (canonical) return canonical;\n\n const configuredKind = isSourceKind(config.dataSourceType) ? config.dataSourceType : undefined;\n const kind: DataSourceType = input.dataSinkId ? 'DATASINK' : (configuredKind ?? 'DATASINK');\n const fields: Record<string, string> = {};\n\n for (const key of FIELD_CONFIG_KEYS) {\n const value = config[key];\n if (typeof value === 'string' && value.length > 0) fields[key] = value;\n }\n\n return {\n source: { kind, ref: kind === 'DATASINK' ? input.dataSinkId : undefined },\n parserId: input.parserId,\n fields,\n };\n}\n\nexport function getBindingForWidget(widget: Widget): WidgetBinding {\n return getWidgetBinding({\n dataSinkId: widget.dataSinkId ?? undefined,\n parserId: widget.parserId ?? undefined,\n config: (widget.config as Record<string, unknown>) ?? {},\n });\n}\n\n/**\n * Reconcile the properties-panel's config edits against the latest persisted\n * config so a panel save never clobbers config keys changed concurrently by\n * another editor.\n *\n * The panel seeds a `localForm.config` snapshot from the widget and then freezes\n * it while it has unsaved changes. Meanwhile a widget's own canvas UI can persist\n * config independently (e.g. a Pivot Table's `rows`/`columns`/`values` dragged on\n * the canvas). Writing the frozen snapshot back on save would wipe those keys.\n *\n * Strategy: start from the latest persisted config (carries concurrent edits),\n * then re-apply only the keys the panel actually changed relative to the baseline\n * it was seeded from — additions/updates as well as removals. Keys the panel left\n * untouched keep their latest persisted value.\n */\nexport function reconcilePanelConfig(\n latest: Record<string, unknown>,\n baseline: Record<string, unknown>,\n edited: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...latest };\n const unchanged = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);\n\n // Additions and updates the panel made relative to its seed baseline.\n for (const key of Object.keys(edited)) {\n if (!unchanged(edited[key], baseline[key])) result[key] = edited[key];\n }\n // Removals the panel made (present at seed time, dropped from the form).\n for (const key of Object.keys(baseline)) {\n if (!(key in edited)) delete result[key];\n }\n return result;\n}\n\n/** Dual-write canonical binding and renderer compatibility fields for one migration release. */\nexport function serializeWidgetBinding(binding: WidgetBinding, config: Record<string, unknown>) {\n const canonical: WidgetBinding = {\n source: { kind: binding.source.kind, ref: binding.source.ref },\n parserId: binding.parserId,\n fields: { ...binding.fields },\n };\n\n return {\n config: {\n ...config,\n binding: canonical,\n dataSourceType: canonical.source.kind,\n ...canonical.fields,\n },\n dataSinkId: canonical.source.kind === 'DATASINK' ? canonical.source.ref : undefined,\n parserId: canonical.parserId,\n };\n}\n\nexport function resolveBindingPath(value: unknown, path: string): unknown {\n return path\n .replace(/^\\$?\\.?/, '')\n .split('.')\n .filter(Boolean)\n .reduce<unknown>((current, segment) => (isRecord(current) ? current[segment] : undefined), value);\n}\n\nexport function validateWidgetBinding(binding: WidgetBinding, sample?: unknown): WidgetBindingValidation {\n const isUnbound = binding.source.kind === 'DATASINK' && !binding.source.ref;\n if (isUnbound) return { status: 'unbound', missingFields: [] };\n if (sample === undefined) return { status: 'bound', missingFields: [] };\n\n const missingFields = Object.entries(binding.fields)\n .filter(([, path]) => resolveBindingPath(sample, path) === undefined)\n .map(([field]) => field);\n\n return { status: missingFields.length > 0 ? 'field_mismatch' : 'bound', missingFields };\n}\n\ninterface BindingStatusInput {\n binding: WidgetBinding;\n loading?: boolean;\n error?: string | null;\n preview?: unknown;\n previewComplete?: boolean;\n}\n\nexport function getWidgetBindingStatus({\n binding,\n loading = false,\n error,\n preview,\n previewComplete = false,\n}: BindingStatusInput): { status: WidgetBindingStatus; missingFields: string[] } {\n if (loading) return { status: 'loading', missingFields: [] };\n if (error) return { status: 'parser_error', missingFields: [] };\n\n const initialValidation = validateWidgetBinding(binding);\n if (initialValidation.status === 'unbound') return initialValidation;\n if (!previewComplete) return initialValidation;\n if (preview == null || (Array.isArray(preview) && preview.length === 0)) {\n return { status: 'no_data', missingFields: [] };\n }\n\n return validateWidgetBinding(binding, Array.isArray(preview) ? preview[0] : preview);\n}\n"],"mappings":"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"}
|