@burdenoff/microfe-bigconsole 2026.727.4 → 2026.729.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"PivotTableWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/pivot-table/PivotTableWidget.tsx"],"sourcesContent":["/**\n * PivotTableWidget Component\n *\n * Interactive pivot table for data analysis with rows, columns, and aggregations.\n * v2.0 spec compliant widget type.\n *\n * In edit mode it exposes a drag-and-drop field shelf: fields can be dragged\n * between the Rows / Columns / Values / Available zones, the pivot recomputes\n * live, and the new layout is persisted to widget config so it survives reload.\n */\n\nimport type { JSX } from 'react';\nimport { type FC, memo, useMemo, useState, useCallback } from 'react';\nimport type { Widget, PivotTableConfig as PivotTableConfigType, PivotValueField } from '../../../types';\nimport { ChevronDown, ChevronRight, Settings, GripVertical } from 'lucide-react';\nimport { useIsEditMode } from '../../../store';\nimport { useWidgetOperations } from '../../../hooks';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n// Re-export types from main types file\nexport type PivotTableConfig = PivotTableConfigType;\nexport type ValueField = PivotValueField;\n\nexport interface PivotTableWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Raw data array */\n data?: Record<string, unknown> | 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 /** Drilldown handler */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\ninterface PivotNode {\n key: string;\n values: Record<string, number>;\n children: Map<string, PivotNode>;\n expanded: boolean;\n depth: number;\n}\n\n/** The arrangement of fields across the pivot's three field zones. */\ninterface PivotLayout {\n rows: string[];\n columns: string[];\n values: ValueField[];\n}\n\n/** Drop targets for the drag-and-drop field shelf. */\ntype PivotZone = 'rows' | 'columns' | 'values' | 'available';\n\nconst AGGREGATIONS: ValueField['aggregation'][] = ['sum', 'count', 'average', 'min', 'max'];\n\n// ============================================================================\n// Aggregation Functions\n// ============================================================================\n\nconst aggregationFunctions: Record<string, (values: number[]) => number> = {\n sum: (values) => values.reduce((a, b) => a + b, 0),\n count: (values) => values.length,\n average: (values) => (values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0),\n min: (values) => (values.length ? Math.min(...values) : 0),\n max: (values) => (values.length ? Math.max(...values) : 0),\n};\n\n// ============================================================================\n// Pivot Engine\n// ============================================================================\n\nfunction buildPivotTree(\n data: Record<string, unknown>[],\n rowFields: string[],\n valueFields: ValueField[],\n depth: number = 0\n): PivotNode {\n const root: PivotNode = {\n key: 'root',\n values: {},\n children: new Map(),\n expanded: true,\n depth,\n };\n\n if (rowFields.length === 0) {\n // Leaf level - aggregate values\n for (const vf of valueFields) {\n const values = data.map((row) => Number(row[vf.field]) || 0).filter((v) => !isNaN(v));\n root.values[vf.field] = aggregationFunctions[vf.aggregation](values);\n }\n return root;\n }\n\n const currentField = rowFields[0];\n const remainingFields = rowFields.slice(1);\n\n // Group data by current field\n const groups = new Map<string, Record<string, unknown>[]>();\n for (const row of data) {\n const key = String(row[currentField] ?? 'Unknown');\n if (!groups.has(key)) {\n groups.set(key, []);\n }\n groups.get(key)!.push(row);\n }\n\n // Build children recursively\n for (const [key, groupData] of groups) {\n const childNode = buildPivotTree(groupData, remainingFields, valueFields, depth + 1);\n childNode.key = key;\n root.children.set(key, childNode);\n }\n\n // Calculate aggregates for this level\n for (const vf of valueFields) {\n const values = data.map((row) => Number(row[vf.field]) || 0).filter((v) => !isNaN(v));\n root.values[vf.field] = aggregationFunctions[vf.aggregation](values);\n }\n\n return root;\n}\n\nfunction getColumnValues(data: Record<string, unknown>[], columnFields: string[]): string[] {\n if (columnFields.length === 0) return ['Total'];\n\n const columnField = columnFields[0];\n const uniqueValues = new Set<string>();\n for (const row of data) {\n uniqueValues.add(String(row[columnField] ?? 'Unknown'));\n }\n return Array.from(uniqueValues).sort();\n}\n\nfunction getValuesByColumn(\n data: Record<string, unknown>[],\n columnFields: string[],\n valueFields: ValueField[],\n rowFilter: Record<string, string>\n): Map<string, Record<string, number>> {\n // Filter data by row values\n let filteredData = data;\n for (const [field, value] of Object.entries(rowFilter)) {\n filteredData = filteredData.filter((row) => String(row[field]) === value);\n }\n\n const result = new Map<string, Record<string, number>>();\n\n if (columnFields.length === 0) {\n // No column grouping - just aggregate\n const values: Record<string, number> = {};\n for (const vf of valueFields) {\n const nums = filteredData.map((row) => Number(row[vf.field]) || 0).filter((v) => !isNaN(v));\n values[vf.field] = aggregationFunctions[vf.aggregation](nums);\n }\n result.set('Total', values);\n return result;\n }\n\n const columnField = columnFields[0];\n const columnValues = getColumnValues(data, columnFields);\n\n for (const colValue of columnValues) {\n const colData = filteredData.filter((row) => String(row[columnField]) === colValue);\n const values: Record<string, number> = {};\n for (const vf of valueFields) {\n const nums = colData.map((row) => Number(row[vf.field]) || 0).filter((v) => !isNaN(v));\n values[vf.field] = aggregationFunctions[vf.aggregation](nums);\n }\n result.set(colValue, values);\n }\n\n return result;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const PivotTableWidget: FC<PivotTableWidgetProps> = memo(function PivotTableWidget({\n widget,\n data: rawData = [],\n onDrilldown,\n}) {\n // Normalize data to array format\n const data = useMemo(() => (Array.isArray(rawData) ? rawData : []), [rawData]);\n const config: PivotTableConfig = useMemo(() => {\n const defaultConfig: PivotTableConfig = {\n rows: [],\n columns: [],\n values: [],\n showRowTotals: true,\n showColumnTotals: true,\n showGrandTotal: true,\n expandable: true,\n numberFormat: 'number',\n decimalPlaces: 0,\n };\n return widget.config ? { ...defaultConfig, ...(widget.config as Partial<PivotTableConfig>) } : defaultConfig;\n }, [widget.config]);\n\n const isEditMode = useIsEditMode();\n const { updateWidget } = useWidgetOperations(undefined, undefined, { skipFetch: true });\n\n // Field arrangement is local state so dragging recomputes the pivot instantly.\n // Seeded from config and kept in sync with it: if config changes externally\n // (properties panel, undo, another editor), we re-seed during render via the\n // \"adjust state on prop change\" pattern. Our own persist round-trip re-seeds\n // to the identical layout, so it can't loop.\n const configSig = JSON.stringify({ rows: config.rows, columns: config.columns, values: config.values });\n const [layout, setLayout] = useState<PivotLayout>({\n rows: config.rows,\n columns: config.columns,\n values: config.values,\n });\n const [seededSig, setSeededSig] = useState(configSig);\n if (configSig !== seededSig) {\n // Config changed externally → re-seed during render (React's documented\n // \"adjusting state on prop change\" pattern). Our own persist re-seeds to the\n // identical layout, so this can't loop.\n setSeededSig(configSig);\n setLayout({ rows: config.rows, columns: config.columns, values: config.values });\n }\n\n const { rows: rowFields, columns: columnFields, values: valueFields } = layout;\n\n // Persist the layout to config (edit mode only — viewers explore locally).\n const persistLayout = useCallback(\n (next: PivotLayout) => {\n if (!isEditMode) return;\n void updateWidget({\n id: widget.id,\n config: {\n ...(widget.config as Record<string, unknown> | undefined),\n rows: next.rows,\n columns: next.columns,\n values: next.values,\n },\n });\n },\n [isEditMode, updateWidget, widget.id, widget.config]\n );\n\n const applyLayout = useCallback(\n (next: PivotLayout) => {\n setLayout(next);\n persistLayout(next);\n },\n [persistLayout]\n );\n\n const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set(['root']));\n\n // Build pivot structure\n const pivotTree = useMemo(() => {\n return buildPivotTree(data, rowFields, valueFields);\n }, [data, rowFields, valueFields]);\n\n const columnValues = useMemo(() => {\n return getColumnValues(data, columnFields);\n }, [data, columnFields]);\n\n // Grand totals per column for the footer — computed once (was previously\n // recomputed for every column value inside the footer's render loop).\n const footerColumnData = useMemo(\n () => (config.showColumnTotals ? getValuesByColumn(data, columnFields, valueFields, {}) : null),\n [config.showColumnTotals, data, columnFields, valueFields]\n );\n\n // All fields present in the data, and those not yet placed in a zone.\n const allFields = useMemo(() => (data.length > 0 ? Object.keys(data[0]) : []), [data]);\n const availableFields = useMemo(() => {\n const used = new Set<string>([...rowFields, ...columnFields, ...valueFields.map((v) => v.field)]);\n return allFields.filter((f) => !used.has(f));\n }, [allFields, rowFields, columnFields, valueFields]);\n\n // Move a field from one zone to another and recompute + persist.\n const moveField = useCallback(\n (field: string, from: PivotZone, to: PivotZone) => {\n if (from === to) return;\n const next: PivotLayout = {\n rows: from === 'rows' ? rowFields.filter((f) => f !== field) : [...rowFields],\n columns: from === 'columns' ? columnFields.filter((f) => f !== field) : [...columnFields],\n values: from === 'values' ? valueFields.filter((v) => v.field !== field) : [...valueFields],\n };\n if (to === 'rows' && !next.rows.includes(field)) next.rows.push(field);\n else if (to === 'columns' && !next.columns.includes(field)) next.columns.push(field);\n else if (to === 'values' && !next.values.some((v) => v.field === field)) {\n next.values.push({ field, aggregation: 'sum' });\n }\n applyLayout(next);\n },\n [rowFields, columnFields, valueFields, applyLayout]\n );\n\n const changeAggregation = useCallback(\n (field: string, aggregation: ValueField['aggregation']) => {\n applyLayout({\n rows: [...rowFields],\n columns: [...columnFields],\n values: valueFields.map((v) => (v.field === field ? { ...v, aggregation } : v)),\n });\n },\n [rowFields, columnFields, valueFields, applyLayout]\n );\n\n // Format number based on config\n const formatValue = useCallback(\n (value: number): string => {\n if (isNaN(value)) return '-';\n\n switch (config.numberFormat) {\n case 'currency':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: config.decimalPlaces,\n maximumFractionDigits: config.decimalPlaces,\n }).format(value);\n case 'percentage':\n return new Intl.NumberFormat('en-US', {\n style: 'percent',\n minimumFractionDigits: config.decimalPlaces,\n maximumFractionDigits: config.decimalPlaces,\n }).format(value / 100);\n default:\n return new Intl.NumberFormat('en-US', {\n minimumFractionDigits: config.decimalPlaces,\n maximumFractionDigits: config.decimalPlaces,\n }).format(value);\n }\n },\n [config.numberFormat, config.decimalPlaces]\n );\n\n // Toggle row expansion\n const toggleRow = useCallback((rowPath: string) => {\n setExpandedRows((prev) => {\n const next = new Set(prev);\n if (next.has(rowPath)) {\n next.delete(rowPath);\n } else {\n next.add(rowPath);\n }\n return next;\n });\n }, []);\n\n // Handle cell click for drilldown\n const handleCellClick = useCallback(\n (rowFilter: Record<string, string>, columnValue: string) => {\n if (onDrilldown) {\n onDrilldown({\n rowFilter,\n columnValue,\n type: 'pivot_cell_click',\n });\n }\n },\n [onDrilldown]\n );\n\n // Render row recursively\n const renderRows = useCallback(\n (node: PivotNode, rowFilter: Record<string, string>, parentPath: string, depth: number): JSX.Element[] => {\n const rows: JSX.Element[] = [];\n const hasChildren = node.children.size > 0;\n const isExpanded = expandedRows.has(parentPath);\n const rowField = rowFields[depth - 1];\n\n if (depth > 0) {\n const columnData = getValuesByColumn(data, columnFields, valueFields, rowFilter);\n\n rows.push(\n <tr key={parentPath} className=\"hover:bg-bg-sunken/50 transition-colors\">\n {/* Row header */}\n <td\n className=\"sticky left-0 bg-bg-surface border-r border-border-default px-3 py-2 font-medium text-sm\"\n style={{ paddingLeft: `${depth * 16 + 8}px` }}\n >\n <div className=\"flex items-center gap-1\">\n {config.expandable && hasChildren && (\n <button\n type=\"button\"\n onClick={() => toggleRow(parentPath)}\n className=\"p-0.5 hover:bg-bg-sunken rounded\"\n >\n {isExpanded ? (\n <ChevronDown className=\"w-4 h-4 text-text-secondary\" />\n ) : (\n <ChevronRight className=\"w-4 h-4 text-text-secondary\" />\n )}\n </button>\n )}\n {!hasChildren && config.expandable && <span className=\"w-5\" />}\n <span>{node.key}</span>\n </div>\n </td>\n\n {/* Value cells */}\n {columnValues.map((colValue) => {\n const cellValues = columnData.get(colValue) || {};\n return valueFields.map((vf) => (\n <td\n key={`${colValue}-${vf.field}`}\n className=\"px-3 py-2 text-sm text-right tabular-nums cursor-pointer hover:bg-action-primary-bg/10\"\n onClick={() => handleCellClick(rowFilter, colValue)}\n >\n {formatValue(cellValues[vf.field] || 0)}\n </td>\n ));\n })}\n\n {/* Row total */}\n {config.showRowTotals &&\n valueFields.map((vf) => (\n <td\n key={`total-${vf.field}`}\n className=\"px-3 py-2 text-sm text-right tabular-nums font-medium bg-bg-sunken/30\"\n >\n {formatValue(node.values[vf.field] || 0)}\n </td>\n ))}\n </tr>\n );\n }\n\n // Render children if expanded\n if (hasChildren && (isExpanded || depth === 0)) {\n const sortedKeys = Array.from(node.children.keys()).sort();\n for (const childKey of sortedKeys) {\n const childNode = node.children.get(childKey)!;\n const childPath = `${parentPath}/${childKey}`;\n const childFilter = rowField ? { ...rowFilter, [rowField]: childKey } : rowFilter;\n rows.push(...renderRows(childNode, childFilter, childPath, depth + 1));\n }\n }\n\n return rows;\n },\n [\n data,\n config,\n rowFields,\n columnFields,\n valueFields,\n columnValues,\n expandedRows,\n formatValue,\n handleCellClick,\n toggleRow,\n ]\n );\n\n const hasLayout = rowFields.length > 0 || valueFields.length > 0;\n\n // No data state\n if (data.length === 0) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full p-8 text-text-secondary\">\n <Settings className=\"w-12 h-12 mb-4 opacity-50\" />\n <p className=\"text-sm\">No data available</p>\n <p className=\"text-xs mt-1\">Configure data source to populate pivot table</p>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full overflow-hidden\">\n {/* Drag-and-drop field shelf (edit mode) */}\n {isEditMode && (\n <PivotFieldShelf\n rowFields={rowFields}\n columnFields={columnFields}\n valueFields={valueFields}\n availableFields={availableFields}\n onMoveField={moveField}\n onChangeAggregation={changeAggregation}\n />\n )}\n\n {!hasLayout ? (\n <div className=\"flex flex-col items-center justify-center flex-1 p-8 text-text-secondary\">\n <Settings className=\"w-12 h-12 mb-4 opacity-50\" />\n <p className=\"text-sm\">Configure Pivot Table</p>\n <p className=\"text-xs mt-1\">\n {isEditMode\n ? 'Drag fields into the Rows and Values zones above'\n : 'Add row fields and value fields in widget settings'}\n </p>\n </div>\n ) : (\n <div className=\"flex-1 overflow-auto\">\n <table className=\"w-full border-collapse\">\n {/* Header */}\n <thead className=\"sticky top-0 z-10\">\n {/* Column headers row */}\n <tr className=\"bg-bg-sunken\">\n <th className=\"sticky left-0 bg-bg-sunken border-r border-b border-border-default px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-text-secondary\">\n {rowFields.length > 0 ? rowFields.map((r) => r.replace(/_/g, ' ')).join(' / ') : 'Values'}\n </th>\n\n {/* Column value headers */}\n {columnValues.map((colValue) => (\n <th\n key={colValue}\n colSpan={valueFields.length || 1}\n className=\"border-b border-border-default px-3 py-2 text-center text-xs font-semibold uppercase tracking-wider text-text-secondary\"\n >\n {colValue}\n </th>\n ))}\n\n {/* Total header */}\n {config.showRowTotals && (\n <th\n colSpan={valueFields.length || 1}\n className=\"border-b border-border-default px-3 py-2 text-center text-xs font-semibold uppercase tracking-wider text-text-secondary bg-bg-sunken/50\"\n >\n Total\n </th>\n )}\n </tr>\n\n {/* Value field headers row (if multiple values) */}\n {valueFields.length > 1 && (\n <tr className=\"bg-bg-surface\">\n <th className=\"sticky left-0 bg-bg-surface border-r border-b border-border-default\" />\n {columnValues.map((colValue) =>\n valueFields.map((vf) => (\n <th\n key={`${colValue}-${vf.field}`}\n className=\"border-b border-border-default px-3 py-1 text-center text-xs text-text-secondary\"\n >\n {vf.label || vf.field.replace(/_/g, ' ')}\n </th>\n ))\n )}\n {config.showRowTotals &&\n valueFields.map((vf) => (\n <th\n key={`total-${vf.field}`}\n className=\"border-b border-border-default px-3 py-1 text-center text-xs text-text-secondary bg-bg-sunken/30\"\n >\n {vf.label || vf.field.replace(/_/g, ' ')}\n </th>\n ))}\n </tr>\n )}\n </thead>\n\n {/* Body */}\n <tbody className=\"divide-y divide-border-default\">{renderRows(pivotTree, {}, 'root', 0)}</tbody>\n\n {/* Footer with column totals */}\n {config.showColumnTotals && (\n <tfoot>\n <tr className=\"bg-bg-sunken/50 font-medium\">\n <td className=\"sticky left-0 bg-bg-sunken/50 border-r border-t border-border-default px-3 py-2 text-sm\">\n Total\n </td>\n {columnValues.map((colValue) => {\n const cellValues = footerColumnData?.get(colValue) || {};\n return valueFields.map((vf) => (\n <td\n key={`footer-${colValue}-${vf.field}`}\n className=\"border-t border-border-default px-3 py-2 text-sm text-right tabular-nums\"\n >\n {formatValue(cellValues[vf.field] || 0)}\n </td>\n ));\n })}\n {config.showRowTotals &&\n config.showGrandTotal &&\n valueFields.map((vf) => (\n <td\n key={`grand-${vf.field}`}\n className=\"border-t border-border-default px-3 py-2 text-sm text-right tabular-nums font-bold bg-bg-sunken\"\n >\n {formatValue(pivotTree.values[vf.field] || 0)}\n </td>\n ))}\n </tr>\n </tfoot>\n )}\n </table>\n </div>\n )}\n </div>\n );\n});\n\n// ============================================================================\n// Field shelf (drag-and-drop zones)\n// ============================================================================\n\ninterface PivotFieldShelfProps {\n rowFields: string[];\n columnFields: string[];\n valueFields: ValueField[];\n availableFields: string[];\n onMoveField: (field: string, from: PivotZone, to: PivotZone) => void;\n onChangeAggregation: (field: string, aggregation: ValueField['aggregation']) => void;\n}\n\nconst DND_MIME = 'application/x-pivot-field';\n\nconst PivotFieldShelf: FC<PivotFieldShelfProps> = ({\n rowFields,\n columnFields,\n valueFields,\n availableFields,\n onMoveField,\n onChangeAggregation,\n}) => {\n const [dragOverZone, setDragOverZone] = useState<PivotZone | null>(null);\n\n const handleDrop = (zone: PivotZone) => (e: React.DragEvent) => {\n e.preventDefault();\n setDragOverZone(null);\n const raw = e.dataTransfer.getData(DND_MIME) || e.dataTransfer.getData('text/plain');\n if (!raw) return;\n try {\n const { field, from } = JSON.parse(raw) as { field: string; from: PivotZone };\n if (field) onMoveField(field, from, zone);\n } catch {\n // Ignore malformed drag payloads.\n }\n };\n\n const chip = (field: string, from: PivotZone, vf?: ValueField) => (\n <span\n key={field}\n draggable\n onDragStart={(e) => {\n const payload = JSON.stringify({ field, from });\n e.dataTransfer.setData(DND_MIME, payload);\n e.dataTransfer.setData('text/plain', payload);\n e.dataTransfer.effectAllowed = 'move';\n }}\n className=\"inline-flex items-center gap-1 px-2 py-1 rounded-md bg-bg-surface border border-border-default text-xs text-text-primary cursor-grab\"\n >\n <GripVertical className=\"w-3 h-3 text-text-tertiary\" aria-hidden=\"true\" />\n <span>{(vf?.label || field).replace(/_/g, ' ')}</span>\n {vf && (\n <select\n value={vf.aggregation}\n onChange={(e) => onChangeAggregation(field, e.target.value as ValueField['aggregation'])}\n onClick={(e) => e.stopPropagation()}\n aria-label={`Aggregation for ${field}`}\n className=\"ml-1 bg-bg-sunken text-text-secondary rounded px-1 py-0.5 text-[10px] uppercase\"\n >\n {AGGREGATIONS.map((agg) => (\n <option key={agg} value={agg}>\n {agg}\n </option>\n ))}\n </select>\n )}\n </span>\n );\n\n const zone = (label: string, name: PivotZone, children: JSX.Element[] | JSX.Element) => (\n <div\n onDragOver={(e) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n if (dragOverZone !== name) setDragOverZone(name);\n }}\n onDragLeave={() => setDragOverZone((z) => (z === name ? null : z))}\n onDrop={handleDrop(name)}\n className={`flex-1 min-w-[8rem] rounded-md border p-2 transition-colors ${\n dragOverZone === name\n ? 'border-action-primary-bg bg-action-primary-bg/5'\n : 'border-border-default bg-bg-sunken/30'\n }`}\n >\n <div className=\"text-[10px] font-semibold uppercase tracking-wider text-text-tertiary mb-1.5\">{label}</div>\n <div className=\"flex flex-wrap gap-1.5 min-h-[1.75rem]\">{children}</div>\n </div>\n );\n\n return (\n <div className=\"flex flex-wrap gap-2 p-2 border-b border-border-default bg-bg-surface\">\n {zone(\n 'Rows',\n 'rows',\n rowFields.map((f) => chip(f, 'rows'))\n )}\n {zone(\n 'Columns',\n 'columns',\n columnFields.map((f) => chip(f, 'columns'))\n )}\n {zone(\n 'Values',\n 'values',\n valueFields.map((vf) => chip(vf.field, 'values', vf))\n )}\n {zone(\n 'Available',\n 'available',\n availableFields.length > 0 ? (\n availableFields.map((f) => chip(f, 'available'))\n ) : (\n <span className=\"text-[11px] text-text-tertiary italic\">All fields in use</span>\n )\n )}\n </div>\n );\n};\n\nexport default PivotTableWidget;\n"],"mappings":"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"}
1
+ {"version":3,"file":"PivotTableWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/pivot-table/PivotTableWidget.tsx"],"sourcesContent":["/**\n * PivotTableWidget Component\n *\n * Interactive pivot table for data analysis with rows, columns, and aggregations.\n * v2.0 spec compliant widget type.\n *\n * In edit mode it exposes a drag-and-drop field shelf: fields can be dragged\n * between the Rows / Columns / Values / Available zones, the pivot recomputes\n * live, and the new layout is persisted to widget config so it survives reload.\n */\n\nimport type { JSX } from 'react';\nimport { type FC, memo, useMemo, useState, useCallback } from 'react';\nimport type { Widget, PivotTableConfig as PivotTableConfigType, PivotValueField } from '../../../types';\nimport { ChevronDown, ChevronRight, Settings, GripVertical } from 'lucide-react';\nimport { useIsEditMode } from '../../../store';\nimport { useWidgetOperations } from '../../../hooks';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n// Re-export types from main types file\nexport type PivotTableConfig = PivotTableConfigType;\nexport type ValueField = PivotValueField;\n\nexport interface PivotTableWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Raw data array */\n data?: Record<string, unknown> | 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 /** Drilldown handler */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\ninterface PivotNode {\n key: string;\n values: Record<string, number>;\n children: Map<string, PivotNode>;\n expanded: boolean;\n depth: number;\n}\n\n/** The arrangement of fields across the pivot's three field zones. */\ninterface PivotLayout {\n rows: string[];\n columns: string[];\n values: ValueField[];\n}\n\n/** Drop targets for the drag-and-drop field shelf. */\ntype PivotZone = 'rows' | 'columns' | 'values' | 'available';\n\nconst AGGREGATIONS: ValueField['aggregation'][] = ['sum', 'count', 'average', 'min', 'max'];\n\n// ============================================================================\n// Aggregation Functions\n// ============================================================================\n\nconst aggregationFunctions: Record<string, (values: number[]) => number> = {\n sum: (values) => values.reduce((a, b) => a + b, 0),\n count: (values) => values.length,\n average: (values) => (values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0),\n min: (values) => (values.length ? Math.min(...values) : 0),\n max: (values) => (values.length ? Math.max(...values) : 0),\n};\n\n// ============================================================================\n// Pivot Engine\n// ============================================================================\n\nfunction buildPivotTree(\n data: Record<string, unknown>[],\n rowFields: string[],\n valueFields: ValueField[],\n depth: number = 0\n): PivotNode {\n const root: PivotNode = {\n key: 'root',\n values: {},\n children: new Map(),\n expanded: true,\n depth,\n };\n\n if (rowFields.length === 0) {\n // Leaf level - aggregate values\n for (const vf of valueFields) {\n const values = data.map((row) => Number(row[vf.field]) || 0).filter((v) => !isNaN(v));\n root.values[vf.field] = aggregationFunctions[vf.aggregation](values);\n }\n return root;\n }\n\n const currentField = rowFields[0];\n const remainingFields = rowFields.slice(1);\n\n // Group data by current field\n const groups = new Map<string, Record<string, unknown>[]>();\n for (const row of data) {\n const key = String(row[currentField] ?? 'Unknown');\n if (!groups.has(key)) {\n groups.set(key, []);\n }\n groups.get(key)!.push(row);\n }\n\n // Build children recursively\n for (const [key, groupData] of groups) {\n const childNode = buildPivotTree(groupData, remainingFields, valueFields, depth + 1);\n childNode.key = key;\n root.children.set(key, childNode);\n }\n\n // Calculate aggregates for this level\n for (const vf of valueFields) {\n const values = data.map((row) => Number(row[vf.field]) || 0).filter((v) => !isNaN(v));\n root.values[vf.field] = aggregationFunctions[vf.aggregation](values);\n }\n\n return root;\n}\n\nfunction getColumnValues(data: Record<string, unknown>[], columnFields: string[]): string[] {\n if (columnFields.length === 0) return ['Total'];\n\n const columnField = columnFields[0];\n const uniqueValues = new Set<string>();\n for (const row of data) {\n uniqueValues.add(String(row[columnField] ?? 'Unknown'));\n }\n return Array.from(uniqueValues).sort();\n}\n\nfunction getValuesByColumn(\n data: Record<string, unknown>[],\n columnFields: string[],\n valueFields: ValueField[],\n rowFilter: Record<string, string>\n): Map<string, Record<string, number>> {\n // Filter data by row values\n let filteredData = data;\n for (const [field, value] of Object.entries(rowFilter)) {\n filteredData = filteredData.filter((row) => String(row[field]) === value);\n }\n\n const result = new Map<string, Record<string, number>>();\n\n if (columnFields.length === 0) {\n // No column grouping - just aggregate\n const values: Record<string, number> = {};\n for (const vf of valueFields) {\n const nums = filteredData.map((row) => Number(row[vf.field]) || 0).filter((v) => !isNaN(v));\n values[vf.field] = aggregationFunctions[vf.aggregation](nums);\n }\n result.set('Total', values);\n return result;\n }\n\n const columnField = columnFields[0];\n const columnValues = getColumnValues(data, columnFields);\n\n for (const colValue of columnValues) {\n const colData = filteredData.filter((row) => String(row[columnField]) === colValue);\n const values: Record<string, number> = {};\n for (const vf of valueFields) {\n const nums = colData.map((row) => Number(row[vf.field]) || 0).filter((v) => !isNaN(v));\n values[vf.field] = aggregationFunctions[vf.aggregation](nums);\n }\n result.set(colValue, values);\n }\n\n return result;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const PivotTableWidget: FC<PivotTableWidgetProps> = memo(function PivotTableWidget({\n widget,\n data: rawData = [],\n onDrilldown,\n}) {\n // Normalize data to array format\n const data = useMemo(() => (Array.isArray(rawData) ? rawData : []), [rawData]);\n const config: PivotTableConfig = useMemo(() => {\n const defaultConfig: PivotTableConfig = {\n rows: [],\n columns: [],\n values: [],\n showRowTotals: true,\n showColumnTotals: true,\n showGrandTotal: true,\n expandable: true,\n numberFormat: 'number',\n decimalPlaces: 0,\n };\n return widget.config ? { ...defaultConfig, ...(widget.config as Partial<PivotTableConfig>) } : defaultConfig;\n }, [widget.config]);\n\n const isEditMode = useIsEditMode();\n const { updateWidget } = useWidgetOperations(undefined, undefined, { skipFetch: true });\n\n // Field arrangement is local state so dragging recomputes the pivot instantly.\n // Seeded from config and kept in sync with it: if config changes externally\n // (properties panel, undo, another editor), we re-seed during render via the\n // \"adjust state on prop change\" pattern. Our own persist round-trip re-seeds\n // to the identical layout, so it can't loop.\n const configSig = JSON.stringify({ rows: config.rows, columns: config.columns, values: config.values });\n const [layout, setLayout] = useState<PivotLayout>({\n rows: config.rows,\n columns: config.columns,\n values: config.values,\n });\n const [seededSig, setSeededSig] = useState(configSig);\n if (configSig !== seededSig) {\n // Config changed externally → re-seed during render (React's documented\n // \"adjusting state on prop change\" pattern). Our own persist re-seeds to the\n // identical layout, so this can't loop.\n setSeededSig(configSig);\n setLayout({ rows: config.rows, columns: config.columns, values: config.values });\n }\n\n const { rows: rowFields, columns: columnFields, values: valueFields } = layout;\n\n // Persist the layout to config (edit mode only — viewers explore locally).\n const persistLayout = useCallback(\n (next: PivotLayout) => {\n if (!isEditMode) return;\n void updateWidget({\n id: widget.id,\n config: {\n ...(widget.config as Record<string, unknown> | undefined),\n rows: next.rows,\n columns: next.columns,\n values: next.values,\n },\n });\n },\n [isEditMode, updateWidget, widget.id, widget.config]\n );\n\n const applyLayout = useCallback(\n (next: PivotLayout) => {\n setLayout(next);\n persistLayout(next);\n },\n [persistLayout]\n );\n\n const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set(['root']));\n\n // Build pivot structure\n const pivotTree = useMemo(() => {\n return buildPivotTree(data, rowFields, valueFields);\n }, [data, rowFields, valueFields]);\n\n const columnValues = useMemo(() => {\n return getColumnValues(data, columnFields);\n }, [data, columnFields]);\n\n // Grand totals per column for the footer — computed once (was previously\n // recomputed for every column value inside the footer's render loop).\n const footerColumnData = useMemo(\n () => (config.showColumnTotals ? getValuesByColumn(data, columnFields, valueFields, {}) : null),\n [config.showColumnTotals, data, columnFields, valueFields]\n );\n\n // All fields present in the data, and those not yet placed in a zone.\n const allFields = useMemo(() => (data.length > 0 ? Object.keys(data[0]) : []), [data]);\n const availableFields = useMemo(() => {\n const used = new Set<string>([...rowFields, ...columnFields, ...valueFields.map((v) => v.field)]);\n return allFields.filter((f) => !used.has(f));\n }, [allFields, rowFields, columnFields, valueFields]);\n\n // Move a field from one zone to another and recompute + persist.\n const moveField = useCallback(\n (field: string, from: PivotZone, to: PivotZone) => {\n if (from === to) return;\n const next: PivotLayout = {\n rows: from === 'rows' ? rowFields.filter((f) => f !== field) : [...rowFields],\n columns: from === 'columns' ? columnFields.filter((f) => f !== field) : [...columnFields],\n values: from === 'values' ? valueFields.filter((v) => v.field !== field) : [...valueFields],\n };\n if (to === 'rows' && !next.rows.includes(field)) next.rows.push(field);\n else if (to === 'columns' && !next.columns.includes(field)) next.columns.push(field);\n else if (to === 'values' && !next.values.some((v) => v.field === field)) {\n next.values.push({ field, aggregation: 'sum' });\n }\n applyLayout(next);\n },\n [rowFields, columnFields, valueFields, applyLayout]\n );\n\n const changeAggregation = useCallback(\n (field: string, aggregation: ValueField['aggregation']) => {\n applyLayout({\n rows: [...rowFields],\n columns: [...columnFields],\n values: valueFields.map((v) => (v.field === field ? { ...v, aggregation } : v)),\n });\n },\n [rowFields, columnFields, valueFields, applyLayout]\n );\n\n // Format number based on config\n const formatValue = useCallback(\n (value: number): string => {\n if (isNaN(value)) return '-';\n\n switch (config.numberFormat) {\n case 'currency':\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n minimumFractionDigits: config.decimalPlaces,\n maximumFractionDigits: config.decimalPlaces,\n }).format(value);\n case 'percentage':\n return new Intl.NumberFormat('en-US', {\n style: 'percent',\n minimumFractionDigits: config.decimalPlaces,\n maximumFractionDigits: config.decimalPlaces,\n }).format(value / 100);\n default:\n return new Intl.NumberFormat('en-US', {\n minimumFractionDigits: config.decimalPlaces,\n maximumFractionDigits: config.decimalPlaces,\n }).format(value);\n }\n },\n [config.numberFormat, config.decimalPlaces]\n );\n\n // Toggle row expansion\n const toggleRow = useCallback((rowPath: string) => {\n setExpandedRows((prev) => {\n const next = new Set(prev);\n if (next.has(rowPath)) {\n next.delete(rowPath);\n } else {\n next.add(rowPath);\n }\n return next;\n });\n }, []);\n\n // Handle cell click for drilldown\n const handleCellClick = useCallback(\n (rowFilter: Record<string, string>, columnValue: string) => {\n if (onDrilldown) {\n onDrilldown({\n rowFilter,\n columnValue,\n type: 'pivot_cell_click',\n });\n }\n },\n [onDrilldown]\n );\n\n // Render row recursively\n const renderRows = useCallback(\n (node: PivotNode, rowFilter: Record<string, string>, parentPath: string, depth: number): JSX.Element[] => {\n const rows: JSX.Element[] = [];\n const hasChildren = node.children.size > 0;\n const isExpanded = expandedRows.has(parentPath);\n const rowField = rowFields[depth - 1];\n\n if (depth > 0) {\n const columnData = getValuesByColumn(data, columnFields, valueFields, rowFilter);\n\n rows.push(\n <tr key={parentPath} className=\"hover:bg-bg-sunken/50 transition-colors\">\n {/* Row header */}\n <td\n className=\"sticky left-0 bg-bg-surface border-r border-border-default px-3 py-2 font-medium text-sm\"\n style={{ paddingLeft: `${depth * 16 + 8}px` }}\n >\n <div className=\"flex items-center gap-1\">\n {config.expandable && hasChildren && (\n <button\n type=\"button\"\n onClick={() => toggleRow(parentPath)}\n className=\"p-0.5 hover:bg-bg-sunken rounded\"\n >\n {isExpanded ? (\n <ChevronDown className=\"w-4 h-4 text-text-secondary\" />\n ) : (\n <ChevronRight className=\"w-4 h-4 text-text-secondary\" />\n )}\n </button>\n )}\n {!hasChildren && config.expandable && <span className=\"w-5\" />}\n <span>{node.key}</span>\n </div>\n </td>\n\n {/* Value cells */}\n {columnValues.map((colValue) => {\n const cellValues = columnData.get(colValue) || {};\n return valueFields.map((vf) => (\n <td\n key={`${colValue}-${vf.field}`}\n className=\"px-3 py-2 text-sm text-right tabular-nums cursor-pointer hover:bg-action-primary-bg/10\"\n onClick={() => handleCellClick(rowFilter, colValue)}\n >\n {formatValue(cellValues[vf.field] || 0)}\n </td>\n ));\n })}\n\n {/* Row total */}\n {config.showRowTotals &&\n valueFields.map((vf) => (\n <td\n key={`total-${vf.field}`}\n className=\"px-3 py-2 text-sm text-right tabular-nums font-medium bg-bg-sunken/30\"\n >\n {formatValue(node.values[vf.field] || 0)}\n </td>\n ))}\n </tr>\n );\n }\n\n // Render children if expanded\n if (hasChildren && (isExpanded || depth === 0)) {\n const sortedKeys = Array.from(node.children.keys()).sort();\n for (const childKey of sortedKeys) {\n const childNode = node.children.get(childKey)!;\n const childPath = `${parentPath}/${childKey}`;\n const childFilter = rowField ? { ...rowFilter, [rowField]: childKey } : rowFilter;\n rows.push(...renderRows(childNode, childFilter, childPath, depth + 1));\n }\n }\n\n return rows;\n },\n [\n data,\n config,\n rowFields,\n columnFields,\n valueFields,\n columnValues,\n expandedRows,\n formatValue,\n handleCellClick,\n toggleRow,\n ]\n );\n\n const hasLayout = rowFields.length > 0 || valueFields.length > 0;\n\n // No data state\n if (data.length === 0) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full p-8 text-text-secondary\">\n <Settings className=\"w-12 h-12 mb-4 opacity-50\" />\n <p className=\"text-sm\">No data available</p>\n <p className=\"text-xs mt-1\">Configure data source to populate pivot table</p>\n </div>\n );\n }\n\n return (\n <div className=\"flex flex-col h-full overflow-hidden\">\n {/* Drag-and-drop field shelf (edit mode) */}\n {isEditMode && (\n <PivotFieldShelf\n rowFields={rowFields}\n columnFields={columnFields}\n valueFields={valueFields}\n availableFields={availableFields}\n onMoveField={moveField}\n onChangeAggregation={changeAggregation}\n />\n )}\n\n {!hasLayout ? (\n <div className=\"flex flex-col items-center justify-center flex-1 p-8 text-text-secondary\">\n <Settings className=\"w-12 h-12 mb-4 opacity-50\" />\n <p className=\"text-sm\">Configure Pivot Table</p>\n <p className=\"text-xs mt-1\">\n {isEditMode\n ? 'Drag fields into the Rows and Values zones above'\n : 'Add row fields and value fields in widget settings'}\n </p>\n </div>\n ) : (\n <div className=\"flex-1 overflow-auto\">\n <table className=\"w-full border-collapse\">\n {/* Header */}\n <thead className=\"sticky top-0 z-10\">\n {/* Column headers row */}\n <tr className=\"bg-bg-sunken\">\n <th className=\"sticky left-0 bg-bg-sunken border-r border-b border-border-default px-3 py-2 text-left text-xs font-semibold uppercase tracking-wider text-text-secondary\">\n {rowFields.length > 0 ? rowFields.map((r) => r.replace(/_/g, ' ')).join(' / ') : 'Values'}\n </th>\n\n {/* Column value headers */}\n {columnValues.map((colValue) => (\n <th\n key={colValue}\n colSpan={valueFields.length || 1}\n className=\"border-b border-border-default px-3 py-2 text-center text-xs font-semibold uppercase tracking-wider text-text-secondary\"\n >\n {colValue}\n </th>\n ))}\n\n {/* Total header */}\n {config.showRowTotals && (\n <th\n colSpan={valueFields.length || 1}\n className=\"border-b border-border-default px-3 py-2 text-center text-xs font-semibold uppercase tracking-wider text-text-secondary bg-bg-sunken/50\"\n >\n Total\n </th>\n )}\n </tr>\n\n {/* Value field headers row (if multiple values) */}\n {valueFields.length > 1 && (\n <tr className=\"bg-bg-surface\">\n <th className=\"sticky left-0 bg-bg-surface border-r border-b border-border-default\" />\n {columnValues.map((colValue) =>\n valueFields.map((vf) => (\n <th\n key={`${colValue}-${vf.field}`}\n className=\"border-b border-border-default px-3 py-1 text-center text-xs text-text-secondary\"\n >\n {vf.label || vf.field.replace(/_/g, ' ')}\n </th>\n ))\n )}\n {config.showRowTotals &&\n valueFields.map((vf) => (\n <th\n key={`total-${vf.field}`}\n className=\"border-b border-border-default px-3 py-1 text-center text-xs text-text-secondary bg-bg-sunken/30\"\n >\n {vf.label || vf.field.replace(/_/g, ' ')}\n </th>\n ))}\n </tr>\n )}\n </thead>\n\n {/* Body */}\n <tbody className=\"divide-y divide-border-default\">{renderRows(pivotTree, {}, 'root', 0)}</tbody>\n\n {/* Footer with column totals */}\n {config.showColumnTotals && (\n <tfoot>\n <tr className=\"bg-bg-sunken/50 font-medium\">\n <td className=\"sticky left-0 bg-bg-sunken/50 border-r border-t border-border-default px-3 py-2 text-sm\">\n Total\n </td>\n {columnValues.map((colValue) => {\n const cellValues = footerColumnData?.get(colValue) || {};\n return valueFields.map((vf) => (\n <td\n key={`footer-${colValue}-${vf.field}`}\n className=\"border-t border-border-default px-3 py-2 text-sm text-right tabular-nums\"\n >\n {formatValue(cellValues[vf.field] || 0)}\n </td>\n ));\n })}\n {config.showRowTotals &&\n config.showGrandTotal &&\n valueFields.map((vf) => (\n <td\n key={`grand-${vf.field}`}\n className=\"border-t border-border-default px-3 py-2 text-sm text-right tabular-nums font-bold bg-bg-sunken\"\n >\n {formatValue(pivotTree.values[vf.field] || 0)}\n </td>\n ))}\n </tr>\n </tfoot>\n )}\n </table>\n </div>\n )}\n </div>\n );\n});\n\n// ============================================================================\n// Field shelf (drag-and-drop zones)\n// ============================================================================\n\ninterface PivotFieldShelfProps {\n rowFields: string[];\n columnFields: string[];\n valueFields: ValueField[];\n availableFields: string[];\n onMoveField: (field: string, from: PivotZone, to: PivotZone) => void;\n onChangeAggregation: (field: string, aggregation: ValueField['aggregation']) => void;\n}\n\nconst DND_MIME = 'application/x-pivot-field';\n\nconst PivotFieldShelf: FC<PivotFieldShelfProps> = ({\n rowFields,\n columnFields,\n valueFields,\n availableFields,\n onMoveField,\n onChangeAggregation,\n}) => {\n const [dragOverZone, setDragOverZone] = useState<PivotZone | null>(null);\n\n const handleDrop = (zone: PivotZone) => (e: React.DragEvent) => {\n e.preventDefault();\n // Isolate the shelf's internal DnD from the dashboard canvas drop zone. The\n // canvas onDragOver forces dropEffect='copy' (for palette widget drops); left\n // to bubble, that conflicts with the chips' effectAllowed='move' and the\n // browser rejects the drop entirely (drop event never fires).\n e.stopPropagation();\n setDragOverZone(null);\n const raw = e.dataTransfer.getData(DND_MIME) || e.dataTransfer.getData('text/plain');\n if (!raw) return;\n try {\n const { field, from } = JSON.parse(raw) as { field: string; from: PivotZone };\n if (field) onMoveField(field, from, zone);\n } catch {\n // Ignore malformed drag payloads.\n }\n };\n\n const chip = (field: string, from: PivotZone, vf?: ValueField) => (\n <span\n key={field}\n draggable\n onDragStart={(e) => {\n const payload = JSON.stringify({ field, from });\n e.dataTransfer.setData(DND_MIME, payload);\n e.dataTransfer.setData('text/plain', payload);\n e.dataTransfer.effectAllowed = 'move';\n }}\n className=\"inline-flex items-center gap-1 px-2 py-1 rounded-md bg-bg-surface border border-border-default text-xs text-text-primary cursor-grab\"\n >\n <GripVertical className=\"w-3 h-3 text-text-tertiary\" aria-hidden=\"true\" />\n <span>{(vf?.label || field).replace(/_/g, ' ')}</span>\n {vf && (\n <select\n value={vf.aggregation}\n onChange={(e) => onChangeAggregation(field, e.target.value as ValueField['aggregation'])}\n onClick={(e) => e.stopPropagation()}\n aria-label={`Aggregation for ${field}`}\n className=\"ml-1 bg-bg-sunken text-text-secondary rounded px-1 py-0.5 text-[10px] uppercase\"\n >\n {AGGREGATIONS.map((agg) => (\n <option key={agg} value={agg}>\n {agg}\n </option>\n ))}\n </select>\n )}\n </span>\n );\n\n const zone = (label: string, name: PivotZone, children: JSX.Element[] | JSX.Element) => (\n <div\n onDragOver={(e) => {\n e.preventDefault();\n // Stop bubbling to the canvas onDragOver, which would override\n // dropEffect to 'copy' and make the browser reject this 'move' drop.\n e.stopPropagation();\n e.dataTransfer.dropEffect = 'move';\n if (dragOverZone !== name) {\n setDragOverZone(name);\n }\n }}\n onDragLeave={() => setDragOverZone((z) => (z === name ? null : z))}\n onDrop={handleDrop(name)}\n className={`flex-1 min-w-[8rem] rounded-md border p-2 transition-colors ${\n dragOverZone === name\n ? 'border-action-primary-bg bg-action-primary-bg/5'\n : 'border-border-default bg-bg-sunken/30'\n }`}\n >\n <div className=\"text-[10px] font-semibold uppercase tracking-wider text-text-tertiary mb-1.5\">{label}</div>\n <div className=\"flex flex-wrap gap-1.5 min-h-[1.75rem]\">{children}</div>\n </div>\n );\n\n return (\n <div className=\"flex flex-wrap gap-2 p-2 border-b border-border-default bg-bg-surface\">\n {zone(\n 'Rows',\n 'rows',\n rowFields.map((f) => chip(f, 'rows'))\n )}\n {zone(\n 'Columns',\n 'columns',\n columnFields.map((f) => chip(f, 'columns'))\n )}\n {zone(\n 'Values',\n 'values',\n valueFields.map((vf) => chip(vf.field, 'values', vf))\n )}\n {zone(\n 'Available',\n 'available',\n availableFields.length > 0 ? (\n availableFields.map((f) => chip(f, 'available'))\n ) : (\n <span className=\"text-[11px] text-text-tertiary italic\">All fields in use</span>\n )\n )}\n </div>\n );\n};\n\nexport default PivotTableWidget;\n"],"mappings":"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"}
@@ -56,7 +56,13 @@ function o(e) {
56
56
  config: e.config ?? {}
57
57
  });
58
58
  }
59
- function s(e, t) {
59
+ function s(e, t, n) {
60
+ let r = { ...e }, i = (e, t) => JSON.stringify(e) === JSON.stringify(t);
61
+ for (let e of Object.keys(n)) i(n[e], t[e]) || (r[e] = n[e]);
62
+ for (let e of Object.keys(t)) e in n || delete r[e];
63
+ return r;
64
+ }
65
+ function c(e, t) {
60
66
  let n = {
61
67
  source: {
62
68
  kind: e.source.kind,
@@ -76,10 +82,10 @@ function s(e, t) {
76
82
  parserId: n.parserId
77
83
  };
78
84
  }
79
- function c(e, t) {
85
+ function l(e, t) {
80
86
  return t.replace(/^\$?\.?/, "").split(".").filter(Boolean).reduce((e, t) => n(e) ? e[t] : void 0, e);
81
87
  }
82
- function l(e, t) {
88
+ function u(e, t) {
83
89
  if (e.source.kind === "DATASINK" && !e.source.ref) return {
84
90
  status: "unbound",
85
91
  missingFields: []
@@ -88,13 +94,13 @@ function l(e, t) {
88
94
  status: "bound",
89
95
  missingFields: []
90
96
  };
91
- let n = Object.entries(e.fields).filter(([, e]) => c(t, e) === void 0).map(([e]) => e);
97
+ let n = Object.entries(e.fields).filter(([, e]) => l(t, e) === void 0).map(([e]) => e);
92
98
  return {
93
99
  status: n.length > 0 ? "field_mismatch" : "bound",
94
100
  missingFields: n
95
101
  };
96
102
  }
97
- function u({ binding: e, loading: t = !1, error: n, preview: r, previewComplete: i = !1 }) {
103
+ function d({ binding: e, loading: t = !1, error: n, preview: r, previewComplete: i = !1 }) {
98
104
  if (t) return {
99
105
  status: "loading",
100
106
  missingFields: []
@@ -103,13 +109,13 @@ function u({ binding: e, loading: t = !1, error: n, preview: r, previewComplete:
103
109
  status: "parser_error",
104
110
  missingFields: []
105
111
  };
106
- let a = l(e);
112
+ let a = u(e);
107
113
  return a.status === "unbound" || !i ? a : r == null || Array.isArray(r) && r.length === 0 ? {
108
114
  status: "no_data",
109
115
  missingFields: []
110
- } : l(e, Array.isArray(r) ? r[0] : r);
116
+ } : u(e, Array.isArray(r) ? r[0] : r);
111
117
  }
112
118
  //#endregion
113
- export { o as getBindingForWidget, a as getWidgetBinding, u as getWidgetBindingStatus, s as serializeWidgetBinding };
119
+ export { o as getBindingForWidget, a as getWidgetBinding, d as getWidgetBindingStatus, s as reconcilePanelConfig, c as serializeWidgetBinding };
114
120
 
115
121
  //# sourceMappingURL=widgetBinding.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"widgetBinding.js","names":[],"sources":["../../../src/bigconsole/utils/widgetBinding.ts"],"sourcesContent":["import type { DataSourceType, Widget, WidgetBinding, WidgetBindingStatus, WidgetBindingValidation } from '../types';\n\nconst SOURCE_KINDS: ReadonlySet<string> = new Set<DataSourceType>([\n 'DATASINK',\n 'REST_API',\n 'GRAPHQL',\n 'SQL',\n 'WEBSOCKET',\n 'MOCK',\n]);\n\nconst FIELD_CONFIG_KEYS = [\n 'labelField',\n 'valueField',\n 'xAxisField',\n 'yAxisField',\n 'categoryField',\n 'seriesField',\n] as const;\n\ninterface LegacyBindingInput {\n dataSinkId?: string;\n parserId?: string;\n config?: Record<string, unknown>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isSourceKind(value: unknown): value is DataSourceType {\n return typeof value === 'string' && SOURCE_KINDS.has(value);\n}\n\nfunction readCanonicalBinding(value: unknown): WidgetBinding | null {\n if (!isRecord(value) || !isRecord(value.source) || !isSourceKind(value.source.kind)) return null;\n\n const fields = isRecord(value.fields)\n ? Object.fromEntries(\n Object.entries(value.fields).filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n )\n : {};\n\n return {\n source: {\n kind: value.source.kind,\n ref: typeof value.source.ref === 'string' ? value.source.ref : undefined,\n },\n parserId: typeof value.parserId === 'string' ? value.parserId : undefined,\n fields,\n };\n}\n\n/** Read canonical binding first, then adapt legacy widget fields without mutating persisted data. */\nexport function getWidgetBinding(input: LegacyBindingInput): WidgetBinding {\n const config = input.config ?? {};\n const canonical = readCanonicalBinding(config.binding);\n if (canonical) return canonical;\n\n const configuredKind = isSourceKind(config.dataSourceType) ? config.dataSourceType : undefined;\n const kind: DataSourceType = input.dataSinkId ? 'DATASINK' : (configuredKind ?? 'DATASINK');\n const fields: Record<string, string> = {};\n\n for (const key of FIELD_CONFIG_KEYS) {\n const value = config[key];\n if (typeof value === 'string' && value.length > 0) fields[key] = value;\n }\n\n return {\n source: { kind, ref: kind === 'DATASINK' ? input.dataSinkId : undefined },\n parserId: input.parserId,\n fields,\n };\n}\n\nexport function getBindingForWidget(widget: Widget): WidgetBinding {\n return getWidgetBinding({\n dataSinkId: widget.dataSinkId ?? undefined,\n parserId: widget.parserId ?? undefined,\n config: (widget.config as Record<string, unknown>) ?? {},\n });\n}\n\n/** Dual-write canonical binding and renderer compatibility fields for one migration release. */\nexport function serializeWidgetBinding(binding: WidgetBinding, config: Record<string, unknown>) {\n const canonical: WidgetBinding = {\n source: { kind: binding.source.kind, ref: binding.source.ref },\n parserId: binding.parserId,\n fields: { ...binding.fields },\n };\n\n return {\n config: {\n ...config,\n binding: canonical,\n dataSourceType: canonical.source.kind,\n ...canonical.fields,\n },\n dataSinkId: canonical.source.kind === 'DATASINK' ? canonical.source.ref : undefined,\n parserId: canonical.parserId,\n };\n}\n\nexport function resolveBindingPath(value: unknown, path: string): unknown {\n return path\n .replace(/^\\$?\\.?/, '')\n .split('.')\n .filter(Boolean)\n .reduce<unknown>((current, segment) => (isRecord(current) ? current[segment] : undefined), value);\n}\n\nexport function validateWidgetBinding(binding: WidgetBinding, sample?: unknown): WidgetBindingValidation {\n const isUnbound = binding.source.kind === 'DATASINK' && !binding.source.ref;\n if (isUnbound) return { status: 'unbound', missingFields: [] };\n if (sample === undefined) return { status: 'bound', missingFields: [] };\n\n const missingFields = Object.entries(binding.fields)\n .filter(([, path]) => resolveBindingPath(sample, path) === undefined)\n .map(([field]) => field);\n\n return { status: missingFields.length > 0 ? 'field_mismatch' : 'bound', missingFields };\n}\n\ninterface BindingStatusInput {\n binding: WidgetBinding;\n loading?: boolean;\n error?: string | null;\n preview?: unknown;\n previewComplete?: boolean;\n}\n\nexport function getWidgetBindingStatus({\n binding,\n loading = false,\n error,\n preview,\n previewComplete = false,\n}: BindingStatusInput): { status: WidgetBindingStatus; missingFields: string[] } {\n if (loading) return { status: 'loading', missingFields: [] };\n if (error) return { status: 'parser_error', missingFields: [] };\n\n const initialValidation = validateWidgetBinding(binding);\n if (initialValidation.status === 'unbound') return initialValidation;\n if (!previewComplete) return initialValidation;\n if (preview == null || (Array.isArray(preview) && preview.length === 0)) {\n return { status: 'no_data', missingFields: [] };\n }\n\n return validateWidgetBinding(binding, Array.isArray(preview) ? preview[0] : preview);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"widgetBinding.js","names":[],"sources":["../../../src/bigconsole/utils/widgetBinding.ts"],"sourcesContent":["import type { DataSourceType, Widget, WidgetBinding, WidgetBindingStatus, WidgetBindingValidation } from '../types';\n\nconst SOURCE_KINDS: ReadonlySet<string> = new Set<DataSourceType>([\n 'DATASINK',\n 'REST_API',\n 'GRAPHQL',\n 'SQL',\n 'WEBSOCKET',\n 'MOCK',\n]);\n\nconst FIELD_CONFIG_KEYS = [\n 'labelField',\n 'valueField',\n 'xAxisField',\n 'yAxisField',\n 'categoryField',\n 'seriesField',\n] as const;\n\ninterface LegacyBindingInput {\n dataSinkId?: string;\n parserId?: string;\n config?: Record<string, unknown>;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isSourceKind(value: unknown): value is DataSourceType {\n return typeof value === 'string' && SOURCE_KINDS.has(value);\n}\n\nfunction readCanonicalBinding(value: unknown): WidgetBinding | null {\n if (!isRecord(value) || !isRecord(value.source) || !isSourceKind(value.source.kind)) return null;\n\n const fields = isRecord(value.fields)\n ? Object.fromEntries(\n Object.entries(value.fields).filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n )\n : {};\n\n return {\n source: {\n kind: value.source.kind,\n ref: typeof value.source.ref === 'string' ? value.source.ref : undefined,\n },\n parserId: typeof value.parserId === 'string' ? value.parserId : undefined,\n fields,\n };\n}\n\n/** Read canonical binding first, then adapt legacy widget fields without mutating persisted data. */\nexport function getWidgetBinding(input: LegacyBindingInput): WidgetBinding {\n const config = input.config ?? {};\n const canonical = readCanonicalBinding(config.binding);\n if (canonical) return canonical;\n\n const configuredKind = isSourceKind(config.dataSourceType) ? config.dataSourceType : undefined;\n const kind: DataSourceType = input.dataSinkId ? 'DATASINK' : (configuredKind ?? 'DATASINK');\n const fields: Record<string, string> = {};\n\n for (const key of FIELD_CONFIG_KEYS) {\n const value = config[key];\n if (typeof value === 'string' && value.length > 0) fields[key] = value;\n }\n\n return {\n source: { kind, ref: kind === 'DATASINK' ? input.dataSinkId : undefined },\n parserId: input.parserId,\n fields,\n };\n}\n\nexport function getBindingForWidget(widget: Widget): WidgetBinding {\n return getWidgetBinding({\n dataSinkId: widget.dataSinkId ?? undefined,\n parserId: widget.parserId ?? undefined,\n config: (widget.config as Record<string, unknown>) ?? {},\n });\n}\n\n/**\n * Reconcile the properties-panel's config edits against the latest persisted\n * config so a panel save never clobbers config keys changed concurrently by\n * another editor.\n *\n * The panel seeds a `localForm.config` snapshot from the widget and then freezes\n * it while it has unsaved changes. Meanwhile a widget's own canvas UI can persist\n * config independently (e.g. a Pivot Table's `rows`/`columns`/`values` dragged on\n * the canvas). Writing the frozen snapshot back on save would wipe those keys.\n *\n * Strategy: start from the latest persisted config (carries concurrent edits),\n * then re-apply only the keys the panel actually changed relative to the baseline\n * it was seeded from — additions/updates as well as removals. Keys the panel left\n * untouched keep their latest persisted value.\n */\nexport function reconcilePanelConfig(\n latest: Record<string, unknown>,\n baseline: Record<string, unknown>,\n edited: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = { ...latest };\n const unchanged = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b);\n\n // Additions and updates the panel made relative to its seed baseline.\n for (const key of Object.keys(edited)) {\n if (!unchanged(edited[key], baseline[key])) result[key] = edited[key];\n }\n // Removals the panel made (present at seed time, dropped from the form).\n for (const key of Object.keys(baseline)) {\n if (!(key in edited)) delete result[key];\n }\n return result;\n}\n\n/** Dual-write canonical binding and renderer compatibility fields for one migration release. */\nexport function serializeWidgetBinding(binding: WidgetBinding, config: Record<string, unknown>) {\n const canonical: WidgetBinding = {\n source: { kind: binding.source.kind, ref: binding.source.ref },\n parserId: binding.parserId,\n fields: { ...binding.fields },\n };\n\n return {\n config: {\n ...config,\n binding: canonical,\n dataSourceType: canonical.source.kind,\n ...canonical.fields,\n },\n dataSinkId: canonical.source.kind === 'DATASINK' ? canonical.source.ref : undefined,\n parserId: canonical.parserId,\n };\n}\n\nexport function resolveBindingPath(value: unknown, path: string): unknown {\n return path\n .replace(/^\\$?\\.?/, '')\n .split('.')\n .filter(Boolean)\n .reduce<unknown>((current, segment) => (isRecord(current) ? current[segment] : undefined), value);\n}\n\nexport function validateWidgetBinding(binding: WidgetBinding, sample?: unknown): WidgetBindingValidation {\n const isUnbound = binding.source.kind === 'DATASINK' && !binding.source.ref;\n if (isUnbound) return { status: 'unbound', missingFields: [] };\n if (sample === undefined) return { status: 'bound', missingFields: [] };\n\n const missingFields = Object.entries(binding.fields)\n .filter(([, path]) => resolveBindingPath(sample, path) === undefined)\n .map(([field]) => field);\n\n return { status: missingFields.length > 0 ? 'field_mismatch' : 'bound', missingFields };\n}\n\ninterface BindingStatusInput {\n binding: WidgetBinding;\n loading?: boolean;\n error?: string | null;\n preview?: unknown;\n previewComplete?: boolean;\n}\n\nexport function getWidgetBindingStatus({\n binding,\n loading = false,\n error,\n preview,\n previewComplete = false,\n}: BindingStatusInput): { status: WidgetBindingStatus; missingFields: string[] } {\n if (loading) return { status: 'loading', missingFields: [] };\n if (error) return { status: 'parser_error', missingFields: [] };\n\n const initialValidation = validateWidgetBinding(binding);\n if (initialValidation.status === 'unbound') return initialValidation;\n if (!previewComplete) return initialValidation;\n if (preview == null || (Array.isArray(preview) && preview.length === 0)) {\n return { status: 'no_data', missingFields: [] };\n }\n\n return validateWidgetBinding(binding, Array.isArray(preview) ? preview[0] : preview);\n}\n"],"mappings":";AAEA,IAAM,IAAoC,IAAI,IAAoB;CAChE;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,EAEI,IAAoB;CACxB;CACA;CACA;CACA;CACA;CACA;CACD;AAQD,SAAS,EAAS,GAAkD;AAClE,QAAO,OAAO,KAAU,cAAY,KAAkB,CAAC,MAAM,QAAQ,EAAM;;AAG7E,SAAS,EAAa,GAAyC;AAC7D,QAAO,OAAO,KAAU,YAAY,EAAa,IAAI,EAAM;;AAG7D,SAAS,EAAqB,GAAsC;AAClE,KAAI,CAAC,EAAS,EAAM,IAAI,CAAC,EAAS,EAAM,OAAO,IAAI,CAAC,EAAa,EAAM,OAAO,KAAK,CAAE,QAAO;CAE5F,IAAM,IAAS,EAAS,EAAM,OAAO,GACjC,OAAO,YACL,OAAO,QAAQ,EAAM,OAAO,CAAC,QAAQ,MAAqC,OAAO,EAAM,MAAO,SAAS,CACxG,GACD,EAAE;AAEN,QAAO;EACL,QAAQ;GACN,MAAM,EAAM,OAAO;GACnB,KAAK,OAAO,EAAM,OAAO,OAAQ,WAAW,EAAM,OAAO,MAAM,KAAA;GAChE;EACD,UAAU,OAAO,EAAM,YAAa,WAAW,EAAM,WAAW,KAAA;EAChE;EACD;;AAIH,SAAgB,EAAiB,GAA0C;CACzE,IAAM,IAAS,EAAM,UAAU,EAAE,EAC3B,IAAY,EAAqB,EAAO,QAAQ;AACtD,KAAI,EAAW,QAAO;CAEtB,IAAM,IAAiB,EAAa,EAAO,eAAe,GAAG,EAAO,iBAAiB,KAAA,GAC/E,IAAuB,EAAM,aAAa,aAAc,KAAkB,YAC1E,IAAiC,EAAE;AAEzC,MAAK,IAAM,KAAO,GAAmB;EACnC,IAAM,IAAQ,EAAO;AACrB,EAAI,OAAO,KAAU,YAAY,EAAM,SAAS,MAAG,EAAO,KAAO;;AAGnE,QAAO;EACL,QAAQ;GAAE;GAAM,KAAK,MAAS,aAAa,EAAM,aAAa,KAAA;GAAW;EACzE,UAAU,EAAM;EAChB;EACD;;AAGH,SAAgB,EAAoB,GAA+B;AACjE,QAAO,EAAiB;EACtB,YAAY,EAAO,cAAc,KAAA;EACjC,UAAU,EAAO,YAAY,KAAA;EAC7B,QAAS,EAAO,UAAsC,EAAE;EACzD,CAAC;;AAkBJ,SAAgB,EACd,GACA,GACA,GACyB;CACzB,IAAM,IAAkC,EAAE,GAAG,GAAQ,EAC/C,KAAa,GAAY,MAAwB,KAAK,UAAU,EAAE,KAAK,KAAK,UAAU,EAAE;AAG9F,MAAK,IAAM,KAAO,OAAO,KAAK,EAAO,CACnC,CAAK,EAAU,EAAO,IAAM,EAAS,GAAK,KAAE,EAAO,KAAO,EAAO;AAGnE,MAAK,IAAM,KAAO,OAAO,KAAK,EAAS,CACrC,CAAM,KAAO,KAAS,OAAO,EAAO;AAEtC,QAAO;;AAIT,SAAgB,EAAuB,GAAwB,GAAiC;CAC9F,IAAM,IAA2B;EAC/B,QAAQ;GAAE,MAAM,EAAQ,OAAO;GAAM,KAAK,EAAQ,OAAO;GAAK;EAC9D,UAAU,EAAQ;EAClB,QAAQ,EAAE,GAAG,EAAQ,QAAQ;EAC9B;AAED,QAAO;EACL,QAAQ;GACN,GAAG;GACH,SAAS;GACT,gBAAgB,EAAU,OAAO;GACjC,GAAG,EAAU;GACd;EACD,YAAY,EAAU,OAAO,SAAS,aAAa,EAAU,OAAO,MAAM,KAAA;EAC1E,UAAU,EAAU;EACrB;;AAGH,SAAgB,EAAmB,GAAgB,GAAuB;AACxE,QAAO,EACJ,QAAQ,WAAW,GAAG,CACtB,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,QAAiB,GAAS,MAAa,EAAS,EAAQ,GAAG,EAAQ,KAAW,KAAA,GAAY,EAAM;;AAGrG,SAAgB,EAAsB,GAAwB,GAA2C;AAEvG,KADkB,EAAQ,OAAO,SAAS,cAAc,CAAC,EAAQ,OAAO,IACzD,QAAO;EAAE,QAAQ;EAAW,eAAe,EAAE;EAAE;AAC9D,KAAI,MAAW,KAAA,EAAW,QAAO;EAAE,QAAQ;EAAS,eAAe,EAAE;EAAE;CAEvE,IAAM,IAAgB,OAAO,QAAQ,EAAQ,OAAO,CACjD,QAAQ,GAAG,OAAU,EAAmB,GAAQ,EAAK,KAAK,KAAA,EAAU,CACpE,KAAK,CAAC,OAAW,EAAM;AAE1B,QAAO;EAAE,QAAQ,EAAc,SAAS,IAAI,mBAAmB;EAAS;EAAe;;AAWzF,SAAgB,EAAuB,EACrC,YACA,aAAU,IACV,UACA,YACA,qBAAkB,MAC6D;AAC/E,KAAI,EAAS,QAAO;EAAE,QAAQ;EAAW,eAAe,EAAE;EAAE;AAC5D,KAAI,EAAO,QAAO;EAAE,QAAQ;EAAgB,eAAe,EAAE;EAAE;CAE/D,IAAM,IAAoB,EAAsB,EAAQ;AAOxD,QANI,EAAkB,WAAW,aAC7B,CAAC,IAAwB,IACzB,KAAW,QAAS,MAAM,QAAQ,EAAQ,IAAI,EAAQ,WAAW,IAC5D;EAAE,QAAQ;EAAW,eAAe,EAAE;EAAE,GAG1C,EAAsB,GAAS,MAAM,QAAQ,EAAQ,GAAG,EAAQ,KAAK,EAAQ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.727.4",
3
+ "version": "2026.729.1",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [