@burdenoff/microfe-bigconsole 2026.713.7 → 2026.714.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigconsole/assistant/assistantApi.js +4 -11
- package/dist/bigconsole/assistant/assistantApi.js.map +1 -1
- package/dist/bigconsole/assistant/conversationHistoryApi.js +79 -0
- package/dist/bigconsole/assistant/conversationHistoryApi.js.map +1 -0
- package/dist/bigconsole/assistant/createSandboxAssistantTransport.js +188 -231
- package/dist/bigconsole/assistant/createSandboxAssistantTransport.js.map +1 -1
- package/dist/bigconsole/components/widgets/WidgetWrapper.js.map +1 -1
- package/dist/bigconsole/components/widgets/metric-card/MetricCardWidget.js.map +1 -1
- package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
- package/dist/bigconsole/utils/widgetTypeMapping.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricCardWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/metric-card/MetricCardWidget.tsx"],"sourcesContent":["/**\n * MetricCardWidget Component\n *\n * Displays a single KPI/metric with trend, comparison, and optional sparkline.\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport type { Widget, MetricCardConfig } from '../../../types';\nimport { TrendIndicator, type TrendDirection } from './TrendIndicator';\nimport { ComparisonBadge } from './ComparisonBadge';\nimport { SparklineChart, type SparklineDataPoint } from './SparklineChart';\nimport { firstObjectRow, inferValueField, unwrapRows } from '../utils/inferFields';\nimport { resolveConditionalStyle, conditionalStyleToClassName } from '../utils/conditionalFormat';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MetricCardWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Fetched data */\n data?: MetricCardData;\n /** Click handler for drilldown */\n onClick?: () => void;\n /** Drilldown handler - receives metric data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface MetricCardData {\n /** Primary value to display */\n value: number | string;\n /** Previous value for comparison */\n previousValue?: number;\n /** Percentage change from previous */\n changePercent?: number;\n /** Trend direction override */\n trend?: TrendDirection;\n /** Sparkline data points */\n sparklineData?: SparklineDataPoint[];\n /** Target value for comparison */\n target?: number;\n /** Unit label (e.g., \"users\", \"$\") */\n unit?: string;\n /** Whether the metric is a currency */\n isCurrency?: boolean;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Format a number based on configuration\n */\nfunction formatValue(value: number | string, config?: MetricCardConfig): string {\n if (typeof value === 'string') return value;\n\n const { format = 'number', prefix, suffix } = config || {};\n\n let formatted: string;\n\n switch (format) {\n case 'currency':\n formatted = new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(value);\n break;\n\n case 'percentage':\n formatted = `${(value * 100).toFixed(1)}%`;\n break;\n\n case 'compact':\n formatted = new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(value);\n break;\n\n case 'number':\n default:\n formatted = new Intl.NumberFormat('en-US', {\n maximumFractionDigits: 2,\n }).format(value);\n }\n\n // Apply prefix/suffix if provided\n if (prefix) formatted = `${prefix}${formatted}`;\n if (suffix) formatted = `${formatted}${suffix}`;\n\n return formatted;\n}\n\n/**\n * Calculate trend direction from change percent\n */\nfunction calculateTrend(changePercent?: number): TrendDirection {\n if (changePercent === undefined || changePercent === 0) return 'flat';\n return changePercent > 0 ? 'up' : 'down';\n}\n\n/**\n * Get sparkline color based on trend\n */\nfunction getSparklineColor(trend: TrendDirection, positiveIsGood: boolean): 'success' | 'error' | 'muted' {\n if (trend === 'flat') return 'muted';\n const isGood = positiveIsGood ? trend === 'up' : trend === 'down';\n return isGood ? 'success' : 'error';\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MetricCardWidget: FC<MetricCardWidgetProps> = memo(function MetricCardWidget({\n widget,\n data: rawData,\n onClick,\n onDrilldown,\n}) {\n const config = widget.config as unknown as MetricCardConfig | undefined;\n\n // Normalize data - extract value from object if valueField is configured\n const data = useMemo((): MetricCardData | undefined => {\n if (!rawData) return undefined;\n\n // Helper to get nested value using dot notation (e.g., \"summary.totalSales\")\n const getNestedValue = (obj: Record<string, unknown>, path: string): unknown => {\n return path.split('.').reduce((current, key) => {\n return current && typeof current === 'object' ? (current as Record<string, unknown>)[key] : undefined;\n }, obj as unknown);\n };\n\n // If data already has 'value' property, use it directly\n if ('value' in rawData && rawData.value !== undefined) {\n return rawData as MetricCardData;\n }\n\n // If valueField is configured, extract the value from that field (supports dot notation)\n const valueField = (config as unknown as Record<string, unknown> | undefined)?.valueField as string | undefined;\n if (valueField && typeof rawData === 'object') {\n const extractedValue = getNestedValue(rawData as unknown as Record<string, unknown>, valueField);\n if (extractedValue !== undefined) {\n // Also try to extract previousValue if previousValueField is configured\n const previousValueField = (config as unknown as Record<string, unknown> | undefined)?.previousValueField as\n | string\n | undefined;\n const previousValue = previousValueField\n ? (getNestedValue(rawData as unknown as Record<string, unknown>, previousValueField) as number | undefined)\n : undefined;\n\n return {\n value: extractedValue as number | string,\n previousValue,\n changePercent:\n previousValue !== undefined && typeof extractedValue === 'number'\n ? ((extractedValue - previousValue) / previousValue) * 100\n : undefined,\n };\n }\n }\n\n // Try common field names at top level\n const commonFields = ['value', 'total', 'amount', 'count', 'sum', 'totalSales', 'totalRevenue', 'totalOrders'];\n for (const field of commonFields) {\n if (typeof rawData === 'object' && field in rawData) {\n const val = (rawData as unknown as Record<string, unknown>)[field];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n\n // Try looking in nested 'summary' object (common pattern)\n if (typeof rawData === 'object' && 'summary' in rawData) {\n const summary = (rawData as Record<string, unknown>).summary as Record<string, unknown> | undefined;\n if (summary && typeof summary === 'object') {\n for (const field of commonFields) {\n if (field in summary) {\n const val = summary[field];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n }\n }\n\n // If rawData is a number or string directly, use it\n if (typeof rawData === 'number' || typeof rawData === 'string') {\n return { value: rawData };\n }\n\n // BOFF-2246 smart default: if rawData is an array (or envelope around an\n // array) with object rows, use the configured valueField if it resolves\n // on any row (supports dotted paths like \"metrics.revenue\") — otherwise\n // pick the first numeric field from row[0]. This path only fires when\n // the earlier top-level extraction didn't find anything, but an explicit\n // `config.valueField` (flat or dotted) must always win over inference.\n const firstRow = firstObjectRow(rawData);\n if (firstRow) {\n const configuredField = (config as unknown as Record<string, unknown> | undefined)?.valueField as\n | string\n | undefined;\n if (configuredField) {\n // Configured field is authoritative. Scan every row for it so sparse\n // envelopes like `{data:[{id:1},{revenue:120}]}` with\n // `valueField:\"revenue\"` still resolve. If no row carries it, return\n // undefined rather than substituting an unrelated numeric column\n // from row[0] (that would silently misrepresent the user's mapping).\n const rows = unwrapRows(rawData) ?? [firstRow];\n for (const r of rows) {\n if (r && typeof r === 'object') {\n const resolved = getNestedValue(r as Record<string, unknown>, configuredField);\n if (typeof resolved === 'number' || typeof resolved === 'string') {\n return { value: resolved };\n }\n }\n }\n return undefined;\n }\n const inferredField = inferValueField(firstRow);\n if (inferredField) {\n const val = firstRow[inferredField];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n\n return undefined;\n }, [rawData, config]);\n\n // Calculate derived values\n const trend = useMemo(() => {\n return data?.trend || calculateTrend(data?.changePercent);\n }, [data?.trend, data?.changePercent]);\n\n const positiveIsGood = config?.positiveIsGood ?? true;\n\n const formattedValue = useMemo(() => {\n // Treat 0 / \"\" as real values — only a missing value renders the em dash.\n // (`if (!data?.value)` would wrongly blank out a legitimate 0, e.g. an ED\n // boarding-compliance of 0% or an available-bed count of 0.)\n if (data?.value === undefined || data?.value === null) return '—';\n return formatValue(data.value, config);\n }, [data?.value, config]);\n\n const sparklineColor = useMemo(() => {\n return getSparklineColor(trend, positiveIsGood);\n }, [trend, positiveIsGood]);\n\n // Conditional formatting on the metric value (threshold-driven color, not\n // just trend direction). Falls back to the default primary text color.\n const valueConditionalClass = useMemo(() => {\n const style = resolveConditionalStyle(data?.value, config?.conditionalRules);\n return conditionalStyleToClassName(style);\n }, [data?.value, config?.conditionalRules]);\n\n // Handle click\n const handleClick = useCallback(() => {\n // Support both legacy onClick and new onDrilldown\n if (onDrilldown) {\n onDrilldown({\n value: data?.value,\n changePercent: data?.changePercent,\n trend,\n target: data?.target,\n });\n } else if (onClick && widget.drilldowns?.length) {\n onClick();\n }\n }, [onClick, onDrilldown, widget.drilldowns, data, trend]);\n\n // Determine if clickable\n const isClickable = Boolean(onDrilldown || (onClick && widget.drilldowns?.length));\n\n return (\n <div\n className={`\n flex flex-col h-full p-4\n ${isClickable ? 'cursor-pointer hover:bg-bg-sunken/50 transition-colors' : ''}\n `}\n onClick={handleClick}\n role={isClickable ? 'button' : undefined}\n tabIndex={isClickable ? 0 : undefined}\n onKeyDown={(e) => {\n if (isClickable && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n handleClick();\n }\n }}\n >\n {/* Subtitle/Label */}\n {config?.subtitle && <p className=\"text-xs text-text-secondary mb-1 truncate\">{config.subtitle}</p>}\n\n {/* Main Value */}\n <div className=\"flex items-baseline gap-2 mb-2\">\n <span\n className={`text-3xl font-semibold tracking-tight ${valueConditionalClass || 'text-text-primary'}`}\n title={String(data?.value)}\n >\n {formattedValue}\n </span>\n\n {/* Trend Indicator (inline) */}\n {config?.showTrend !== false && data?.changePercent !== undefined && (\n <TrendIndicator direction={trend} size=\"md\" positiveIsGood={positiveIsGood} />\n )}\n </div>\n\n {/* Comparison Badge */}\n {config?.showComparison !== false && data?.changePercent !== undefined && (\n <div className=\"mb-3\">\n <ComparisonBadge\n value={data.changePercent}\n label={config?.comparisonLabel}\n positiveIsGood={positiveIsGood}\n size=\"sm\"\n />\n </div>\n )}\n\n {/* Target Progress */}\n {data?.target !== undefined && typeof data.value === 'number' && (\n <div className=\"mb-3\">\n <div className=\"flex justify-between text-xs text-text-secondary mb-1\">\n <span>Target: {formatValue(data.target, config)}</span>\n <span>{((data.value / data.target) * 100).toFixed(0)}%</span>\n </div>\n <div className=\"h-1.5 bg-bg-sunken rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all ${\n data.value >= data.target ? 'bg-state-success' : 'bg-action-primary-bg'\n }`}\n style={{\n width: `${Math.min((data.value / data.target) * 100, 100)}%`,\n }}\n />\n </div>\n </div>\n )}\n\n {/* Sparkline */}\n {config?.showSparkline !== false && data?.sparklineData?.length ? (\n <div className=\"mt-auto pt-2\">\n <SparklineChart data={data.sparklineData} width={120} height={32} color={sparklineColor} showFill />\n </div>\n ) : null}\n\n {/* Drilldown Indicator */}\n {isClickable && (\n <div className=\"mt-auto pt-2 flex items-center justify-end\">\n <span className=\"text-xs text-text-secondary flex items-center gap-1\">\n View details\n <svg className=\"w-3 h-3\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </span>\n </div>\n )}\n </div>\n );\n});\n\nexport default MetricCardWidget;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MetricCardWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/metric-card/MetricCardWidget.tsx"],"sourcesContent":["/**\n * MetricCardWidget Component\n *\n * Displays a single KPI/metric with trend, comparison, and optional sparkline.\n */\n\nimport { type FC, memo, useMemo, useCallback } from 'react';\nimport type { Widget, MetricCardConfig } from '../../../types';\nimport { TrendIndicator, type TrendDirection } from './TrendIndicator';\nimport { ComparisonBadge } from './ComparisonBadge';\nimport { SparklineChart, type SparklineDataPoint } from './SparklineChart';\nimport { firstObjectRow, inferValueField, unwrapRows } from '../utils/inferFields';\nimport { resolveConditionalStyle, conditionalStyleToClassName } from '../utils/conditionalFormat';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface MetricCardWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Fetched data */\n data?: MetricCardData;\n /** Click handler for drilldown */\n onClick?: () => void;\n /** Drilldown handler - receives metric data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\nexport interface MetricCardData {\n /** Primary value to display */\n value: number | string;\n /** Previous value for comparison */\n previousValue?: number;\n /** Percentage change from previous */\n changePercent?: number;\n /** Trend direction override */\n trend?: TrendDirection;\n /** Sparkline data points */\n sparklineData?: SparklineDataPoint[];\n /** Target value for comparison */\n target?: number;\n /** Unit label (e.g., \"users\", \"$\") */\n unit?: string;\n /** Whether the metric is a currency */\n isCurrency?: boolean;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Format a number based on configuration\n */\nfunction formatValue(value: number | string, config?: MetricCardConfig): string {\n if (typeof value === 'string') return value;\n\n const { format = 'number', prefix, suffix } = config || {};\n\n let formatted: string;\n\n switch (format) {\n case 'currency':\n formatted = new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(value);\n break;\n\n case 'percentage':\n formatted = `${(value * 100).toFixed(1)}%`;\n break;\n\n case 'compact':\n formatted = new Intl.NumberFormat('en-US', {\n notation: 'compact',\n maximumFractionDigits: 1,\n }).format(value);\n break;\n\n case 'number':\n default:\n formatted = new Intl.NumberFormat('en-US', {\n maximumFractionDigits: 2,\n }).format(value);\n }\n\n // Apply prefix/suffix if provided\n if (prefix) formatted = `${prefix}${formatted}`;\n if (suffix) formatted = `${formatted}${suffix}`;\n\n return formatted;\n}\n\n/**\n * Calculate trend direction from change percent\n */\nfunction calculateTrend(changePercent?: number): TrendDirection {\n if (changePercent === undefined || changePercent === 0) return 'flat';\n return changePercent > 0 ? 'up' : 'down';\n}\n\n/**\n * Get sparkline color based on trend\n */\nfunction getSparklineColor(trend: TrendDirection, positiveIsGood: boolean): 'success' | 'error' | 'muted' {\n if (trend === 'flat') return 'muted';\n const isGood = positiveIsGood ? trend === 'up' : trend === 'down';\n return isGood ? 'success' : 'error';\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const MetricCardWidget: FC<MetricCardWidgetProps> = memo(function MetricCardWidget({\n widget,\n data: rawData,\n onClick,\n onDrilldown,\n}) {\n const config = widget.config as unknown as MetricCardConfig | undefined;\n\n // Normalize data - extract value from object if valueField is configured\n const data = useMemo((): MetricCardData | undefined => {\n if (!rawData) return undefined;\n\n // Helper to get nested value using dot notation (e.g., \"summary.totalSales\")\n const getNestedValue = (obj: Record<string, unknown>, path: string): unknown => {\n return path.split('.').reduce((current, key) => {\n return current && typeof current === 'object' ? (current as Record<string, unknown>)[key] : undefined;\n }, obj as unknown);\n };\n\n // If data already has 'value' property, use it directly\n if ('value' in rawData && rawData.value !== undefined) {\n return rawData as MetricCardData;\n }\n\n // If valueField is configured, extract the value from that field (supports dot notation)\n const valueField = (config as unknown as Record<string, unknown> | undefined)?.valueField as string | undefined;\n if (valueField && typeof rawData === 'object') {\n const extractedValue = getNestedValue(rawData as unknown as Record<string, unknown>, valueField);\n if (extractedValue !== undefined) {\n // Also try to extract previousValue if previousValueField is configured\n const previousValueField = (config as unknown as Record<string, unknown> | undefined)?.previousValueField as\n string | undefined;\n const previousValue = previousValueField\n ? (getNestedValue(rawData as unknown as Record<string, unknown>, previousValueField) as number | undefined)\n : undefined;\n\n return {\n value: extractedValue as number | string,\n previousValue,\n changePercent:\n previousValue !== undefined && typeof extractedValue === 'number'\n ? ((extractedValue - previousValue) / previousValue) * 100\n : undefined,\n };\n }\n }\n\n // Try common field names at top level\n const commonFields = ['value', 'total', 'amount', 'count', 'sum', 'totalSales', 'totalRevenue', 'totalOrders'];\n for (const field of commonFields) {\n if (typeof rawData === 'object' && field in rawData) {\n const val = (rawData as unknown as Record<string, unknown>)[field];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n\n // Try looking in nested 'summary' object (common pattern)\n if (typeof rawData === 'object' && 'summary' in rawData) {\n const summary = (rawData as Record<string, unknown>).summary as Record<string, unknown> | undefined;\n if (summary && typeof summary === 'object') {\n for (const field of commonFields) {\n if (field in summary) {\n const val = summary[field];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n }\n }\n\n // If rawData is a number or string directly, use it\n if (typeof rawData === 'number' || typeof rawData === 'string') {\n return { value: rawData };\n }\n\n // BOFF-2246 smart default: if rawData is an array (or envelope around an\n // array) with object rows, use the configured valueField if it resolves\n // on any row (supports dotted paths like \"metrics.revenue\") — otherwise\n // pick the first numeric field from row[0]. This path only fires when\n // the earlier top-level extraction didn't find anything, but an explicit\n // `config.valueField` (flat or dotted) must always win over inference.\n const firstRow = firstObjectRow(rawData);\n if (firstRow) {\n const configuredField = (config as unknown as Record<string, unknown> | undefined)?.valueField as\n string | undefined;\n if (configuredField) {\n // Configured field is authoritative. Scan every row for it so sparse\n // envelopes like `{data:[{id:1},{revenue:120}]}` with\n // `valueField:\"revenue\"` still resolve. If no row carries it, return\n // undefined rather than substituting an unrelated numeric column\n // from row[0] (that would silently misrepresent the user's mapping).\n const rows = unwrapRows(rawData) ?? [firstRow];\n for (const r of rows) {\n if (r && typeof r === 'object') {\n const resolved = getNestedValue(r as Record<string, unknown>, configuredField);\n if (typeof resolved === 'number' || typeof resolved === 'string') {\n return { value: resolved };\n }\n }\n }\n return undefined;\n }\n const inferredField = inferValueField(firstRow);\n if (inferredField) {\n const val = firstRow[inferredField];\n if (typeof val === 'number' || typeof val === 'string') {\n return { value: val };\n }\n }\n }\n\n return undefined;\n }, [rawData, config]);\n\n // Calculate derived values\n const trend = useMemo(() => {\n return data?.trend || calculateTrend(data?.changePercent);\n }, [data?.trend, data?.changePercent]);\n\n const positiveIsGood = config?.positiveIsGood ?? true;\n\n const formattedValue = useMemo(() => {\n // Treat 0 / \"\" as real values — only a missing value renders the em dash.\n // (`if (!data?.value)` would wrongly blank out a legitimate 0, e.g. an ED\n // boarding-compliance of 0% or an available-bed count of 0.)\n if (data?.value === undefined || data?.value === null) return '—';\n return formatValue(data.value, config);\n }, [data?.value, config]);\n\n const sparklineColor = useMemo(() => {\n return getSparklineColor(trend, positiveIsGood);\n }, [trend, positiveIsGood]);\n\n // Conditional formatting on the metric value (threshold-driven color, not\n // just trend direction). Falls back to the default primary text color.\n const valueConditionalClass = useMemo(() => {\n const style = resolveConditionalStyle(data?.value, config?.conditionalRules);\n return conditionalStyleToClassName(style);\n }, [data?.value, config?.conditionalRules]);\n\n // Handle click\n const handleClick = useCallback(() => {\n // Support both legacy onClick and new onDrilldown\n if (onDrilldown) {\n onDrilldown({\n value: data?.value,\n changePercent: data?.changePercent,\n trend,\n target: data?.target,\n });\n } else if (onClick && widget.drilldowns?.length) {\n onClick();\n }\n }, [onClick, onDrilldown, widget.drilldowns, data, trend]);\n\n // Determine if clickable\n const isClickable = Boolean(onDrilldown || (onClick && widget.drilldowns?.length));\n\n return (\n <div\n className={`\n flex flex-col h-full p-4\n ${isClickable ? 'cursor-pointer hover:bg-bg-sunken/50 transition-colors' : ''}\n `}\n onClick={handleClick}\n role={isClickable ? 'button' : undefined}\n tabIndex={isClickable ? 0 : undefined}\n onKeyDown={(e) => {\n if (isClickable && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n handleClick();\n }\n }}\n >\n {/* Subtitle/Label */}\n {config?.subtitle && <p className=\"text-xs text-text-secondary mb-1 truncate\">{config.subtitle}</p>}\n\n {/* Main Value */}\n <div className=\"flex items-baseline gap-2 mb-2\">\n <span\n className={`text-3xl font-semibold tracking-tight ${valueConditionalClass || 'text-text-primary'}`}\n title={String(data?.value)}\n >\n {formattedValue}\n </span>\n\n {/* Trend Indicator (inline) */}\n {config?.showTrend !== false && data?.changePercent !== undefined && (\n <TrendIndicator direction={trend} size=\"md\" positiveIsGood={positiveIsGood} />\n )}\n </div>\n\n {/* Comparison Badge */}\n {config?.showComparison !== false && data?.changePercent !== undefined && (\n <div className=\"mb-3\">\n <ComparisonBadge\n value={data.changePercent}\n label={config?.comparisonLabel}\n positiveIsGood={positiveIsGood}\n size=\"sm\"\n />\n </div>\n )}\n\n {/* Target Progress */}\n {data?.target !== undefined && typeof data.value === 'number' && (\n <div className=\"mb-3\">\n <div className=\"flex justify-between text-xs text-text-secondary mb-1\">\n <span>Target: {formatValue(data.target, config)}</span>\n <span>{((data.value / data.target) * 100).toFixed(0)}%</span>\n </div>\n <div className=\"h-1.5 bg-bg-sunken rounded-full overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all ${\n data.value >= data.target ? 'bg-state-success' : 'bg-action-primary-bg'\n }`}\n style={{\n width: `${Math.min((data.value / data.target) * 100, 100)}%`,\n }}\n />\n </div>\n </div>\n )}\n\n {/* Sparkline */}\n {config?.showSparkline !== false && data?.sparklineData?.length ? (\n <div className=\"mt-auto pt-2\">\n <SparklineChart data={data.sparklineData} width={120} height={32} color={sparklineColor} showFill />\n </div>\n ) : null}\n\n {/* Drilldown Indicator */}\n {isClickable && (\n <div className=\"mt-auto pt-2 flex items-center justify-end\">\n <span className=\"text-xs text-text-secondary flex items-center gap-1\">\n View details\n <svg className=\"w-3 h-3\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M9 5l7 7-7 7\" />\n </svg>\n </span>\n </div>\n )}\n </div>\n );\n});\n\nexport default MetricCardWidget;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WidgetDemoPage.js","names":[],"sources":["../../../src/bigconsole/pages/WidgetDemoPage.tsx"],"sourcesContent":["/**\n * WidgetDemoPage\n *\n * Demonstrates all 18 widget types.\n * Widgets show empty states when no real data is available.\n * Connect a DataSink + Parser pipeline for real data processing.\n */\n\nimport { type FC, useState, useMemo } from 'react';\nimport { IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport type { Widget } from '../types';\nimport { WidgetWrapper } from '../components/widgets';\n\n// Widget type string literals\ntype WidgetType =\n | 'metric_card'\n | 'kpi_card_comparison'\n | 'chart'\n | 'funnel_chart'\n | 'table'\n | 'pivot_table'\n | 'gauge'\n | 'progress'\n | 'list'\n | 'form'\n | 'text'\n | 'iframe'\n | 'map'\n | 'heatmap'\n | 'calendar'\n | 'kanban'\n | 'timeline'\n | 'custom';\n\n// ============================================================================\n// Widget Types for Demo\n// ============================================================================\n\nconst WIDGET_TYPES: { type: WidgetType; title: string; description: string }[] = [\n { type: 'metric_card', title: 'Metric Card', description: 'Single metric with trend and sparkline' },\n { type: 'kpi_card_comparison', title: 'KPI Card Comparison', description: 'Multiple KPIs with comparisons' },\n { type: 'chart', title: 'Chart (Line/Bar/Area)', description: 'Time-series data visualization' },\n { type: 'funnel_chart', title: 'Funnel Chart', description: 'Conversion funnel with stages' },\n { type: 'table', title: 'Table', description: 'Data table with sort/filter/pagination' },\n { type: 'pivot_table', title: 'Pivot Table', description: 'Multi-dimensional data analysis' },\n { type: 'gauge', title: 'Gauge', description: 'Circular/linear gauge with thresholds' },\n { type: 'progress', title: 'Progress', description: 'Progress bar with milestones' },\n { type: 'list', title: 'List', description: 'Scrollable list with items' },\n { type: 'form', title: 'Form (Adaptive Card)', description: 'Interactive form with inputs' },\n { type: 'text', title: 'Text (Markdown)', description: 'Rich text with markdown support' },\n { type: 'iframe', title: 'iFrame', description: 'Embedded external content' },\n { type: 'map', title: 'Map', description: 'Geographic data with markers' },\n { type: 'heatmap', title: 'Heatmap', description: 'Grid visualization with color intensity' },\n { type: 'calendar', title: 'Calendar', description: 'Event calendar view' },\n { type: 'kanban', title: 'Kanban', description: 'Task board with columns' },\n { type: 'timeline', title: 'Timeline (Gantt)', description: 'Project timeline with tasks' },\n { type: 'custom', title: 'Custom', description: 'User-defined widget rendering' },\n];\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const WidgetDemoPage: FC = () => {\n const [filter, setFilter] = useState('');\n const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');\n\n // Generate demo widgets from all types\n const demoWidgets = useMemo(() => {\n const filtered = filter\n ? WIDGET_TYPES.filter(\n (w) => w.type.includes(filter.toLowerCase()) || w.title.toLowerCase().includes(filter.toLowerCase())\n )\n : WIDGET_TYPES;\n\n return filtered.map(\n (widgetDef, index): Widget => ({\n id: `demo-${widgetDef.type}`,\n pageId: 'demo-page',\n dashboardId: 'demo-dashboard',\n type: widgetDef.type,\n title: widgetDef.title,\n // No data source - widgets show empty state until connected to DataSink\n config: {},\n position: {\n x: (index % 3) * 4,\n y: Math.floor(index / 3) * 4,\n width: 4,\n height: 4,\n },\n positionX: (index % 3) * 4,\n positionY: Math.floor(index / 3) * 4,\n positionWidth: 4,\n positionHeight: 4,\n refreshInterval: undefined,\n lastRefreshedAt: undefined,\n metadata: { description: widgetDef.description },\n context: {},\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n })\n );\n }, [filter]);\n\n return (\n <div className=\"h-full flex flex-col bg-bg-canvas\">\n {/* Header */}\n <div className=\"flex-shrink-0 border-b border-border-default bg-bg-surface px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-xl font-semibold text-text-primary\">Widget Demo</h1>\n <p className=\"text-sm text-text-secondary mt-1\">All 18 widget types - connect a DataSink for real data</p>\n </div>\n <div className=\"flex items-center gap-4\">\n {/* Search Filter */}\n <div className=\"relative\">\n <input\n type=\"text\"\n placeholder=\"Filter widgets...\"\n value={filter}\n onChange={(e) => setFilter(e.target.value)}\n className=\"\n w-64 px-3 py-2 pl-[3.25rem]\n text-sm\n bg-bg-sunken\n border border-border-default\n rounded-lg\n text-text-primary\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-text-secondary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n </div>\n\n {/* View Toggle */}\n <div className=\"flex items-center bg-bg-sunken rounded-lg p-1\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`\n px-3 py-1.5 text-sm font-medium rounded\n transition-colors\n ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }\n `}\n >\n Grid\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`\n px-3 py-1.5 text-sm font-medium rounded\n transition-colors\n ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }\n `}\n >\n List\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* Widget Grid */}\n <div className=\"flex-1 overflow-auto p-6\">\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n This gallery previews every widget type BigConsole can render on a dashboard — metrics, charts, tables, maps,\n kanban boards and more. Use it to see what each widget looks like and pick the right one before wiring it to a\n real DataSink in the dashboard builder.\n </PagePurpose>\n\n <div\n className={\n viewMode === 'grid' ? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6' : 'flex flex-col gap-4'\n }\n >\n {demoWidgets.map((widget) => {\n const widgetDef = WIDGET_TYPES.find((w) => w.type === widget.type);\n return (\n <div key={widget.id} className={viewMode === 'grid' ? 'h-80' : 'h-64'}>\n <div className=\"h-full flex flex-col\">\n {/* Widget Description */}\n <div className=\"flex-shrink-0 mb-2\">\n <span className=\"text-xs text-text-tertiary\">\n Type: <code className=\"font-mono text-action-primary-text\">{widget.type}</code>\n </span>\n {widgetDef && <span className=\"text-xs text-text-tertiary ml-2\">- {widgetDef.description}</span>}\n </div>\n {/* Widget */}\n <div className=\"flex-1 min-h-0\">\n <WidgetWrapper widget={widget} />\n </div>\n </div>\n </div>\n );\n })}\n </div>\n\n {/* Empty State */}\n {demoWidgets.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No widgets match your filter\"\n description=\"Try a different search term to preview a widget type.\"\n />\n )}\n </div>\n\n {/* Footer Stats */}\n <div className=\"flex-shrink-0 border-t border-border-default bg-bg-surface px-6 py-3\">\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>\n Showing {demoWidgets.length} of {WIDGET_TYPES.length} widget types\n </span>\n <span>Widgets show empty state until connected to DataSink + Parser</span>\n </div>\n </div>\n </div>\n );\n};\n\nexport default WidgetDemoPage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WidgetDemoPage.js","names":[],"sources":["../../../src/bigconsole/pages/WidgetDemoPage.tsx"],"sourcesContent":["/**\n * WidgetDemoPage\n *\n * Demonstrates all 18 widget types.\n * Widgets show empty states when no real data is available.\n * Connect a DataSink + Parser pipeline for real data processing.\n */\n\nimport { type FC, useState, useMemo } from 'react';\nimport { IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport type { Widget } from '../types';\nimport { WidgetWrapper } from '../components/widgets';\n\n// Widget type string literals\ntype WidgetType =\n | 'metric_card'\n | 'kpi_card_comparison'\n | 'chart'\n | 'funnel_chart'\n | 'table'\n | 'pivot_table'\n | 'gauge'\n | 'progress'\n | 'list'\n | 'form'\n | 'text'\n | 'iframe'\n | 'map'\n | 'heatmap'\n | 'calendar'\n | 'kanban'\n | 'timeline'\n | 'custom';\n\n// ============================================================================\n// Widget Types for Demo\n// ============================================================================\n\nconst WIDGET_TYPES: { type: WidgetType; title: string; description: string }[] = [\n { type: 'metric_card', title: 'Metric Card', description: 'Single metric with trend and sparkline' },\n { type: 'kpi_card_comparison', title: 'KPI Card Comparison', description: 'Multiple KPIs with comparisons' },\n { type: 'chart', title: 'Chart (Line/Bar/Area)', description: 'Time-series data visualization' },\n { type: 'funnel_chart', title: 'Funnel Chart', description: 'Conversion funnel with stages' },\n { type: 'table', title: 'Table', description: 'Data table with sort/filter/pagination' },\n { type: 'pivot_table', title: 'Pivot Table', description: 'Multi-dimensional data analysis' },\n { type: 'gauge', title: 'Gauge', description: 'Circular/linear gauge with thresholds' },\n { type: 'progress', title: 'Progress', description: 'Progress bar with milestones' },\n { type: 'list', title: 'List', description: 'Scrollable list with items' },\n { type: 'form', title: 'Form (Adaptive Card)', description: 'Interactive form with inputs' },\n { type: 'text', title: 'Text (Markdown)', description: 'Rich text with markdown support' },\n { type: 'iframe', title: 'iFrame', description: 'Embedded external content' },\n { type: 'map', title: 'Map', description: 'Geographic data with markers' },\n { type: 'heatmap', title: 'Heatmap', description: 'Grid visualization with color intensity' },\n { type: 'calendar', title: 'Calendar', description: 'Event calendar view' },\n { type: 'kanban', title: 'Kanban', description: 'Task board with columns' },\n { type: 'timeline', title: 'Timeline (Gantt)', description: 'Project timeline with tasks' },\n { type: 'custom', title: 'Custom', description: 'User-defined widget rendering' },\n];\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const WidgetDemoPage: FC = () => {\n const [filter, setFilter] = useState('');\n const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');\n\n // Generate demo widgets from all types\n const demoWidgets = useMemo(() => {\n const filtered = filter\n ? WIDGET_TYPES.filter(\n (w) => w.type.includes(filter.toLowerCase()) || w.title.toLowerCase().includes(filter.toLowerCase())\n )\n : WIDGET_TYPES;\n\n return filtered.map((widgetDef, index): Widget => ({\n id: `demo-${widgetDef.type}`,\n pageId: 'demo-page',\n dashboardId: 'demo-dashboard',\n type: widgetDef.type,\n title: widgetDef.title,\n // No data source - widgets show empty state until connected to DataSink\n config: {},\n position: {\n x: (index % 3) * 4,\n y: Math.floor(index / 3) * 4,\n width: 4,\n height: 4,\n },\n positionX: (index % 3) * 4,\n positionY: Math.floor(index / 3) * 4,\n positionWidth: 4,\n positionHeight: 4,\n refreshInterval: undefined,\n lastRefreshedAt: undefined,\n metadata: { description: widgetDef.description },\n context: {},\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n }));\n }, [filter]);\n\n return (\n <div className=\"h-full flex flex-col bg-bg-canvas\">\n {/* Header */}\n <div className=\"flex-shrink-0 border-b border-border-default bg-bg-surface px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-xl font-semibold text-text-primary\">Widget Demo</h1>\n <p className=\"text-sm text-text-secondary mt-1\">All 18 widget types - connect a DataSink for real data</p>\n </div>\n <div className=\"flex items-center gap-4\">\n {/* Search Filter */}\n <div className=\"relative\">\n <input\n type=\"text\"\n placeholder=\"Filter widgets...\"\n value={filter}\n onChange={(e) => setFilter(e.target.value)}\n className=\"\n w-64 px-3 py-2 pl-[3.25rem]\n text-sm\n bg-bg-sunken\n border border-border-default\n rounded-lg\n text-text-primary\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n \"\n />\n <svg\n className=\"absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-text-secondary\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"\n />\n </svg>\n </div>\n\n {/* View Toggle */}\n <div className=\"flex items-center bg-bg-sunken rounded-lg p-1\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`\n px-3 py-1.5 text-sm font-medium rounded\n transition-colors\n ${\n viewMode === 'grid'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }\n `}\n >\n Grid\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`\n px-3 py-1.5 text-sm font-medium rounded\n transition-colors\n ${\n viewMode === 'list'\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }\n `}\n >\n List\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* Widget Grid */}\n <div className=\"flex-1 overflow-auto p-6\">\n <PagePurpose className=\"mb-[var(--space-sectionGap)]\">\n This gallery previews every widget type BigConsole can render on a dashboard — metrics, charts, tables, maps,\n kanban boards and more. Use it to see what each widget looks like and pick the right one before wiring it to a\n real DataSink in the dashboard builder.\n </PagePurpose>\n\n <div\n className={\n viewMode === 'grid' ? 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6' : 'flex flex-col gap-4'\n }\n >\n {demoWidgets.map((widget) => {\n const widgetDef = WIDGET_TYPES.find((w) => w.type === widget.type);\n return (\n <div key={widget.id} className={viewMode === 'grid' ? 'h-80' : 'h-64'}>\n <div className=\"h-full flex flex-col\">\n {/* Widget Description */}\n <div className=\"flex-shrink-0 mb-2\">\n <span className=\"text-xs text-text-tertiary\">\n Type: <code className=\"font-mono text-action-primary-text\">{widget.type}</code>\n </span>\n {widgetDef && <span className=\"text-xs text-text-tertiary ml-2\">- {widgetDef.description}</span>}\n </div>\n {/* Widget */}\n <div className=\"flex-1 min-h-0\">\n <WidgetWrapper widget={widget} />\n </div>\n </div>\n </div>\n );\n })}\n </div>\n\n {/* Empty State */}\n {demoWidgets.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No widgets match your filter\"\n description=\"Try a different search term to preview a widget type.\"\n />\n )}\n </div>\n\n {/* Footer Stats */}\n <div className=\"flex-shrink-0 border-t border-border-default bg-bg-surface px-6 py-3\">\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>\n Showing {demoWidgets.length} of {WIDGET_TYPES.length} widget types\n </span>\n <span>Widgets show empty state until connected to DataSink + Parser</span>\n </div>\n </div>\n </div>\n );\n};\n\nexport default WidgetDemoPage;\n"],"mappings":";;;;;;AAsCA,IAAM,IAA2E;CAC/E;EAAE,MAAM;EAAe,OAAO;EAAe,aAAa;EAA0C;CACpG;EAAE,MAAM;EAAuB,OAAO;EAAuB,aAAa;EAAkC;CAC5G;EAAE,MAAM;EAAS,OAAO;EAAyB,aAAa;EAAkC;CAChG;EAAE,MAAM;EAAgB,OAAO;EAAgB,aAAa;EAAiC;CAC7F;EAAE,MAAM;EAAS,OAAO;EAAS,aAAa;EAA0C;CACxF;EAAE,MAAM;EAAe,OAAO;EAAe,aAAa;EAAmC;CAC7F;EAAE,MAAM;EAAS,OAAO;EAAS,aAAa;EAAyC;CACvF;EAAE,MAAM;EAAY,OAAO;EAAY,aAAa;EAAgC;CACpF;EAAE,MAAM;EAAQ,OAAO;EAAQ,aAAa;EAA8B;CAC1E;EAAE,MAAM;EAAQ,OAAO;EAAwB,aAAa;EAAgC;CAC5F;EAAE,MAAM;EAAQ,OAAO;EAAmB,aAAa;EAAmC;CAC1F;EAAE,MAAM;EAAU,OAAO;EAAU,aAAa;EAA6B;CAC7E;EAAE,MAAM;EAAO,OAAO;EAAO,aAAa;EAAgC;CAC1E;EAAE,MAAM;EAAW,OAAO;EAAW,aAAa;EAA2C;CAC7F;EAAE,MAAM;EAAY,OAAO;EAAY,aAAa;EAAuB;CAC3E;EAAE,MAAM;EAAU,OAAO;EAAU,aAAa;EAA2B;CAC3E;EAAE,MAAM;EAAY,OAAO;EAAoB,aAAa;EAA+B;CAC3F;EAAE,MAAM;EAAU,OAAO;EAAU,aAAa;EAAiC;CAClF,EAMY,UAA2B;CACtC,IAAM,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAU,KAAe,EAA0B,OAAO,EAG3D,IAAc,SACD,IACb,EAAa,QACV,MAAM,EAAE,KAAK,SAAS,EAAO,aAAa,CAAC,IAAI,EAAE,MAAM,aAAa,CAAC,SAAS,EAAO,aAAa,CAAC,CACrG,GACD,GAEY,KAAK,GAAW,OAAmB;EACjD,IAAI,QAAQ,EAAU;EACtB,QAAQ;EACR,aAAa;EACb,MAAM,EAAU;EAChB,OAAO,EAAU;EAEjB,QAAQ,EAAE;EACV,UAAU;GACR,GAAI,IAAQ,IAAK;GACjB,GAAG,KAAK,MAAM,IAAQ,EAAE,GAAG;GAC3B,OAAO;GACP,QAAQ;GACT;EACD,WAAY,IAAQ,IAAK;EACzB,WAAW,KAAK,MAAM,IAAQ,EAAE,GAAG;EACnC,eAAe;EACf,gBAAgB;EAChB,iBAAiB,KAAA;EACjB,iBAAiB,KAAA;EACjB,UAAU,EAAE,aAAa,EAAU,aAAa;EAChD,SAAS,EAAE;EACX,cAAc,EAAE;EAChB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC,EAAE,EACF,CAAC,EAAO,CAAC;AAEZ,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA0C;MAAgB,CAAA,EACxE,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAA0D,CAAA,CACtG,EAAA,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAEE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QACE,MAAK;QACL,aAAY;QACZ,OAAO;QACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;QAC1C,WAAU;QAUV,CAAA,EACF,kBAAC,OAAD;QACE,WAAU;QACV,MAAK;QACL,QAAO;QACP,SAAQ;kBAER,kBAAC,QAAD;SACE,eAAc;SACd,gBAAe;SACf,aAAa;SACb,GAAE;SACF,CAAA;QACE,CAAA,CACF;UAGN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAY,OAAO;QAClC,WAAW;;;oBAIP,MAAa,SACT,8CACA,8CACL;;kBAEJ;QAEQ,CAAA,EACT,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAY,OAAO;QAClC,WAAW;;;oBAIP,MAAa,SACT,8CACA,8CACL;;kBAEJ;QAEQ,CAAA,CACL;SACF;QACF;;IACF,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAa,WAAU;gBAA+B;MAIxC,CAAA;KAEd,kBAAC,OAAD;MACE,WACE,MAAa,SAAS,yDAAyD;gBAGhF,EAAY,KAAK,MAAW;OAC3B,IAAM,IAAY,EAAa,MAAM,MAAM,EAAE,SAAS,EAAO,KAAK;AAClE,cACE,kBAAC,OAAD;QAAqB,WAAW,MAAa,SAAS,SAAS;kBAC7D,kBAAC,OAAD;SAAK,WAAU;mBAAf,CAEE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CAA6C,UACrC,kBAAC,QAAD;YAAM,WAAU;sBAAsC,EAAO;YAAY,CAAA,CAC1E;cACN,KAAa,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CAAkD,MAAG,EAAU,YAAmB;aAC5F;aAEN,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,GAAD,EAAuB,WAAU,CAAA;UAC7B,CAAA,CACF;;QACF,EAdI,EAAO,GAcX;QAER;MACE,CAAA;KAGL,EAAY,WAAW,KACtB,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA;KAEA;;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD,EAAA,UAAA;MAAM;MACK,EAAY;MAAO;MAAK,EAAa;MAAO;MAChD,EAAA,CAAA,EACP,kBAAC,QAAD,EAAA,UAAM,iEAAoE,CAAA,CACtE;;IACF,CAAA;GACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgetTypeMapping.js","names":[],"sources":["../../../src/bigconsole/utils/widgetTypeMapping.ts"],"sourcesContent":["/**\n * Widget Type Mapping Utilities\n *\n * Maps internal UI widget types (snake_case) to federation-accessible GraphQL enum values (UPPER_CASE).\n * The supergraph only exposes certain WidgetType values - other values are marked @inaccessible.\n *\n * Federation-accessible values: AREA_CHART, BAR_CHART, FUNNEL, HEATMAP, KPI, LINE_CHART, PIE_CHART, RETENTION, TABLE\n */\n\nimport type { WidgetType as InternalWidgetType } from '../types';\n\n// Federation-accessible WidgetType enum values\nexport type FederationWidgetType =\n
|
|
1
|
+
{"version":3,"file":"widgetTypeMapping.js","names":[],"sources":["../../../src/bigconsole/utils/widgetTypeMapping.ts"],"sourcesContent":["/**\n * Widget Type Mapping Utilities\n *\n * Maps internal UI widget types (snake_case) to federation-accessible GraphQL enum values (UPPER_CASE).\n * The supergraph only exposes certain WidgetType values - other values are marked @inaccessible.\n *\n * Federation-accessible values: AREA_CHART, BAR_CHART, FUNNEL, HEATMAP, KPI, LINE_CHART, PIE_CHART, RETENTION, TABLE\n */\n\nimport type { WidgetType as InternalWidgetType } from '../types';\n\n// Federation-accessible WidgetType enum values\nexport type FederationWidgetType =\n 'AREA_CHART' | 'BAR_CHART' | 'FUNNEL' | 'HEATMAP' | 'KPI' | 'LINE_CHART' | 'PIE_CHART' | 'RETENTION' | 'TABLE';\n\n/**\n * Maps internal UI widget types to federation-accessible GraphQL enum values.\n *\n * This mapping is necessary because:\n * 1. The UI uses snake_case widget types (e.g., 'kpi_card_comparison', 'metric_card')\n * 2. The BigConsole GraphQL schema defines many widget types\n * 3. But only some are exposed via federation (@inaccessible hides the rest)\n * 4. When creating/updating widgets through the gateway, we must use accessible values\n */\nexport function mapToFederationWidgetType(internalType: InternalWidgetType): FederationWidgetType {\n const mapping: Record<InternalWidgetType, FederationWidgetType> = {\n // KPI/Metric types -> KPI\n kpi_card_comparison: 'KPI',\n metric_card: 'KPI',\n\n // Chart types -> appropriate chart enum\n chart: 'LINE_CHART',\n funnel_chart: 'FUNNEL',\n\n // Direct mappings (already federation-accessible when uppercase)\n table: 'TABLE',\n pivot_table: 'TABLE',\n\n // Gauge and progress -> KPI (closest match)\n gauge: 'KPI',\n progress: 'KPI',\n\n // List -> TABLE\n list: 'TABLE',\n\n // Form -> TABLE (data display fallback)\n form: 'TABLE',\n\n // Text/iframe -> KPI (content display fallback)\n text: 'KPI',\n iframe: 'KPI',\n\n // Spatial/temporal types\n map: 'HEATMAP',\n heatmap: 'HEATMAP',\n calendar: 'TABLE',\n kanban: 'TABLE',\n timeline: 'TABLE',\n\n // Retention / cohort grid (federation-accessible)\n retention: 'RETENTION',\n\n // Custom -> KPI (generic fallback)\n custom: 'KPI',\n };\n\n return mapping[internalType] || 'KPI';\n}\n\n/**\n * Maps federation GraphQL enum values back to internal UI widget types.\n * Used when receiving data from the API.\n *\n * Note: This is a best-effort reverse mapping since multiple internal types\n * may map to the same federation type. The actual internal type should be\n * stored in widget metadata/config if precise type preservation is needed.\n */\nexport function mapFromFederationWidgetType(federationType: string): InternalWidgetType {\n // Normalize to uppercase for comparison\n const normalizedType = federationType.toUpperCase();\n\n const reverseMapping: Record<string, InternalWidgetType> = {\n KPI: 'kpi_card_comparison',\n LINE_CHART: 'chart',\n BAR_CHART: 'chart',\n AREA_CHART: 'chart',\n PIE_CHART: 'chart',\n TABLE: 'table',\n FUNNEL: 'funnel_chart',\n HEATMAP: 'heatmap',\n RETENTION: 'table',\n };\n\n return reverseMapping[normalizedType] || 'metric_card';\n}\n\n/**\n * Checks if a widget type is directly accessible via federation.\n * If true, it can be used as-is in GraphQL mutations through the gateway.\n */\nexport function isFederationAccessible(type: string): boolean {\n const accessibleTypes = new Set([\n 'AREA_CHART',\n 'BAR_CHART',\n 'FUNNEL',\n 'HEATMAP',\n 'KPI',\n 'LINE_CHART',\n 'PIE_CHART',\n 'RETENTION',\n 'TABLE',\n ]);\n\n return accessibleTypes.has(type.toUpperCase());\n}\n\n/**\n * Normalizes a widget type for API calls.\n * If the type is already federation-accessible, returns it uppercase.\n * Otherwise, maps it to the closest federation-accessible type.\n */\nexport function normalizeWidgetTypeForApi(type: string): FederationWidgetType {\n // Check if already a federation type (case-insensitive)\n if (isFederationAccessible(type)) {\n return type.toUpperCase() as FederationWidgetType;\n }\n\n // Otherwise, map from internal type\n return mapToFederationWidgetType(type as InternalWidgetType);\n}\n"],"mappings":";AAwBA,SAAgB,EAA0B,GAAwD;AA0ChG,QAzCkE;EAEhE,qBAAqB;EACrB,aAAa;EAGb,OAAO;EACP,cAAc;EAGd,OAAO;EACP,aAAa;EAGb,OAAO;EACP,UAAU;EAGV,MAAM;EAGN,MAAM;EAGN,MAAM;EACN,QAAQ;EAGR,KAAK;EACL,SAAS;EACT,UAAU;EACV,QAAQ;EACR,UAAU;EAGV,WAAW;EAGX,QAAQ;EACT,CAEc,MAAiB;;AAWlC,SAAgB,EAA4B,GAA4C;AAgBtF,QAZ2D;EACzD,KAAK;EACL,YAAY;EACZ,WAAW;EACX,YAAY;EACZ,WAAW;EACX,OAAO;EACP,QAAQ;EACR,SAAS;EACT,WAAW;EACZ,CAZsB,EAAe,aAAa,KAcV;;AAO3C,SAAgB,EAAuB,GAAuB;AAa5D,QAZwB,IAAI,IAAI;EAC9B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC,CAEqB,IAAI,EAAK,aAAa,CAAC;;AAQhD,SAAgB,EAA0B,GAAoC;AAO5E,QALI,EAAuB,EAAK,GACvB,EAAK,aAAa,GAIpB,EAA0B,EAA2B"}
|