@burdenoff/microfe-bigconsole 2026.613.1 → 2026.613.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetRegistry.js","names":[],"sources":["../../../../src/bigconsole/components/widgets/WidgetRegistry.ts"],"sourcesContent":["/**\n * Widget Registry\n *\n * Central registry mapping widget types to their components,\n * metadata, and configuration schemas.\n */\n\nimport type { ComponentType } from 'react';\nimport type { WidgetType, WidgetCategory, Widget } from '../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface WidgetComponentProps {\n /** The widget data */\n widget: Widget;\n /** Widget data (processed) */\n data: Record<string, unknown>;\n /** Whether the widget is in edit mode */\n isEditMode?: boolean;\n /** Whether the widget is selected */\n isSelected?: boolean;\n /** Whether data is loading */\n isLoading?: boolean;\n /** Error message if any */\n error?: string | null;\n /** Callback when widget is clicked */\n onClick?: () => void;\n /** Callback for drilldown navigation */\n onDrilldown?: (params: Record<string, unknown>) => void;\n}\n\nexport interface WidgetDefinition {\n /** Widget type */\n type: WidgetType;\n /** Human-readable name */\n name: string;\n /** Description */\n description: string;\n /** Lucide icon name */\n icon: string;\n /** Category for grouping */\n category: WidgetCategory;\n /** Component to render. Null for all current widget types because the render path\n * uses WidgetRendererFactory + RendererRegistry instead of this field. */\n component: ComponentType<WidgetComponentProps> | null;\n /** Default configuration */\n defaultConfig: Record<string, unknown>;\n /** Default size (fixed grid) */\n defaultSize: { width: number; height: number };\n /** Default responsive position (v1.0 spec) */\n defaultResponsive: { xs: number; sm: number; md: number; lg: number };\n /** Minimum size constraints */\n minSize: { width: number; height: number };\n /** Maximum size constraints */\n maxSize: { width: number; height: number };\n /** Whether the widget supports data binding */\n supportsDataBinding: boolean;\n /** Whether the widget supports drilldown */\n supportsDrilldown: boolean;\n /** Whether the widget supports auto-refresh */\n supportsAutoRefresh: boolean;\n /** Tags for search/filter */\n tags: string[];\n}\n\n// ============================================================================\n// Widget Definitions\n// ============================================================================\n\nexport const WIDGET_DEFINITIONS: Record<WidgetType, WidgetDefinition> = {\n // ============================================================================\n // Core Visualization (6 types)\n // ============================================================================\n\n kpi_card_comparison: {\n type: 'kpi_card_comparison',\n name: 'KPI Card Comparison',\n description: 'Compare multiple KPIs side by side with trends',\n icon: 'LayoutDashboard',\n category: 'KPI',\n component: null,\n defaultConfig: {\n comparisons: [],\n showSparklines: true,\n layout: 'horizontal',\n format: 'number',\n },\n defaultSize: { width: 6, height: 3 },\n defaultResponsive: { xs: 12, sm: 6, md: 6, lg: 4 },\n minSize: { width: 4, height: 2 },\n maxSize: { width: 12, height: 6 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['kpi', 'comparison', 'metrics', 'multi'],\n },\n\n metric_card: {\n type: 'metric_card',\n name: 'Metric Card',\n description: 'Display a single KPI value with trend indicator',\n icon: 'TrendingUp',\n category: 'KPI',\n component: null,\n defaultConfig: {\n format: 'number',\n decimalPlaces: 0,\n showTrend: true,\n showSparkline: false,\n },\n defaultSize: { width: 3, height: 2 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 3 },\n minSize: { width: 2, height: 2 },\n maxSize: { width: 6, height: 4 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['kpi', 'metric', 'number', 'trend'],\n },\n\n chart: {\n type: 'chart',\n name: 'Chart',\n description: 'Multi-type chart (Line, Bar, Area, Pie)',\n icon: 'BarChart3',\n category: 'Visualization',\n component: null,\n defaultConfig: {\n chartType: 'line',\n showLegend: true,\n legendPosition: 'bottom',\n showGrid: true,\n showTooltip: true,\n },\n defaultSize: { width: 6, height: 4 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 3, height: 3 },\n maxSize: { width: 12, height: 8 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['chart', 'graph', 'visualization', 'line', 'bar', 'area', 'pie'],\n },\n\n funnel_chart: {\n type: 'funnel_chart',\n name: 'Funnel Chart',\n description: 'Visualize conversion or drop-off stages',\n icon: 'Filter',\n category: 'Visualization',\n component: null,\n defaultConfig: {\n showLabels: true,\n showValues: true,\n showPercentages: true,\n orientation: 'vertical',\n },\n defaultSize: { width: 4, height: 5 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 3, height: 4 },\n maxSize: { width: 8, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['funnel', 'conversion', 'stages', 'pipeline'],\n },\n\n table: {\n type: 'table',\n name: 'Data Table',\n description: 'Display data in sortable, filterable rows and columns',\n icon: 'Table',\n category: 'Data',\n component: null,\n defaultConfig: {\n pageSize: 10,\n sortable: true,\n filterable: true,\n stickyHeader: true,\n columns: [],\n },\n defaultSize: { width: 8, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 8, lg: 8 },\n minSize: { width: 4, height: 3 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['table', 'data', 'grid', 'rows'],\n },\n\n pivot_table: {\n type: 'pivot_table',\n name: 'Pivot Table',\n description: 'Interactive pivot table for data analysis',\n icon: 'Grid3x3',\n category: 'Data',\n component: null,\n defaultConfig: {\n rows: [],\n columns: [],\n values: [],\n aggregation: 'sum',\n showTotals: true,\n },\n defaultSize: { width: 10, height: 6 },\n defaultResponsive: { xs: 12, sm: 12, md: 12, lg: 10 },\n minSize: { width: 6, height: 4 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['pivot', 'table', 'analysis', 'aggregation'],\n },\n\n // ============================================================================\n // Indicators (2 types)\n // ============================================================================\n\n gauge: {\n type: 'gauge',\n name: 'Gauge',\n description: 'Display a value within a range',\n icon: 'Gauge',\n category: 'KPI',\n component: null,\n defaultConfig: {\n variant: 'circular',\n min: 0,\n max: 100,\n showValue: true,\n showLabels: true,\n thresholds: [\n { value: 33, color: 'red' },\n { value: 66, color: 'yellow' },\n { value: 100, color: 'green' },\n ],\n },\n defaultSize: { width: 3, height: 3 },\n defaultResponsive: { xs: 6, sm: 4, md: 3, lg: 3 },\n minSize: { width: 2, height: 2 },\n maxSize: { width: 6, height: 6 },\n supportsDataBinding: true,\n supportsDrilldown: false,\n supportsAutoRefresh: true,\n tags: ['gauge', 'meter', 'dial', 'indicator'],\n },\n\n progress: {\n type: 'progress',\n name: 'Progress Bar',\n description: 'Show progress towards a goal with milestones',\n icon: 'Activity',\n category: 'KPI',\n component: null,\n defaultConfig: {\n showPercentage: true,\n showValue: false,\n target: null,\n milestones: [],\n },\n defaultSize: { width: 4, height: 2 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 3, height: 2 },\n maxSize: { width: 12, height: 3 },\n supportsDataBinding: true,\n supportsDrilldown: false,\n supportsAutoRefresh: true,\n tags: ['progress', 'bar', 'goal', 'completion'],\n },\n\n // ============================================================================\n // Data Display (2 types)\n // ============================================================================\n\n list: {\n type: 'list',\n name: 'List',\n description: 'Display items in a scrollable list',\n icon: 'List',\n category: 'Data',\n component: null,\n defaultConfig: {\n showMetadata: true,\n showActions: false,\n showBadges: true,\n maxItems: 10,\n itemClickable: true,\n },\n defaultSize: { width: 4, height: 5 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 3, height: 3 },\n maxSize: { width: 8, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['list', 'items', 'scroll'],\n },\n\n form: {\n type: 'form',\n name: 'Form',\n description: 'Interactive form widget for data input',\n icon: 'FileText',\n category: 'Data',\n component: null,\n defaultConfig: {\n fields: [],\n submitAction: null,\n layout: 'vertical',\n showLabels: true,\n },\n defaultSize: { width: 6, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 4, height: 3 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: false,\n supportsAutoRefresh: false,\n tags: ['form', 'input', 'submit', 'fields'],\n },\n\n // ============================================================================\n // Content (2 types)\n // ============================================================================\n\n text: {\n type: 'text',\n name: 'Text',\n description: 'Rich text or markdown content widget',\n icon: 'Type',\n category: 'Content',\n component: null,\n defaultConfig: {\n content: '',\n format: 'markdown',\n alignment: 'left',\n },\n defaultSize: { width: 4, height: 3 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 2, height: 1 },\n maxSize: { width: 12, height: 12 },\n supportsDataBinding: false,\n supportsDrilldown: false,\n supportsAutoRefresh: false,\n tags: ['text', 'markdown', 'content', 'rich text'],\n },\n\n iframe: {\n type: 'iframe',\n name: 'Iframe',\n description: 'Embed external content via iframe',\n icon: 'ExternalLink',\n category: 'Content',\n component: null,\n defaultConfig: {\n url: '',\n sandbox: 'allow-scripts allow-same-origin',\n allowFullscreen: false,\n },\n defaultSize: { width: 6, height: 4 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 3, height: 2 },\n maxSize: { width: 12, height: 12 },\n supportsDataBinding: false,\n supportsDrilldown: false,\n supportsAutoRefresh: false,\n tags: ['iframe', 'embed', 'external', 'web'],\n },\n\n // ============================================================================\n // Spatial & Temporal (5 types)\n // ============================================================================\n\n map: {\n type: 'map',\n name: 'Map',\n description: 'Geographic map visualization with markers',\n icon: 'MapPin',\n category: 'Visualization',\n component: null,\n defaultConfig: {\n provider: 'mapbox',\n center: { lat: 0, lng: 0 },\n zoom: 2,\n showMarkers: true,\n showRegions: false,\n },\n defaultSize: { width: 6, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 4, height: 3 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['map', 'geo', 'location', 'markers'],\n },\n\n heatmap: {\n type: 'heatmap',\n name: 'Heatmap',\n description: 'Display values in a color-coded grid',\n icon: 'Grid3x3',\n category: 'Visualization',\n component: null,\n defaultConfig: {\n xAxisField: '',\n yAxisField: '',\n valueField: '',\n // HeatmapWidget renders intensity from `colorScheme` (token-based Tailwind\n // classes), not `colorScale`; keep the scale empty rather than seeding it\n // with CSS var() strings that a data array can never resolve.\n colorScale: [],\n colorScheme: 'blue',\n showValues: true,\n },\n defaultSize: { width: 6, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 4, height: 4 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['heatmap', 'grid', 'density', 'color'],\n },\n\n calendar: {\n type: 'calendar',\n name: 'Calendar',\n description: 'Calendar view for events and schedules',\n icon: 'Calendar',\n category: 'Temporal',\n component: null,\n defaultConfig: {\n view: 'month',\n showWeekNumbers: false,\n firstDayOfWeek: 0,\n eventField: '',\n },\n defaultSize: { width: 8, height: 6 },\n defaultResponsive: { xs: 12, sm: 12, md: 8, lg: 8 },\n minSize: { width: 6, height: 4 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['calendar', 'events', 'schedule', 'dates'],\n },\n\n kanban: {\n type: 'kanban',\n name: 'Kanban Board',\n description: 'Kanban board for task and workflow management',\n icon: 'Columns',\n category: 'Data',\n component: null,\n defaultConfig: {\n columns: [],\n cardTitleField: '',\n cardDescriptionField: '',\n columnField: '',\n allowDragDrop: true,\n },\n defaultSize: { width: 12, height: 6 },\n defaultResponsive: { xs: 12, sm: 12, md: 12, lg: 12 },\n minSize: { width: 8, height: 4 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['kanban', 'board', 'tasks', 'workflow'],\n },\n\n timeline: {\n type: 'timeline',\n name: 'Timeline',\n description: 'Timeline or Gantt chart for project scheduling',\n icon: 'GitBranch',\n category: 'Temporal',\n component: null,\n defaultConfig: {\n startDateField: '',\n endDateField: '',\n titleField: '',\n groupField: '',\n showToday: true,\n },\n defaultSize: { width: 12, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 12, lg: 12 },\n minSize: { width: 8, height: 3 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['timeline', 'gantt', 'project', 'schedule'],\n },\n\n // ============================================================================\n // Extensibility (1 type)\n // ============================================================================\n\n custom: {\n type: 'custom',\n name: 'Custom Widget',\n description: 'Custom widget with user-defined rendering',\n icon: 'Puzzle',\n category: 'Custom',\n component: null,\n defaultConfig: {\n componentId: '',\n props: {},\n },\n defaultSize: { width: 4, height: 4 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 2, height: 2 },\n maxSize: { width: 12, height: 12 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['custom', 'plugin', 'extension'],\n },\n};\n\n// ============================================================================\n// Registry Functions\n// ============================================================================\n\n/**\n * Default widget definition for unknown types\n */\nconst DEFAULT_WIDGET_DEFINITION: WidgetDefinition = {\n type: 'custom' as WidgetType,\n name: 'Unknown Widget',\n description: 'Unknown widget type',\n icon: 'Puzzle',\n category: 'Custom',\n component: null,\n defaultConfig: {},\n defaultSize: { width: 4, height: 4 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 2, height: 2 },\n maxSize: { width: 12, height: 12 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['unknown'],\n};\n\n/**\n * Get widget definition by type\n * Handles both lowercase (frontend) and uppercase (backend) types\n */\nexport function getWidgetDefinition(type: WidgetType | string): WidgetDefinition {\n // Try exact match first\n if (WIDGET_DEFINITIONS[type as WidgetType]) {\n return WIDGET_DEFINITIONS[type as WidgetType];\n }\n\n // Try lowercase version (backend returns uppercase for some types)\n const lowerType = type.toLowerCase() as WidgetType;\n if (WIDGET_DEFINITIONS[lowerType]) {\n return WIDGET_DEFINITIONS[lowerType];\n }\n\n // Return default definition for unknown types\n return { ...DEFAULT_WIDGET_DEFINITION, type: type as WidgetType };\n}\n\n/**\n * Get all widget definitions\n */\nexport function getAllWidgetDefinitions(): WidgetDefinition[] {\n return Object.values(WIDGET_DEFINITIONS);\n}\n\n/**\n * Get widget definitions by category\n */\nexport function getWidgetsByCategory(category: WidgetCategory): WidgetDefinition[] {\n return Object.values(WIDGET_DEFINITIONS).filter((def) => def.category === category);\n}\n\n/**\n * Search widget definitions by query\n */\nexport function searchWidgets(query: string): WidgetDefinition[] {\n const lowerQuery = query.toLowerCase();\n return Object.values(WIDGET_DEFINITIONS).filter(\n (def) =>\n def.name.toLowerCase().includes(lowerQuery) ||\n def.description.toLowerCase().includes(lowerQuery) ||\n def.tags.some((tag) => tag.includes(lowerQuery))\n );\n}\n\n/**\n * Canonical mapping from widget category to human-readable label.\n * Shared across WidgetTypeSelector, WidgetPalette, and other UI.\n */\nexport const WIDGET_CATEGORY_LABELS: Record<WidgetCategory, string> = {\n KPI: 'Metrics & KPIs',\n Visualization: 'Charts & Visualization',\n Data: 'Data Display',\n Content: 'Content',\n Temporal: 'Temporal & Scheduling',\n Custom: 'Custom',\n Installed: 'Installed from Store',\n};\n\n/**\n * Get widget categories with their definitions\n */\nexport function getWidgetCategories(): {\n category: WidgetCategory;\n label: string;\n widgets: WidgetDefinition[];\n}[] {\n const categories: WidgetCategory[] = ['KPI', 'Visualization', 'Data', 'Content', 'Temporal', 'Custom', 'Installed'];\n\n return categories.map((category) => ({\n category,\n label: WIDGET_CATEGORY_LABELS[category],\n widgets: getWidgetsByCategory(category),\n }));\n}\n\n/**\n * Validate widget size constraints\n */\nexport function validateWidgetSize(\n type: WidgetType,\n width: number,\n height: number\n): { valid: boolean; width: number; height: number } {\n const def = WIDGET_DEFINITIONS[type];\n\n const clampedWidth = Math.min(Math.max(width, def.minSize.width), def.maxSize.width);\n const clampedHeight = Math.min(Math.max(height, def.minSize.height), def.maxSize.height);\n\n return {\n valid: width === clampedWidth && height === clampedHeight,\n width: clampedWidth,\n height: clampedHeight,\n };\n}\n\n/**\n * Get a stable key for a widget definition (handles installed widgets with componentId)\n */\nexport function getWidgetKey(widget: WidgetDefinition): string {\n if (widget.defaultConfig?.componentId) {\n return `installed-${widget.defaultConfig.componentId}`;\n }\n if (widget.category === 'Installed') {\n return `installed-${widget.name}`;\n }\n return widget.type;\n}\n\nexport default WIDGET_DEFINITIONS;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WidgetRegistry.js","names":[],"sources":["../../../../src/bigconsole/components/widgets/WidgetRegistry.ts"],"sourcesContent":["/**\n * Widget Registry\n *\n * Central registry mapping widget types to their components,\n * metadata, and configuration schemas.\n */\n\nimport type { ComponentType } from 'react';\nimport type { WidgetType, WidgetCategory, Widget } from '../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface WidgetComponentProps {\n /** The widget data */\n widget: Widget;\n /** Widget data (processed) */\n data: Record<string, unknown>;\n /** Whether the widget is in edit mode */\n isEditMode?: boolean;\n /** Whether the widget is selected */\n isSelected?: boolean;\n /** Whether data is loading */\n isLoading?: boolean;\n /** Error message if any */\n error?: string | null;\n /** Callback when widget is clicked */\n onClick?: () => void;\n /** Callback for drilldown navigation */\n onDrilldown?: (params: Record<string, unknown>) => void;\n}\n\nexport interface WidgetDefinition {\n /** Widget type */\n type: WidgetType;\n /** Human-readable name */\n name: string;\n /** Description */\n description: string;\n /** Lucide icon name */\n icon: string;\n /** Category for grouping */\n category: WidgetCategory;\n /** Component to render. Null for all current widget types because the render path\n * uses WidgetRendererFactory + RendererRegistry instead of this field. */\n component: ComponentType<WidgetComponentProps> | null;\n /** Default configuration */\n defaultConfig: Record<string, unknown>;\n /** Default size (fixed grid) */\n defaultSize: { width: number; height: number };\n /**\n * Default responsive position (v1.0 spec). `xl` is optional and falls back\n * to `lg` when not set so existing widget defaults remain valid.\n */\n defaultResponsive: { xs: number; sm: number; md: number; lg: number; xl?: number };\n /** Minimum size constraints */\n minSize: { width: number; height: number };\n /** Maximum size constraints */\n maxSize: { width: number; height: number };\n /** Whether the widget supports data binding */\n supportsDataBinding: boolean;\n /** Whether the widget supports drilldown */\n supportsDrilldown: boolean;\n /** Whether the widget supports auto-refresh */\n supportsAutoRefresh: boolean;\n /** Tags for search/filter */\n tags: string[];\n}\n\n// ============================================================================\n// Widget Definitions\n// ============================================================================\n\nexport const WIDGET_DEFINITIONS: Record<WidgetType, WidgetDefinition> = {\n // ============================================================================\n // Core Visualization (6 types)\n // ============================================================================\n\n kpi_card_comparison: {\n type: 'kpi_card_comparison',\n name: 'KPI Card Comparison',\n description: 'Compare multiple KPIs side by side with trends',\n icon: 'LayoutDashboard',\n category: 'KPI',\n component: null,\n defaultConfig: {\n comparisons: [],\n showSparklines: true,\n layout: 'horizontal',\n format: 'number',\n },\n defaultSize: { width: 6, height: 3 },\n defaultResponsive: { xs: 12, sm: 6, md: 6, lg: 4 },\n minSize: { width: 4, height: 2 },\n maxSize: { width: 12, height: 6 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['kpi', 'comparison', 'metrics', 'multi'],\n },\n\n metric_card: {\n type: 'metric_card',\n name: 'Metric Card',\n description: 'Display a single KPI value with trend indicator',\n icon: 'TrendingUp',\n category: 'KPI',\n component: null,\n defaultConfig: {\n format: 'number',\n decimalPlaces: 0,\n showTrend: true,\n showSparkline: false,\n },\n defaultSize: { width: 3, height: 2 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 3 },\n minSize: { width: 2, height: 2 },\n maxSize: { width: 6, height: 4 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['kpi', 'metric', 'number', 'trend'],\n },\n\n chart: {\n type: 'chart',\n name: 'Chart',\n description: 'Multi-type chart (Line, Bar, Area, Pie)',\n icon: 'BarChart3',\n category: 'Visualization',\n component: null,\n defaultConfig: {\n chartType: 'line',\n showLegend: true,\n legendPosition: 'bottom',\n showGrid: true,\n showTooltip: true,\n },\n defaultSize: { width: 6, height: 4 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 3, height: 3 },\n maxSize: { width: 12, height: 8 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['chart', 'graph', 'visualization', 'line', 'bar', 'area', 'pie'],\n },\n\n funnel_chart: {\n type: 'funnel_chart',\n name: 'Funnel Chart',\n description: 'Visualize conversion or drop-off stages',\n icon: 'Filter',\n category: 'Visualization',\n component: null,\n defaultConfig: {\n showLabels: true,\n showValues: true,\n showPercentages: true,\n orientation: 'vertical',\n },\n defaultSize: { width: 4, height: 5 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 3, height: 4 },\n maxSize: { width: 8, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['funnel', 'conversion', 'stages', 'pipeline'],\n },\n\n table: {\n type: 'table',\n name: 'Data Table',\n description: 'Display data in sortable, filterable rows and columns',\n icon: 'Table',\n category: 'Data',\n component: null,\n defaultConfig: {\n pageSize: 10,\n sortable: true,\n filterable: true,\n stickyHeader: true,\n columns: [],\n },\n defaultSize: { width: 8, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 8, lg: 8 },\n minSize: { width: 4, height: 3 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['table', 'data', 'grid', 'rows'],\n },\n\n pivot_table: {\n type: 'pivot_table',\n name: 'Pivot Table',\n description: 'Interactive pivot table for data analysis',\n icon: 'Grid3x3',\n category: 'Data',\n component: null,\n defaultConfig: {\n rows: [],\n columns: [],\n values: [],\n aggregation: 'sum',\n showTotals: true,\n },\n defaultSize: { width: 10, height: 6 },\n defaultResponsive: { xs: 12, sm: 12, md: 12, lg: 10 },\n minSize: { width: 6, height: 4 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['pivot', 'table', 'analysis', 'aggregation'],\n },\n\n // ============================================================================\n // Indicators (2 types)\n // ============================================================================\n\n gauge: {\n type: 'gauge',\n name: 'Gauge',\n description: 'Display a value within a range',\n icon: 'Gauge',\n category: 'KPI',\n component: null,\n defaultConfig: {\n variant: 'circular',\n min: 0,\n max: 100,\n showValue: true,\n showLabels: true,\n thresholds: [\n { value: 33, color: 'red' },\n { value: 66, color: 'yellow' },\n { value: 100, color: 'green' },\n ],\n },\n defaultSize: { width: 3, height: 3 },\n defaultResponsive: { xs: 6, sm: 4, md: 3, lg: 3 },\n minSize: { width: 2, height: 2 },\n maxSize: { width: 6, height: 6 },\n supportsDataBinding: true,\n supportsDrilldown: false,\n supportsAutoRefresh: true,\n tags: ['gauge', 'meter', 'dial', 'indicator'],\n },\n\n progress: {\n type: 'progress',\n name: 'Progress Bar',\n description: 'Show progress towards a goal with milestones',\n icon: 'Activity',\n category: 'KPI',\n component: null,\n defaultConfig: {\n showPercentage: true,\n showValue: false,\n target: null,\n milestones: [],\n },\n defaultSize: { width: 4, height: 2 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 3, height: 2 },\n maxSize: { width: 12, height: 3 },\n supportsDataBinding: true,\n supportsDrilldown: false,\n supportsAutoRefresh: true,\n tags: ['progress', 'bar', 'goal', 'completion'],\n },\n\n // ============================================================================\n // Data Display (2 types)\n // ============================================================================\n\n list: {\n type: 'list',\n name: 'List',\n description: 'Display items in a scrollable list',\n icon: 'List',\n category: 'Data',\n component: null,\n defaultConfig: {\n showMetadata: true,\n showActions: false,\n showBadges: true,\n maxItems: 10,\n itemClickable: true,\n },\n defaultSize: { width: 4, height: 5 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 3, height: 3 },\n maxSize: { width: 8, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['list', 'items', 'scroll'],\n },\n\n form: {\n type: 'form',\n name: 'Form',\n description: 'Interactive form widget for data input',\n icon: 'FileText',\n category: 'Data',\n component: null,\n defaultConfig: {\n fields: [],\n submitAction: null,\n layout: 'vertical',\n showLabels: true,\n },\n defaultSize: { width: 6, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 4, height: 3 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: false,\n supportsAutoRefresh: false,\n tags: ['form', 'input', 'submit', 'fields'],\n },\n\n // ============================================================================\n // Content (2 types)\n // ============================================================================\n\n text: {\n type: 'text',\n name: 'Text',\n description: 'Rich text or markdown content widget',\n icon: 'Type',\n category: 'Content',\n component: null,\n defaultConfig: {\n content: '',\n format: 'markdown',\n alignment: 'left',\n },\n defaultSize: { width: 4, height: 3 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 2, height: 1 },\n maxSize: { width: 12, height: 12 },\n supportsDataBinding: false,\n supportsDrilldown: false,\n supportsAutoRefresh: false,\n tags: ['text', 'markdown', 'content', 'rich text'],\n },\n\n iframe: {\n type: 'iframe',\n name: 'Iframe',\n description: 'Embed external content via iframe',\n icon: 'ExternalLink',\n category: 'Content',\n component: null,\n defaultConfig: {\n url: '',\n sandbox: 'allow-scripts allow-same-origin',\n allowFullscreen: false,\n },\n defaultSize: { width: 6, height: 4 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 3, height: 2 },\n maxSize: { width: 12, height: 12 },\n supportsDataBinding: false,\n supportsDrilldown: false,\n supportsAutoRefresh: false,\n tags: ['iframe', 'embed', 'external', 'web'],\n },\n\n // ============================================================================\n // Spatial & Temporal (5 types)\n // ============================================================================\n\n map: {\n type: 'map',\n name: 'Map',\n description: 'Geographic map visualization with markers',\n icon: 'MapPin',\n category: 'Visualization',\n component: null,\n defaultConfig: {\n provider: 'mapbox',\n center: { lat: 0, lng: 0 },\n zoom: 2,\n showMarkers: true,\n showRegions: false,\n },\n defaultSize: { width: 6, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 4, height: 3 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['map', 'geo', 'location', 'markers'],\n },\n\n heatmap: {\n type: 'heatmap',\n name: 'Heatmap',\n description: 'Display values in a color-coded grid',\n icon: 'Grid3x3',\n category: 'Visualization',\n component: null,\n defaultConfig: {\n xAxisField: '',\n yAxisField: '',\n valueField: '',\n // HeatmapWidget renders intensity from `colorScheme` (token-based Tailwind\n // classes), not `colorScale`; keep the scale empty rather than seeding it\n // with CSS var() strings that a data array can never resolve.\n colorScale: [],\n colorScheme: 'blue',\n showValues: true,\n },\n defaultSize: { width: 6, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 6, lg: 6 },\n minSize: { width: 4, height: 4 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['heatmap', 'grid', 'density', 'color'],\n },\n\n calendar: {\n type: 'calendar',\n name: 'Calendar',\n description: 'Calendar view for events and schedules',\n icon: 'Calendar',\n category: 'Temporal',\n component: null,\n defaultConfig: {\n view: 'month',\n showWeekNumbers: false,\n firstDayOfWeek: 0,\n eventField: '',\n },\n defaultSize: { width: 8, height: 6 },\n defaultResponsive: { xs: 12, sm: 12, md: 8, lg: 8 },\n minSize: { width: 6, height: 4 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['calendar', 'events', 'schedule', 'dates'],\n },\n\n kanban: {\n type: 'kanban',\n name: 'Kanban Board',\n description: 'Kanban board for task and workflow management',\n icon: 'Columns',\n category: 'Data',\n component: null,\n defaultConfig: {\n columns: [],\n cardTitleField: '',\n cardDescriptionField: '',\n columnField: '',\n allowDragDrop: true,\n },\n defaultSize: { width: 12, height: 6 },\n defaultResponsive: { xs: 12, sm: 12, md: 12, lg: 12 },\n minSize: { width: 8, height: 4 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['kanban', 'board', 'tasks', 'workflow'],\n },\n\n timeline: {\n type: 'timeline',\n name: 'Timeline',\n description: 'Timeline or Gantt chart for project scheduling',\n icon: 'GitBranch',\n category: 'Temporal',\n component: null,\n defaultConfig: {\n startDateField: '',\n endDateField: '',\n titleField: '',\n groupField: '',\n showToday: true,\n },\n defaultSize: { width: 12, height: 5 },\n defaultResponsive: { xs: 12, sm: 12, md: 12, lg: 12 },\n minSize: { width: 8, height: 3 },\n maxSize: { width: 12, height: 10 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['timeline', 'gantt', 'project', 'schedule'],\n },\n\n // ============================================================================\n // Extensibility (1 type)\n // ============================================================================\n\n custom: {\n type: 'custom',\n name: 'Custom Widget',\n description: 'Custom widget with user-defined rendering',\n icon: 'Puzzle',\n category: 'Custom',\n component: null,\n defaultConfig: {\n componentId: '',\n props: {},\n },\n defaultSize: { width: 4, height: 4 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 2, height: 2 },\n maxSize: { width: 12, height: 12 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['custom', 'plugin', 'extension'],\n },\n};\n\n// ============================================================================\n// Registry Functions\n// ============================================================================\n\n/**\n * Default widget definition for unknown types\n */\nconst DEFAULT_WIDGET_DEFINITION: WidgetDefinition = {\n type: 'custom' as WidgetType,\n name: 'Unknown Widget',\n description: 'Unknown widget type',\n icon: 'Puzzle',\n category: 'Custom',\n component: null,\n defaultConfig: {},\n defaultSize: { width: 4, height: 4 },\n defaultResponsive: { xs: 12, sm: 6, md: 4, lg: 4 },\n minSize: { width: 2, height: 2 },\n maxSize: { width: 12, height: 12 },\n supportsDataBinding: true,\n supportsDrilldown: true,\n supportsAutoRefresh: true,\n tags: ['unknown'],\n};\n\n/**\n * Get widget definition by type\n * Handles both lowercase (frontend) and uppercase (backend) types\n */\nexport function getWidgetDefinition(type: WidgetType | string): WidgetDefinition {\n // Try exact match first\n if (WIDGET_DEFINITIONS[type as WidgetType]) {\n return WIDGET_DEFINITIONS[type as WidgetType];\n }\n\n // Try lowercase version (backend returns uppercase for some types)\n const lowerType = type.toLowerCase() as WidgetType;\n if (WIDGET_DEFINITIONS[lowerType]) {\n return WIDGET_DEFINITIONS[lowerType];\n }\n\n // Return default definition for unknown types\n return { ...DEFAULT_WIDGET_DEFINITION, type: type as WidgetType };\n}\n\n/**\n * Get all widget definitions\n */\nexport function getAllWidgetDefinitions(): WidgetDefinition[] {\n return Object.values(WIDGET_DEFINITIONS);\n}\n\n/**\n * Get widget definitions by category\n */\nexport function getWidgetsByCategory(category: WidgetCategory): WidgetDefinition[] {\n return Object.values(WIDGET_DEFINITIONS).filter((def) => def.category === category);\n}\n\n/**\n * Search widget definitions by query\n */\nexport function searchWidgets(query: string): WidgetDefinition[] {\n const lowerQuery = query.toLowerCase();\n return Object.values(WIDGET_DEFINITIONS).filter(\n (def) =>\n def.name.toLowerCase().includes(lowerQuery) ||\n def.description.toLowerCase().includes(lowerQuery) ||\n def.tags.some((tag) => tag.includes(lowerQuery))\n );\n}\n\n/**\n * Canonical mapping from widget category to human-readable label.\n * Shared across WidgetTypeSelector, WidgetPalette, and other UI.\n */\nexport const WIDGET_CATEGORY_LABELS: Record<WidgetCategory, string> = {\n KPI: 'Metrics & KPIs',\n Visualization: 'Charts & Visualization',\n Data: 'Data Display',\n Content: 'Content',\n Temporal: 'Temporal & Scheduling',\n Custom: 'Custom',\n Installed: 'Installed from Store',\n};\n\n/**\n * Get widget categories with their definitions\n */\nexport function getWidgetCategories(): {\n category: WidgetCategory;\n label: string;\n widgets: WidgetDefinition[];\n}[] {\n const categories: WidgetCategory[] = ['KPI', 'Visualization', 'Data', 'Content', 'Temporal', 'Custom', 'Installed'];\n\n return categories.map((category) => ({\n category,\n label: WIDGET_CATEGORY_LABELS[category],\n widgets: getWidgetsByCategory(category),\n }));\n}\n\n/**\n * Validate widget size constraints\n */\nexport function validateWidgetSize(\n type: WidgetType,\n width: number,\n height: number\n): { valid: boolean; width: number; height: number } {\n const def = WIDGET_DEFINITIONS[type];\n\n const clampedWidth = Math.min(Math.max(width, def.minSize.width), def.maxSize.width);\n const clampedHeight = Math.min(Math.max(height, def.minSize.height), def.maxSize.height);\n\n return {\n valid: width === clampedWidth && height === clampedHeight,\n width: clampedWidth,\n height: clampedHeight,\n };\n}\n\n/**\n * Get a stable key for a widget definition (handles installed widgets with componentId)\n */\nexport function getWidgetKey(widget: WidgetDefinition): string {\n if (widget.defaultConfig?.componentId) {\n return `installed-${widget.defaultConfig.componentId}`;\n }\n if (widget.category === 'Installed') {\n return `installed-${widget.name}`;\n }\n return widget.type;\n}\n\nexport default WIDGET_DEFINITIONS;\n"],"mappings":"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"}
|
|
@@ -15,7 +15,8 @@ function g(e) {
|
|
|
15
15
|
xs: r.xs ?? 12,
|
|
16
16
|
sm: r.sm ?? 6,
|
|
17
17
|
md: r.md ?? 4,
|
|
18
|
-
lg: r.lg ?? 3
|
|
18
|
+
lg: r.lg ?? 3,
|
|
19
|
+
...r.xl !== void 0 && r.xl !== null ? { xl: r.xl } : {}
|
|
19
20
|
} : null, o = e.description || n?.description || "", s = e.config || {}, c = s._internalWidgetType;
|
|
20
21
|
return {
|
|
21
22
|
id: e.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWidgetOperations.js","names":[],"sources":["../../../src/bigconsole/hooks/useWidgetOperations.ts"],"sourcesContent":["/**\n * useWidgetOperations Hook\n *\n * Provides CRUD operations for widgets using GraphQL.\n */\n\nimport { useCallback, useState, useMemo, useEffect } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport type {\n Widget,\n WidgetType,\n WidgetPosition,\n ResponsivePosition,\n ResponsivePositionInput,\n RendererType,\n} from '../types';\nimport { useWidgetStore } from '../store';\nimport { normalizeWidgetTypeForApi, mapFromFederationWidgetType } from '../utils/widgetTypeMapping';\nimport {\n ListWidgetsForPageDocument,\n ListWidgetsByDashboardDocument,\n CreateWidgetDocument,\n UpdateWidgetDocument,\n DeleteWidgetDocument,\n DuplicateWidgetDocument,\n BatchUpdateWidgetPositionsDocument,\n RefreshWidgetDataDocument,\n} from '../../generated/wspace-operations';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface CreateWidgetInput {\n pageId: string;\n dashboardId?: string;\n type: WidgetType;\n title: string;\n description?: string;\n // v2.0: Renderer and template support\n renderer?: RendererType;\n template?: Record<string, unknown> | null;\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId?: string;\n datasetId?: string;\n parserId?: string;\n parserRules?: string;\n config?: Record<string, unknown>;\n // Fixed Grid Position (legacy v1.2)\n position: WidgetPosition;\n // Responsive Position (v1.0 spec)\n positionResponsive?: ResponsivePositionInput;\n rowOrder?: number;\n refreshInterval?: number;\n metadata?: Record<string, unknown>;\n}\n\ninterface UpdateWidgetInput {\n id: string;\n title?: string;\n description?: string;\n // v2.0: Renderer and template support\n renderer?: RendererType;\n template?: Record<string, unknown> | null;\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId?: string;\n datasetId?: string;\n parserId?: string;\n parserRules?: string;\n config?: Record<string, unknown>;\n // Fixed Grid Position (legacy v1.2)\n position?: WidgetPosition;\n // Responsive Position (v1.0 spec)\n positionResponsive?: ResponsivePositionInput;\n rowOrder?: number;\n refreshInterval?: number | null;\n cacheTTL?: number | null;\n metadata?: Record<string, unknown>;\n}\n\ninterface WidgetOperationsResult {\n // Query state\n loading: boolean;\n error: Error | null;\n widgets: Widget[];\n\n // Operations\n createWidget: (input: CreateWidgetInput) => Promise<Widget | null>;\n updateWidget: (input: UpdateWidgetInput) => Promise<Widget | null>;\n deleteWidget: (id: string) => Promise<boolean>;\n duplicateWidget: (id: string, newPosition?: WidgetPosition, newTitle?: string) => Promise<Widget | null>;\n batchUpdatePositions: (updates: Array<{ id: string; position: WidgetPosition }>) => Promise<boolean>;\n refreshWidgetData: (widgetId: string) => Promise<void>;\n refetch: () => Promise<void>;\n}\n\n// ============================================================================\n// Helper: Normalize widget from GraphQL response\n// ============================================================================\n\nfunction normalizeWidget(widget: Record<string, unknown>): Widget {\n const actions = widget.actions as unknown[] | undefined;\n const metadata = widget.metadata as Record<string, unknown> | undefined;\n const respPos = widget.positionResponsive as Record<string, unknown> | undefined;\n\n // Debug: Log raw widget from GraphQL\n\n // Fixed Grid Position (legacy v1.2)\n const position = (widget.position as WidgetPosition) || {\n x: (widget.positionX as number) ?? 0,\n y: (widget.positionY as number) ?? 0,\n width: (widget.positionWidth as number) ?? 4,\n height: (widget.positionHeight as number) ?? 4,\n };\n\n // Responsive Position (v1.0 spec)\n const positionResponsive: ResponsivePosition | null = respPos\n ? {\n xs: (respPos.xs as number) ?? 12,\n sm: (respPos.sm as number) ?? 6,\n md: (respPos.md as number) ?? 4,\n lg: (respPos.lg as number) ?? 3,\n }\n : null;\n\n // Read description from metadata if not directly available\n const description = (widget.description as string) || (metadata?.description as string) || '';\n\n // Get the config object\n const config = (widget.config as Record<string, unknown>) || {};\n\n // Check for stored internal widget type in config (preserves original type like 'gauge', 'progress', etc.)\n // This is needed because multiple internal types map to the same federation type (e.g., gauge -> KPI)\n const internalType = config._internalWidgetType as string | undefined;\n\n return {\n id: widget.id,\n pageId: widget.pageId || 'local',\n dashboardId: widget.dashboardId,\n // Use stored internal type if available, otherwise map from federation type\n type: internalType || mapFromFederationWidgetType((widget.type as string) || 'KPI'),\n title: widget.title || 'Untitled Widget',\n description: description,\n // v2.0: Renderer and template support\n renderer: widget.renderer || 'BIGCONSOLE',\n template: widget.template || null,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: widget.dataSinkId || null,\n datasetId: widget.datasetId || null,\n parserId: widget.parserId || null,\n parserRules: widget.parserRules || null,\n config: config,\n // Fixed Grid Position (legacy v1.2)\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // Responsive Position (v1.0 spec)\n positionResponsive,\n rowOrder: (widget.rowOrder as number | null) ?? null,\n refreshInterval: widget.refreshInterval,\n lastRefreshedAt: widget.lastRefreshedAt,\n cacheTTL: (widget.cacheTTL as number | null) ?? null,\n metadata: metadata,\n context: widget.context,\n drilldown: widget.drilldown,\n translations: widget.translations || [],\n // v2.0: Widget actions\n actions: actions || [],\n // Include widget data from backend\n data: widget.data || null,\n createdAt: widget.createdAt || new Date().toISOString(),\n updatedAt: widget.updatedAt || new Date().toISOString(),\n } as Widget;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\ninterface UseWidgetOperationsOptions {\n /** Skip auto-fetching widgets on mount (use when parent already fetches) */\n skipFetch?: boolean;\n}\n\nexport function useWidgetOperations(\n pageId?: string,\n dashboardId?: string,\n options: UseWidgetOperationsOptions = {}\n): WidgetOperationsResult {\n const { skipFetch = false } = options;\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Get Apollo Client from context\n const apolloClient = useApolloClient();\n\n // Store actions\n const widgetsMap = useWidgetStore((state) => state.widgets);\n const setWidgets = useWidgetStore((state) => state.setWidgets);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetInStore = useWidgetStore((state) => state.updateWidget);\n const removeWidgetFromStore = useWidgetStore((state) => state.removeWidget);\n\n // Get widgets array from store - memoized\n const widgets = useMemo(() => Array.from(widgetsMap.values()), [widgetsMap]);\n\n // Fetch widgets from backend\n const fetchWidgets = useCallback(async () => {\n if (!pageId && !dashboardId) {\n return;\n }\n\n try {\n setLoading(true);\n setError(null);\n\n if (dashboardId) {\n const result = await apolloClient.query<Record<string, unknown>>({\n query: ListWidgetsByDashboardDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n const widgetsList = (result.data?.listWidgetsByDashboard as Record<string, unknown>[]) || [];\n setWidgets(widgetsList.map(normalizeWidget));\n } else if (pageId) {\n const result = await apolloClient.query<Record<string, unknown>>({\n query: ListWidgetsForPageDocument,\n variables: { pageId },\n fetchPolicy: 'network-only',\n });\n const listResult = result.data?.listWidgets as Record<string, unknown> | undefined;\n const edges = (listResult?.edges as { node: Record<string, unknown> }[]) || [];\n const widgetsList = edges.map((edge: { node: Record<string, unknown> }) => edge.node);\n setWidgets(widgetsList.map(normalizeWidget));\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch widgets');\n setError(error);\n } finally {\n setLoading(false);\n }\n }, [apolloClient, pageId, dashboardId, setWidgets]);\n\n // Fetch on mount and when pageId/dashboardId changes (unless skipFetch is true)\n useEffect(() => {\n if (!skipFetch) {\n fetchWidgets();\n }\n }, [fetchWidgets, skipFetch]);\n\n // Create widget\n const createWidget = useCallback(\n async (input: CreateWidgetInput): Promise<Widget | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Build mutation input\n // Map internal widget type to federation-accessible type for API calls\n const federationType = normalizeWidgetTypeForApi(input.type);\n\n // Store the original internal widget type in config for proper restoration\n // This is needed because multiple internal types (gauge, progress, text, etc.)\n // map to the same federation type (KPI), but we need to preserve the original\n // type for correct widget rendering\n const configWithInternalType = {\n ...(input.config || {}),\n _internalWidgetType: input.type,\n };\n\n const mutationInput: Record<string, unknown> = {\n pageId: input.pageId,\n type: federationType,\n title: input.title,\n // v2.0: Renderer and template support\n renderer: input.renderer || 'BIGCONSOLE',\n template: input.template || null,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: input.dataSinkId,\n datasetId: input.datasetId,\n parserId: input.parserId,\n parserRules: input.parserRules,\n config: configWithInternalType,\n // Fixed Grid Position (legacy v1.2)\n position: {\n x: input.position.x,\n y: input.position.y,\n width: input.position.width,\n height: input.position.height,\n },\n refreshInterval: input.refreshInterval,\n metadata: input.metadata,\n };\n\n // Add Responsive Position if provided (v1.0 spec)\n if (input.positionResponsive) {\n mutationInput.positionResponsive = {\n xs: input.positionResponsive.xs ?? 12,\n sm: input.positionResponsive.sm ?? 6,\n md: input.positionResponsive.md ?? 4,\n lg: input.positionResponsive.lg ?? 3,\n };\n }\n\n // Add rowOrder if provided\n if (input.rowOrder !== undefined) {\n mutationInput.rowOrder = input.rowOrder;\n }\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: CreateWidgetDocument,\n variables: { input: mutationInput },\n });\n\n const createResult = result.data?.createBigConsoleWidget as Record<string, unknown> | undefined;\n if (createResult) {\n const newWidget = normalizeWidget(createResult);\n addWidgetToStore(newWidget);\n return newWidget;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to create widget');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, addWidgetToStore]\n );\n\n // Update widget\n const updateWidget = useCallback(\n async (input: UpdateWidgetInput): Promise<Widget | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Get latest widget from store (avoid stale closure)\n const currentWidgetsMap = useWidgetStore.getState().widgets;\n const existingWidget = currentWidgetsMap.get(input.id);\n if (!existingWidget) {\n throw new Error(`Widget not found: ${input.id}`);\n }\n\n const updateInput: Record<string, unknown> = { id: input.id };\n if (input.title !== undefined) updateInput.title = input.title;\n // v2.0: Renderer and template support\n if (input.renderer !== undefined) updateInput.renderer = input.renderer;\n if (input.template !== undefined) updateInput.template = input.template;\n // v2.0: Data source fields (DataSink → Parser → Widget)\n if (input.dataSinkId !== undefined) updateInput.dataSinkId = input.dataSinkId;\n if (input.datasetId !== undefined) updateInput.datasetId = input.datasetId;\n if (input.parserId !== undefined) updateInput.parserId = input.parserId;\n if (input.parserRules !== undefined) updateInput.parserRules = input.parserRules;\n if (input.config !== undefined) updateInput.config = input.config;\n if (input.refreshInterval !== undefined) updateInput.refreshInterval = input.refreshInterval;\n if (input.metadata !== undefined) updateInput.metadata = input.metadata;\n\n // Fixed Grid Position (legacy v1.2)\n if (input.position) {\n updateInput.position = {\n x: input.position.x,\n y: input.position.y,\n width: input.position.width,\n height: input.position.height,\n };\n }\n\n // Responsive Position (v1.0 spec)\n if (input.positionResponsive) {\n updateInput.positionResponsive = {\n xs: input.positionResponsive.xs,\n sm: input.positionResponsive.sm,\n md: input.positionResponsive.md,\n lg: input.positionResponsive.lg,\n };\n }\n\n // Row order\n if (input.rowOrder !== undefined) {\n updateInput.rowOrder = input.rowOrder;\n }\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: UpdateWidgetDocument,\n variables: { input: updateInput },\n });\n\n if (result.error) {\n throw result.error;\n }\n\n const updateResult = result.data?.updateBigConsoleWidget as Record<string, unknown> | undefined;\n if (updateResult) {\n const updatedWidget = normalizeWidget(updateResult);\n updateWidgetInStore(input.id, updatedWidget);\n return updatedWidget;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update widget');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, updateWidgetInStore]\n );\n\n // Delete widget\n const deleteWidget = useCallback(\n async (id: string): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: DeleteWidgetDocument,\n variables: { id },\n });\n\n if (result.data?.deleteBigConsoleWidget) {\n removeWidgetFromStore(id);\n return true;\n }\n\n return false;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to delete widget');\n setError(error);\n return false;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, removeWidgetFromStore]\n );\n\n // Duplicate widget\n const duplicateWidget = useCallback(\n async (id: string, newPosition?: WidgetPosition, newTitle?: string): Promise<Widget | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Get latest widget from store (avoid stale closure)\n const currentWidgetsMap = useWidgetStore.getState().widgets;\n const existingWidget = currentWidgetsMap.get(id);\n if (!existingWidget) {\n throw new Error(`Widget not found: ${id}`);\n }\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: DuplicateWidgetDocument,\n variables: {\n id,\n newPosition: newPosition\n ? {\n x: newPosition.x,\n y: newPosition.y,\n width: newPosition.width,\n height: newPosition.height,\n }\n : undefined,\n newTitle,\n },\n });\n\n if (result.error) {\n throw result.error;\n }\n\n const dupResult = result.data?.duplicateWidget as Record<string, unknown> | undefined;\n if (dupResult) {\n const duplicatedWidget = normalizeWidget(dupResult);\n addWidgetToStore(duplicatedWidget);\n return duplicatedWidget;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to duplicate widget');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, addWidgetToStore]\n );\n\n // Batch update positions\n const batchUpdatePositions = useCallback(\n async (updates: Array<{ id: string; position: WidgetPosition }>): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: BatchUpdateWidgetPositionsDocument,\n variables: {\n updates: updates.map((u) => ({\n id: u.id,\n position: {\n x: u.position.x,\n y: u.position.y,\n width: u.position.width,\n height: u.position.height,\n },\n })),\n },\n });\n\n if (result.data?.batchUpdateWidgetPositions) {\n // Update local store with results\n updates.forEach((update) => {\n updateWidgetInStore(update.id, {\n position: update.position,\n positionX: update.position.x,\n positionY: update.position.y,\n positionWidth: update.position.width,\n positionHeight: update.position.height,\n updatedAt: new Date().toISOString(),\n });\n });\n return true;\n }\n\n return false;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to batch update positions');\n setError(error);\n return false;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, updateWidgetInStore]\n );\n\n // Refresh widget data\n const refreshWidgetData = useCallback(\n async (widgetId: string): Promise<void> => {\n try {\n await apolloClient.mutate<Record<string, unknown>>({\n mutation: RefreshWidgetDataDocument,\n variables: { widgetId },\n });\n } catch (err) {}\n },\n [apolloClient]\n );\n\n // Refetch\n const refetch = useCallback(async (): Promise<void> => {\n await fetchWidgets();\n }, [fetchWidgets]);\n\n return {\n loading,\n error,\n widgets,\n createWidget,\n updateWidget,\n deleteWidget,\n duplicateWidget,\n batchUpdatePositions,\n refreshWidgetData,\n refetch,\n };\n}\n\nexport default useWidgetOperations;\n"],"mappings":";;;;;;;AAoGA,SAAS,EAAgB,GAAyC;CAChE,IAAM,IAAU,EAAO,SACjB,IAAW,EAAO,UAClB,IAAU,EAAO,oBAKjB,IAAY,EAAO,YAA+B;EACtD,GAAI,EAAO,aAAwB;EACnC,GAAI,EAAO,aAAwB;EACnC,OAAQ,EAAO,iBAA4B;EAC3C,QAAS,EAAO,kBAA6B;EAC9C,EAGK,IAAgD,IAClD;EACE,IAAK,EAAQ,MAAiB;EAC9B,IAAK,EAAQ,MAAiB;EAC9B,IAAK,EAAQ,MAAiB;EAC9B,IAAK,EAAQ,MAAiB;EAC/B,GACD,MAGE,IAAe,EAAO,eAA2B,GAAU,eAA0B,IAGrF,IAAU,EAAO,UAAsC,EAAE,EAIzD,IAAe,EAAO;AAE5B,QAAO;EACL,IAAI,EAAO;EACX,QAAQ,EAAO,UAAU;EACzB,aAAa,EAAO;EAEpB,MAAM,KAAgB,EAA6B,EAAO,QAAmB,MAAM;EACnF,OAAO,EAAO,SAAS;EACV;EAEb,UAAU,EAAO,YAAY;EAC7B,UAAU,EAAO,YAAY;EAE7B,YAAY,EAAO,cAAc;EACjC,WAAW,EAAO,aAAa;EAC/B,UAAU,EAAO,YAAY;EAC7B,aAAa,EAAO,eAAe;EAC3B;EAER;EACA,WAAW,EAAS;EACpB,WAAW,EAAS;EACpB,eAAe,EAAS;EACxB,gBAAgB,EAAS;EAEzB;EACA,UAAW,EAAO,YAA8B;EAChD,iBAAiB,EAAO;EACxB,iBAAiB,EAAO;EACxB,UAAW,EAAO,YAA8B;EACtC;EACV,SAAS,EAAO;EAChB,WAAW,EAAO;EAClB,cAAc,EAAO,gBAAgB,EAAE;EAEvC,SAAS,KAAW,EAAE;EAEtB,MAAM,EAAO,QAAQ;EACrB,WAAW,EAAO,8BAAa,IAAI,MAAM,EAAC,aAAa;EACvD,WAAW,EAAO,8BAAa,IAAI,MAAM,EAAC,aAAa;EACxD;;AAYH,SAAgB,EACd,GACA,GACA,IAAsC,EAAE,EAChB;CACxB,IAAM,EAAE,eAAY,OAAU,GACxB,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAuB,KAAK,EAGhD,IAAe,GAAiB,EAGhC,IAAa,GAAgB,MAAU,EAAM,QAAQ,EACrD,IAAa,GAAgB,MAAU,EAAM,WAAW,EACxD,IAAmB,GAAgB,MAAU,EAAM,UAAU,EAC7D,IAAsB,GAAgB,MAAU,EAAM,aAAa,EACnE,IAAwB,GAAgB,MAAU,EAAM,aAAa,EAGrE,IAAU,QAAc,MAAM,KAAK,EAAW,QAAQ,CAAC,EAAE,CAAC,EAAW,CAAC,EAGtE,IAAe,EAAY,YAAY;AACvC,SAAC,KAAU,CAAC,GAIhB,KAAI;AAIF,GAHA,EAAW,GAAK,EAChB,EAAS,KAAK,EAEV,IAOF,IANe,MAAM,EAAa,MAA+B;IAC/D,OAAO;IACP,WAAW,EAAE,gBAAa;IAC1B,aAAa;IACd,CAAC,EAC0B,MAAM,0BAAwD,EAAE,EACrE,IAAI,EAAgB,CAAC,GACnC,KAST,IARe,MAAM,EAAa,MAA+B;IAC/D,OAAO;IACP,WAAW,EAAE,WAAQ;IACrB,aAAa;IACd,CAAC,EACwB,MAAM,aACL,SAAiD,EAAE,EACpD,KAAK,MAA4C,EAAK,KAAK,CAC9D,IAAI,EAAgB,CAAC;WAEvC,GAAK;AAEZ,KADc,aAAe,QAAQ,IAAM,gBAAI,MAAM,0BAA0B,CAChE;YACP;AACR,KAAW,GAAM;;IAElB;EAAC;EAAc;EAAQ;EAAa;EAAW,CAAC;AAkUnD,QA/TA,QAAgB;AACd,EAAK,KACH,GAAc;IAEf,CAAC,GAAc,EAAU,CAAC,EA2TtB;EACL;EACA;EACA;EACA,cA5TmB,EACnB,OAAO,MAAqD;AAC1D,OAAI;AAEF,IADA,EAAW,GAAK,EAChB,EAAS,KAAK;IAId,IAAM,IAAiB,EAA0B,EAAM,KAAK,EAMtD,IAAyB;KAC7B,GAAI,EAAM,UAAU,EAAE;KACtB,qBAAqB,EAAM;KAC5B,EAEK,IAAyC;KAC7C,QAAQ,EAAM;KACd,MAAM;KACN,OAAO,EAAM;KAEb,UAAU,EAAM,YAAY;KAC5B,UAAU,EAAM,YAAY;KAE5B,YAAY,EAAM;KAClB,WAAW,EAAM;KACjB,UAAU,EAAM;KAChB,aAAa,EAAM;KACnB,QAAQ;KAER,UAAU;MACR,GAAG,EAAM,SAAS;MAClB,GAAG,EAAM,SAAS;MAClB,OAAO,EAAM,SAAS;MACtB,QAAQ,EAAM,SAAS;MACxB;KACD,iBAAiB,EAAM;KACvB,UAAU,EAAM;KACjB;AAaD,IAVI,EAAM,uBACR,EAAc,qBAAqB;KACjC,IAAI,EAAM,mBAAmB,MAAM;KACnC,IAAI,EAAM,mBAAmB,MAAM;KACnC,IAAI,EAAM,mBAAmB,MAAM;KACnC,IAAI,EAAM,mBAAmB,MAAM;KACpC,GAIC,EAAM,aAAa,KAAA,MACrB,EAAc,WAAW,EAAM;IAQjC,IAAM,KALS,MAAM,EAAa,OAAgC;KAChE,UAAU;KACV,WAAW,EAAE,OAAO,GAAe;KACpC,CAAC,EAE0B,MAAM;AAClC,QAAI,GAAc;KAChB,IAAM,IAAY,EAAgB,EAAa;AAE/C,YADA,EAAiB,EAAU,EACpB;;AAGT,WAAO;YACA,GAAK;AAGZ,WADA,EADc,aAAe,QAAQ,IAAM,gBAAI,MAAM,0BAA0B,CAChE,EACR;aACC;AACR,MAAW,GAAM;;KAGrB,CAAC,GAAc,EAAiB,CACjC;EA6OC,cA1OmB,EACnB,OAAO,MAAqD;AAC1D,OAAI;AAOF,QANA,EAAW,GAAK,EAChB,EAAS,KAAK,EAKV,CAFsB,EAAe,UAAU,CAAC,QACX,IAAI,EAAM,GAAG,CAEpD,OAAU,MAAM,qBAAqB,EAAM,KAAK;IAGlD,IAAM,IAAuC,EAAE,IAAI,EAAM,IAAI;AAmC7D,IAlCI,EAAM,UAAU,KAAA,MAAW,EAAY,QAAQ,EAAM,QAErD,EAAM,aAAa,KAAA,MAAW,EAAY,WAAW,EAAM,WAC3D,EAAM,aAAa,KAAA,MAAW,EAAY,WAAW,EAAM,WAE3D,EAAM,eAAe,KAAA,MAAW,EAAY,aAAa,EAAM,aAC/D,EAAM,cAAc,KAAA,MAAW,EAAY,YAAY,EAAM,YAC7D,EAAM,aAAa,KAAA,MAAW,EAAY,WAAW,EAAM,WAC3D,EAAM,gBAAgB,KAAA,MAAW,EAAY,cAAc,EAAM,cACjE,EAAM,WAAW,KAAA,MAAW,EAAY,SAAS,EAAM,SACvD,EAAM,oBAAoB,KAAA,MAAW,EAAY,kBAAkB,EAAM,kBACzE,EAAM,aAAa,KAAA,MAAW,EAAY,WAAW,EAAM,WAG3D,EAAM,aACR,EAAY,WAAW;KACrB,GAAG,EAAM,SAAS;KAClB,GAAG,EAAM,SAAS;KAClB,OAAO,EAAM,SAAS;KACtB,QAAQ,EAAM,SAAS;KACxB,GAIC,EAAM,uBACR,EAAY,qBAAqB;KAC/B,IAAI,EAAM,mBAAmB;KAC7B,IAAI,EAAM,mBAAmB;KAC7B,IAAI,EAAM,mBAAmB;KAC7B,IAAI,EAAM,mBAAmB;KAC9B,GAIC,EAAM,aAAa,KAAA,MACrB,EAAY,WAAW,EAAM;IAG/B,IAAM,IAAS,MAAM,EAAa,OAAgC;KAChE,UAAU;KACV,WAAW,EAAE,OAAO,GAAa;KAClC,CAAC;AAEF,QAAI,EAAO,MACT,OAAM,EAAO;IAGf,IAAM,IAAe,EAAO,MAAM;AAClC,QAAI,GAAc;KAChB,IAAM,IAAgB,EAAgB,EAAa;AAEnD,YADA,EAAoB,EAAM,IAAI,EAAc,EACrC;;AAGT,WAAO;YACA,GAAK;AAGZ,WADA,EADc,aAAe,QAAQ,IAAM,gBAAI,MAAM,0BAA0B,CAChE,EACR;aACC;AACR,MAAW,GAAM;;KAGrB,CAAC,GAAc,EAAoB,CACpC;EA6JC,cA1JmB,EACnB,OAAO,MAAiC;AACtC,OAAI;AAcF,WAbA,EAAW,GAAK,EAChB,EAAS,KAAK,GAEC,MAAM,EAAa,OAAgC;KAChE,UAAU;KACV,WAAW,EAAE,OAAI;KAClB,CAAC,EAES,MAAM,0BACf,EAAsB,EAAG,EAClB,MAGF;YACA,GAAK;AAGZ,WADA,EADc,aAAe,QAAQ,IAAM,gBAAI,MAAM,0BAA0B,CAChE,EACR;aACC;AACR,MAAW,GAAM;;KAGrB,CAAC,GAAc,EAAsB,CACtC;EAiIC,iBA9HsB,EACtB,OAAO,GAAY,GAA8B,MAA8C;AAC7F,OAAI;AAOF,QANA,EAAW,GAAK,EAChB,EAAS,KAAK,EAKV,CAFsB,EAAe,UAAU,CAAC,QACX,IAAI,EAAG,CAE9C,OAAU,MAAM,qBAAqB,IAAK;IAG5C,IAAM,IAAS,MAAM,EAAa,OAAgC;KAChE,UAAU;KACV,WAAW;MACT;MACA,aAAa,IACT;OACE,GAAG,EAAY;OACf,GAAG,EAAY;OACf,OAAO,EAAY;OACnB,QAAQ,EAAY;OACrB,GACD,KAAA;MACJ;MACD;KACF,CAAC;AAEF,QAAI,EAAO,MACT,OAAM,EAAO;IAGf,IAAM,IAAY,EAAO,MAAM;AAC/B,QAAI,GAAW;KACb,IAAM,IAAmB,EAAgB,EAAU;AAEnD,YADA,EAAiB,EAAiB,EAC3B;;AAGT,WAAO;YACA,GAAK;AAGZ,WADA,EADc,aAAe,QAAQ,IAAM,gBAAI,MAAM,6BAA6B,CACnE,EACR;aACC;AACR,MAAW,GAAM;;KAGrB,CAAC,GAAc,EAAiB,CACjC;EA6EC,sBA1E2B,EAC3B,OAAO,MAA+E;AACpF,OAAI;AAkCF,WAjCA,EAAW,GAAK,EAChB,EAAS,KAAK,GAEC,MAAM,EAAa,OAAgC;KAChE,UAAU;KACV,WAAW,EACT,SAAS,EAAQ,KAAK,OAAO;MAC3B,IAAI,EAAE;MACN,UAAU;OACR,GAAG,EAAE,SAAS;OACd,GAAG,EAAE,SAAS;OACd,OAAO,EAAE,SAAS;OAClB,QAAQ,EAAE,SAAS;OACpB;MACF,EAAE,EACJ;KACF,CAAC,EAES,MAAM,8BAEf,EAAQ,SAAS,MAAW;AAC1B,OAAoB,EAAO,IAAI;MAC7B,UAAU,EAAO;MACjB,WAAW,EAAO,SAAS;MAC3B,WAAW,EAAO,SAAS;MAC3B,eAAe,EAAO,SAAS;MAC/B,gBAAgB,EAAO,SAAS;MAChC,4BAAW,IAAI,MAAM,EAAC,aAAa;MACpC,CAAC;MACF,EACK,MAGF;YACA,GAAK;AAGZ,WADA,EADc,aAAe,QAAQ,IAAM,gBAAI,MAAM,mCAAmC,CACzE,EACR;aACC;AACR,MAAW,GAAM;;KAGrB,CAAC,GAAc,EAAoB,CACpC;EA6BC,mBA1BwB,EACxB,OAAO,MAAoC;AACzC,OAAI;AACF,UAAM,EAAa,OAAgC;KACjD,UAAU;KACV,WAAW,EAAE,aAAU;KACxB,CAAC;WACU;KAEhB,CAAC,EAAa,CACf;EAiBC,SAdc,EAAY,YAA2B;AACrD,SAAM,GAAc;KACnB,CAAC,EAAa,CAAC;EAajB"}
|
|
1
|
+
{"version":3,"file":"useWidgetOperations.js","names":[],"sources":["../../../src/bigconsole/hooks/useWidgetOperations.ts"],"sourcesContent":["/**\n * useWidgetOperations Hook\n *\n * Provides CRUD operations for widgets using GraphQL.\n */\n\nimport { useCallback, useState, useMemo, useEffect } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport type {\n Widget,\n WidgetType,\n WidgetPosition,\n ResponsivePosition,\n ResponsivePositionInput,\n RendererType,\n} from '../types';\nimport { useWidgetStore } from '../store';\nimport { normalizeWidgetTypeForApi, mapFromFederationWidgetType } from '../utils/widgetTypeMapping';\nimport {\n ListWidgetsForPageDocument,\n ListWidgetsByDashboardDocument,\n CreateWidgetDocument,\n UpdateWidgetDocument,\n DeleteWidgetDocument,\n DuplicateWidgetDocument,\n BatchUpdateWidgetPositionsDocument,\n RefreshWidgetDataDocument,\n} from '../../generated/wspace-operations';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface CreateWidgetInput {\n pageId: string;\n dashboardId?: string;\n type: WidgetType;\n title: string;\n description?: string;\n // v2.0: Renderer and template support\n renderer?: RendererType;\n template?: Record<string, unknown> | null;\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId?: string;\n datasetId?: string;\n parserId?: string;\n parserRules?: string;\n config?: Record<string, unknown>;\n // Fixed Grid Position (legacy v1.2)\n position: WidgetPosition;\n // Responsive Position (v1.0 spec)\n positionResponsive?: ResponsivePositionInput;\n rowOrder?: number;\n refreshInterval?: number;\n metadata?: Record<string, unknown>;\n}\n\ninterface UpdateWidgetInput {\n id: string;\n title?: string;\n description?: string;\n // v2.0: Renderer and template support\n renderer?: RendererType;\n template?: Record<string, unknown> | null;\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId?: string;\n datasetId?: string;\n parserId?: string;\n parserRules?: string;\n config?: Record<string, unknown>;\n // Fixed Grid Position (legacy v1.2)\n position?: WidgetPosition;\n // Responsive Position (v1.0 spec)\n positionResponsive?: ResponsivePositionInput;\n rowOrder?: number;\n refreshInterval?: number | null;\n cacheTTL?: number | null;\n metadata?: Record<string, unknown>;\n}\n\ninterface WidgetOperationsResult {\n // Query state\n loading: boolean;\n error: Error | null;\n widgets: Widget[];\n\n // Operations\n createWidget: (input: CreateWidgetInput) => Promise<Widget | null>;\n updateWidget: (input: UpdateWidgetInput) => Promise<Widget | null>;\n deleteWidget: (id: string) => Promise<boolean>;\n duplicateWidget: (id: string, newPosition?: WidgetPosition, newTitle?: string) => Promise<Widget | null>;\n batchUpdatePositions: (updates: Array<{ id: string; position: WidgetPosition }>) => Promise<boolean>;\n refreshWidgetData: (widgetId: string) => Promise<void>;\n refetch: () => Promise<void>;\n}\n\n// ============================================================================\n// Helper: Normalize widget from GraphQL response\n// ============================================================================\n\nfunction normalizeWidget(widget: Record<string, unknown>): Widget {\n const actions = widget.actions as unknown[] | undefined;\n const metadata = widget.metadata as Record<string, unknown> | undefined;\n const respPos = widget.positionResponsive as Record<string, unknown> | undefined;\n\n // Debug: Log raw widget from GraphQL\n\n // Fixed Grid Position (legacy v1.2)\n const position = (widget.position as WidgetPosition) || {\n x: (widget.positionX as number) ?? 0,\n y: (widget.positionY as number) ?? 0,\n width: (widget.positionWidth as number) ?? 4,\n height: (widget.positionHeight as number) ?? 4,\n };\n\n // Responsive Position (v1.0 spec)\n const positionResponsive: ResponsivePosition | null = respPos\n ? {\n xs: (respPos.xs as number) ?? 12,\n sm: (respPos.sm as number) ?? 6,\n md: (respPos.md as number) ?? 4,\n lg: (respPos.lg as number) ?? 3,\n // xl is optional; fall back to lg when absent (server has no xl tier yet)\n ...(respPos.xl !== undefined && respPos.xl !== null ? { xl: respPos.xl as number } : {}),\n }\n : null;\n\n // Read description from metadata if not directly available\n const description = (widget.description as string) || (metadata?.description as string) || '';\n\n // Get the config object\n const config = (widget.config as Record<string, unknown>) || {};\n\n // Check for stored internal widget type in config (preserves original type like 'gauge', 'progress', etc.)\n // This is needed because multiple internal types map to the same federation type (e.g., gauge -> KPI)\n const internalType = config._internalWidgetType as string | undefined;\n\n return {\n id: widget.id,\n pageId: widget.pageId || 'local',\n dashboardId: widget.dashboardId,\n // Use stored internal type if available, otherwise map from federation type\n type: internalType || mapFromFederationWidgetType((widget.type as string) || 'KPI'),\n title: widget.title || 'Untitled Widget',\n description: description,\n // v2.0: Renderer and template support\n renderer: widget.renderer || 'BIGCONSOLE',\n template: widget.template || null,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: widget.dataSinkId || null,\n datasetId: widget.datasetId || null,\n parserId: widget.parserId || null,\n parserRules: widget.parserRules || null,\n config: config,\n // Fixed Grid Position (legacy v1.2)\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // Responsive Position (v1.0 spec)\n positionResponsive,\n rowOrder: (widget.rowOrder as number | null) ?? null,\n refreshInterval: widget.refreshInterval,\n lastRefreshedAt: widget.lastRefreshedAt,\n cacheTTL: (widget.cacheTTL as number | null) ?? null,\n metadata: metadata,\n context: widget.context,\n drilldown: widget.drilldown,\n translations: widget.translations || [],\n // v2.0: Widget actions\n actions: actions || [],\n // Include widget data from backend\n data: widget.data || null,\n createdAt: widget.createdAt || new Date().toISOString(),\n updatedAt: widget.updatedAt || new Date().toISOString(),\n } as Widget;\n}\n\n// ============================================================================\n// Hook\n// ============================================================================\n\ninterface UseWidgetOperationsOptions {\n /** Skip auto-fetching widgets on mount (use when parent already fetches) */\n skipFetch?: boolean;\n}\n\nexport function useWidgetOperations(\n pageId?: string,\n dashboardId?: string,\n options: UseWidgetOperationsOptions = {}\n): WidgetOperationsResult {\n const { skipFetch = false } = options;\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Get Apollo Client from context\n const apolloClient = useApolloClient();\n\n // Store actions\n const widgetsMap = useWidgetStore((state) => state.widgets);\n const setWidgets = useWidgetStore((state) => state.setWidgets);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetInStore = useWidgetStore((state) => state.updateWidget);\n const removeWidgetFromStore = useWidgetStore((state) => state.removeWidget);\n\n // Get widgets array from store - memoized\n const widgets = useMemo(() => Array.from(widgetsMap.values()), [widgetsMap]);\n\n // Fetch widgets from backend\n const fetchWidgets = useCallback(async () => {\n if (!pageId && !dashboardId) {\n return;\n }\n\n try {\n setLoading(true);\n setError(null);\n\n if (dashboardId) {\n const result = await apolloClient.query<Record<string, unknown>>({\n query: ListWidgetsByDashboardDocument,\n variables: { dashboardId },\n fetchPolicy: 'network-only',\n });\n const widgetsList = (result.data?.listWidgetsByDashboard as Record<string, unknown>[]) || [];\n setWidgets(widgetsList.map(normalizeWidget));\n } else if (pageId) {\n const result = await apolloClient.query<Record<string, unknown>>({\n query: ListWidgetsForPageDocument,\n variables: { pageId },\n fetchPolicy: 'network-only',\n });\n const listResult = result.data?.listWidgets as Record<string, unknown> | undefined;\n const edges = (listResult?.edges as { node: Record<string, unknown> }[]) || [];\n const widgetsList = edges.map((edge: { node: Record<string, unknown> }) => edge.node);\n setWidgets(widgetsList.map(normalizeWidget));\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch widgets');\n setError(error);\n } finally {\n setLoading(false);\n }\n }, [apolloClient, pageId, dashboardId, setWidgets]);\n\n // Fetch on mount and when pageId/dashboardId changes (unless skipFetch is true)\n useEffect(() => {\n if (!skipFetch) {\n fetchWidgets();\n }\n }, [fetchWidgets, skipFetch]);\n\n // Create widget\n const createWidget = useCallback(\n async (input: CreateWidgetInput): Promise<Widget | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Build mutation input\n // Map internal widget type to federation-accessible type for API calls\n const federationType = normalizeWidgetTypeForApi(input.type);\n\n // Store the original internal widget type in config for proper restoration\n // This is needed because multiple internal types (gauge, progress, text, etc.)\n // map to the same federation type (KPI), but we need to preserve the original\n // type for correct widget rendering\n const configWithInternalType = {\n ...(input.config || {}),\n _internalWidgetType: input.type,\n };\n\n const mutationInput: Record<string, unknown> = {\n pageId: input.pageId,\n type: federationType,\n title: input.title,\n // v2.0: Renderer and template support\n renderer: input.renderer || 'BIGCONSOLE',\n template: input.template || null,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: input.dataSinkId,\n datasetId: input.datasetId,\n parserId: input.parserId,\n parserRules: input.parserRules,\n config: configWithInternalType,\n // Fixed Grid Position (legacy v1.2)\n position: {\n x: input.position.x,\n y: input.position.y,\n width: input.position.width,\n height: input.position.height,\n },\n refreshInterval: input.refreshInterval,\n metadata: input.metadata,\n };\n\n // Add Responsive Position if provided (v1.0 spec)\n if (input.positionResponsive) {\n mutationInput.positionResponsive = {\n xs: input.positionResponsive.xs ?? 12,\n sm: input.positionResponsive.sm ?? 6,\n md: input.positionResponsive.md ?? 4,\n lg: input.positionResponsive.lg ?? 3,\n };\n }\n\n // Add rowOrder if provided\n if (input.rowOrder !== undefined) {\n mutationInput.rowOrder = input.rowOrder;\n }\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: CreateWidgetDocument,\n variables: { input: mutationInput },\n });\n\n const createResult = result.data?.createBigConsoleWidget as Record<string, unknown> | undefined;\n if (createResult) {\n const newWidget = normalizeWidget(createResult);\n addWidgetToStore(newWidget);\n return newWidget;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to create widget');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, addWidgetToStore]\n );\n\n // Update widget\n const updateWidget = useCallback(\n async (input: UpdateWidgetInput): Promise<Widget | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Get latest widget from store (avoid stale closure)\n const currentWidgetsMap = useWidgetStore.getState().widgets;\n const existingWidget = currentWidgetsMap.get(input.id);\n if (!existingWidget) {\n throw new Error(`Widget not found: ${input.id}`);\n }\n\n const updateInput: Record<string, unknown> = { id: input.id };\n if (input.title !== undefined) updateInput.title = input.title;\n // v2.0: Renderer and template support\n if (input.renderer !== undefined) updateInput.renderer = input.renderer;\n if (input.template !== undefined) updateInput.template = input.template;\n // v2.0: Data source fields (DataSink → Parser → Widget)\n if (input.dataSinkId !== undefined) updateInput.dataSinkId = input.dataSinkId;\n if (input.datasetId !== undefined) updateInput.datasetId = input.datasetId;\n if (input.parserId !== undefined) updateInput.parserId = input.parserId;\n if (input.parserRules !== undefined) updateInput.parserRules = input.parserRules;\n if (input.config !== undefined) updateInput.config = input.config;\n if (input.refreshInterval !== undefined) updateInput.refreshInterval = input.refreshInterval;\n if (input.metadata !== undefined) updateInput.metadata = input.metadata;\n\n // Fixed Grid Position (legacy v1.2)\n if (input.position) {\n updateInput.position = {\n x: input.position.x,\n y: input.position.y,\n width: input.position.width,\n height: input.position.height,\n };\n }\n\n // Responsive Position (v1.0 spec)\n if (input.positionResponsive) {\n updateInput.positionResponsive = {\n xs: input.positionResponsive.xs,\n sm: input.positionResponsive.sm,\n md: input.positionResponsive.md,\n lg: input.positionResponsive.lg,\n };\n }\n\n // Row order\n if (input.rowOrder !== undefined) {\n updateInput.rowOrder = input.rowOrder;\n }\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: UpdateWidgetDocument,\n variables: { input: updateInput },\n });\n\n if (result.error) {\n throw result.error;\n }\n\n const updateResult = result.data?.updateBigConsoleWidget as Record<string, unknown> | undefined;\n if (updateResult) {\n const updatedWidget = normalizeWidget(updateResult);\n updateWidgetInStore(input.id, updatedWidget);\n return updatedWidget;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to update widget');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, updateWidgetInStore]\n );\n\n // Delete widget\n const deleteWidget = useCallback(\n async (id: string): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: DeleteWidgetDocument,\n variables: { id },\n });\n\n if (result.data?.deleteBigConsoleWidget) {\n removeWidgetFromStore(id);\n return true;\n }\n\n return false;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to delete widget');\n setError(error);\n return false;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, removeWidgetFromStore]\n );\n\n // Duplicate widget\n const duplicateWidget = useCallback(\n async (id: string, newPosition?: WidgetPosition, newTitle?: string): Promise<Widget | null> => {\n try {\n setLoading(true);\n setError(null);\n\n // Get latest widget from store (avoid stale closure)\n const currentWidgetsMap = useWidgetStore.getState().widgets;\n const existingWidget = currentWidgetsMap.get(id);\n if (!existingWidget) {\n throw new Error(`Widget not found: ${id}`);\n }\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: DuplicateWidgetDocument,\n variables: {\n id,\n newPosition: newPosition\n ? {\n x: newPosition.x,\n y: newPosition.y,\n width: newPosition.width,\n height: newPosition.height,\n }\n : undefined,\n newTitle,\n },\n });\n\n if (result.error) {\n throw result.error;\n }\n\n const dupResult = result.data?.duplicateWidget as Record<string, unknown> | undefined;\n if (dupResult) {\n const duplicatedWidget = normalizeWidget(dupResult);\n addWidgetToStore(duplicatedWidget);\n return duplicatedWidget;\n }\n\n return null;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to duplicate widget');\n setError(error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, addWidgetToStore]\n );\n\n // Batch update positions\n const batchUpdatePositions = useCallback(\n async (updates: Array<{ id: string; position: WidgetPosition }>): Promise<boolean> => {\n try {\n setLoading(true);\n setError(null);\n\n const result = await apolloClient.mutate<Record<string, unknown>>({\n mutation: BatchUpdateWidgetPositionsDocument,\n variables: {\n updates: updates.map((u) => ({\n id: u.id,\n position: {\n x: u.position.x,\n y: u.position.y,\n width: u.position.width,\n height: u.position.height,\n },\n })),\n },\n });\n\n if (result.data?.batchUpdateWidgetPositions) {\n // Update local store with results\n updates.forEach((update) => {\n updateWidgetInStore(update.id, {\n position: update.position,\n positionX: update.position.x,\n positionY: update.position.y,\n positionWidth: update.position.width,\n positionHeight: update.position.height,\n updatedAt: new Date().toISOString(),\n });\n });\n return true;\n }\n\n return false;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to batch update positions');\n setError(error);\n return false;\n } finally {\n setLoading(false);\n }\n },\n [apolloClient, updateWidgetInStore]\n );\n\n // Refresh widget data\n const refreshWidgetData = useCallback(\n async (widgetId: string): Promise<void> => {\n try {\n await apolloClient.mutate<Record<string, unknown>>({\n mutation: RefreshWidgetDataDocument,\n variables: { widgetId },\n });\n } catch (err) {}\n },\n [apolloClient]\n );\n\n // Refetch\n const refetch = useCallback(async (): Promise<void> => {\n await fetchWidgets();\n }, [fetchWidgets]);\n\n return {\n loading,\n error,\n widgets,\n createWidget,\n updateWidget,\n deleteWidget,\n duplicateWidget,\n batchUpdatePositions,\n refreshWidgetData,\n refetch,\n };\n}\n\nexport default useWidgetOperations;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgetPositioning.js","names":[],"sources":["../../../src/bigconsole/utils/widgetPositioning.ts"],"sourcesContent":["/**\n * Widget Positioning Utilities\n *\n * Smart placement algorithm for dashboard widgets.\n * Places widgets horizontally first (left-to-right), then moves to next row.\n * Uses a grid occupancy map to find the first available slot.\n */\n\nimport type { Widget } from '../types';\n\nexport interface WidgetSize {\n width: number;\n height: number;\n}\n\nexport interface WidgetPosition {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface GridConfig {\n cols: number;\n maxRows?: number;\n}\n\n/**\n * Default grid configuration\n */\nexport const DEFAULT_GRID_CONFIG: GridConfig = {\n cols: 12,\n maxRows: 100,\n};\n\n/**\n * Responsive breakpoints configuration\n */\nexport const RESPONSIVE_BREAKPOINTS = {\n lg: 1200,\n md: 996,\n sm: 768,\n xs: 480,\n} as const;\n\n/**\n * Columns per breakpoint for responsive layout\n */\nexport const RESPONSIVE_COLS = {\n lg: 12,\n md: 10,\n sm: 6,\n xs: 4,\n} as const;\n\n/**\n * Creates an occupancy map from existing widgets\n * Each occupied cell is marked with the widget ID\n */\nfunction createOccupancyMap(widgets: Widget[]): Map<string, string> {\n const occupancyMap = new Map<string, string>();\n\n widgets.forEach((widget) => {\n const x = widget.positionX ?? 0;\n const y = widget.positionY ?? 0;\n const w = widget.positionWidth ?? 4;\n const h = widget.positionHeight ?? 4;\n\n for (let dx = 0; dx < w; dx++) {\n for (let dy = 0; dy < h; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n });\n\n return occupancyMap;\n}\n\n/**\n * Checks if a widget can be placed at the given position\n */\nfunction canPlaceWidget(\n occupancyMap: Map<string, string>,\n x: number,\n y: number,\n width: number,\n height: number,\n cols: number\n): boolean {\n // Check if widget fits within grid columns\n if (x + width > cols) {\n return false;\n }\n\n // Check if all required cells are available\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Finds the next available position for a widget using smart placement.\n * Scans row-by-row, left-to-right to find the first slot that fits.\n *\n * @param existingWidgets - Array of existing widgets on the dashboard\n * @param newWidgetSize - Size of the new widget to place\n * @param config - Grid configuration (columns, max rows)\n * @returns Position where the new widget should be placed\n */\nexport function findNextAvailablePosition(\n existingWidgets: Widget[],\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols, maxRows = 100 } = config;\n const { width, height } = newWidgetSize;\n\n // Handle empty dashboard - place at origin\n if (existingWidgets.length === 0) {\n return { x: 0, y: 0 };\n }\n\n // Create occupancy map\n const occupancyMap = createOccupancyMap(existingWidgets);\n\n // Find the maximum Y to limit our search\n const maxExistingY = Math.max(...existingWidgets.map((w) => (w.positionY ?? 0) + (w.positionHeight ?? 4)));\n\n // Scan row by row, left to right, to find first available slot\n for (let y = 0; y <= maxExistingY + height && y < maxRows; y++) {\n for (let x = 0; x <= cols - width; x++) {\n if (canPlaceWidget(occupancyMap, x, y, width, height, cols)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: place at the bottom of existing widgets\n return { x: 0, y: maxExistingY };\n}\n\n/**\n * Finds position to the right of a specific widget\n * Falls back to smart placement if no space to the right\n */\nexport function findPositionNextToWidget(\n existingWidgets: Widget[],\n referenceWidgetId: string,\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const referenceWidget = existingWidgets.find((w) => w.id === referenceWidgetId);\n\n if (!referenceWidget) {\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n }\n\n const refX = referenceWidget.positionX ?? 0;\n const refY = referenceWidget.positionY ?? 0;\n const refW = referenceWidget.positionWidth ?? 4;\n\n // Try to place to the right of reference widget\n const newX = refX + refW;\n const newY = refY;\n\n // Check if it fits\n if (newX + newWidgetSize.width <= cols) {\n const occupancyMap = createOccupancyMap(existingWidgets.filter((w) => w.id !== referenceWidgetId));\n\n if (canPlaceWidget(occupancyMap, newX, newY, newWidgetSize.width, newWidgetSize.height, cols)) {\n return { x: newX, y: newY };\n }\n }\n\n // Fall back to smart placement\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n}\n\n/**\n * Calculates position from drop coordinates (for drag-and-drop)\n */\nexport function calculatePositionFromDrop(\n dropX: number,\n dropY: number,\n canvasRect: DOMRect,\n containerWidth: number,\n widgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const columnWidth = containerWidth / cols;\n const rowHeight = 60; // Match DashboardGrid rowHeight\n\n const relativeX = dropX - canvasRect.left;\n const relativeY = dropY - canvasRect.top;\n\n const gridX = Math.max(0, Math.min(cols - widgetSize.width, Math.floor(relativeX / columnWidth)));\n const gridY = Math.max(0, Math.floor(relativeY / rowHeight));\n\n return { x: gridX, y: gridY };\n}\n\n/**\n * Gets responsive columns for a given container width\n */\nexport function getResponsiveCols(containerWidth: number): number {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return RESPONSIVE_COLS.lg;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return RESPONSIVE_COLS.md;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return RESPONSIVE_COLS.sm;\n }\n return RESPONSIVE_COLS.xs;\n}\n\n/**\n * Adjusts widget positions for a new column count (responsive)\n * Ensures widgets don't overflow the grid\n */\nexport function adjustPositionsForCols(\n widgets: Widget[],\n newCols: number\n): Map<string, { x: number; y: number; width: number; height: number }> {\n const adjustedPositions = new Map<string, { x: number; y: number; width: number; height: number }>();\n\n // Sort widgets by their original position (top-left first)\n const sortedWidgets = [...widgets].sort((a, b) => {\n const aY = a.positionY ?? 0;\n const bY = b.positionY ?? 0;\n const aX = a.positionX ?? 0;\n const bX = b.positionX ?? 0;\n return aY !== bY ? aY - bY : aX - bX;\n });\n\n // Recalculate positions for the new column count\n const occupancyMap = new Map<string, string>();\n\n sortedWidgets.forEach((widget) => {\n const width = Math.min(widget.positionWidth ?? 4, newCols);\n const height = widget.positionHeight ?? 4;\n\n // Find next available position\n let placed = false;\n for (let y = 0; y < 100 && !placed; y++) {\n for (let x = 0; x <= newCols - width && !placed; x++) {\n let canPlace = true;\n for (let dx = 0; dx < width && canPlace; dx++) {\n for (let dy = 0; dy < height && canPlace; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n canPlace = false;\n }\n }\n }\n\n if (canPlace) {\n // Mark cells as occupied\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n\n adjustedPositions.set(widget.id, { x, y, width, height });\n placed = true;\n }\n }\n }\n });\n\n return adjustedPositions;\n}\n"],"mappings":";AA8BA,IAAa,IAAkC;CAC7C,MAAM;CACN,SAAS;CACV;AA0BD,SAAS,EAAmB,GAAwC;CAClE,IAAM,oBAAe,IAAI,KAAqB;AAe9C,QAbA,EAAQ,SAAS,MAAW;EAC1B,IAAM,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,iBAAiB,GAC5B,IAAI,EAAO,kBAAkB;AAEnC,OAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,MAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,GAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,KAAM,EAAO,GAAG;GAGtD,EAEK;;AAMT,SAAS,EACP,GACA,GACA,GACA,GACA,GACA,GACS;AAET,KAAI,IAAI,IAAQ,EACd,QAAO;AAIT,MAAK,IAAI,IAAK,GAAG,IAAK,GAAO,IAC3B,MAAK,IAAI,IAAK,GAAG,IAAK,GAAQ,IAC5B,KAAI,EAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,IAAK,CACzC,QAAO;AAKb,QAAO;;AAYT,SAAgB,EACd,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,SAAM,aAAU,QAAQ,GAC1B,EAAE,UAAO,cAAW;AAG1B,KAAI,EAAgB,WAAW,EAC7B,QAAO;EAAE,GAAG;EAAG,GAAG;EAAG;CAIvB,IAAM,IAAe,EAAmB,EAAgB,EAGlD,IAAe,KAAK,IAAI,GAAG,EAAgB,KAAK,OAAO,EAAE,aAAa,MAAM,EAAE,kBAAkB,GAAG,CAAC;AAG1G,MAAK,IAAI,IAAI,GAAG,KAAK,IAAe,KAAU,IAAI,GAAS,IACzD,MAAK,IAAI,IAAI,GAAG,KAAK,IAAO,GAAO,IACjC,KAAI,EAAe,GAAc,GAAG,GAAG,GAAO,GAAQ,EAAK,CACzD,QAAO;EAAE;EAAG;EAAG;AAMrB,QAAO;EAAE,GAAG;EAAG,GAAG;EAAc;;AA4ClC,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,YAAS,GACX,IAAc,IAAiB,GAG/B,IAAY,IAAQ,EAAW,MAC/B,IAAY,IAAQ,EAAW;AAKrC,QAAO;EAAE,GAHK,KAAK,IAAI,GAAG,KAAK,IAAI,IAAO,EAAW,OAAO,KAAK,MAAM,IAAY,EAAY,CAAC,CAAC;EAG9E,GAFL,KAAK,IAAI,GAAG,KAAK,MAAM,IAAY,GAAU,CAAC;EAE/B"}
|
|
1
|
+
{"version":3,"file":"widgetPositioning.js","names":[],"sources":["../../../src/bigconsole/utils/widgetPositioning.ts"],"sourcesContent":["/**\n * Widget Positioning Utilities\n *\n * Smart placement algorithm for dashboard widgets.\n * Places widgets horizontally first (left-to-right), then moves to next row.\n * Uses a grid occupancy map to find the first available slot.\n */\n\nimport type { ResponsivePosition, Widget } from '../types';\n\nexport interface WidgetSize {\n width: number;\n height: number;\n}\n\nexport interface WidgetPosition {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface GridConfig {\n cols: number;\n maxRows?: number;\n}\n\n/**\n * Default grid configuration\n */\nexport const DEFAULT_GRID_CONFIG: GridConfig = {\n cols: 12,\n maxRows: 100,\n};\n\n/**\n * Responsive breakpoints configuration\n */\nexport const RESPONSIVE_BREAKPOINTS = {\n xl: 1536,\n lg: 1200,\n md: 996,\n sm: 768,\n xs: 480,\n} as const;\n\n/**\n * Columns per breakpoint for responsive layout.\n * xl keeps the same 12-column grid as lg so existing widget spans remain\n * valid; the extra width simply yields wider columns on very large screens.\n */\nexport const RESPONSIVE_COLS = {\n xl: 12,\n lg: 12,\n md: 10,\n sm: 6,\n xs: 4,\n} as const;\n\n/**\n * Creates an occupancy map from existing widgets\n * Each occupied cell is marked with the widget ID\n */\nfunction createOccupancyMap(widgets: Widget[]): Map<string, string> {\n const occupancyMap = new Map<string, string>();\n\n widgets.forEach((widget) => {\n const x = widget.positionX ?? 0;\n const y = widget.positionY ?? 0;\n const w = widget.positionWidth ?? 4;\n const h = widget.positionHeight ?? 4;\n\n for (let dx = 0; dx < w; dx++) {\n for (let dy = 0; dy < h; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n });\n\n return occupancyMap;\n}\n\n/**\n * Checks if a widget can be placed at the given position\n */\nfunction canPlaceWidget(\n occupancyMap: Map<string, string>,\n x: number,\n y: number,\n width: number,\n height: number,\n cols: number\n): boolean {\n // Check if widget fits within grid columns\n if (x + width > cols) {\n return false;\n }\n\n // Check if all required cells are available\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Finds the next available position for a widget using smart placement.\n * Scans row-by-row, left-to-right to find the first slot that fits.\n *\n * @param existingWidgets - Array of existing widgets on the dashboard\n * @param newWidgetSize - Size of the new widget to place\n * @param config - Grid configuration (columns, max rows)\n * @returns Position where the new widget should be placed\n */\nexport function findNextAvailablePosition(\n existingWidgets: Widget[],\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols, maxRows = 100 } = config;\n const { width, height } = newWidgetSize;\n\n // Handle empty dashboard - place at origin\n if (existingWidgets.length === 0) {\n return { x: 0, y: 0 };\n }\n\n // Create occupancy map\n const occupancyMap = createOccupancyMap(existingWidgets);\n\n // Find the maximum Y to limit our search\n const maxExistingY = Math.max(...existingWidgets.map((w) => (w.positionY ?? 0) + (w.positionHeight ?? 4)));\n\n // Scan row by row, left to right, to find first available slot\n for (let y = 0; y <= maxExistingY + height && y < maxRows; y++) {\n for (let x = 0; x <= cols - width; x++) {\n if (canPlaceWidget(occupancyMap, x, y, width, height, cols)) {\n return { x, y };\n }\n }\n }\n\n // Fallback: place at the bottom of existing widgets\n return { x: 0, y: maxExistingY };\n}\n\n/**\n * Finds position to the right of a specific widget\n * Falls back to smart placement if no space to the right\n */\nexport function findPositionNextToWidget(\n existingWidgets: Widget[],\n referenceWidgetId: string,\n newWidgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const referenceWidget = existingWidgets.find((w) => w.id === referenceWidgetId);\n\n if (!referenceWidget) {\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n }\n\n const refX = referenceWidget.positionX ?? 0;\n const refY = referenceWidget.positionY ?? 0;\n const refW = referenceWidget.positionWidth ?? 4;\n\n // Try to place to the right of reference widget\n const newX = refX + refW;\n const newY = refY;\n\n // Check if it fits\n if (newX + newWidgetSize.width <= cols) {\n const occupancyMap = createOccupancyMap(existingWidgets.filter((w) => w.id !== referenceWidgetId));\n\n if (canPlaceWidget(occupancyMap, newX, newY, newWidgetSize.width, newWidgetSize.height, cols)) {\n return { x: newX, y: newY };\n }\n }\n\n // Fall back to smart placement\n return findNextAvailablePosition(existingWidgets, newWidgetSize, config);\n}\n\n/**\n * Calculates position from drop coordinates (for drag-and-drop)\n */\nexport function calculatePositionFromDrop(\n dropX: number,\n dropY: number,\n canvasRect: DOMRect,\n containerWidth: number,\n widgetSize: WidgetSize,\n config: GridConfig = DEFAULT_GRID_CONFIG\n): { x: number; y: number } {\n const { cols } = config;\n const columnWidth = containerWidth / cols;\n const rowHeight = 60; // Match DashboardGrid rowHeight\n\n const relativeX = dropX - canvasRect.left;\n const relativeY = dropY - canvasRect.top;\n\n const gridX = Math.max(0, Math.min(cols - widgetSize.width, Math.floor(relativeX / columnWidth)));\n const gridY = Math.max(0, Math.floor(relativeY / rowHeight));\n\n return { x: gridX, y: gridY };\n}\n\n/**\n * Gets responsive columns for a given container width\n */\nexport function getResponsiveCols(containerWidth: number): number {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.xl) {\n return RESPONSIVE_COLS.xl;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return RESPONSIVE_COLS.lg;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return RESPONSIVE_COLS.md;\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return RESPONSIVE_COLS.sm;\n }\n return RESPONSIVE_COLS.xs;\n}\n\n/** Responsive breakpoint tier identifiers, ordered smallest → largest. */\nexport type ResponsiveTier = 'xs' | 'sm' | 'md' | 'lg' | 'xl';\n\n/**\n * Resolves the column span for a responsive tier, applying the documented\n * fallback chain. `xl` is optional and falls back to `lg` when not set, so\n * existing dashboards (xs/sm/md/lg only) are unaffected on xl screens.\n */\nexport function getResponsiveSpan(position: ResponsivePosition, tier: ResponsiveTier): number {\n switch (tier) {\n case 'xl':\n return position.xl ?? position.lg;\n case 'lg':\n return position.lg;\n case 'md':\n return position.md;\n case 'sm':\n return position.sm;\n case 'xs':\n default:\n return position.xs;\n }\n}\n\n/**\n * Maps a container width to its responsive tier.\n */\nexport function getResponsiveTier(containerWidth: number): ResponsiveTier {\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.xl) {\n return 'xl';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.lg) {\n return 'lg';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.md) {\n return 'md';\n }\n if (containerWidth >= RESPONSIVE_BREAKPOINTS.sm) {\n return 'sm';\n }\n return 'xs';\n}\n\n/**\n * Adjusts widget positions for a new column count (responsive)\n * Ensures widgets don't overflow the grid\n */\nexport function adjustPositionsForCols(\n widgets: Widget[],\n newCols: number\n): Map<string, { x: number; y: number; width: number; height: number }> {\n const adjustedPositions = new Map<string, { x: number; y: number; width: number; height: number }>();\n\n // Sort widgets by their original position (top-left first)\n const sortedWidgets = [...widgets].sort((a, b) => {\n const aY = a.positionY ?? 0;\n const bY = b.positionY ?? 0;\n const aX = a.positionX ?? 0;\n const bX = b.positionX ?? 0;\n return aY !== bY ? aY - bY : aX - bX;\n });\n\n // Recalculate positions for the new column count\n const occupancyMap = new Map<string, string>();\n\n sortedWidgets.forEach((widget) => {\n const width = Math.min(widget.positionWidth ?? 4, newCols);\n const height = widget.positionHeight ?? 4;\n\n // Find next available position\n let placed = false;\n for (let y = 0; y < 100 && !placed; y++) {\n for (let x = 0; x <= newCols - width && !placed; x++) {\n let canPlace = true;\n for (let dx = 0; dx < width && canPlace; dx++) {\n for (let dy = 0; dy < height && canPlace; dy++) {\n if (occupancyMap.has(`${x + dx},${y + dy}`)) {\n canPlace = false;\n }\n }\n }\n\n if (canPlace) {\n // Mark cells as occupied\n for (let dx = 0; dx < width; dx++) {\n for (let dy = 0; dy < height; dy++) {\n occupancyMap.set(`${x + dx},${y + dy}`, widget.id);\n }\n }\n\n adjustedPositions.set(widget.id, { x, y, width, height });\n placed = true;\n }\n }\n }\n });\n\n return adjustedPositions;\n}\n"],"mappings":";AA8BA,IAAa,IAAkC;CAC7C,MAAM;CACN,SAAS;CACV;AA8BD,SAAS,EAAmB,GAAwC;CAClE,IAAM,oBAAe,IAAI,KAAqB;AAe9C,QAbA,EAAQ,SAAS,MAAW;EAC1B,IAAM,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,aAAa,GACxB,IAAI,EAAO,iBAAiB,GAC5B,IAAI,EAAO,kBAAkB;AAEnC,OAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,MAAK,IAAI,IAAK,GAAG,IAAK,GAAG,IACvB,GAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,KAAM,EAAO,GAAG;GAGtD,EAEK;;AAMT,SAAS,EACP,GACA,GACA,GACA,GACA,GACA,GACS;AAET,KAAI,IAAI,IAAQ,EACd,QAAO;AAIT,MAAK,IAAI,IAAK,GAAG,IAAK,GAAO,IAC3B,MAAK,IAAI,IAAK,GAAG,IAAK,GAAQ,IAC5B,KAAI,EAAa,IAAI,GAAG,IAAI,EAAG,GAAG,IAAI,IAAK,CACzC,QAAO;AAKb,QAAO;;AAYT,SAAgB,EACd,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,SAAM,aAAU,QAAQ,GAC1B,EAAE,UAAO,cAAW;AAG1B,KAAI,EAAgB,WAAW,EAC7B,QAAO;EAAE,GAAG;EAAG,GAAG;EAAG;CAIvB,IAAM,IAAe,EAAmB,EAAgB,EAGlD,IAAe,KAAK,IAAI,GAAG,EAAgB,KAAK,OAAO,EAAE,aAAa,MAAM,EAAE,kBAAkB,GAAG,CAAC;AAG1G,MAAK,IAAI,IAAI,GAAG,KAAK,IAAe,KAAU,IAAI,GAAS,IACzD,MAAK,IAAI,IAAI,GAAG,KAAK,IAAO,GAAO,IACjC,KAAI,EAAe,GAAc,GAAG,GAAG,GAAO,GAAQ,EAAK,CACzD,QAAO;EAAE;EAAG;EAAG;AAMrB,QAAO;EAAE,GAAG;EAAG,GAAG;EAAc;;AA4ClC,SAAgB,EACd,GACA,GACA,GACA,GACA,GACA,IAAqB,GACK;CAC1B,IAAM,EAAE,YAAS,GACX,IAAc,IAAiB,GAG/B,IAAY,IAAQ,EAAW,MAC/B,IAAY,IAAQ,EAAW;AAKrC,QAAO;EAAE,GAHK,KAAK,IAAI,GAAG,KAAK,IAAI,IAAO,EAAW,OAAO,KAAK,MAAM,IAAY,EAAY,CAAC,CAAC;EAG9E,GAFL,KAAK,IAAI,GAAG,KAAK,MAAM,IAAY,GAAU,CAAC;EAE/B"}
|