@burdenoff/microfe-bigconsole 2026.729.2 → 2026.729.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigconsole/components/widgets/ActionConfigDialog.js +412 -305
- package/dist/bigconsole/components/widgets/ActionConfigDialog.js.map +1 -1
- package/dist/bigconsole/components/widgets/PropertiesPanel.js +481 -420
- package/dist/bigconsole/components/widgets/PropertiesPanel.js.map +1 -1
- package/dist/bigconsole/hooks/useEventWebhooks.js +6 -2
- package/dist/bigconsole/hooks/useEventWebhooks.js.map +1 -1
- package/package.json +3 -3
|
@@ -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 {\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-status-success-bg',\n WARNING: 'bg-status-warning-bg',\n DANGER: 'bg-status-error-bg',\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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"}
|
|
1
|
+
{"version":3,"file":"PropertiesPanel.js","names":[],"sources":["../../../../src/bigconsole/components/widgets/PropertiesPanel.tsx"],"sourcesContent":["/**\n * PropertiesPanel Component\n *\n * Right-side panel for widget configuration.\n * v2.0: Added Actions tab for managing widget actions.\n * Uses fe-libs shared components for consistent styling.\n */\n\nimport React, { type FC, memo, useCallback, useState, useEffect, useRef, useMemo } from 'react';\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@burdenoff/fe-libs/ui';\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@burdenoff/fe-libs/ui';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Input } from '@burdenoff/fe-libs/ui';\nimport { Textarea } from '@burdenoff/fe-libs/ui';\nimport { Label } from '@burdenoff/fe-libs/ui';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@burdenoff/fe-libs/ui';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent } from '@burdenoff/fe-libs/ui';\nimport { Plus, ChevronUp, ChevronDown, Pencil, Trash2, ExternalLink, Check, X, Info } from 'lucide-react';\nimport type { Widget, DataSourceType, RendererType } from '../../types';\nimport { useWidgetStore } from '../../store';\nimport { useWidgetOperations, useDrilldownOperations } from '../../hooks';\nimport {\n useWidgetActions,\n type WidgetAction,\n type CreateWidgetActionInput,\n type UpdateWidgetActionInput,\n} from '../../hooks/useWidgetActions';\nimport { getWidgetDefinition } from './WidgetRegistry';\nimport { ActionConfigDialog, type ActionSaveData, type ActionWebhookDraft } from './ActionConfigDialog';\nimport { useEventWebhooks, type EventWebhookMapping } from '../../hooks/useEventWebhooks';\nimport { WebhookConfigPanel } from './webhook-config';\nimport { EventLogViewer } from './event-logs';\nimport { DrilldownConfigPanel, type DrilldownConfig } from './DrilldownConfigPanel';\n// Data Source selectors\nimport { useDataSinkOperations } from '../../hooks/useDataSinkOperations';\nimport { useParserOperations } from '../../hooks/useParserOperations';\nimport { AdvancedSection, AppearanceSection, DataBindingSection, GeneralSection } from '../builder/inspector';\nimport {\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// The event a button emits when clicked. MUST match DashboardGrid.handleWidgetAction\n// (`widget:${name.toLowerCase().replace(/\\s+/g,'_')}:clicked`) so a webhook mapping\n// created here actually fires for the click.\nfunction eventPatternForAction(actionName: string): string {\n return `widget:${actionName.toLowerCase().replace(/\\s+/g, '_')}:clicked`;\n}\n\n// Action style colors\nconst ACTION_STYLE_COLORS: Record<string, string> = {\n PRIMARY: 'bg-action-primary',\n SECONDARY: 'bg-muted-foreground',\n SUCCESS: 'bg-status-success-bg',\n WARNING: 'bg-status-warning-bg',\n DANGER: 'bg-status-error-bg',\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 // Webhook mappings for this widget — used to pair a button with an HTTP call\n // inline (\"When clicked → call a webhook\") without leaving the Add-Button flow.\n const {\n webhooks,\n loading: webhooksLoading,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n refetch: refetchWebhooks,\n } = useEventWebhooks({\n widgetId: selectedWidgetId || undefined,\n skipFetch: !selectedWidgetId,\n });\n\n // Match a webhook mapping to an action (by its emitted event) within a given list.\n const findWebhookIn = useCallback(\n (list: EventWebhookMapping[], actionName: string) => {\n const pattern = eventPatternForAction(actionName);\n return list.find((w) => w.widgetId === selectedWidgetId && w.eventPattern === pattern) ?? null;\n },\n [selectedWidgetId]\n );\n\n // Find the webhook mapping paired to an action from the currently-loaded list.\n const webhookForAction = useCallback(\n (actionName: string) => findWebhookIn(webhooks, actionName),\n [findWebhookIn, webhooks]\n );\n\n // Create/update/remove the paired webhook mapping to match the dialog's outcome.\n // Returns false if a required webhook mutation failed so the caller can surface\n // it instead of silently leaving a button that looks wired but isn't (#5).\n const reconcileActionWebhook = useCallback(\n async (actionName: string, draft: ActionWebhookDraft | null | undefined): Promise<boolean> => {\n if (!selectedWidgetId || draft === undefined) return true;\n const pattern = eventPatternForAction(actionName);\n let existing = webhookForAction(actionName);\n // If the mapping list is still loading, a null lookup is unreliable and a\n // create here would duplicate an existing mapping (two endpoints firing per\n // click). Force a fresh fetch and re-check before deciding there is none (#7).\n if (!existing && webhooksLoading) {\n const res = await refetchWebhooks();\n const fresh = (res?.data?.listEventWebhookMappings ?? []) as EventWebhookMapping[];\n existing = findWebhookIn(fresh, actionName);\n }\n if (draft && draft.url) {\n if (existing) {\n // Re-activate on save — a mapping disabled in the Webhooks panel would\n // otherwise show as wired while clicks are still ignored (#4).\n const res = await updateWebhook({\n id: existing.id,\n eventPattern: pattern,\n webhookUrl: draft.url,\n method: draft.method,\n isActive: true,\n });\n return res !== null;\n }\n const res = await createWebhook({\n widgetId: selectedWidgetId,\n eventPattern: pattern,\n webhookUrl: draft.url,\n method: draft.method,\n });\n return res !== null;\n }\n if (draft === null && existing) {\n // Outcome switched away from \"call a webhook\" — drop the binding.\n return deleteWebhook(existing.id);\n }\n return true;\n },\n [\n selectedWidgetId,\n webhookForAction,\n webhooksLoading,\n refetchWebhooks,\n findWebhookIn,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n ]\n );\n\n const [isActionDialogOpen, setIsActionDialogOpen] = useState(false);\n const [editingAction, setEditingAction] = useState<WidgetAction | undefined>(undefined);\n const [actionSaving, setActionSaving] = useState(false);\n // When a webhook mutation fails after the action itself saved, we keep the\n // dialog open on the persisted action so the user can retry. That reassigns\n // the dialog's `action` prop and re-runs its reset effect, which would wipe the\n // URL the user just typed (the mapping never persisted, so `editingWebhook` is\n // null). Stash the attempted draft here and feed it back as `initialWebhook` so\n // the dialog re-seeds it instead of clearing it.\n //\n // Tri-state: `undefined` = no retry draft (use the persisted mapping);\n // an object = a failed webhook create/update to re-seed; `null` = a failed\n // \"switch to Emit / remove webhook\" so the retry keeps the Emit selection\n // instead of falling back to the still-present old mapping (#8).\n const [pendingWebhookDraft, setPendingWebhookDraft] = useState<ActionWebhookDraft | null | undefined>(undefined);\n // Surfaces a save failure inside the dialog so the user isn't left with a\n // silently-editable form and no idea what went wrong (#9).\n const [pendingActionError, setPendingActionError] = useState<string | null>(null);\n\n // Prefill the dialog's webhook field when editing an action already paired with one.\n const editingWebhook = useMemo<ActionWebhookDraft | null>(() => {\n if (!editingAction) return null;\n const paired = webhookForAction(editingAction.name);\n return paired ? { url: paired.webhookUrl, method: paired.method } : null;\n }, [editingAction, webhookForAction]);\n\n // A retry draft (from a failed save) wins over the persisted mapping — including\n // an explicit `null` removal retry, which must NOT fall back to `editingWebhook`.\n const dialogInitialWebhook = pendingWebhookDraft !== undefined ? pendingWebhookDraft : editingWebhook;\n\n // Pre-compute each action's paired webhook once per render instead of running a\n // `.find()` inside the action-list JSX for every row on every render.\n const webhookByActionName = useMemo(() => {\n const map = new Map<string, EventWebhookMapping>();\n for (const a of actions) {\n const paired = webhookForAction(a.name);\n if (paired) map.set(a.name, paired);\n }\n return map;\n }, [actions, webhookForAction]);\n\n const handleAddAction = useCallback(() => {\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setEditingAction(undefined);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleEditAction = useCallback((action: WidgetAction) => {\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setEditingAction(action);\n setIsActionDialogOpen(true);\n }, []);\n\n const handleSaveAction = useCallback(\n async (data: ActionSaveData, webhook: ActionWebhookDraft | null) => {\n if (!selectedWidgetId) return;\n\n setActionSaving(true);\n setPendingActionError(null);\n try {\n // `data` carries only action fields; the webhook binding arrives as a\n // separate arg so it can never leak into the GraphQL mutation input.\n const isUpdate = 'id' in data && !!data.id;\n\n let newAction: WidgetAction | null = null;\n if (isUpdate) {\n newAction = await updateAction(data as UpdateWidgetActionInput);\n } else {\n newAction = await createAction(data as Omit<CreateWidgetActionInput, 'widgetId'>);\n }\n\n // Immediately sync to widget store after successful save\n // This ensures the widget displays the action right away\n if (newAction) {\n const currentActions = [...actions];\n if (isUpdate) {\n // Update existing action\n const idx = currentActions.findIndex((a) => a.id === newAction?.id);\n if (idx >= 0) {\n currentActions[idx] = newAction;\n }\n } else {\n // Add new action\n currentActions.push(newAction);\n }\n // Sort by order and update store\n const sortedActions = currentActions.sort((a, b) => a.order - b.order);\n updateWidgetInStore(selectedWidgetId, { actions: sortedActions });\n\n // Pair (or unpair) the webhook that actually gives the button its behaviour.\n const webhookOk = await reconcileActionWebhook(newAction.name, webhook);\n if (!webhookOk) {\n // The action saved but its webhook did not. Keep the dialog open on\n // the now-persisted action so the user can retry (as an update, not a\n // duplicate). Stash the typed draft (which may be `null` for a failed\n // switch-to-Emit) so re-seeding the dialog keeps the user's choice (#8),\n // and surface why the save didn't fully land (#9).\n setPendingWebhookDraft(webhook);\n setPendingActionError(\n webhook\n ? 'The button was saved, but its webhook could not be saved. Check the URL and try again.'\n : 'The button was saved, but its old webhook could not be removed. Try again.'\n );\n setEditingAction(newAction);\n return;\n }\n\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n }\n } catch (err) {\n // Keep the dialog open (we never closed it in the throw path) and surface\n // the failure to the user — console.log is globally no-op'd here, so warn (#9).\n console.warn('Failed to save widget action:', err);\n setPendingActionError(err instanceof Error ? err.message : 'Could not save the button. Please try again.');\n } finally {\n setActionSaving(false);\n }\n },\n [selectedWidgetId, actions, createAction, updateAction, updateWidgetInStore, reconcileActionWebhook]\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 target = actions.find((a) => a.id === actionId);\n const success = await deleteAction(actionId);\n if (success) {\n // Immediately sync to widget store after deletion\n const updatedActions = actions.filter((a) => a.id !== actionId);\n updateWidgetInStore(selectedWidgetId, { actions: updatedActions });\n\n // Best-effort: remove the webhook mapping this button was paired with.\n if (target) {\n const paired = webhookForAction(target.name);\n if (paired) await deleteWebhook(paired.id);\n }\n }\n }\n },\n [selectedWidgetId, actions, deleteAction, updateWidgetInStore, webhookForAction, deleteWebhook]\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 truncate\">\n {webhookByActionName.has(action.name)\n ? `→ ${webhookByActionName.get(action.name)?.method} ${webhookByActionName.get(action.name)?.webhookUrl}`\n : 'Emits an event'}\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 initialWebhook={dialogInitialWebhook}\n isLoading={actionSaving}\n saveError={pendingActionError}\n onClose={() => {\n setIsActionDialogOpen(false);\n setEditingAction(undefined);\n setPendingWebhookDraft(undefined);\n setPendingActionError(null);\n }}\n onSave={handleSaveAction}\n />\n </SheetContent>\n </Sheet>\n );\n});\n\nexport default PropertiesPanel;\n"],"mappings":"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"}
|
|
@@ -42,8 +42,12 @@ function u(u = {}) {
|
|
|
42
42
|
g
|
|
43
43
|
]), N = o(async (e) => {
|
|
44
44
|
try {
|
|
45
|
+
let { id: t, ...r } = e;
|
|
45
46
|
return (await w({
|
|
46
|
-
variables: {
|
|
47
|
+
variables: {
|
|
48
|
+
id: t,
|
|
49
|
+
input: r
|
|
50
|
+
},
|
|
47
51
|
refetchQueries: [{
|
|
48
52
|
query: n,
|
|
49
53
|
variables: {
|
|
@@ -129,7 +133,7 @@ function u(u = {}) {
|
|
|
129
133
|
deleteWebhook: P,
|
|
130
134
|
toggleWebhook: F,
|
|
131
135
|
testWebhook: I,
|
|
132
|
-
refetch: x
|
|
136
|
+
refetch: o(async () => await x() ?? {}, [x])
|
|
133
137
|
};
|
|
134
138
|
}
|
|
135
139
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEventWebhooks.js","names":[],"sources":["../../../src/bigconsole/hooks/useEventWebhooks.ts"],"sourcesContent":["/**\n * useEventWebhooks Hook (v2.0)\n *\n * Hook for managing event webhook mappings.\n * Provides CRUD operations for configuring webhooks that receive widget events.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n ListEventWebhookMappingsDocument,\n CreateEventWebhookMappingDocument,\n UpdateEventWebhookMappingDocument,\n DeleteEventWebhookMappingDocument,\n ToggleEventWebhookMappingDocument,\n TestWebhookMappingDocument,\n} from '../../generated/wspace-operations';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\nexport interface RetryPolicy {\n maxRetries: number;\n backoffMs: number;\n backoffMultiplier: number;\n}\n\nexport interface EventWebhookMapping {\n id: string;\n workspaceId: string;\n dashboardId: string | null;\n widgetId: string | null;\n eventPattern: string;\n webhookUrl: string;\n method: HttpMethod;\n headers: Record<string, string> | null;\n payloadTemplate: string | null;\n retryPolicy: RetryPolicy | null;\n isActive: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface CreateEventWebhookMappingInput {\n dashboardId?: string;\n widgetId?: string;\n eventPattern: string;\n webhookUrl: string;\n method?: HttpMethod;\n headers?: Record<string, string>;\n payloadTemplate?: string;\n retryPolicy?: RetryPolicy;\n}\n\nexport interface UpdateEventWebhookMappingInput {\n id: string;\n eventPattern?: string;\n webhookUrl?: string;\n method?: HttpMethod;\n headers?: Record<string, string> | null;\n payloadTemplate?: string | null;\n retryPolicy?: RetryPolicy | null;\n isActive?: boolean;\n}\n\nexport interface WebhookTestResult {\n success: boolean;\n statusCode: number | null;\n responseBody: unknown;\n errorMessage: string | null;\n latencyMs: number;\n}\n\nexport interface UseEventWebhooksOptions {\n /** Dashboard ID to filter webhooks */\n dashboardId?: string;\n /** Widget ID to filter webhooks */\n widgetId?: string;\n /** Filter by active status */\n isActive?: boolean;\n /** Skip fetching on mount */\n skipFetch?: boolean;\n /** Callback when webhooks are loaded */\n onWebhooksLoaded?: (webhooks: EventWebhookMapping[]) => void;\n /** Callback on error */\n onError?: (error: Error) => void;\n}\n\nexport interface UseEventWebhooksResult {\n /** Loading state */\n loading: boolean;\n /** Error state */\n error: Error | null;\n /** List of webhook mappings */\n webhooks: EventWebhookMapping[];\n /** Create a new webhook mapping */\n createWebhook: (input: CreateEventWebhookMappingInput) => Promise<EventWebhookMapping | null>;\n /** Update an existing webhook mapping */\n updateWebhook: (input: UpdateEventWebhookMappingInput) => Promise<EventWebhookMapping | null>;\n /** Delete a webhook mapping */\n deleteWebhook: (id: string) => Promise<boolean>;\n /** Toggle webhook active status */\n toggleWebhook: (id: string, isActive: boolean) => Promise<EventWebhookMapping | null>;\n /** Test a webhook by sending a test event */\n testWebhook: (id: string) => Promise<WebhookTestResult | null>;\n /** Refetch webhooks */\n refetch: () => void;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useEventWebhooks(options: UseEventWebhooksOptions = {}): UseEventWebhooksResult {\n const { dashboardId, widgetId, isActive, skipFetch = false, onWebhooksLoaded, onError } = options;\n\n const [error, setError] = useState<Error | null>(null);\n\n // Query for fetching webhooks\n const {\n data,\n loading: queryLoading,\n refetch,\n } = useQuery(ListEventWebhookMappingsDocument, {\n variables: { dashboardId, widgetId, isActive },\n skip: skipFetch,\n onCompleted: (data: Record<string, unknown>) => {\n const mappings = data?.listEventWebhookMappings as EventWebhookMapping[] | undefined;\n if (mappings) {\n onWebhooksLoaded?.(mappings);\n }\n },\n onError: (err: Error) => {\n setError(err);\n onError?.(err);\n },\n } as Record<string, unknown>);\n\n // Mutations\n const [createMutation, { loading: createLoading }] = useMutation(CreateEventWebhookMappingDocument);\n const [updateMutation, { loading: updateLoading }] = useMutation(UpdateEventWebhookMappingDocument);\n const [deleteMutation, { loading: deleteLoading }] = useMutation(DeleteEventWebhookMappingDocument);\n const [toggleMutation, { loading: toggleLoading }] = useMutation(ToggleEventWebhookMappingDocument);\n const [testMutation, { loading: testLoading }] = useMutation(TestWebhookMappingDocument);\n\n // Create webhook\n const createWebhook = useCallback(\n async (input: CreateEventWebhookMappingInput): Promise<EventWebhookMapping | null> => {\n try {\n const result = await createMutation({\n variables: { input },\n refetchQueries: [\n {\n query: ListEventWebhookMappingsDocument,\n variables: { dashboardId, widgetId, isActive },\n },\n ],\n });\n\n const mutationData = result.data as { createEventWebhookMapping?: EventWebhookMapping } | null | undefined;\n const webhook = mutationData?.createEventWebhookMapping;\n if (webhook) {\n }\n return webhook || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to create webhook');\n setError(error);\n onError?.(error);\n return null;\n }\n },\n [createMutation, dashboardId, widgetId, isActive, onError]\n );\n\n // Update webhook\n const updateWebhook = useCallback(\n async (input: UpdateEventWebhookMappingInput): Promise<EventWebhookMapping | null> => {\n try {\n const result = await updateMutation({\n variables: { input },\n refetchQueries: [\n {\n query: ListEventWebhookMappingsDocument,\n variables: { dashboardId, widgetId, isActive },\n },\n ],\n });\n\n const updateData = result.data as { updateEventWebhookMapping?: EventWebhookMapping } | null | undefined;\n const webhook = updateData?.updateEventWebhookMapping;\n if (webhook) {\n }\n return webhook || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update webhook');\n setError(error);\n onError?.(error);\n return null;\n }\n },\n [updateMutation, dashboardId, widgetId, isActive, onError]\n );\n\n // Delete webhook\n const deleteWebhook = useCallback(\n async (id: string): Promise<boolean> => {\n try {\n await deleteMutation({\n variables: { id },\n refetchQueries: [\n {\n query: ListEventWebhookMappingsDocument,\n variables: { dashboardId, widgetId, isActive },\n },\n ],\n });\n\n return true;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to delete webhook');\n setError(error);\n onError?.(error);\n return false;\n }\n },\n [deleteMutation, dashboardId, widgetId, isActive, onError]\n );\n\n // Toggle webhook\n const toggleWebhook = useCallback(\n async (id: string, active: boolean): Promise<EventWebhookMapping | null> => {\n try {\n const result = await toggleMutation({\n variables: { id, isActive: active },\n refetchQueries: [\n {\n query: ListEventWebhookMappingsDocument,\n variables: { dashboardId, widgetId, isActive },\n },\n ],\n });\n\n const toggleData = result.data as { toggleEventWebhookMapping?: EventWebhookMapping } | null | undefined;\n const webhook = toggleData?.toggleEventWebhookMapping;\n if (webhook) {\n }\n return webhook || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to toggle webhook');\n setError(error);\n onError?.(error);\n return null;\n }\n },\n [toggleMutation, dashboardId, widgetId, isActive, onError]\n );\n\n // Test webhook\n const testWebhook = useCallback(\n async (id: string): Promise<WebhookTestResult | null> => {\n try {\n const result = await testMutation({\n variables: { id },\n });\n\n const testData = result.data as { testWebhookMapping?: WebhookTestResult } | null | undefined;\n const testResult = testData?.testWebhookMapping;\n if (testResult) {\n }\n return testResult || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to test webhook');\n setError(error);\n onError?.(error);\n return null;\n }\n },\n [testMutation, onError]\n );\n\n // Combine loading states\n const loading = queryLoading || createLoading || updateLoading || deleteLoading || toggleLoading || testLoading;\n\n // Extract webhooks from query data\n const queryData = data as { listEventWebhookMappings?: EventWebhookMapping[] } | undefined;\n const webhooks: EventWebhookMapping[] = queryData?.listEventWebhookMappings || [];\n\n return {\n loading,\n error,\n webhooks,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n toggleWebhook,\n testWebhook,\n refetch,\n };\n}\n\nexport default useEventWebhooks;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useEventWebhooks.js","names":[],"sources":["../../../src/bigconsole/hooks/useEventWebhooks.ts"],"sourcesContent":["/**\n * useEventWebhooks Hook (v2.0)\n *\n * Hook for managing event webhook mappings.\n * Provides CRUD operations for configuring webhooks that receive widget events.\n */\n\nimport { useCallback, useState } from 'react';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n ListEventWebhookMappingsDocument,\n CreateEventWebhookMappingDocument,\n UpdateEventWebhookMappingDocument,\n DeleteEventWebhookMappingDocument,\n ToggleEventWebhookMappingDocument,\n TestWebhookMappingDocument,\n} from '../../generated/wspace-operations';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\nexport interface RetryPolicy {\n maxRetries: number;\n backoffMs: number;\n backoffMultiplier: number;\n}\n\nexport interface EventWebhookMapping {\n id: string;\n workspaceId: string;\n dashboardId: string | null;\n widgetId: string | null;\n eventPattern: string;\n webhookUrl: string;\n method: HttpMethod;\n headers: Record<string, string> | null;\n payloadTemplate: string | null;\n retryPolicy: RetryPolicy | null;\n isActive: boolean;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface CreateEventWebhookMappingInput {\n dashboardId?: string;\n widgetId?: string;\n eventPattern: string;\n webhookUrl: string;\n method?: HttpMethod;\n headers?: Record<string, string>;\n payloadTemplate?: string;\n retryPolicy?: RetryPolicy;\n}\n\nexport interface UpdateEventWebhookMappingInput {\n id: string;\n eventPattern?: string;\n webhookUrl?: string;\n method?: HttpMethod;\n headers?: Record<string, string> | null;\n payloadTemplate?: string | null;\n retryPolicy?: RetryPolicy | null;\n isActive?: boolean;\n}\n\nexport interface WebhookTestResult {\n success: boolean;\n statusCode: number | null;\n responseBody: unknown;\n errorMessage: string | null;\n latencyMs: number;\n}\n\nexport interface UseEventWebhooksOptions {\n /** Dashboard ID to filter webhooks */\n dashboardId?: string;\n /** Widget ID to filter webhooks */\n widgetId?: string;\n /** Filter by active status */\n isActive?: boolean;\n /** Skip fetching on mount */\n skipFetch?: boolean;\n /** Callback when webhooks are loaded */\n onWebhooksLoaded?: (webhooks: EventWebhookMapping[]) => void;\n /** Callback on error */\n onError?: (error: Error) => void;\n}\n\nexport interface UseEventWebhooksResult {\n /** Loading state */\n loading: boolean;\n /** Error state */\n error: Error | null;\n /** List of webhook mappings */\n webhooks: EventWebhookMapping[];\n /** Create a new webhook mapping */\n createWebhook: (input: CreateEventWebhookMappingInput) => Promise<EventWebhookMapping | null>;\n /** Update an existing webhook mapping */\n updateWebhook: (input: UpdateEventWebhookMappingInput) => Promise<EventWebhookMapping | null>;\n /** Delete a webhook mapping */\n deleteWebhook: (id: string) => Promise<boolean>;\n /** Toggle webhook active status */\n toggleWebhook: (id: string, isActive: boolean) => Promise<EventWebhookMapping | null>;\n /** Test a webhook by sending a test event */\n testWebhook: (id: string) => Promise<WebhookTestResult | null>;\n /** Refetch webhooks, resolving with the fresh list */\n refetch: () => Promise<{ data?: { listEventWebhookMappings?: EventWebhookMapping[] } | null }>;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useEventWebhooks(options: UseEventWebhooksOptions = {}): UseEventWebhooksResult {\n const { dashboardId, widgetId, isActive, skipFetch = false, onWebhooksLoaded, onError } = options;\n\n const [error, setError] = useState<Error | null>(null);\n\n // Query for fetching webhooks\n const {\n data,\n loading: queryLoading,\n refetch,\n } = useQuery(ListEventWebhookMappingsDocument, {\n variables: { dashboardId, widgetId, isActive },\n skip: skipFetch,\n onCompleted: (data: Record<string, unknown>) => {\n const mappings = data?.listEventWebhookMappings as EventWebhookMapping[] | undefined;\n if (mappings) {\n onWebhooksLoaded?.(mappings);\n }\n },\n onError: (err: Error) => {\n setError(err);\n onError?.(err);\n },\n } as Record<string, unknown>);\n\n // Mutations\n const [createMutation, { loading: createLoading }] = useMutation(CreateEventWebhookMappingDocument);\n const [updateMutation, { loading: updateLoading }] = useMutation(UpdateEventWebhookMappingDocument);\n const [deleteMutation, { loading: deleteLoading }] = useMutation(DeleteEventWebhookMappingDocument);\n const [toggleMutation, { loading: toggleLoading }] = useMutation(ToggleEventWebhookMappingDocument);\n const [testMutation, { loading: testLoading }] = useMutation(TestWebhookMappingDocument);\n\n // Create webhook\n const createWebhook = useCallback(\n async (input: CreateEventWebhookMappingInput): Promise<EventWebhookMapping | null> => {\n try {\n const result = await createMutation({\n variables: { input },\n refetchQueries: [\n {\n query: ListEventWebhookMappingsDocument,\n variables: { dashboardId, widgetId, isActive },\n },\n ],\n });\n\n const mutationData = result.data as { createEventWebhookMapping?: EventWebhookMapping } | null | undefined;\n const webhook = mutationData?.createEventWebhookMapping;\n if (webhook) {\n }\n return webhook || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to create webhook');\n setError(error);\n onError?.(error);\n return null;\n }\n },\n [createMutation, dashboardId, widgetId, isActive, onError]\n );\n\n // Update webhook\n const updateWebhook = useCallback(\n async (input: UpdateEventWebhookMappingInput): Promise<EventWebhookMapping | null> => {\n try {\n // The mutation declares `id` as a separate required variable; the server\n // input type has no `id` field. Split it out so `$id` is provided and no\n // unknown `id` leaks into `$input`.\n const { id, ...rest } = input;\n const result = await updateMutation({\n variables: { id, input: rest },\n refetchQueries: [\n {\n query: ListEventWebhookMappingsDocument,\n variables: { dashboardId, widgetId, isActive },\n },\n ],\n });\n\n const updateData = result.data as { updateEventWebhookMapping?: EventWebhookMapping } | null | undefined;\n const webhook = updateData?.updateEventWebhookMapping;\n if (webhook) {\n }\n return webhook || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update webhook');\n setError(error);\n onError?.(error);\n return null;\n }\n },\n [updateMutation, dashboardId, widgetId, isActive, onError]\n );\n\n // Delete webhook\n const deleteWebhook = useCallback(\n async (id: string): Promise<boolean> => {\n try {\n await deleteMutation({\n variables: { id },\n refetchQueries: [\n {\n query: ListEventWebhookMappingsDocument,\n variables: { dashboardId, widgetId, isActive },\n },\n ],\n });\n\n return true;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to delete webhook');\n setError(error);\n onError?.(error);\n return false;\n }\n },\n [deleteMutation, dashboardId, widgetId, isActive, onError]\n );\n\n // Toggle webhook\n const toggleWebhook = useCallback(\n async (id: string, active: boolean): Promise<EventWebhookMapping | null> => {\n try {\n const result = await toggleMutation({\n variables: { id, isActive: active },\n refetchQueries: [\n {\n query: ListEventWebhookMappingsDocument,\n variables: { dashboardId, widgetId, isActive },\n },\n ],\n });\n\n const toggleData = result.data as { toggleEventWebhookMapping?: EventWebhookMapping } | null | undefined;\n const webhook = toggleData?.toggleEventWebhookMapping;\n if (webhook) {\n }\n return webhook || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to toggle webhook');\n setError(error);\n onError?.(error);\n return null;\n }\n },\n [toggleMutation, dashboardId, widgetId, isActive, onError]\n );\n\n // Test webhook\n const testWebhook = useCallback(\n async (id: string): Promise<WebhookTestResult | null> => {\n try {\n const result = await testMutation({\n variables: { id },\n });\n\n const testData = result.data as { testWebhookMapping?: WebhookTestResult } | null | undefined;\n const testResult = testData?.testWebhookMapping;\n if (testResult) {\n }\n return testResult || null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to test webhook');\n setError(error);\n onError?.(error);\n return null;\n }\n },\n [testMutation, onError]\n );\n\n // Combine loading states\n const loading = queryLoading || createLoading || updateLoading || deleteLoading || toggleLoading || testLoading;\n\n // Extract webhooks from query data\n const queryData = data as { listEventWebhookMappings?: EventWebhookMapping[] } | undefined;\n const webhooks: EventWebhookMapping[] = queryData?.listEventWebhookMappings || [];\n\n // Wrap the Apollo refetch so callers get a typed fresh list (the raw refetch\n // resolves with `QueryResult<unknown>`). Callers use this to re-check for an\n // existing mapping before creating, avoiding duplicates during initial load.\n const refetchWebhooks = useCallback(async (): Promise<{\n data?: { listEventWebhookMappings?: EventWebhookMapping[] } | null;\n }> => {\n const res = (await refetch()) as { data?: { listEventWebhookMappings?: EventWebhookMapping[] } | null };\n return res ?? {};\n }, [refetch]);\n\n return {\n loading,\n error,\n webhooks,\n createWebhook,\n updateWebhook,\n deleteWebhook,\n toggleWebhook,\n testWebhook,\n refetch: refetchWebhooks,\n };\n}\n\nexport default useEventWebhooks;\n"],"mappings":"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"}
|