@burdenoff/microfe-bigconsole 2026.612.6 → 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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"}
@@ -1,125 +1,172 @@
1
- import { memo as e, useCallback as t } from "react";
2
- import { jsx as n, jsxs as r } from "react/jsx-runtime";
1
+ import { memo as e, useEffect as t, useMemo as n, useRef as r, useState as i } from "react";
2
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
3
+ import s from "leaflet";
3
4
  //#region src/bigconsole/components/widgets/map-widget/MapWidget.tsx
4
- var i = e(function({ widget: e, data: i, onClick: a, onDrilldown: o }) {
5
- let s = i?.markers || [], c = i?.center || {
6
- lat: 0,
7
- lng: 0
8
- }, l = t((e) => {
9
- o ? o(e) : a && a(e);
10
- }, [a, o]), u = !!(o || a);
11
- return s.length ? /* @__PURE__ */ r("div", {
12
- className: "flex flex-col h-full p-4",
5
+ var c = "https://unpkg.com/leaflet@1.9.4/dist/leaflet.css", l = "sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=", u = !1;
6
+ function d() {
7
+ if (u || document.getElementById("leaflet-css")) return;
8
+ u = !0;
9
+ let e = document.createElement("link");
10
+ e.id = "leaflet-css", e.rel = "stylesheet", e.href = c, e.integrity = l, e.crossOrigin = "anonymous", e.referrerPolicy = "no-referrer", e.onerror = () => {
11
+ console.warn("[bigconsole] Leaflet CSS failed to load; map styling may be broken."), u = !1, e.remove();
12
+ }, document.head.appendChild(e);
13
+ }
14
+ var f = "var(--color-chart-1, #3b82f6)";
15
+ function p(e) {
16
+ if (typeof e != "string") return f;
17
+ let t = e.trim();
18
+ return t.length > 64 || /[;{}<>"'\\]/.test(t) || /\burl\s*\(/i.test(t) || !/^[#a-zA-Z0-9(),.%/_\s-]+$/.test(t) ? f : t;
19
+ }
20
+ function m(e) {
21
+ return typeof e == "number" && Number.isFinite(e);
22
+ }
23
+ function h(e) {
24
+ let t = e;
25
+ return t && m(t.lat) && m(t.lng) ? {
26
+ lat: t.lat,
27
+ lng: t.lng
28
+ } : void 0;
29
+ }
30
+ function g(e, t) {
31
+ let n = Array.isArray(e) ? void 0 : e, r = Array.isArray(e) ? e : e?.markers;
32
+ return {
33
+ markers: Array.isArray(r) ? r.filter((e) => e && m(e.lat) && m(e.lng)) : [],
34
+ center: h(n?.center),
35
+ zoom: m(n?.zoom) ? n?.zoom : void 0,
36
+ fallbackCenter: h(t.center),
37
+ fallbackZoom: m(t.zoom) ? t.zoom : void 0
38
+ };
39
+ }
40
+ var _ = e(function({ widget: e, data: c, onClick: l, onDrilldown: u }) {
41
+ let f = r(null), h = r(null), _ = r(null), [v, y] = i("loading"), b = n(() => e?.config ?? {}, [e?.config]), x = b.showMarkers !== !1, { markers: S, center: C, zoom: w, fallbackCenter: T, fallbackZoom: E } = n(() => g(c, b), [c, b]), D = r(() => void 0);
42
+ return t(() => {
43
+ D.current = (e) => {
44
+ u ? u(e) : l && l(e);
45
+ };
46
+ }, [l, u]), t(() => {
47
+ let e = f.current;
48
+ if (!e) return;
49
+ d();
50
+ let t;
51
+ try {
52
+ t = s.map(e, {
53
+ center: [20, 0],
54
+ zoom: 2,
55
+ scrollWheelZoom: !0,
56
+ attributionControl: !0
57
+ });
58
+ let n = s.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
59
+ attribution: "© OpenStreetMap contributors",
60
+ maxZoom: 18
61
+ }), r = !1;
62
+ n.on("tileload", () => {
63
+ r = !0, y((e) => e === "error" ? "ready" : e);
64
+ }), n.on("tileerror", () => {
65
+ r || y("error");
66
+ }), n.addTo(t), h.current = t, _.current = null, y("ready");
67
+ } catch {
68
+ y("error");
69
+ return;
70
+ }
71
+ let n = typeof ResizeObserver < "u" ? new ResizeObserver(() => h.current?.invalidateSize()) : void 0;
72
+ n?.observe(e);
73
+ let r = window.setTimeout(() => t.invalidateSize(), 80);
74
+ return () => {
75
+ window.clearTimeout(r), n?.disconnect(), t.remove(), h.current = null;
76
+ };
77
+ }, []), t(() => {
78
+ let e = h.current;
79
+ if (!e) return;
80
+ let t = s.layerGroup();
81
+ if (x) for (let e of S) {
82
+ let n = document.createElement("div");
83
+ Object.assign(n.style, {
84
+ width: "14px",
85
+ height: "14px",
86
+ borderRadius: "50% 50% 50% 0",
87
+ transform: "rotate(-45deg)",
88
+ background: p(e.color),
89
+ border: "2px solid #fff",
90
+ boxShadow: "0 1px 4px rgba(0,0,0,.4)"
91
+ });
92
+ let r = s.divIcon({
93
+ className: "bc-map-marker",
94
+ html: n,
95
+ iconSize: [14, 14],
96
+ iconAnchor: [7, 14]
97
+ }), i = s.marker([e.lat, e.lng], { icon: r }).addTo(t), a = document.createElement("div"), o = document.createElement("div");
98
+ if (o.style.fontWeight = "600", o.textContent = e.popup || e.name || "Location", a.appendChild(o), m(e.value)) {
99
+ let t = document.createElement("div");
100
+ t.textContent = e.value.toLocaleString(), a.appendChild(t);
101
+ }
102
+ i.bindPopup(a), i.on("click", () => D.current(e));
103
+ }
104
+ t.addTo(e);
105
+ let n = JSON.stringify({
106
+ m: S.map((e) => [e.lat, e.lng]),
107
+ c: C ? [C.lat, C.lng] : null,
108
+ z: w ?? null,
109
+ fc: T ? [T.lat, T.lng] : null,
110
+ fz: E ?? null
111
+ });
112
+ if (n !== _.current) if (_.current = n, C) e.setView([C.lat, C.lng], w ?? e.getZoom());
113
+ else if (S.length === 1) e.setView([S[0].lat, S[0].lng], w ?? 8);
114
+ else if (S.length > 1) {
115
+ let t = s.latLngBounds(S.map((e) => [e.lat, e.lng]));
116
+ e.fitBounds(t, {
117
+ padding: [32, 32],
118
+ maxZoom: 12
119
+ });
120
+ } else T ? e.setView([T.lat, T.lng], w ?? E ?? e.getZoom()) : m(w ?? E) && e.setZoom(w ?? E);
121
+ let r = window.setTimeout(() => e.invalidateSize(), 0);
122
+ return () => {
123
+ window.clearTimeout(r), t.remove();
124
+ };
125
+ }, [
126
+ S,
127
+ C,
128
+ w,
129
+ T,
130
+ E,
131
+ x
132
+ ]), /* @__PURE__ */ o("div", {
133
+ className: "relative h-full w-full overflow-hidden rounded-lg bg-bg-muted",
13
134
  children: [
14
- /* @__PURE__ */ r("div", {
15
- className: "flex-1 relative bg-bg-muted rounded-lg overflow-hidden",
16
- children: [/* @__PURE__ */ n("div", {
17
- className: "absolute inset-0 flex items-center justify-center",
18
- children: /* @__PURE__ */ n("svg", {
19
- className: "w-32 h-32 text-text-tertiary/30",
135
+ /* @__PURE__ */ a("div", {
136
+ ref: f,
137
+ className: "h-full w-full"
138
+ }),
139
+ v !== "error" && S.length === 0 && /* @__PURE__ */ a("div", {
140
+ className: "pointer-events-none absolute inset-0 z-[500] flex items-center justify-center bg-bg-muted/70 text-sm text-text-secondary",
141
+ children: /* @__PURE__ */ o("div", {
142
+ className: "text-center",
143
+ children: [/* @__PURE__ */ o("svg", {
144
+ className: "mx-auto mb-2 h-12 w-12 text-text-tertiary",
20
145
  fill: "none",
21
146
  stroke: "currentColor",
22
147
  viewBox: "0 0 24 24",
23
- children: /* @__PURE__ */ n("path", {
148
+ children: [/* @__PURE__ */ a("path", {
24
149
  strokeLinecap: "round",
25
150
  strokeLinejoin: "round",
26
- strokeWidth: .5,
27
- d: "M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
28
- })
29
- })
30
- }), /* @__PURE__ */ n("div", {
31
- className: "absolute inset-0 p-4",
32
- children: /* @__PURE__ */ n("div", {
33
- className: "flex flex-wrap gap-2 justify-center",
34
- children: s.slice(0, 8).map((e, t) => /* @__PURE__ */ r("div", {
35
- className: `
36
- flex items-center gap-1.5 px-2 py-1 rounded-full
37
- bg-bg-surface border border-border-default shadow-sm
38
- ${u ? "cursor-pointer hover:border-action-primary-bg" : ""}
39
- transition-colors
40
- `,
41
- onClick: () => l(e),
42
- title: e.popup || e.name,
43
- children: [
44
- /* @__PURE__ */ n("div", {
45
- className: "w-2 h-2 rounded-full",
46
- style: { backgroundColor: e.color || "var(--color-chart-1)" }
47
- }),
48
- /* @__PURE__ */ n("span", {
49
- className: "text-xs text-text-primary truncate max-w-[80px]",
50
- children: e.name
51
- }),
52
- e.value !== void 0 && /* @__PURE__ */ n("span", {
53
- className: "text-xs text-text-secondary",
54
- children: e.value.toLocaleString()
55
- })
56
- ]
57
- }, t))
58
- })
59
- })]
60
- }),
61
- /* @__PURE__ */ n("div", {
62
- className: "mt-3 space-y-1 max-h-[120px] overflow-auto",
63
- children: s.map((e, t) => /* @__PURE__ */ r("div", {
64
- className: `
65
- flex items-center justify-between p-2 rounded
66
- hover:bg-bg-muted/50 transition-colors
67
- ${u ? "cursor-pointer" : ""}
68
- `,
69
- onClick: () => l(e),
70
- children: [/* @__PURE__ */ r("div", {
71
- className: "flex items-center gap-2",
72
- children: [/* @__PURE__ */ n("div", {
73
- className: "w-2 h-2 rounded-full flex-shrink-0",
74
- style: { backgroundColor: e.color || "var(--color-chart-1)" }
75
- }), /* @__PURE__ */ n("span", {
76
- className: "text-xs text-text-primary",
77
- children: e.name
151
+ strokeWidth: 1.5,
152
+ d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
153
+ }), /* @__PURE__ */ a("path", {
154
+ strokeLinecap: "round",
155
+ strokeLinejoin: "round",
156
+ strokeWidth: 1.5,
157
+ d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z"
78
158
  })]
79
- }), e.value !== void 0 && /* @__PURE__ */ n("span", {
80
- className: "text-xs font-medium text-text-secondary",
81
- children: e.value.toLocaleString()
82
- })]
83
- }, t))
84
- }),
85
- /* @__PURE__ */ n("div", {
86
- className: "mt-2 pt-2 border-t border-border-default",
87
- children: /* @__PURE__ */ r("p", {
88
- className: "text-[10px] text-text-tertiary text-center",
89
- children: [
90
- "Center: ",
91
- c.lat.toFixed(2),
92
- ", ",
93
- c.lng.toFixed(2)
94
- ]
159
+ }), /* @__PURE__ */ a("p", { children: "No map data" })]
95
160
  })
161
+ }),
162
+ v === "error" && /* @__PURE__ */ a("div", {
163
+ className: "absolute inset-0 z-[500] flex items-center justify-center bg-bg-muted px-4 text-center text-sm text-text-secondary",
164
+ children: "The map needs an internet connection and couldn’t load."
96
165
  })
97
166
  ]
98
- }) : /* @__PURE__ */ n("div", {
99
- className: "flex items-center justify-center h-full text-text-secondary text-sm",
100
- children: /* @__PURE__ */ r("div", {
101
- className: "text-center",
102
- children: [/* @__PURE__ */ r("svg", {
103
- className: "w-12 h-12 mx-auto mb-2 text-text-tertiary",
104
- fill: "none",
105
- stroke: "currentColor",
106
- viewBox: "0 0 24 24",
107
- children: [/* @__PURE__ */ n("path", {
108
- strokeLinecap: "round",
109
- strokeLinejoin: "round",
110
- strokeWidth: 1.5,
111
- d: "M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
112
- }), /* @__PURE__ */ n("path", {
113
- strokeLinecap: "round",
114
- strokeLinejoin: "round",
115
- strokeWidth: 1.5,
116
- d: "M15 11a3 3 0 11-6 0 3 3 0 016 0z"
117
- })]
118
- }), /* @__PURE__ */ n("p", { children: "No map data" })]
119
- })
120
167
  });
121
168
  });
122
169
  //#endregion
123
- export { i as default };
170
+ export { _ as default };
124
171
 
125
172
  //# sourceMappingURL=MapWidget.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MapWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/map-widget/MapWidget.tsx"],"sourcesContent":["/**\n * MapWidget Component\n *\n * Displays geographic data with markers (placeholder implementation).\n */\n\nimport { type FC, memo, useCallback } from 'react';\nimport type { Widget } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MapWidgetProps {\n widget: Widget;\n data?: MapData;\n onClick?: (marker: MapMarker) => void;\n /** Drilldown handler - receives selected marker data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface MapMarker {\n name: string;\n lat: number;\n lng: number;\n value?: number;\n popup?: string;\n color?: string;\n}\n\nexport interface MapData {\n center?: { lat: number; lng: number };\n zoom?: number;\n markers?: MapMarker[];\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MapWidget: FC<MapWidgetProps> = memo(function MapWidget({ widget: _widget, data, onClick, onDrilldown }) {\n const markers = data?.markers || [];\n const center = data?.center || { lat: 0, lng: 0 };\n\n // Handle marker click with drilldown support\n const handleMarkerClick = useCallback(\n (marker: MapMarker) => {\n if (onDrilldown) {\n onDrilldown(marker as unknown as Record<string, unknown>);\n } else if (onClick) {\n onClick(marker);\n }\n },\n [onClick, onDrilldown]\n );\n\n const isClickable = Boolean(onDrilldown || onClick);\n\n if (!markers.length) {\n return (\n <div className=\"flex items-center justify-center h-full text-text-secondary text-sm\">\n <div className=\"text-center\">\n <svg\n className=\"w-12 h-12 mx-auto mb-2 text-text-tertiary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={1.5} d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n <p>No map data</p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full p-4\">\n {/* Map placeholder with markers list */}\n <div className=\"flex-1 relative bg-bg-muted rounded-lg overflow-hidden\">\n {/* Simulated map background */}\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <svg className=\"w-32 h-32 text-text-tertiary/30\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={0.5}\n d=\"M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n </div>\n\n {/* Markers overlay */}\n <div className=\"absolute inset-0 p-4\">\n <div className=\"flex flex-wrap gap-2 justify-center\">\n {markers.slice(0, 8).map((marker, index) => (\n <div\n key={index}\n className={`\n flex items-center gap-1.5 px-2 py-1 rounded-full\n bg-bg-surface border border-border-default shadow-sm\n ${isClickable ? 'cursor-pointer hover:border-action-primary-bg' : ''}\n transition-colors\n `}\n onClick={() => handleMarkerClick(marker)}\n title={marker.popup || marker.name}\n >\n <div\n className=\"w-2 h-2 rounded-full\"\n style={{ backgroundColor: marker.color || 'var(--color-chart-1)' }}\n />\n <span className=\"text-xs text-text-primary truncate max-w-[80px]\">{marker.name}</span>\n {marker.value !== undefined && (\n <span className=\"text-xs text-text-secondary\">{marker.value.toLocaleString()}</span>\n )}\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Location list */}\n <div className=\"mt-3 space-y-1 max-h-[120px] overflow-auto\">\n {markers.map((marker, index) => (\n <div\n key={index}\n className={`\n flex items-center justify-between p-2 rounded\n hover:bg-bg-muted/50 transition-colors\n ${isClickable ? 'cursor-pointer' : ''}\n `}\n onClick={() => handleMarkerClick(marker)}\n >\n <div className=\"flex items-center gap-2\">\n <div\n className=\"w-2 h-2 rounded-full flex-shrink-0\"\n style={{ backgroundColor: marker.color || 'var(--color-chart-1)' }}\n />\n <span className=\"text-xs text-text-primary\">{marker.name}</span>\n </div>\n {marker.value !== undefined && (\n <span className=\"text-xs font-medium text-text-secondary\">{marker.value.toLocaleString()}</span>\n )}\n </div>\n ))}\n </div>\n\n {/* Center coordinates */}\n <div className=\"mt-2 pt-2 border-t border-border-default\">\n <p className=\"text-[10px] text-text-tertiary text-center\">\n Center: {center.lat.toFixed(2)}, {center.lng.toFixed(2)}\n </p>\n </div>\n </div>\n );\n});\n\nexport default MapWidget;\n"],"mappings":";;;AAwCA,IAAa,IAAgC,EAAK,SAAmB,EAAE,QAAQ,GAAS,SAAM,YAAS,kBAAe;CACpH,IAAM,IAAU,GAAM,WAAW,EAAE,EAC7B,IAAS,GAAM,UAAU;EAAE,KAAK;EAAG,KAAK;EAAG,EAG3C,IAAoB,GACvB,MAAsB;AACrB,EAAI,IACF,EAAY,EAA6C,GAChD,KACT,EAAQ,EAAO;IAGnB,CAAC,GAAS,EAAY,CACvB,EAEK,IAAc,GAAQ,KAAe;AA0B3C,QAxBK,EAAQ,SAyBX,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;MAAkC,MAAK;MAAO,QAAO;MAAe,SAAQ;gBACzF,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA;KACF,CAAA,EAGN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAQ,MAAM,GAAG,EAAE,CAAC,KAAK,GAAQ,MAChC,kBAAC,OAAD;OAEE,WAAW;;;sBAGL,IAAc,kDAAkD,GAAG;;;OAGzE,eAAe,EAAkB,EAAO;OACxC,OAAO,EAAO,SAAS,EAAO;iBAThC;QAWE,kBAAC,OAAD;SACE,WAAU;SACV,OAAO,EAAE,iBAAiB,EAAO,SAAS,wBAAwB;SAClE,CAAA;QACF,kBAAC,QAAD;SAAM,WAAU;mBAAmD,EAAO;SAAY,CAAA;QACrF,EAAO,UAAU,KAAA,KAChB,kBAAC,QAAD;SAAM,WAAU;mBAA+B,EAAO,MAAM,gBAAgB;SAAQ,CAAA;QAElF;SAlBC,EAkBD,CACN;MACE,CAAA;KACF,CAAA,CACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAQ,KAAK,GAAQ,MACpB,kBAAC,OAAD;KAEE,WAAW;;;kBAGL,IAAc,mBAAmB,GAAG;;KAE1C,eAAe,EAAkB,EAAO;eAP1C,CASE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OACE,WAAU;OACV,OAAO,EAAE,iBAAiB,EAAO,SAAS,wBAAwB;OAClE,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBAA6B,EAAO;OAAY,CAAA,CAC5D;SACL,EAAO,UAAU,KAAA,KAChB,kBAAC,QAAD;MAAM,WAAU;gBAA2C,EAAO,MAAM,gBAAgB;MAAQ,CAAA,CAE9F;OAlBC,EAkBD,CACN;IACE,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eAAb;MAA0D;MAC/C,EAAO,IAAI,QAAQ,EAAE;MAAC;MAAG,EAAO,IAAI,QAAQ,EAAE;MACrD;;IACA,CAAA;GACF;MAnGJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IACE,WAAU;IACV,MAAK;IACL,QAAO;IACP,SAAQ;cAJV,CAME,kBAAC,QAAD;KACE,eAAc;KACd,gBAAe;KACf,aAAa;KACb,GAAE;KACF,CAAA,EACF,kBAAC,QAAD;KAAM,eAAc;KAAQ,gBAAe;KAAQ,aAAa;KAAK,GAAE;KAAqC,CAAA,CACxG;OACN,kBAAC,KAAD,EAAA,UAAG,eAAe,CAAA,CACd;;EACF,CAAA;EAmFV"}
1
+ {"version":3,"file":"MapWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/map-widget/MapWidget.tsx"],"sourcesContent":["/**\n * MapWidget Component\n *\n * Interactive geographic map (Leaflet + OpenStreetMap tiles) with data-bound\n * markers. Leaflet's stylesheet is loaded from a CDN at runtime (with SRI) because\n * the build-time MFE library bundle does not ship a component's CSS to the host\n * shell — consistent with the map being an inherently online feature (tiles).\n */\n\nimport { type FC, memo, useEffect, useMemo, useRef, useState } from 'react';\nimport L from 'leaflet';\nimport type { Widget } from '../../../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MapWidgetProps {\n widget: Widget;\n data?: MapData;\n onClick?: (marker: MapMarker) => void;\n /** Drilldown handler - receives selected marker data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface MapMarker {\n name: string;\n lat: number;\n lng: number;\n value?: number;\n popup?: string;\n color?: string;\n}\n\nexport interface MapData {\n center?: { lat: number; lng: number };\n zoom?: number;\n markers?: MapMarker[];\n}\n\n// ============================================================================\n// Leaflet CSS (runtime CDN injection)\n// ============================================================================\n\nconst LEAFLET_CSS = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css';\n// SRI for leaflet@1.9.4/dist/leaflet.css (immutable versioned URL) — guards against a CDN compromise.\nconst LEAFLET_CSS_SRI = 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=';\n\nlet leafletCssRequested = false;\n\nfunction ensureLeafletCss(): void {\n // Module-level guard in addition to the DOM check so two MapWidgets mounting in\n // the same tick can't both append the <link> before either lands in the DOM.\n if (leafletCssRequested || document.getElementById('leaflet-css')) return;\n leafletCssRequested = true;\n const link = document.createElement('link');\n link.id = 'leaflet-css';\n link.rel = 'stylesheet';\n link.href = LEAFLET_CSS;\n link.integrity = LEAFLET_CSS_SRI;\n link.crossOrigin = 'anonymous';\n // Don't leak the current path (workspace/dashboard IDs) to the CDN.\n link.referrerPolicy = 'no-referrer';\n // Leaflet needs its stylesheet for correct tile/marker positioning. If the CDN\n // is unreachable or the SRI check fails the load is otherwise silent, so warn and\n // clear the guard/element to allow a later mount to retry.\n link.onerror = () => {\n console.warn('[bigconsole] Leaflet CSS failed to load; map styling may be broken.');\n leafletCssRequested = false;\n link.remove();\n };\n document.head.appendChild(link);\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nconst DEFAULT_MARKER_COLOR = 'var(--color-chart-1, #3b82f6)';\n\n/**\n * Marker colours come from widget data and are interpolated into an inline\n * `style` attribute, so restrict them to a safe character set (hex / rgb() /\n * hsl() / css var() / named colours). Anything outside that — or containing\n * style-breaking characters — falls back to the default token colour.\n */\nfunction safeColor(color: unknown): string {\n // Marker data is untrusted: a parser can emit a non-string colour (e.g. a numeric\n // status code) that slips past the lat/lng filter, so guard the type before trim().\n if (typeof color !== 'string') return DEFAULT_MARKER_COLOR;\n const trimmed = color.trim();\n if (trimmed.length > 64) return DEFAULT_MARKER_COLOR;\n if (/[;{}<>\"'\\\\]/.test(trimmed)) return DEFAULT_MARKER_COLOR;\n // Block `url()` so a marker colour can't smuggle an exfiltration beacon\n // (e.g. `url(//evil.com/pixel)`) through the otherwise-permissive char set.\n if (/\\burl\\s*\\(/i.test(trimmed)) return DEFAULT_MARKER_COLOR;\n // Allow `_` (custom-property names) and `/` (modern `rgb(r g b / a)` syntax);\n // the dangerous-char guard above still blocks anything that could break out of\n // the inline style.\n if (!/^[#a-zA-Z0-9(),.%/_\\s-]+$/.test(trimmed)) return DEFAULT_MARKER_COLOR;\n return trimmed;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n}\n\n/**\n * Normalize the bound widget data into markers + optional center/zoom. Accepts\n * the documented `{ markers, center, zoom }` shape, or a bare array of markers\n * (parser output), and drops any entry without valid coordinates.\n */\ninterface NormalizedMapData {\n markers: MapMarker[];\n /** Center explicitly supplied by the data payload — wins over markers. */\n center?: { lat: number; lng: number };\n /** Zoom explicitly supplied by the data payload. */\n zoom?: number;\n /** Center from widget config — a static fallback used only when there are no markers. */\n fallbackCenter?: { lat: number; lng: number };\n /** Zoom from widget config — fallback for the no-marker case. */\n fallbackZoom?: number;\n}\n\nfunction asLatLng(v: unknown): { lat: number; lng: number } | undefined {\n const c = v as { lat?: unknown; lng?: unknown } | undefined;\n return c && isFiniteNumber(c.lat) && isFiniteNumber(c.lng) ? { lat: c.lat, lng: c.lng } : undefined;\n}\n\nfunction normalizeMapData(data: MapData | MapMarker[] | undefined, config: Record<string, unknown>): NormalizedMapData {\n // The widget may be handed either the documented { markers, center, zoom } shape\n // or a bare marker array (parser output); narrow once so the rest is type-safe.\n const payload: MapData | undefined = Array.isArray(data) ? undefined : data;\n const rawMarkers: unknown = Array.isArray(data) ? data : data?.markers;\n const markers: MapMarker[] = Array.isArray(rawMarkers)\n ? (rawMarkers as MapMarker[]).filter((m) => m && isFiniteNumber(m.lat) && isFiniteNumber(m.lng))\n : [];\n\n // Data-payload center/zoom are an explicit intent and win over markers. Config\n // center/zoom (e.g. the WidgetRegistry default of {0,0}/2) are only a static\n // fallback for marker-less maps — otherwise that default would pin every marker\n // dataset to the world view instead of framing the markers.\n return {\n markers,\n center: asLatLng(payload?.center),\n zoom: isFiniteNumber(payload?.zoom) ? payload?.zoom : undefined,\n fallbackCenter: asLatLng(config.center),\n fallbackZoom: isFiniteNumber(config.zoom) ? (config.zoom as number) : undefined,\n };\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MapWidget: FC<MapWidgetProps> = memo(function MapWidget({ widget, data, onClick, onDrilldown }) {\n const containerRef = useRef<HTMLDivElement>(null);\n const mapRef = useRef<L.Map | null>(null);\n // Signature of the last view we auto-framed to; lets us re-render markers on\n // every data change without snapping the viewport back when the framing\n // inputs (coords/center/zoom) are unchanged — e.g. a refetch of identical data.\n const lastFrameSigRef = useRef<string | null>(null);\n const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading');\n\n const config = useMemo(() => (widget?.config ?? {}) as Record<string, unknown>, [widget?.config]);\n const showMarkers = config.showMarkers !== false;\n const { markers, center, zoom, fallbackCenter, fallbackZoom } = useMemo(\n () => normalizeMapData(data, config),\n [data, config]\n );\n\n // Keep the latest click handler in a ref so marker bindings don't need rebinding.\n // Assign in an effect (not during render) per the React 19 useRef guidance.\n const clickRef = useRef<(marker: MapMarker) => void>(() => undefined);\n useEffect(() => {\n clickRef.current = (marker: MapMarker) => {\n if (onDrilldown) onDrilldown(marker as unknown as Record<string, unknown>);\n else if (onClick) onClick(marker);\n };\n }, [onClick, onDrilldown]);\n\n // Initialise the map once.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return undefined;\n ensureLeafletCss();\n\n let map: L.Map;\n try {\n map = L.map(el, { center: [20, 0], zoom: 2, scrollWheelZoom: true, attributionControl: true });\n const tiles = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n attribution: '© OpenStreetMap contributors',\n maxZoom: 18,\n });\n // Leaflet reports tile failures asynchronously, so addTo() won't throw when\n // tiles are blocked/offline. Surface the error overlay only if a tile errors\n // before any tile has loaded (true offline/blocked), tolerating the odd\n // transient miss once the base map is up.\n let anyTileLoaded = false;\n tiles.on('tileload', () => {\n anyTileLoaded = true;\n // Recover if an early tile miss had shown the offline overlay — a flaky\n // connection that later succeeds shouldn't stay hidden behind it.\n setStatus((s) => (s === 'error' ? 'ready' : s));\n });\n tiles.on('tileerror', () => {\n if (!anyTileLoaded) setStatus('error');\n });\n tiles.addTo(map);\n mapRef.current = map;\n // Fresh map ⇒ force the next marker effect to (re)frame it. Without this the\n // frame-signature ref would survive a remount (e.g. React StrictMode's\n // double-invoke) and the new map would stay stuck at this init view.\n lastFrameSigRef.current = null;\n setStatus('ready');\n } catch {\n setStatus('error');\n return undefined;\n }\n\n // Widgets live in a resizable grid; keep tiles correct as the box changes.\n const ro =\n typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => mapRef.current?.invalidateSize()) : undefined;\n ro?.observe(el);\n const t = window.setTimeout(() => map.invalidateSize(), 80);\n\n return () => {\n window.clearTimeout(t);\n ro?.disconnect();\n map.remove();\n mapRef.current = null;\n };\n }, []);\n\n // Render markers + frame the view whenever the data changes.\n useEffect(() => {\n const map = mapRef.current;\n if (!map) return undefined;\n\n const layer = L.layerGroup();\n if (showMarkers) {\n for (const marker of markers) {\n // Build the pin as a DOM element (not an HTML string) so the marker colour\n // is assigned via the style API and can never be interpreted as markup —\n // defense-in-depth alongside safeColor().\n const pin = document.createElement('div');\n Object.assign(pin.style, {\n width: '14px',\n height: '14px',\n borderRadius: '50% 50% 50% 0',\n transform: 'rotate(-45deg)',\n background: safeColor(marker.color),\n border: '2px solid #fff',\n boxShadow: '0 1px 4px rgba(0,0,0,.4)',\n });\n const icon = L.divIcon({\n className: 'bc-map-marker',\n html: pin,\n iconSize: [14, 14],\n iconAnchor: [7, 14],\n });\n const m = L.marker([marker.lat, marker.lng], { icon }).addTo(layer);\n\n // bindPopup treats a string as HTML; build a text node so marker-supplied\n // text can never be interpreted as markup (defense-in-depth).\n const popupEl = document.createElement('div');\n const title = document.createElement('div');\n title.style.fontWeight = '600';\n title.textContent = marker.popup || marker.name || 'Location';\n popupEl.appendChild(title);\n if (isFiniteNumber(marker.value)) {\n const val = document.createElement('div');\n val.textContent = marker.value.toLocaleString();\n popupEl.appendChild(val);\n }\n m.bindPopup(popupEl);\n m.on('click', () => clickRef.current(marker));\n }\n }\n layer.addTo(map);\n\n // Only (re)frame the view when the framing inputs actually change — otherwise\n // a data-prop reference change (e.g. a refetch returning identical markers)\n // would discard the user's current pan/zoom.\n const frameSig = JSON.stringify({\n m: markers.map((mk) => [mk.lat, mk.lng]),\n c: center ? [center.lat, center.lng] : null,\n z: zoom ?? null,\n fc: fallbackCenter ? [fallbackCenter.lat, fallbackCenter.lng] : null,\n fz: fallbackZoom ?? null,\n });\n if (frameSig !== lastFrameSigRef.current) {\n lastFrameSigRef.current = frameSig;\n // Framing priority: data-payload center → frame the markers → config fallback\n // center (no markers) → config/payload zoom only → world view. Markers win over\n // the config default so a palette-created map frames its data instead of {0,0}.\n if (center) {\n map.setView([center.lat, center.lng], zoom ?? map.getZoom());\n } else if (markers.length === 1) {\n map.setView([markers[0].lat, markers[0].lng], zoom ?? 8);\n } else if (markers.length > 1) {\n const bounds = L.latLngBounds(markers.map((m) => [m.lat, m.lng] as [number, number]));\n map.fitBounds(bounds, { padding: [32, 32], maxZoom: 12 });\n } else if (fallbackCenter) {\n map.setView([fallbackCenter.lat, fallbackCenter.lng], zoom ?? fallbackZoom ?? map.getZoom());\n } else if (isFiniteNumber(zoom ?? fallbackZoom)) {\n map.setZoom((zoom ?? fallbackZoom) as number);\n }\n }\n\n const resize = window.setTimeout(() => map.invalidateSize(), 0);\n\n return () => {\n window.clearTimeout(resize);\n layer.remove();\n };\n }, [markers, center, zoom, fallbackCenter, fallbackZoom, showMarkers]);\n\n return (\n <div className=\"relative h-full w-full overflow-hidden rounded-lg bg-bg-muted\">\n <div ref={containerRef} className=\"h-full w-full\" />\n\n {status !== 'error' && markers.length === 0 && (\n <div className=\"pointer-events-none absolute inset-0 z-[500] flex items-center justify-center bg-bg-muted/70 text-sm text-text-secondary\">\n <div className=\"text-center\">\n <svg\n className=\"mx-auto mb-2 h-12 w-12 text-text-tertiary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\"\n />\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={1.5}\n d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\"\n />\n </svg>\n <p>No map data</p>\n </div>\n </div>\n )}\n\n {status === 'error' && (\n <div className=\"absolute inset-0 z-[500] flex items-center justify-center bg-bg-muted px-4 text-center text-sm text-text-secondary\">\n The map needs an internet connection and couldn’t load.\n </div>\n )}\n </div>\n );\n});\n\nexport default MapWidget;\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":"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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.612.6",
3
+ "version": "2026.613.2",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [
@@ -53,6 +53,7 @@
53
53
  "graphql": "^16.10.0",
54
54
  "graphql-ws": "^5.16.0",
55
55
  "immer": "^11.1.0",
56
+ "leaflet": "^1.9.4",
56
57
  "lucide-react": "^0.468.0",
57
58
  "nanoid": "^5.1.6",
58
59
  "popmotion": "^11.0.5",
@@ -78,6 +79,7 @@
78
79
  "@graphql-codegen/typescript-react-apollo": "^4.0.0",
79
80
  "@types/react": "^19.0.2",
80
81
  "@types/react-dom": "^19.0.2",
82
+ "@types/leaflet": "^1.9.21",
81
83
  "@types/react-grid-layout": "^1.3.5",
82
84
  "@types/uuid": "^11.0.0",
83
85
  "@vitejs/plugin-react": "^4.3.4",