@burdenoff/microfe-bigconsole 2026.801.2 → 2026.803.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":"TableCell.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/table/TableCell.tsx"],"sourcesContent":["/**\n * TableCell Component\n *\n * Table cell with formatting and rendering options.\n */\n\nimport { type FC, memo, type ReactNode } from 'react';\nimport type { ColumnDefinition } from './TableHeader';\nimport {\n type ConditionalRule,\n resolveConditionalStyle,\n conditionalStyleToClassName,\n intentToCssVar,\n CONDITIONAL_ICON_PATHS,\n} from '../utils/conditionalFormat';\nimport { SparklineChart } from '../metric-card/SparklineChart';\n\n/** In-cell mini-chart kinds. */\nexport type CellChart = 'sparkline';\n\n/**\n * Extract finite numeric values from a sparkline cell value (an array of raw\n * numbers or `{value}` objects). Non-finite values (NaN/Infinity) are rejected\n * in both forms. Returns null when the value isn't a usable numeric array — so\n * the caller can decide how to render: a chart (≥2 points), a single value\n * (1 point), or fall through to the default renderer.\n */\nfunction extractSparklineValues(value: unknown): number[] | null {\n if (!Array.isArray(value) || value.length === 0) return null;\n const nums: number[] = [];\n for (const item of value) {\n if (typeof item === 'number' && Number.isFinite(item)) {\n nums.push(item);\n } else if (\n item &&\n typeof item === 'object' &&\n typeof (item as { value?: unknown }).value === 'number' &&\n Number.isFinite((item as { value: number }).value)\n ) {\n nums.push((item as { value: number }).value);\n }\n }\n return nums.length ? nums : null;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type CellFormat = 'text' | 'number' | 'currency' | 'percentage' | 'date' | 'badge' | 'link';\n\nexport interface CellFormatConfig {\n /** Format type */\n type: CellFormat;\n /** Currency code for currency format */\n currency?: string;\n /** Date format options */\n dateOptions?: Intl.DateTimeFormatOptions;\n /** Badge color mapping (value -> color) */\n badgeColors?: Record<string, string>;\n /** Link URL pattern (use {value} as placeholder) */\n linkPattern?: string;\n}\n\nexport interface TableCellProps {\n /** Cell value */\n value: unknown;\n /** Column definition */\n column: ColumnDefinition;\n /** Format configuration */\n format?: CellFormatConfig;\n /** Row data for link patterns and conditional rule field lookups */\n rowData?: Record<string, unknown>;\n /** Conditional formatting rules to evaluate against this cell */\n conditionalRules?: ConditionalRule[];\n /** Max absolute value in this column, used to scale in-cell data bars */\n dataBarMax?: number;\n /** When set, freeze this cell sticky-left at the given px offset */\n stickyLeft?: number;\n /** Render the cell as an in-cell mini chart (e.g. sparkline) instead of a value */\n cellChart?: CellChart;\n /** Click handler */\n onClick?: () => void;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction formatValue(value: unknown, format?: CellFormatConfig): ReactNode {\n if (value === null || value === undefined) {\n return <span className=\"text-text-secondary\">—</span>;\n }\n\n const formatType = format?.type || 'text';\n\n switch (formatType) {\n case 'number':\n return typeof value === 'number' ? new Intl.NumberFormat('en-US').format(value) : String(value);\n\n case 'currency':\n return typeof value === 'number'\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: format?.currency || 'USD',\n }).format(value)\n : String(value);\n\n case 'percentage':\n return typeof value === 'number' ? `${(value * 100).toFixed(1)}%` : String(value);\n\n case 'date':\n if (value instanceof Date) {\n return new Intl.DateTimeFormat('en-US', format?.dateOptions || { dateStyle: 'medium' }).format(value);\n }\n if (typeof value === 'string' || typeof value === 'number') {\n const date = new Date(value);\n if (!isNaN(date.getTime())) {\n return new Intl.DateTimeFormat('en-US', format?.dateOptions || { dateStyle: 'medium' }).format(date);\n }\n }\n return String(value);\n\n case 'badge': {\n const stringValue = String(value);\n const badgeColor = format?.badgeColors?.[stringValue] || 'bg-bg-sunken text-text-primary';\n return (\n <span\n className={`\n inline-flex items-center\n px-2 py-0.5\n text-xs font-medium\n rounded-full\n ${badgeColor}\n `}\n >\n {stringValue}\n </span>\n );\n }\n\n case 'link':\n return <span className=\"text-action-primary-bg hover:underline cursor-pointer\">{String(value)}</span>;\n\n case 'text':\n default:\n return String(value);\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const TableCell: FC<TableCellProps> = memo(function TableCell({\n value,\n column,\n format,\n rowData,\n conditionalRules,\n dataBarMax,\n stickyLeft,\n cellChart,\n onClick,\n}) {\n const cf = resolveConditionalStyle(value, conditionalRules, rowData);\n const cfClass = conditionalStyleToClassName(cf);\n const isFrozen = stickyLeft != null;\n // In-cell sparkline: only when the column opts in and the value is numeric.\n const sparkValues = cellChart === 'sparkline' ? extractSparklineValues(value) : null;\n // Range for the sparkline's accessible label (reduce, not spread → no call-stack risk).\n const sparkMin = sparkValues ? sparkValues.reduce((a, b) => Math.min(a, b), Infinity) : 0;\n const sparkMax = sparkValues ? sparkValues.reduce((a, b) => Math.max(a, b), -Infinity) : 0;\n\n // In-cell data bar width (0–100%) scaled against the column max.\n const dataBarWidth =\n cf?.dataBar && typeof value === 'number' && dataBarMax && dataBarMax > 0\n ? Math.max(0, Math.min(100, (Math.abs(value) / dataBarMax) * 100))\n : null;\n\n return (\n <td\n className={`\n relative px-4 py-3\n text-sm text-text-primary\n border-b border-border-default\n ${column.align === 'center' ? 'text-center' : ''}\n ${column.align === 'right' ? 'text-right' : 'text-left'}\n ${onClick ? 'cursor-pointer hover:text-action-primary-bg' : ''}\n ${isFrozen ? 'sticky z-10 bg-bg-surface' : ''}\n ${cfClass}\n `}\n style={isFrozen ? { left: stickyLeft, width: column.width } : undefined}\n onClick={onClick}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n onKeyDown={(e) => {\n if (onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick();\n }\n }}\n >\n {dataBarWidth != null && (\n <span\n aria-hidden=\"true\"\n className=\"absolute inset-y-1 left-1 rounded-sm opacity-20 pointer-events-none\"\n style={{\n width: `calc(${dataBarWidth}% - 0.5rem)`,\n backgroundColor: intentToCssVar(cf?.dataBarColor ?? 'info'),\n }}\n />\n )}\n {sparkValues && sparkValues.length >= 2 ? (\n // ≥2 points → draw the sparkline (SparklineChart needs >1 to avoid a\n // divide-by-zero). Labelled for screen readers since the SVG is decorative.\n <span\n className=\"relative inline-flex items-center\"\n role=\"img\"\n aria-label={`Sparkline, ${sparkValues.length} points, ${sparkMin.toLocaleString()} to ${sparkMax.toLocaleString()}`}\n >\n <SparklineChart data={sparkValues.map((v) => ({ value: v }))} width={96} height={28} color=\"primary\" />\n </span>\n ) : sparkValues && sparkValues.length === 1 ? (\n // A single numeric point can't be charted — render the value itself\n // (not the raw array, which would stringify to \"[object Object]\").\n <span className=\"relative inline-flex items-center\">{formatValue(sparkValues[0], format)}</span>\n ) : (\n <span className=\"relative inline-flex items-center gap-1.5\">\n {cf?.icon && (\n <svg className=\"w-3.5 h-3.5 shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d={CONDITIONAL_ICON_PATHS[cf.icon]} />\n </svg>\n )}\n {formatValue(value, format)}\n </span>\n )}\n </td>\n );\n});\n\nexport default TableCell;\n"],"mappings":"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"}
1
+ {"version":3,"file":"TableCell.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/table/TableCell.tsx"],"sourcesContent":["/**\n * TableCell Component\n *\n * Table cell with formatting and rendering options.\n */\n\nimport { type FC, memo, type ReactNode } from 'react';\nimport type { ColumnDefinition } from './TableHeader';\nimport {\n type ConditionalRule,\n resolveConditionalStyle,\n conditionalStyleToClassName,\n intentToCssVar,\n CONDITIONAL_ICON_PATHS,\n} from '../utils/conditionalFormat';\nimport { SparklineChart } from '../metric-card/SparklineChart';\nimport { resolveCellLinkHref } from './cellLink';\n\n/** In-cell mini-chart kinds. */\nexport type CellChart = 'sparkline';\n\n/**\n * Extract finite numeric values from a sparkline cell value (an array of raw\n * numbers or `{value}` objects). Non-finite values (NaN/Infinity) are rejected\n * in both forms. Returns null when the value isn't a usable numeric array — so\n * the caller can decide how to render: a chart (≥2 points), a single value\n * (1 point), or fall through to the default renderer.\n */\nfunction extractSparklineValues(value: unknown): number[] | null {\n if (!Array.isArray(value) || value.length === 0) return null;\n const nums: number[] = [];\n for (const item of value) {\n if (typeof item === 'number' && Number.isFinite(item)) {\n nums.push(item);\n } else if (\n item &&\n typeof item === 'object' &&\n typeof (item as { value?: unknown }).value === 'number' &&\n Number.isFinite((item as { value: number }).value)\n ) {\n nums.push((item as { value: number }).value);\n }\n }\n return nums.length ? nums : null;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type CellFormat = 'text' | 'number' | 'currency' | 'percentage' | 'date' | 'badge' | 'link';\n\nexport interface CellFormatConfig {\n /** Format type */\n type: CellFormat;\n /** Currency code for currency format */\n currency?: string;\n /** Date format options */\n dateOptions?: Intl.DateTimeFormatOptions;\n /** Badge color mapping (value -> color) */\n badgeColors?: Record<string, string>;\n /** Link URL pattern (use {value} as placeholder) */\n linkPattern?: string;\n}\n\nexport interface TableCellProps {\n /** Cell value */\n value: unknown;\n /** Column definition */\n column: ColumnDefinition;\n /** Format configuration */\n format?: CellFormatConfig;\n /** Row data for link patterns and conditional rule field lookups */\n rowData?: Record<string, unknown>;\n /** Conditional formatting rules to evaluate against this cell */\n conditionalRules?: ConditionalRule[];\n /** Max absolute value in this column, used to scale in-cell data bars */\n dataBarMax?: number;\n /** When set, freeze this cell sticky-left at the given px offset */\n stickyLeft?: number;\n /** Render the cell as an in-cell mini chart (e.g. sparkline) instead of a value */\n cellChart?: CellChart;\n /** Click handler */\n onClick?: () => void;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction formatValue(value: unknown, format?: CellFormatConfig): ReactNode {\n if (value === null || value === undefined) {\n return <span className=\"text-text-secondary\">—</span>;\n }\n\n const formatType = format?.type || 'text';\n\n switch (formatType) {\n case 'number':\n return typeof value === 'number' ? new Intl.NumberFormat('en-US').format(value) : String(value);\n\n case 'currency':\n return typeof value === 'number'\n ? new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: format?.currency || 'USD',\n }).format(value)\n : String(value);\n\n case 'percentage':\n return typeof value === 'number' ? `${(value * 100).toFixed(1)}%` : String(value);\n\n case 'date':\n if (value instanceof Date) {\n return new Intl.DateTimeFormat('en-US', format?.dateOptions || { dateStyle: 'medium' }).format(value);\n }\n if (typeof value === 'string' || typeof value === 'number') {\n const date = new Date(value);\n if (!isNaN(date.getTime())) {\n return new Intl.DateTimeFormat('en-US', format?.dateOptions || { dateStyle: 'medium' }).format(date);\n }\n }\n return String(value);\n\n case 'badge': {\n const stringValue = String(value);\n const badgeColor = format?.badgeColors?.[stringValue] || 'bg-bg-sunken text-text-primary';\n return (\n <span\n className={`\n inline-flex items-center\n px-2 py-0.5\n text-xs font-medium\n rounded-full\n ${badgeColor}\n `}\n >\n {stringValue}\n </span>\n );\n }\n\n // `link` renders its visible text like any other value here; the navigable\n // <a> wrapper (which needs rowData to resolve the href) is applied in the\n // component body. A link column with no resolvable URL stays honest plain\n // text rather than fake link-styled text that goes nowhere.\n case 'link':\n case 'text':\n default:\n return String(value);\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const TableCell: FC<TableCellProps> = memo(function TableCell({\n value,\n column,\n format,\n rowData,\n conditionalRules,\n dataBarMax,\n stickyLeft,\n cellChart,\n onClick,\n}) {\n const cf = resolveConditionalStyle(value, conditionalRules, rowData);\n const cfClass = conditionalStyleToClassName(cf);\n const isFrozen = stickyLeft != null;\n // In-cell sparkline: only when the column opts in and the value is numeric.\n const sparkValues = cellChart === 'sparkline' ? extractSparklineValues(value) : null;\n // Range for the sparkline's accessible label (reduce, not spread → no call-stack risk).\n const sparkMin = sparkValues ? sparkValues.reduce((a, b) => Math.min(a, b), Infinity) : 0;\n const sparkMax = sparkValues ? sparkValues.reduce((a, b) => Math.max(a, b), -Infinity) : 0;\n\n // In-cell data bar width (0–100%) scaled against the column max.\n const dataBarWidth =\n cf?.dataBar && typeof value === 'number' && dataBarMax && dataBarMax > 0\n ? Math.max(0, Math.min(100, (Math.abs(value) / dataBarMax) * 100))\n : null;\n\n // Navigable href for a `link`-formatted cell (null when nothing safe resolves).\n const linkHref = format?.type === 'link' ? resolveCellLinkHref(format.linkPattern, value, rowData) : null;\n\n return (\n <td\n className={`\n relative px-4 py-3\n text-sm text-text-primary\n border-b border-border-default\n ${column.align === 'center' ? 'text-center' : ''}\n ${column.align === 'right' ? 'text-right' : 'text-left'}\n ${onClick ? 'cursor-pointer hover:text-action-primary-bg' : ''}\n ${isFrozen ? 'sticky z-10 bg-bg-surface' : ''}\n ${cfClass}\n `}\n style={isFrozen ? { left: stickyLeft, width: column.width } : undefined}\n onClick={onClick}\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n onKeyDown={(e) => {\n if (onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick();\n }\n }}\n >\n {dataBarWidth != null && (\n <span\n aria-hidden=\"true\"\n className=\"absolute inset-y-1 left-1 rounded-sm opacity-20 pointer-events-none\"\n style={{\n width: `calc(${dataBarWidth}% - 0.5rem)`,\n backgroundColor: intentToCssVar(cf?.dataBarColor ?? 'info'),\n }}\n />\n )}\n {sparkValues && sparkValues.length >= 2 ? (\n // ≥2 points → draw the sparkline (SparklineChart needs >1 to avoid a\n // divide-by-zero). Labelled for screen readers since the SVG is decorative.\n <span\n className=\"relative inline-flex items-center\"\n role=\"img\"\n aria-label={`Sparkline, ${sparkValues.length} points, ${sparkMin.toLocaleString()} to ${sparkMax.toLocaleString()}`}\n >\n <SparklineChart data={sparkValues.map((v) => ({ value: v }))} width={96} height={28} color=\"primary\" />\n </span>\n ) : sparkValues && sparkValues.length === 1 ? (\n // A single numeric point can't be charted — render the value itself\n // (not the raw array, which would stringify to \"[object Object]\").\n <span className=\"relative inline-flex items-center\">{formatValue(sparkValues[0], format)}</span>\n ) : (\n <span className=\"relative inline-flex items-center gap-1.5\">\n {cf?.icon && (\n <svg className=\"w-3.5 h-3.5 shrink-0\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d={CONDITIONAL_ICON_PATHS[cf.icon]} />\n </svg>\n )}\n {linkHref ? (\n <a\n href={linkHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title={linkHref}\n className=\"text-action-primary-bg hover:underline\"\n // Don't let the click bubble to the row (cross-filter / drilldown).\n onClick={(e) => e.stopPropagation()}\n >\n {formatValue(value, format)}\n </a>\n ) : (\n formatValue(value, format)\n )}\n </span>\n )}\n </td>\n );\n});\n\nexport default TableCell;\n"],"mappings":"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"}
@@ -150,7 +150,10 @@ var h = c(function({ widget: c, data: h = [], onRowClick: g, onDrilldown: _ }) {
150
150
  ]), I = u(() => {
151
151
  let e = {};
152
152
  return v?.columns?.forEach((t) => {
153
- t.format && (t.key || t.field) && (e[t.key || t.field || ""] = { type: t.format });
153
+ t.format && (t.key || t.field) && (e[t.key || t.field || ""] = {
154
+ type: t.format,
155
+ ...t.linkPattern ? { linkPattern: t.linkPattern } : {}
156
+ });
154
157
  }), e;
155
158
  }, [v?.columns]), L = u(() => {
156
159
  let e = {};
@@ -1 +1 @@
1
- {"version":3,"file":"TableWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/table/TableWidget.tsx"],"sourcesContent":["/**\n * TableWidget Component\n *\n * Data table with sorting, filtering, and pagination.\n */\n\nimport { type FC, memo, useState, useMemo, useCallback, useRef, useEffect } from 'react';\nimport type { Widget, TableConfig, TableColumnConfig } from '../../../types';\nimport { TableHeader, type ColumnDefinition, type SortState, type SortDirection } from './TableHeader';\nimport { TableCell, type CellFormatConfig, type CellChart } from './TableCell';\nimport { TablePagination, type PaginationState } from './TablePagination';\nimport { resolveRowClassName, type ConditionalRule } from '../utils/conditionalFormat';\n// Phase 2: Cross-filtering support\nimport { useWidgetCrossFilter, useCrossFilterContext } from '../../../contexts';\nimport { useIsEditMode } from '../../../store';\nimport { useWidgetConfigPersist } from '../../../hooks';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TableWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Table data (array of rows) */\n data?: Record<string, unknown>[];\n /** Click handler for row drilldown */\n onRowClick?: (row: Record<string, unknown>, index: number) => void;\n /** Drilldown handler - receives selected row data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction sortData(data: Record<string, unknown>[], sortState: SortState): Record<string, unknown>[] {\n if (!sortState.column || !sortState.direction) {\n return data;\n }\n\n return [...data].sort((a, b) => {\n const aValue = a[sortState.column!];\n const bValue = b[sortState.column!];\n\n // Handle null/undefined\n if (aValue == null && bValue == null) return 0;\n if (aValue == null) return sortState.direction === 'asc' ? 1 : -1;\n if (bValue == null) return sortState.direction === 'asc' ? -1 : 1;\n\n // Compare values\n let comparison = 0;\n if (typeof aValue === 'number' && typeof bValue === 'number') {\n comparison = aValue - bValue;\n } else {\n comparison = String(aValue).localeCompare(String(bValue));\n }\n\n return sortState.direction === 'asc' ? comparison : -comparison;\n });\n}\n\nfunction filterData(\n data: Record<string, unknown>[],\n searchQuery: string,\n columns: ColumnDefinition[]\n): Record<string, unknown>[] {\n if (!searchQuery.trim()) {\n return data;\n }\n\n const query = searchQuery.toLowerCase();\n return data.filter((row) =>\n columns.some((col) => {\n const value = row[col.key];\n return value != null && String(value).toLowerCase().includes(query);\n })\n );\n}\n\n/** Format a footer total respecting the column's cell format. */\nfunction formatTotal(value: number, formatType?: string, currency?: string): string {\n switch (formatType) {\n case 'currency':\n return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency || 'USD' }).format(value);\n case 'percentage':\n return `${(value * 100).toFixed(1)}%`;\n case 'number':\n return new Intl.NumberFormat('en-US').format(value);\n default:\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const TableWidget: FC<TableWidgetProps> = memo(function TableWidget({\n widget,\n data: rawData = [],\n onRowClick,\n onDrilldown,\n}) {\n const config = widget.config as unknown as TableConfig | undefined;\n\n // Normalize data - handle both array and object formats\n const data = useMemo(() => {\n // If it's already an array, use it directly\n if (Array.isArray(rawData)) {\n return rawData;\n }\n\n // If it's an object, try to find an array property\n if (rawData && typeof rawData === 'object') {\n // Check common array property names\n const possibleArrayProps = ['data', 'items', 'rows', 'results', 'records', 'list', 'sales', 'entries', 'values'];\n for (const prop of possibleArrayProps) {\n if (Array.isArray((rawData as Record<string, unknown>)[prop])) {\n return (rawData as Record<string, unknown>)[prop] as Record<string, unknown>[];\n }\n }\n\n // If object has array values, use the first one\n const values = Object.values(rawData);\n for (const value of values) {\n if (Array.isArray(value)) {\n return value as Record<string, unknown>[];\n }\n }\n\n // If it's a single object, wrap it in an array\n return [rawData as Record<string, unknown>];\n }\n\n return [];\n }, [rawData]);\n\n // Phase 2: Cross-filtering support\n const { handleDataClick: triggerCrossFilter, enabled: crossFilterEnabled } = useWidgetCrossFilter(\n widget.id,\n // Map first column to filter ID by default, can be configured\n config?.crossFilterMappings\n );\n const { clearAllCrossFilters, hasCrossFilters } = useCrossFilterContext();\n\n // State\n const [sortState, setSortState] = useState<SortState>({\n column: config?.defaultSortColumn || null,\n direction: (config?.defaultSortDirection as SortDirection) || null,\n });\n const [searchQuery, setSearchQuery] = useState('');\n const [pagination, setPagination] = useState<PaginationState>({\n page: 0,\n pageSize: config?.pageSize || 10,\n totalItems: 0,\n });\n\n // Interactive column resize/reorder (edit mode only). Local overrides give\n // live feedback; the committed layout is persisted to config so it survives\n // reload (see commitColumnLayout).\n const isEditMode = useIsEditMode();\n const { persistConfig } = useWidgetConfigPersist(widget);\n const [widthOverrides, setWidthOverrides] = useState<Record<string, number>>({});\n const [columnOrder, setColumnOrder] = useState<string[] | null>(null);\n\n // Build column definitions from config or data\n const baseColumns: ColumnDefinition[] = useMemo(() => {\n if (config?.columns?.length) {\n return (config.columns as (string | TableColumnConfig)[]).map((col) => {\n // Handle both string and object column definitions\n if (typeof col === 'string') {\n return {\n key: col,\n header: col.charAt(0).toUpperCase() + col.slice(1).replace(/_/g, ' '),\n sortable: true,\n };\n }\n // Object column definition — accept both .key and .field\n const fieldKey = col.key || col.field || '';\n return {\n key: fieldKey,\n header: col.label || col.header || fieldKey,\n sortable: col.sortable !== false,\n width: col.width != null ? String(col.width) : undefined,\n align: col.align as ColumnDefinition['align'],\n };\n }) as ColumnDefinition[];\n }\n\n // Auto-generate from first data row\n if (data.length > 0) {\n return Object.keys(data[0]).map((key) => ({\n key,\n header: key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, ' '),\n sortable: true,\n }));\n }\n\n return [];\n }, [config?.columns, data]);\n\n // Apply the live reorder + resize overrides on top of the base columns. Any\n // column not named in columnOrder keeps its original relative position at the\n // end (handles columns added since the order was set).\n const columns: ColumnDefinition[] = useMemo(() => {\n let cols = baseColumns;\n if (columnOrder) {\n const byKey = new Map(baseColumns.map((c) => [c.key, c]));\n const ordered = columnOrder.map((k) => byKey.get(k)).filter((c): c is ColumnDefinition => c !== undefined);\n const rest = baseColumns.filter((c) => !columnOrder.includes(c.key));\n cols = [...ordered, ...rest];\n }\n if (Object.keys(widthOverrides).length > 0) {\n cols = cols.map((c) => (widthOverrides[c.key] != null ? { ...c, width: `${widthOverrides[c.key]}px` } : c));\n }\n return cols;\n }, [baseColumns, columnOrder, widthOverrides]);\n\n // Persist the current column order + widths to config.columns. Builds explicit\n // column objects (preserving any existing per-column config), which also pins\n // a previously auto-generated column set so order/width survive reload.\n const commitColumnLayout = useCallback(\n (orderKeys: string[], widths: Record<string, number>) => {\n const existing = new Map<string, TableColumnConfig>();\n config?.columns?.forEach((c) => {\n const k = c.key || c.field;\n if (k) existing.set(k, c);\n });\n const baseKeys = baseColumns.map((c) => c.key);\n const finalKeys = [\n ...orderKeys.filter((k) => baseKeys.includes(k)),\n ...baseKeys.filter((k) => !orderKeys.includes(k)),\n ];\n const newColumns: TableColumnConfig[] = finalKeys.map((key) => {\n const base = baseColumns.find((c) => c.key === key);\n const prev = existing.get(key);\n const next: TableColumnConfig = { ...(prev ?? {}), key };\n // Preserve a header label for columns that had none in config (auto-generated).\n if (!next.label && !next.header && base?.header) next.label = base.header;\n const w = widths[key];\n if (w != null) next.width = w;\n return next;\n });\n void persistConfig({ columns: newColumns });\n },\n [baseColumns, config?.columns, persistConfig]\n );\n\n const handleColumnResize = useCallback((key: string, widthPx: number) => {\n setWidthOverrides((prev) => ({ ...prev, [key]: widthPx }));\n }, []);\n\n const handleColumnResizeEnd = useCallback(\n (key: string, widthPx: number) => {\n const widths = { ...widthOverrides, [key]: widthPx };\n setWidthOverrides(widths);\n const order = columnOrder ?? baseColumns.map((c) => c.key);\n commitColumnLayout(order, widths);\n },\n [widthOverrides, columnOrder, baseColumns, commitColumnLayout]\n );\n\n const handleColumnReorder = useCallback(\n (sourceKey: string, targetKey: string) => {\n const current = columnOrder ?? baseColumns.map((c) => c.key);\n const from = current.indexOf(sourceKey);\n if (from < 0 || sourceKey === targetKey) return;\n const next = [...current];\n next.splice(from, 1);\n // Insert before the target's *post-removal* index, so dropping onto a\n // column always lands the source just before it regardless of direction.\n const insertAt = next.indexOf(targetKey);\n if (insertAt < 0) return;\n next.splice(insertAt, 0, sourceKey);\n setColumnOrder(next);\n commitColumnLayout(next, widthOverrides);\n },\n [columnOrder, baseColumns, widthOverrides, commitColumnLayout]\n );\n\n // Build cell format configs\n const cellFormats: Record<string, CellFormatConfig> = useMemo(() => {\n const formats: Record<string, CellFormatConfig> = {};\n config?.columns?.forEach((col) => {\n if (col.format && (col.key || col.field)) {\n formats[col.key || col.field || ''] = { type: col.format };\n }\n });\n return formats;\n }, [config?.columns]);\n\n // Per-column in-cell chart kind (e.g. sparkline).\n const columnCellCharts: Record<string, CellChart | undefined> = useMemo(() => {\n const map: Record<string, CellChart | undefined> = {};\n config?.columns?.forEach((col) => {\n const fieldKey = col.key || col.field;\n if (fieldKey && col.cellChart) {\n map[fieldKey] = col.cellChart;\n }\n });\n return map;\n }, [config?.columns]);\n\n // Per-column conditional rules (column-level overrides win over table-level).\n const tableRules = config?.conditionalRules;\n // Row-level conditional rules: tint the whole <tr> (escalation/breach rows).\n const rowRules = config?.rowRules;\n const columnRules: Record<string, ConditionalRule[] | undefined> = useMemo(() => {\n const map: Record<string, ConditionalRule[] | undefined> = {};\n config?.columns?.forEach((col) => {\n const fieldKey = col.key || col.field;\n if (fieldKey && col.conditionalRules?.length) {\n map[fieldKey] = col.conditionalRules;\n }\n });\n return map;\n }, [config?.columns]);\n\n // Per-column totals/aggregates for the footer row.\n const columnTotals: Record<string, TableColumnConfig['total']> = useMemo(() => {\n const map: Record<string, TableColumnConfig['total']> = {};\n config?.columns?.forEach((col) => {\n const fieldKey = col.key || col.field;\n if (fieldKey && col.total && col.total !== 'none') {\n map[fieldKey] = col.total;\n }\n });\n return map;\n }, [config?.columns]);\n\n const hasTotals = config?.showTotals === true || Object.keys(columnTotals).length > 0;\n\n // Process data: filter, sort, paginate\n const processedData = useMemo(() => {\n let result = data;\n\n // Filter\n if (config?.showSearch !== false) {\n result = filterData(result, searchQuery, columns);\n }\n\n // Sort\n result = sortData(result, sortState);\n\n return result;\n }, [data, searchQuery, sortState, columns, config?.showSearch]);\n\n // Max absolute numeric value per column — scales in-cell data bars.\n const dataBarMaxes: Record<string, number> = useMemo(() => {\n const maxes: Record<string, number> = {};\n for (const col of columns) {\n let max = 0;\n for (const row of processedData) {\n const v = row[col.key];\n if (typeof v === 'number' && Number.isFinite(v)) {\n max = Math.max(max, Math.abs(v));\n }\n }\n maxes[col.key] = max;\n }\n return maxes;\n }, [columns, processedData]);\n\n // Footer totals/aggregates per column (computed over the filtered set).\n const footerTotals: Record<string, number | null> = useMemo(() => {\n if (!hasTotals) return {};\n const result: Record<string, number | null> = {};\n for (const col of columns) {\n const agg = columnTotals[col.key];\n if (!agg) {\n result[col.key] = null;\n continue;\n }\n const nums = processedData\n .map((row) => row[col.key])\n .filter((v): v is number => typeof v === 'number' && Number.isFinite(v));\n if (agg === 'count') {\n result[col.key] = processedData.length;\n } else if (nums.length === 0) {\n result[col.key] = null;\n } else if (agg === 'sum') {\n result[col.key] = nums.reduce((a, b) => a + b, 0);\n } else if (agg === 'avg') {\n result[col.key] = nums.reduce((a, b) => a + b, 0) / nums.length;\n } else if (agg === 'min') {\n result[col.key] = Math.min(...nums);\n } else if (agg === 'max') {\n result[col.key] = Math.max(...nums);\n } else {\n result[col.key] = null;\n }\n }\n return result;\n }, [hasTotals, columns, columnTotals, processedData]);\n\n // Update total items when data changes\n useMemo(() => {\n setPagination((prev) => ({\n ...prev,\n totalItems: processedData.length,\n }));\n }, [processedData.length]);\n\n // Paginate\n const paginatedData = useMemo(() => {\n if (config?.showPagination === false) {\n return processedData;\n }\n const start = pagination.page * pagination.pageSize;\n return processedData.slice(start, start + pagination.pageSize);\n }, [processedData, pagination, config?.showPagination]);\n\n // ----------------------------------------------------------------------------\n // Row virtualization (windowing) — render only the visible slice of a large\n // dataset. Active when config.virtualized is set; bypasses pagination so the\n // whole filtered set scrolls in one container. No external dependency: a fixed\n // row height + top/bottom spacer rows reproduce the full scroll height.\n // ----------------------------------------------------------------------------\n const virtualized = config?.virtualized === true;\n const rowHeight = config?.rowHeight && config.rowHeight > 0 ? config.rowHeight : 44;\n const scrollRef = useRef<HTMLDivElement>(null);\n const [scrollTop, setScrollTop] = useState(0);\n const [viewportH, setViewportH] = useState(0);\n\n useEffect(() => {\n if (!virtualized) return undefined;\n const el = scrollRef.current;\n if (!el) return undefined;\n setViewportH(el.clientHeight);\n const ro =\n typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => setViewportH(el.clientHeight)) : undefined;\n ro?.observe(el);\n return () => ro?.disconnect();\n }, [virtualized]);\n\n const OVERSCAN = 6;\n const virtualWindow = useMemo(() => {\n if (!virtualized) return null;\n const total = processedData.length;\n const visibleCount = viewportH > 0 ? Math.ceil(viewportH / rowHeight) : 20;\n const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - OVERSCAN);\n const endIndex = Math.min(total, startIndex + visibleCount + OVERSCAN * 2);\n return {\n rows: processedData.slice(startIndex, endIndex),\n topSpacer: startIndex * rowHeight,\n bottomSpacer: Math.max(0, (total - endIndex) * rowHeight),\n startIndex,\n };\n }, [virtualized, processedData, viewportH, scrollTop, rowHeight]);\n\n // The rows actually rendered in <tbody>: windowed slice or the paginated page.\n const renderRows = virtualized && virtualWindow ? virtualWindow.rows : paginatedData;\n const rowBaseIndex = virtualized && virtualWindow ? virtualWindow.startIndex : 0;\n\n // Sticky-left frozen columns: give the first N columns explicit widths so the\n // header and body align, then compute each one's cumulative left offset.\n const frozenCount = config?.frozenColumns && config.frozenColumns > 0 ? config.frozenColumns : 0;\n const FROZEN_DEFAULT_W = 160;\n const { displayColumns, frozenLefts } = useMemo(() => {\n if (frozenCount <= 0) return { displayColumns: columns, frozenLefts: {} as Record<string, number> };\n const lefts: Record<string, number> = {};\n let cursor = 0;\n const cols = columns.map((col, i) => {\n if (i < frozenCount) {\n const widthPx = col.width ? parseInt(col.width, 10) || FROZEN_DEFAULT_W : FROZEN_DEFAULT_W;\n lefts[col.key] = cursor;\n cursor += widthPx;\n return { ...col, width: `${widthPx}px` };\n }\n return col;\n });\n return { displayColumns: cols, frozenLefts: lefts };\n }, [columns, frozenCount]);\n\n // Handlers\n const handleSort = useCallback((column: string) => {\n setSortState((prev) => {\n if (prev.column !== column) {\n return { column, direction: 'asc' };\n }\n const nextDirection: SortDirection = prev.direction === 'asc' ? 'desc' : prev.direction === 'desc' ? null : 'asc';\n return {\n column: nextDirection ? column : null,\n direction: nextDirection,\n };\n });\n }, []);\n\n const handlePageChange = useCallback((page: number) => {\n setPagination((prev) => ({ ...prev, page }));\n }, []);\n\n const handlePageSizeChange = useCallback((pageSize: number) => {\n setPagination((prev) => ({ ...prev, pageSize, page: 0 }));\n }, []);\n\n const handleRowClick = useCallback(\n (row: Record<string, unknown>, index: number) => {\n // Trigger cross-filtering on row click when explicitly enabled via config\n const hasCrossFilterConfig = config?.crossFilterMappings?.length || config?.enableCrossFilter;\n if (crossFilterEnabled && hasCrossFilterConfig) {\n const firstColumn = columns[0]?.key;\n const label = firstColumn ? String(row[firstColumn]) : undefined;\n triggerCrossFilter(row as Record<string, unknown>, label);\n }\n\n // Trigger drilldown if configured\n if (onDrilldown) {\n onDrilldown(row);\n } else if (onRowClick && widget.drilldowns?.length) {\n onRowClick(row, index);\n }\n },\n [\n onRowClick,\n onDrilldown,\n widget.drilldowns,\n crossFilterEnabled,\n triggerCrossFilter,\n columns,\n config?.crossFilterMappings,\n config?.enableCrossFilter,\n ]\n );\n\n // Export to CSV\n const handleExport = useCallback(() => {\n const headers = columns.map((c) => c.header).join(',');\n const rows = processedData.map((row) =>\n columns\n .map((col) => {\n const value = row[col.key];\n if (value == null) return '';\n const str = String(value);\n // Escape quotes and wrap in quotes if contains comma\n if (str.includes(',') || str.includes('\"')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n })\n .join(',')\n );\n const csv = [headers, ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `${widget.title || 'table'}-${new Date().toISOString().split('T')[0]}.csv`;\n a.click();\n URL.revokeObjectURL(url);\n }, [columns, processedData, widget.title]);\n\n // Row is only clickable if drilldown or cross-filter is explicitly configured\n const hasCrossFilterConfig = config?.crossFilterMappings?.length || config?.enableCrossFilter;\n const isClickable = Boolean(onDrilldown || (onRowClick && widget.drilldowns?.length) || hasCrossFilterConfig);\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Cross-filter active indicator with clear button */}\n {hasCrossFilters && (\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-border-default\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n clearAllCrossFilters();\n }}\n className=\"\n inline-flex items-center gap-1.5\n px-2.5 py-1 rounded-md\n text-xs font-medium\n bg-action-primary-bg/10 text-action-primary-text\n hover:bg-action-primary-bg/20\n transition-colors\n \"\n >\n <svg className=\"w-3.5 h-3.5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 19l-7-7m0 0l7-7m-7 7h18\" />\n </svg>\n Clear Filter\n </button>\n </div>\n )}\n\n {/* Toolbar */}\n {(config?.showSearch !== false || config?.showExport !== false) && (\n <div className=\"flex items-center justify-between p-3 border-b border-border-default\">\n {/* Search */}\n {config?.showSearch !== false && (\n <div className=\"relative flex items-center\">\n <svg\n className=\"absolute left-3 w-4 h-4 text-text-secondary pointer-events-none\"\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 <input\n type=\"text\"\n placeholder=\"Search products...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"\n pl-10 pr-4 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-action-primary-bg\n w-48 sm:w-64\n placeholder:text-text-tertiary\n \"\n />\n </div>\n )}\n\n {/* Export */}\n {config?.showExport !== false && (\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"\n inline-flex items-center gap-2\n px-3 py-2\n text-sm font-medium\n text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n hover:bg-bg-sunken\n transition-colors\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n Export CSV\n </button>\n )}\n </div>\n )}\n\n {/* Table */}\n <div\n ref={scrollRef}\n className=\"flex-1 overflow-auto\"\n onScroll={virtualized ? (e) => setScrollTop((e.target as HTMLDivElement).scrollTop) : undefined}\n >\n <table className=\"w-full\">\n <TableHeader\n columns={displayColumns}\n sortState={sortState}\n onSort={handleSort}\n frozenLefts={frozenCount > 0 ? frozenLefts : undefined}\n editable={isEditMode}\n onColumnResize={isEditMode ? handleColumnResize : undefined}\n onColumnResizeEnd={isEditMode ? handleColumnResizeEnd : undefined}\n onColumnReorder={isEditMode ? handleColumnReorder : undefined}\n />\n <tbody>\n {/* Virtualization top spacer */}\n {virtualized && virtualWindow && virtualWindow.topSpacer > 0 && (\n <tr aria-hidden=\"true\" style={{ height: virtualWindow.topSpacer }}>\n <td colSpan={displayColumns.length} style={{ padding: 0, border: 'none' }} />\n </tr>\n )}\n {renderRows.map((row, i) => {\n const rowIndex = rowBaseIndex + i;\n return (\n <tr\n key={rowIndex}\n className={`\n ${isClickable ? 'cursor-pointer hover:bg-bg-sunken/50' : ''}\n ${rowIndex % 2 === 0 ? 'bg-bg-surface' : 'bg-bg-sunken/30'}\n ${resolveRowClassName(row, rowRules)}\n `}\n style={virtualized ? { height: rowHeight } : undefined}\n onClick={() => handleRowClick(row, rowIndex)}\n >\n {displayColumns.map((column) => (\n <TableCell\n key={column.key}\n value={row[column.key]}\n column={column}\n format={cellFormats[column.key]}\n rowData={row}\n conditionalRules={columnRules[column.key] ?? tableRules}\n dataBarMax={dataBarMaxes[column.key]}\n stickyLeft={frozenLefts[column.key]}\n cellChart={columnCellCharts[column.key]}\n />\n ))}\n </tr>\n );\n })}\n {/* Virtualization bottom spacer */}\n {virtualized && virtualWindow && virtualWindow.bottomSpacer > 0 && (\n <tr aria-hidden=\"true\" style={{ height: virtualWindow.bottomSpacer }}>\n <td colSpan={displayColumns.length} style={{ padding: 0, border: 'none' }} />\n </tr>\n )}\n </tbody>\n {hasTotals && processedData.length > 0 && (\n <tfoot>\n <tr className=\"bg-bg-sunken/50 font-semibold border-t-2 border-border-default\">\n {displayColumns.map((column, colIndex) => {\n const total = footerTotals[column.key];\n const format = cellFormats[column.key];\n const frozen = frozenLefts[column.key];\n return (\n <td\n key={column.key}\n className={`\n px-4 py-3 text-sm text-text-primary\n ${column.align === 'center' ? 'text-center' : ''}\n ${column.align === 'right' ? 'text-right' : 'text-left'}\n ${frozen != null ? 'sticky z-10 bg-bg-sunken' : ''}\n `}\n style={frozen != null ? { left: frozen } : undefined}\n >\n {total != null\n ? formatTotal(total, format?.type, format?.currency)\n : colIndex === 0\n ? 'Total'\n : ''}\n </td>\n );\n })}\n </tr>\n </tfoot>\n )}\n </table>\n\n {processedData.length === 0 && (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n {searchQuery ? 'No matching records found' : 'No data available'}\n </div>\n )}\n </div>\n\n {/* Pagination (suppressed in virtualized mode — the whole set scrolls) */}\n {!virtualized && config?.showPagination !== false && processedData.length > 0 && (\n <TablePagination\n pagination={{ ...pagination, totalItems: processedData.length }}\n onPageChange={handlePageChange}\n onPageSizeChange={handlePageSizeChange}\n />\n )}\n </div>\n );\n});\n\nexport default TableWidget;\n"],"mappings":"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"}
1
+ {"version":3,"file":"TableWidget.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/table/TableWidget.tsx"],"sourcesContent":["/**\n * TableWidget Component\n *\n * Data table with sorting, filtering, and pagination.\n */\n\nimport { type FC, memo, useState, useMemo, useCallback, useRef, useEffect } from 'react';\nimport type { Widget, TableConfig, TableColumnConfig } from '../../../types';\nimport { TableHeader, type ColumnDefinition, type SortState, type SortDirection } from './TableHeader';\nimport { TableCell, type CellFormatConfig, type CellChart } from './TableCell';\nimport { TablePagination, type PaginationState } from './TablePagination';\nimport { resolveRowClassName, type ConditionalRule } from '../utils/conditionalFormat';\n// Phase 2: Cross-filtering support\nimport { useWidgetCrossFilter, useCrossFilterContext } from '../../../contexts';\nimport { useIsEditMode } from '../../../store';\nimport { useWidgetConfigPersist } from '../../../hooks';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TableWidgetProps {\n /** Widget configuration */\n widget: Widget;\n /** Table data (array of rows) */\n data?: Record<string, unknown>[];\n /** Click handler for row drilldown */\n onRowClick?: (row: Record<string, unknown>, index: number) => void;\n /** Drilldown handler - receives selected row data */\n onDrilldown?: (selectedData: Record<string, unknown>) => void;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\nfunction sortData(data: Record<string, unknown>[], sortState: SortState): Record<string, unknown>[] {\n if (!sortState.column || !sortState.direction) {\n return data;\n }\n\n return [...data].sort((a, b) => {\n const aValue = a[sortState.column!];\n const bValue = b[sortState.column!];\n\n // Handle null/undefined\n if (aValue == null && bValue == null) return 0;\n if (aValue == null) return sortState.direction === 'asc' ? 1 : -1;\n if (bValue == null) return sortState.direction === 'asc' ? -1 : 1;\n\n // Compare values\n let comparison = 0;\n if (typeof aValue === 'number' && typeof bValue === 'number') {\n comparison = aValue - bValue;\n } else {\n comparison = String(aValue).localeCompare(String(bValue));\n }\n\n return sortState.direction === 'asc' ? comparison : -comparison;\n });\n}\n\nfunction filterData(\n data: Record<string, unknown>[],\n searchQuery: string,\n columns: ColumnDefinition[]\n): Record<string, unknown>[] {\n if (!searchQuery.trim()) {\n return data;\n }\n\n const query = searchQuery.toLowerCase();\n return data.filter((row) =>\n columns.some((col) => {\n const value = row[col.key];\n return value != null && String(value).toLowerCase().includes(query);\n })\n );\n}\n\n/** Format a footer total respecting the column's cell format. */\nfunction formatTotal(value: number, formatType?: string, currency?: string): string {\n switch (formatType) {\n case 'currency':\n return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency || 'USD' }).format(value);\n case 'percentage':\n return `${(value * 100).toFixed(1)}%`;\n case 'number':\n return new Intl.NumberFormat('en-US').format(value);\n default:\n return Number.isInteger(value) ? String(value) : value.toFixed(2);\n }\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const TableWidget: FC<TableWidgetProps> = memo(function TableWidget({\n widget,\n data: rawData = [],\n onRowClick,\n onDrilldown,\n}) {\n const config = widget.config as unknown as TableConfig | undefined;\n\n // Normalize data - handle both array and object formats\n const data = useMemo(() => {\n // If it's already an array, use it directly\n if (Array.isArray(rawData)) {\n return rawData;\n }\n\n // If it's an object, try to find an array property\n if (rawData && typeof rawData === 'object') {\n // Check common array property names\n const possibleArrayProps = ['data', 'items', 'rows', 'results', 'records', 'list', 'sales', 'entries', 'values'];\n for (const prop of possibleArrayProps) {\n if (Array.isArray((rawData as Record<string, unknown>)[prop])) {\n return (rawData as Record<string, unknown>)[prop] as Record<string, unknown>[];\n }\n }\n\n // If object has array values, use the first one\n const values = Object.values(rawData);\n for (const value of values) {\n if (Array.isArray(value)) {\n return value as Record<string, unknown>[];\n }\n }\n\n // If it's a single object, wrap it in an array\n return [rawData as Record<string, unknown>];\n }\n\n return [];\n }, [rawData]);\n\n // Phase 2: Cross-filtering support\n const { handleDataClick: triggerCrossFilter, enabled: crossFilterEnabled } = useWidgetCrossFilter(\n widget.id,\n // Map first column to filter ID by default, can be configured\n config?.crossFilterMappings\n );\n const { clearAllCrossFilters, hasCrossFilters } = useCrossFilterContext();\n\n // State\n const [sortState, setSortState] = useState<SortState>({\n column: config?.defaultSortColumn || null,\n direction: (config?.defaultSortDirection as SortDirection) || null,\n });\n const [searchQuery, setSearchQuery] = useState('');\n const [pagination, setPagination] = useState<PaginationState>({\n page: 0,\n pageSize: config?.pageSize || 10,\n totalItems: 0,\n });\n\n // Interactive column resize/reorder (edit mode only). Local overrides give\n // live feedback; the committed layout is persisted to config so it survives\n // reload (see commitColumnLayout).\n const isEditMode = useIsEditMode();\n const { persistConfig } = useWidgetConfigPersist(widget);\n const [widthOverrides, setWidthOverrides] = useState<Record<string, number>>({});\n const [columnOrder, setColumnOrder] = useState<string[] | null>(null);\n\n // Build column definitions from config or data\n const baseColumns: ColumnDefinition[] = useMemo(() => {\n if (config?.columns?.length) {\n return (config.columns as (string | TableColumnConfig)[]).map((col) => {\n // Handle both string and object column definitions\n if (typeof col === 'string') {\n return {\n key: col,\n header: col.charAt(0).toUpperCase() + col.slice(1).replace(/_/g, ' '),\n sortable: true,\n };\n }\n // Object column definition — accept both .key and .field\n const fieldKey = col.key || col.field || '';\n return {\n key: fieldKey,\n header: col.label || col.header || fieldKey,\n sortable: col.sortable !== false,\n width: col.width != null ? String(col.width) : undefined,\n align: col.align as ColumnDefinition['align'],\n };\n }) as ColumnDefinition[];\n }\n\n // Auto-generate from first data row\n if (data.length > 0) {\n return Object.keys(data[0]).map((key) => ({\n key,\n header: key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, ' '),\n sortable: true,\n }));\n }\n\n return [];\n }, [config?.columns, data]);\n\n // Apply the live reorder + resize overrides on top of the base columns. Any\n // column not named in columnOrder keeps its original relative position at the\n // end (handles columns added since the order was set).\n const columns: ColumnDefinition[] = useMemo(() => {\n let cols = baseColumns;\n if (columnOrder) {\n const byKey = new Map(baseColumns.map((c) => [c.key, c]));\n const ordered = columnOrder.map((k) => byKey.get(k)).filter((c): c is ColumnDefinition => c !== undefined);\n const rest = baseColumns.filter((c) => !columnOrder.includes(c.key));\n cols = [...ordered, ...rest];\n }\n if (Object.keys(widthOverrides).length > 0) {\n cols = cols.map((c) => (widthOverrides[c.key] != null ? { ...c, width: `${widthOverrides[c.key]}px` } : c));\n }\n return cols;\n }, [baseColumns, columnOrder, widthOverrides]);\n\n // Persist the current column order + widths to config.columns. Builds explicit\n // column objects (preserving any existing per-column config), which also pins\n // a previously auto-generated column set so order/width survive reload.\n const commitColumnLayout = useCallback(\n (orderKeys: string[], widths: Record<string, number>) => {\n const existing = new Map<string, TableColumnConfig>();\n config?.columns?.forEach((c) => {\n const k = c.key || c.field;\n if (k) existing.set(k, c);\n });\n const baseKeys = baseColumns.map((c) => c.key);\n const finalKeys = [\n ...orderKeys.filter((k) => baseKeys.includes(k)),\n ...baseKeys.filter((k) => !orderKeys.includes(k)),\n ];\n const newColumns: TableColumnConfig[] = finalKeys.map((key) => {\n const base = baseColumns.find((c) => c.key === key);\n const prev = existing.get(key);\n const next: TableColumnConfig = { ...(prev ?? {}), key };\n // Preserve a header label for columns that had none in config (auto-generated).\n if (!next.label && !next.header && base?.header) next.label = base.header;\n const w = widths[key];\n if (w != null) next.width = w;\n return next;\n });\n void persistConfig({ columns: newColumns });\n },\n [baseColumns, config?.columns, persistConfig]\n );\n\n const handleColumnResize = useCallback((key: string, widthPx: number) => {\n setWidthOverrides((prev) => ({ ...prev, [key]: widthPx }));\n }, []);\n\n const handleColumnResizeEnd = useCallback(\n (key: string, widthPx: number) => {\n const widths = { ...widthOverrides, [key]: widthPx };\n setWidthOverrides(widths);\n const order = columnOrder ?? baseColumns.map((c) => c.key);\n commitColumnLayout(order, widths);\n },\n [widthOverrides, columnOrder, baseColumns, commitColumnLayout]\n );\n\n const handleColumnReorder = useCallback(\n (sourceKey: string, targetKey: string) => {\n const current = columnOrder ?? baseColumns.map((c) => c.key);\n const from = current.indexOf(sourceKey);\n if (from < 0 || sourceKey === targetKey) return;\n const next = [...current];\n next.splice(from, 1);\n // Insert before the target's *post-removal* index, so dropping onto a\n // column always lands the source just before it regardless of direction.\n const insertAt = next.indexOf(targetKey);\n if (insertAt < 0) return;\n next.splice(insertAt, 0, sourceKey);\n setColumnOrder(next);\n commitColumnLayout(next, widthOverrides);\n },\n [columnOrder, baseColumns, widthOverrides, commitColumnLayout]\n );\n\n // Build cell format configs\n const cellFormats: Record<string, CellFormatConfig> = useMemo(() => {\n const formats: Record<string, CellFormatConfig> = {};\n config?.columns?.forEach((col) => {\n if (col.format && (col.key || col.field)) {\n formats[col.key || col.field || ''] = {\n type: col.format,\n ...(col.linkPattern ? { linkPattern: col.linkPattern } : {}),\n };\n }\n });\n return formats;\n }, [config?.columns]);\n\n // Per-column in-cell chart kind (e.g. sparkline).\n const columnCellCharts: Record<string, CellChart | undefined> = useMemo(() => {\n const map: Record<string, CellChart | undefined> = {};\n config?.columns?.forEach((col) => {\n const fieldKey = col.key || col.field;\n if (fieldKey && col.cellChart) {\n map[fieldKey] = col.cellChart;\n }\n });\n return map;\n }, [config?.columns]);\n\n // Per-column conditional rules (column-level overrides win over table-level).\n const tableRules = config?.conditionalRules;\n // Row-level conditional rules: tint the whole <tr> (escalation/breach rows).\n const rowRules = config?.rowRules;\n const columnRules: Record<string, ConditionalRule[] | undefined> = useMemo(() => {\n const map: Record<string, ConditionalRule[] | undefined> = {};\n config?.columns?.forEach((col) => {\n const fieldKey = col.key || col.field;\n if (fieldKey && col.conditionalRules?.length) {\n map[fieldKey] = col.conditionalRules;\n }\n });\n return map;\n }, [config?.columns]);\n\n // Per-column totals/aggregates for the footer row.\n const columnTotals: Record<string, TableColumnConfig['total']> = useMemo(() => {\n const map: Record<string, TableColumnConfig['total']> = {};\n config?.columns?.forEach((col) => {\n const fieldKey = col.key || col.field;\n if (fieldKey && col.total && col.total !== 'none') {\n map[fieldKey] = col.total;\n }\n });\n return map;\n }, [config?.columns]);\n\n const hasTotals = config?.showTotals === true || Object.keys(columnTotals).length > 0;\n\n // Process data: filter, sort, paginate\n const processedData = useMemo(() => {\n let result = data;\n\n // Filter\n if (config?.showSearch !== false) {\n result = filterData(result, searchQuery, columns);\n }\n\n // Sort\n result = sortData(result, sortState);\n\n return result;\n }, [data, searchQuery, sortState, columns, config?.showSearch]);\n\n // Max absolute numeric value per column — scales in-cell data bars.\n const dataBarMaxes: Record<string, number> = useMemo(() => {\n const maxes: Record<string, number> = {};\n for (const col of columns) {\n let max = 0;\n for (const row of processedData) {\n const v = row[col.key];\n if (typeof v === 'number' && Number.isFinite(v)) {\n max = Math.max(max, Math.abs(v));\n }\n }\n maxes[col.key] = max;\n }\n return maxes;\n }, [columns, processedData]);\n\n // Footer totals/aggregates per column (computed over the filtered set).\n const footerTotals: Record<string, number | null> = useMemo(() => {\n if (!hasTotals) return {};\n const result: Record<string, number | null> = {};\n for (const col of columns) {\n const agg = columnTotals[col.key];\n if (!agg) {\n result[col.key] = null;\n continue;\n }\n const nums = processedData\n .map((row) => row[col.key])\n .filter((v): v is number => typeof v === 'number' && Number.isFinite(v));\n if (agg === 'count') {\n result[col.key] = processedData.length;\n } else if (nums.length === 0) {\n result[col.key] = null;\n } else if (agg === 'sum') {\n result[col.key] = nums.reduce((a, b) => a + b, 0);\n } else if (agg === 'avg') {\n result[col.key] = nums.reduce((a, b) => a + b, 0) / nums.length;\n } else if (agg === 'min') {\n result[col.key] = Math.min(...nums);\n } else if (agg === 'max') {\n result[col.key] = Math.max(...nums);\n } else {\n result[col.key] = null;\n }\n }\n return result;\n }, [hasTotals, columns, columnTotals, processedData]);\n\n // Update total items when data changes\n useMemo(() => {\n setPagination((prev) => ({\n ...prev,\n totalItems: processedData.length,\n }));\n }, [processedData.length]);\n\n // Paginate\n const paginatedData = useMemo(() => {\n if (config?.showPagination === false) {\n return processedData;\n }\n const start = pagination.page * pagination.pageSize;\n return processedData.slice(start, start + pagination.pageSize);\n }, [processedData, pagination, config?.showPagination]);\n\n // ----------------------------------------------------------------------------\n // Row virtualization (windowing) — render only the visible slice of a large\n // dataset. Active when config.virtualized is set; bypasses pagination so the\n // whole filtered set scrolls in one container. No external dependency: a fixed\n // row height + top/bottom spacer rows reproduce the full scroll height.\n // ----------------------------------------------------------------------------\n const virtualized = config?.virtualized === true;\n const rowHeight = config?.rowHeight && config.rowHeight > 0 ? config.rowHeight : 44;\n const scrollRef = useRef<HTMLDivElement>(null);\n const [scrollTop, setScrollTop] = useState(0);\n const [viewportH, setViewportH] = useState(0);\n\n useEffect(() => {\n if (!virtualized) return undefined;\n const el = scrollRef.current;\n if (!el) return undefined;\n setViewportH(el.clientHeight);\n const ro =\n typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => setViewportH(el.clientHeight)) : undefined;\n ro?.observe(el);\n return () => ro?.disconnect();\n }, [virtualized]);\n\n const OVERSCAN = 6;\n const virtualWindow = useMemo(() => {\n if (!virtualized) return null;\n const total = processedData.length;\n const visibleCount = viewportH > 0 ? Math.ceil(viewportH / rowHeight) : 20;\n const startIndex = Math.max(0, Math.floor(scrollTop / rowHeight) - OVERSCAN);\n const endIndex = Math.min(total, startIndex + visibleCount + OVERSCAN * 2);\n return {\n rows: processedData.slice(startIndex, endIndex),\n topSpacer: startIndex * rowHeight,\n bottomSpacer: Math.max(0, (total - endIndex) * rowHeight),\n startIndex,\n };\n }, [virtualized, processedData, viewportH, scrollTop, rowHeight]);\n\n // The rows actually rendered in <tbody>: windowed slice or the paginated page.\n const renderRows = virtualized && virtualWindow ? virtualWindow.rows : paginatedData;\n const rowBaseIndex = virtualized && virtualWindow ? virtualWindow.startIndex : 0;\n\n // Sticky-left frozen columns: give the first N columns explicit widths so the\n // header and body align, then compute each one's cumulative left offset.\n const frozenCount = config?.frozenColumns && config.frozenColumns > 0 ? config.frozenColumns : 0;\n const FROZEN_DEFAULT_W = 160;\n const { displayColumns, frozenLefts } = useMemo(() => {\n if (frozenCount <= 0) return { displayColumns: columns, frozenLefts: {} as Record<string, number> };\n const lefts: Record<string, number> = {};\n let cursor = 0;\n const cols = columns.map((col, i) => {\n if (i < frozenCount) {\n const widthPx = col.width ? parseInt(col.width, 10) || FROZEN_DEFAULT_W : FROZEN_DEFAULT_W;\n lefts[col.key] = cursor;\n cursor += widthPx;\n return { ...col, width: `${widthPx}px` };\n }\n return col;\n });\n return { displayColumns: cols, frozenLefts: lefts };\n }, [columns, frozenCount]);\n\n // Handlers\n const handleSort = useCallback((column: string) => {\n setSortState((prev) => {\n if (prev.column !== column) {\n return { column, direction: 'asc' };\n }\n const nextDirection: SortDirection = prev.direction === 'asc' ? 'desc' : prev.direction === 'desc' ? null : 'asc';\n return {\n column: nextDirection ? column : null,\n direction: nextDirection,\n };\n });\n }, []);\n\n const handlePageChange = useCallback((page: number) => {\n setPagination((prev) => ({ ...prev, page }));\n }, []);\n\n const handlePageSizeChange = useCallback((pageSize: number) => {\n setPagination((prev) => ({ ...prev, pageSize, page: 0 }));\n }, []);\n\n const handleRowClick = useCallback(\n (row: Record<string, unknown>, index: number) => {\n // Trigger cross-filtering on row click when explicitly enabled via config\n const hasCrossFilterConfig = config?.crossFilterMappings?.length || config?.enableCrossFilter;\n if (crossFilterEnabled && hasCrossFilterConfig) {\n const firstColumn = columns[0]?.key;\n const label = firstColumn ? String(row[firstColumn]) : undefined;\n triggerCrossFilter(row as Record<string, unknown>, label);\n }\n\n // Trigger drilldown if configured\n if (onDrilldown) {\n onDrilldown(row);\n } else if (onRowClick && widget.drilldowns?.length) {\n onRowClick(row, index);\n }\n },\n [\n onRowClick,\n onDrilldown,\n widget.drilldowns,\n crossFilterEnabled,\n triggerCrossFilter,\n columns,\n config?.crossFilterMappings,\n config?.enableCrossFilter,\n ]\n );\n\n // Export to CSV\n const handleExport = useCallback(() => {\n const headers = columns.map((c) => c.header).join(',');\n const rows = processedData.map((row) =>\n columns\n .map((col) => {\n const value = row[col.key];\n if (value == null) return '';\n const str = String(value);\n // Escape quotes and wrap in quotes if contains comma\n if (str.includes(',') || str.includes('\"')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n })\n .join(',')\n );\n const csv = [headers, ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `${widget.title || 'table'}-${new Date().toISOString().split('T')[0]}.csv`;\n a.click();\n URL.revokeObjectURL(url);\n }, [columns, processedData, widget.title]);\n\n // Row is only clickable if drilldown or cross-filter is explicitly configured\n const hasCrossFilterConfig = config?.crossFilterMappings?.length || config?.enableCrossFilter;\n const isClickable = Boolean(onDrilldown || (onRowClick && widget.drilldowns?.length) || hasCrossFilterConfig);\n\n return (\n <div className=\"flex flex-col h-full\">\n {/* Cross-filter active indicator with clear button */}\n {hasCrossFilters && (\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-border-default\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n clearAllCrossFilters();\n }}\n className=\"\n inline-flex items-center gap-1.5\n px-2.5 py-1 rounded-md\n text-xs font-medium\n bg-action-primary-bg/10 text-action-primary-text\n hover:bg-action-primary-bg/20\n transition-colors\n \"\n >\n <svg className=\"w-3.5 h-3.5\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M10 19l-7-7m0 0l7-7m-7 7h18\" />\n </svg>\n Clear Filter\n </button>\n </div>\n )}\n\n {/* Toolbar */}\n {(config?.showSearch !== false || config?.showExport !== false) && (\n <div className=\"flex items-center justify-between p-3 border-b border-border-default\">\n {/* Search */}\n {config?.showSearch !== false && (\n <div className=\"relative flex items-center\">\n <svg\n className=\"absolute left-3 w-4 h-4 text-text-secondary pointer-events-none\"\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 <input\n type=\"text\"\n placeholder=\"Search products...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"\n pl-10 pr-4 py-2\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg focus:border-action-primary-bg\n w-48 sm:w-64\n placeholder:text-text-tertiary\n \"\n />\n </div>\n )}\n\n {/* Export */}\n {config?.showExport !== false && (\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"\n inline-flex items-center gap-2\n px-3 py-2\n text-sm font-medium\n text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n hover:bg-bg-sunken\n transition-colors\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4\"\n />\n </svg>\n Export CSV\n </button>\n )}\n </div>\n )}\n\n {/* Table */}\n <div\n ref={scrollRef}\n className=\"flex-1 overflow-auto\"\n onScroll={virtualized ? (e) => setScrollTop((e.target as HTMLDivElement).scrollTop) : undefined}\n >\n <table className=\"w-full\">\n <TableHeader\n columns={displayColumns}\n sortState={sortState}\n onSort={handleSort}\n frozenLefts={frozenCount > 0 ? frozenLefts : undefined}\n editable={isEditMode}\n onColumnResize={isEditMode ? handleColumnResize : undefined}\n onColumnResizeEnd={isEditMode ? handleColumnResizeEnd : undefined}\n onColumnReorder={isEditMode ? handleColumnReorder : undefined}\n />\n <tbody>\n {/* Virtualization top spacer */}\n {virtualized && virtualWindow && virtualWindow.topSpacer > 0 && (\n <tr aria-hidden=\"true\" style={{ height: virtualWindow.topSpacer }}>\n <td colSpan={displayColumns.length} style={{ padding: 0, border: 'none' }} />\n </tr>\n )}\n {renderRows.map((row, i) => {\n const rowIndex = rowBaseIndex + i;\n return (\n <tr\n key={rowIndex}\n className={`\n ${isClickable ? 'cursor-pointer hover:bg-bg-sunken/50' : ''}\n ${rowIndex % 2 === 0 ? 'bg-bg-surface' : 'bg-bg-sunken/30'}\n ${resolveRowClassName(row, rowRules)}\n `}\n style={virtualized ? { height: rowHeight } : undefined}\n onClick={() => handleRowClick(row, rowIndex)}\n >\n {displayColumns.map((column) => (\n <TableCell\n key={column.key}\n value={row[column.key]}\n column={column}\n format={cellFormats[column.key]}\n rowData={row}\n conditionalRules={columnRules[column.key] ?? tableRules}\n dataBarMax={dataBarMaxes[column.key]}\n stickyLeft={frozenLefts[column.key]}\n cellChart={columnCellCharts[column.key]}\n />\n ))}\n </tr>\n );\n })}\n {/* Virtualization bottom spacer */}\n {virtualized && virtualWindow && virtualWindow.bottomSpacer > 0 && (\n <tr aria-hidden=\"true\" style={{ height: virtualWindow.bottomSpacer }}>\n <td colSpan={displayColumns.length} style={{ padding: 0, border: 'none' }} />\n </tr>\n )}\n </tbody>\n {hasTotals && processedData.length > 0 && (\n <tfoot>\n <tr className=\"bg-bg-sunken/50 font-semibold border-t-2 border-border-default\">\n {displayColumns.map((column, colIndex) => {\n const total = footerTotals[column.key];\n const format = cellFormats[column.key];\n const frozen = frozenLefts[column.key];\n return (\n <td\n key={column.key}\n className={`\n px-4 py-3 text-sm text-text-primary\n ${column.align === 'center' ? 'text-center' : ''}\n ${column.align === 'right' ? 'text-right' : 'text-left'}\n ${frozen != null ? 'sticky z-10 bg-bg-sunken' : ''}\n `}\n style={frozen != null ? { left: frozen } : undefined}\n >\n {total != null\n ? formatTotal(total, format?.type, format?.currency)\n : colIndex === 0\n ? 'Total'\n : ''}\n </td>\n );\n })}\n </tr>\n </tfoot>\n )}\n </table>\n\n {processedData.length === 0 && (\n <div className=\"flex items-center justify-center py-12 text-text-secondary\">\n {searchQuery ? 'No matching records found' : 'No data available'}\n </div>\n )}\n </div>\n\n {/* Pagination (suppressed in virtualized mode — the whole set scrolls) */}\n {!virtualized && config?.showPagination !== false && processedData.length > 0 && (\n <TablePagination\n pagination={{ ...pagination, totalItems: processedData.length }}\n onPageChange={handlePageChange}\n onPageSizeChange={handlePageSizeChange}\n />\n )}\n </div>\n );\n});\n\nexport default TableWidget;\n"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ //#region src/bigconsole/components/widgets/table/cellLink.ts
2
+ function e(e) {
3
+ return /^https?:\/\//i.test(e);
4
+ }
5
+ function t(t, n, r) {
6
+ if (t && t.trim()) {
7
+ let i = !1, a = t.replace(/\{([^{}]+)\}/g, (e, t) => {
8
+ let a = t.trim(), o = !1, s = a.lastIndexOf("|");
9
+ s !== -1 && a.slice(s + 1).trim() === "encode" && (o = !0, a = a.slice(0, s).trim());
10
+ let c = a === "value" ? n : r?.[a], l = c == null ? "" : String(c);
11
+ return l.trim() === "" ? (i = !0, "") : o ? encodeURIComponent(l) : l;
12
+ });
13
+ if (i) return null;
14
+ let o = a.trim();
15
+ return o && e(o) ? o : null;
16
+ }
17
+ if (typeof n == "string") {
18
+ let t = n.trim();
19
+ if (e(t)) return t;
20
+ }
21
+ return null;
22
+ }
23
+ //#endregion
24
+ export { t as resolveCellLinkHref };
25
+
26
+ //# sourceMappingURL=cellLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cellLink.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/table/cellLink.ts"],"sourcesContent":["/**\n * Cell link resolution\n *\n * Turns a `link`-formatted table cell into a navigable href. Kept as a pure,\n * React-free helper so it can be unit-tested in isolation and reused by both the\n * live table and the embed table renderer.\n */\n\n/** Only http(s) is navigable — everything else (e.g. `javascript:`) is rejected. */\nfunction isSafeHref(candidate: string): boolean {\n return /^https?:\\/\\//i.test(candidate);\n}\n\n/**\n * Resolve the href for a `link`-formatted cell.\n *\n * Two modes:\n * - **Pattern** — `linkPattern` contains `{token}` placeholders. `{value}`\n * substitutes the cell value; any other `{field}` substitutes `rowData[field]`\n * (e.g. `\"{Url}\"`, or `\"https://github.com/Algoshred/{Repo}/pull/{value}\"`).\n * Field names may contain spaces/hyphens/etc. (table keys come from arbitrary\n * data-source/CSV column names, e.g. `{Issue URL}`, `{repo-name}`); a token is\n * any run of non-brace characters and is trimmed before lookup. A token that\n * resolves to null/undefined **or a blank string** rejects the whole href, so\n * an optional-but-empty field degrades to plain text rather than emitting a\n * broken `https://host//pull/42`.\n *\n * Substituted values are **raw** by default so a `{Url}` whose value is a full\n * URL survives intact. Add a `|encode` suffix to a token — `{value|encode}` —\n * to `encodeURIComponent` just that substitution (for query params etc., e.g.\n * `\"https://github.com/search?q={value|encode}\"`).\n *\n * NOTE: `value` is a reserved token — it always resolves to the cell value and\n * shadows a row field literally named `value`. Reference other columns by their\n * own name instead.\n * - **Auto-detect** — no pattern: if the cell value is itself an http(s) URL,\n * use it directly.\n *\n * Returns `null` when nothing safe/navigable resolves, so the caller can fall\n * back to rendering plain text instead of a dead link.\n */\nexport function resolveCellLinkHref(\n linkPattern: string | undefined,\n value: unknown,\n rowData?: Record<string, unknown>\n): string | null {\n if (linkPattern && linkPattern.trim()) {\n let hasMissingToken = false;\n // Match any `{...}` token (non-brace run) so field names with spaces,\n // hyphens or symbols — common in real column headers — still resolve.\n const substituted = linkPattern.replace(/\\{([^{}]+)\\}/g, (_match, token: string) => {\n // Optional `|encode` suffix opts the token into encodeURIComponent.\n let key = token.trim();\n let encode = false;\n const pipe = key.lastIndexOf('|');\n if (pipe !== -1 && key.slice(pipe + 1).trim() === 'encode') {\n encode = true;\n key = key.slice(0, pipe).trim();\n }\n const raw = key === 'value' ? value : rowData?.[key];\n const str = raw == null ? '' : String(raw);\n // Missing (null/undefined) or blank field — never emit a half-built URL.\n if (str.trim() === '') {\n hasMissingToken = true;\n return '';\n }\n return encode ? encodeURIComponent(str) : str;\n });\n if (hasMissingToken) return null;\n const trimmed = substituted.trim();\n // Every balanced `{token}` in the pattern has already been replaced above\n // (missing ones set hasMissingToken), so we DON'T re-scan for a stray `{` —\n // a substituted value may legitimately contain braces (e.g. a URL like\n // `.../v1?filter={status:active}`) and must not be discarded.\n if (!trimmed) return null;\n return isSafeHref(trimmed) ? trimmed : null;\n }\n\n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (isSafeHref(trimmed)) return trimmed;\n }\n\n return null;\n}\n"],"mappings":";AASA,SAAS,EAAW,GAA4B;AAC9C,QAAO,gBAAgB,KAAK,EAAU;;AA+BxC,SAAgB,EACd,GACA,GACA,GACe;AACf,KAAI,KAAe,EAAY,MAAM,EAAE;EACrC,IAAI,IAAkB,IAGhB,IAAc,EAAY,QAAQ,kBAAkB,GAAQ,MAAkB;GAElF,IAAI,IAAM,EAAM,MAAM,EAClB,IAAS,IACP,IAAO,EAAI,YAAY,IAAI;AACjC,GAAI,MAAS,MAAM,EAAI,MAAM,IAAO,EAAE,CAAC,MAAM,KAAK,aAChD,IAAS,IACT,IAAM,EAAI,MAAM,GAAG,EAAK,CAAC,MAAM;GAEjC,IAAM,IAAM,MAAQ,UAAU,IAAQ,IAAU,IAC1C,IAAM,KAAO,OAAO,KAAK,OAAO,EAAI;AAM1C,UAJI,EAAI,MAAM,KAAK,MACjB,IAAkB,IACX,MAEF,IAAS,mBAAmB,EAAI,GAAG;IAC1C;AACF,MAAI,EAAiB,QAAO;EAC5B,IAAM,IAAU,EAAY,MAAM;AAMlC,SADK,KACE,EAAW,EAAQ,GAAG,IAAU;;AAGzC,KAAI,OAAO,KAAU,UAAU;EAC7B,IAAM,IAAU,EAAM,MAAM;AAC5B,MAAI,EAAW,EAAQ,CAAE,QAAO;;AAGlC,QAAO"}
@@ -26,16 +26,19 @@ var t = e`
26
26
  $defaultDashboardName: String
27
27
  $defaultCategory: String
28
28
  $defaultTags: [String!]
29
+ $includeSampleData: Boolean
29
30
  ) {
30
31
  buildDashboardTemplateContent(
31
32
  dashboardId: $dashboardId
32
33
  defaultDashboardName: $defaultDashboardName
33
34
  defaultCategory: $defaultCategory
34
35
  defaultTags: $defaultTags
36
+ includeSampleData: $includeSampleData
35
37
  ) {
36
38
  content
37
39
  sinkCount
38
40
  unknownSinkIds
41
+ sampleDataSinkCount
39
42
  }
40
43
  }
41
44
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"storeTemplates.js","names":[],"sources":["../../../src/bigconsole/graphql/storeTemplates.ts"],"sourcesContent":["/**\n * GraphQL documents for store-distributed dashboard templates.\n *\n * The two catalog reads are generated from\n * `src/operations/global/store/queries/*.graphql` in the usual way.\n *\n * `IMPORT_DASHBOARD_TEMPLATE` is defined inline as an interim measure.\n * `importDashboardTemplate` is new in wspace-bigconsole-svc, so it is not yet\n * present in `graphql/wspace.graphql` — that file is only ever refreshed by\n * `bun run schema:pull` against a live gateway (hand-editing it is a repo\n * guardrail), and the gateway supergraph only carries the field once the\n * service has deployed and Hive has reconciled.\n *\n * It cannot simply be dropped into `src/operations/wspace/**` yet either:\n * `src/generated` is gitignored and CI regenerates it, so an operation file\n * referencing a field the checked-in schema lacks fails codegen and breaks the\n * build. Once `schema:pull` has run, move this document into\n * `src/operations/wspace/dashboard/mutations/importDashboardTemplate.graphql`\n * and import the generated `ImportDashboardTemplateDocument` instead — the\n * shape is unchanged.\n *\n * @module bigconsole/graphql/storeTemplates\n */\nimport { gql } from '@apollo/client';\n\nexport {\n BrowseDashboardTemplatesDocument as BROWSE_DASHBOARD_TEMPLATES,\n GetDashboardTemplateDetailDocument as GET_DASHBOARD_TEMPLATE_DETAIL,\n CreateDashboardTemplateAppDocument as CREATE_TEMPLATE_APP,\n CreateDashboardTemplateVersionDocument as CREATE_TEMPLATE_VERSION,\n} from '../../generated/global-operations';\n\nexport const IMPORT_DASHBOARD_TEMPLATE = gql`\n mutation ImportDashboardTemplate($input: ImportDashboardTemplateInput!) {\n importDashboardTemplate(input: $input) {\n success\n errorMessage\n unresolvedSlots\n resolvedSlots\n widgetsNeedingData\n warnings\n stats {\n pagesCreated\n widgetsCreated\n }\n dashboard {\n id\n name\n }\n }\n }\n`;\n\n/**\n * Same interim treatment as IMPORT_DASHBOARD_TEMPLATE, and for the same reason:\n * buildDashboardTemplateContent is new in wspace-bigconsole-svc and is not yet\n * in the checked-in wspace schema.\n */\nexport const BUILD_DASHBOARD_TEMPLATE_CONTENT = gql`\n query BuildDashboardTemplateContent(\n $dashboardId: ID!\n $defaultDashboardName: String\n $defaultCategory: String\n $defaultTags: [String!]\n ) {\n buildDashboardTemplateContent(\n dashboardId: $dashboardId\n defaultDashboardName: $defaultDashboardName\n defaultCategory: $defaultCategory\n defaultTags: $defaultTags\n ) {\n content\n sinkCount\n unknownSinkIds\n }\n }\n`;\n"],"mappings":";;;AAgCA,IAAa,IAA4B,CAAG;;;;;;;;;;;;;;;;;;;GA0B/B,IAAmC,CAAG"}
1
+ {"version":3,"file":"storeTemplates.js","names":[],"sources":["../../../src/bigconsole/graphql/storeTemplates.ts"],"sourcesContent":["/**\n * GraphQL documents for store-distributed dashboard templates.\n *\n * The two catalog reads are generated from\n * `src/operations/global/store/queries/*.graphql` in the usual way.\n *\n * `IMPORT_DASHBOARD_TEMPLATE` is defined inline as an interim measure.\n * `importDashboardTemplate` is new in wspace-bigconsole-svc, so it is not yet\n * present in `graphql/wspace.graphql` — that file is only ever refreshed by\n * `bun run schema:pull` against a live gateway (hand-editing it is a repo\n * guardrail), and the gateway supergraph only carries the field once the\n * service has deployed and Hive has reconciled.\n *\n * It cannot simply be dropped into `src/operations/wspace/**` yet either:\n * `src/generated` is gitignored and CI regenerates it, so an operation file\n * referencing a field the checked-in schema lacks fails codegen and breaks the\n * build. Once `schema:pull` has run, move this document into\n * `src/operations/wspace/dashboard/mutations/importDashboardTemplate.graphql`\n * and import the generated `ImportDashboardTemplateDocument` instead — the\n * shape is unchanged.\n *\n * @module bigconsole/graphql/storeTemplates\n */\nimport { gql } from '@apollo/client';\n\nexport {\n BrowseDashboardTemplatesDocument as BROWSE_DASHBOARD_TEMPLATES,\n GetDashboardTemplateDetailDocument as GET_DASHBOARD_TEMPLATE_DETAIL,\n CreateDashboardTemplateAppDocument as CREATE_TEMPLATE_APP,\n CreateDashboardTemplateVersionDocument as CREATE_TEMPLATE_VERSION,\n} from '../../generated/global-operations';\n\nexport const IMPORT_DASHBOARD_TEMPLATE = gql`\n mutation ImportDashboardTemplate($input: ImportDashboardTemplateInput!) {\n importDashboardTemplate(input: $input) {\n success\n errorMessage\n unresolvedSlots\n resolvedSlots\n widgetsNeedingData\n warnings\n stats {\n pagesCreated\n widgetsCreated\n }\n dashboard {\n id\n name\n }\n }\n }\n`;\n\n/**\n * Same interim treatment as IMPORT_DASHBOARD_TEMPLATE, and for the same reason:\n * buildDashboardTemplateContent is new in wspace-bigconsole-svc and is not yet\n * in the checked-in wspace schema.\n */\nexport const BUILD_DASHBOARD_TEMPLATE_CONTENT = gql`\n query BuildDashboardTemplateContent(\n $dashboardId: ID!\n $defaultDashboardName: String\n $defaultCategory: String\n $defaultTags: [String!]\n $includeSampleData: Boolean\n ) {\n buildDashboardTemplateContent(\n dashboardId: $dashboardId\n defaultDashboardName: $defaultDashboardName\n defaultCategory: $defaultCategory\n defaultTags: $defaultTags\n includeSampleData: $includeSampleData\n ) {\n content\n sinkCount\n unknownSinkIds\n sampleDataSinkCount\n }\n }\n`;\n"],"mappings":";;;AAgCA,IAAa,IAA4B,CAAG;;;;;;;;;;;;;;;;;;;GA0B/B,IAAmC,CAAG"}
@@ -117,10 +117,11 @@ function p(p = {}) {
117
117
  success: !1,
118
118
  errorMessage: e,
119
119
  sinkCount: 0,
120
- unknownSinkIds: []
120
+ unknownSinkIds: [],
121
+ sampleDataSinkCount: 0
121
122
  });
122
123
  if (!_) return r("Store is unavailable right now.");
123
- let a, o = 0, s = [];
124
+ let a, o = 0, s = [], c = 0;
124
125
  try {
125
126
  let t = (await v.query({
126
127
  query: i,
@@ -128,12 +129,13 @@ function p(p = {}) {
128
129
  dashboardId: e.dashboardId,
129
130
  defaultDashboardName: e.name,
130
131
  defaultCategory: e.category ?? null,
131
- defaultTags: e.tags ?? []
132
+ defaultTags: e.tags ?? [],
133
+ includeSampleData: e.includeSampleData ?? !1
132
134
  },
133
135
  fetchPolicy: "network-only"
134
136
  })).data?.buildDashboardTemplateContent;
135
137
  if (!t?.content) return r("Could not build a template from this dashboard.");
136
- a = t.content, o = t.sinkCount, s = t.unknownSinkIds ?? [];
138
+ a = t.content, o = t.sinkCount, s = t.unknownSinkIds ?? [], c = t.sampleDataSinkCount ?? 0;
137
139
  } catch (e) {
138
140
  return r(e instanceof Error ? e.message : "Failed to build the template.");
139
141
  }
@@ -165,17 +167,20 @@ function p(p = {}) {
165
167
  applicationId: i.id,
166
168
  status: i.status,
167
169
  sinkCount: o,
168
- unknownSinkIds: s
170
+ unknownSinkIds: s,
171
+ sampleDataSinkCount: c
169
172
  }) : {
170
173
  ...r("The developer portal did not create the app."),
171
174
  sinkCount: o,
172
- unknownSinkIds: s
175
+ unknownSinkIds: s,
176
+ sampleDataSinkCount: c
173
177
  };
174
178
  } catch (e) {
175
179
  return {
176
180
  ...r(e instanceof Error ? e.message : "Failed to publish to the developer portal."),
177
181
  sinkCount: o,
178
- unknownSinkIds: s
182
+ unknownSinkIds: s,
183
+ sampleDataSinkCount: c
179
184
  };
180
185
  }
181
186
  }, [_, v]);
@@ -1 +1 @@
1
- {"version":3,"file":"useStoreDashboardTemplates.js","names":[],"sources":["../../../src/bigconsole/hooks/useStoreDashboardTemplates.ts"],"sourcesContent":["/**\n * Store-distributed dashboard templates.\n *\n * Dashboard templates live in the global store as `StoreProduct` rows of type\n * `DASHBOARD` whose `content` column holds a `DashboardTemplateContent`\n * document. This mirrors how FluidGrids sources its workflow templates\n * (`browseStore(type: WORKFLOW)` → `content.definitionJson` →\n * `importWorkflowTemplate`).\n *\n * Two gateways are involved: the catalog is read from the **global** gateway,\n * and the install mutation runs against the **workspace** gateway, which is\n * where the dashboard is created.\n *\n * @module bigconsole/hooks/useStoreDashboardTemplates\n */\nimport { useApolloClient } from '@apollo/client/react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport {\n BROWSE_DASHBOARD_TEMPLATES,\n BUILD_DASHBOARD_TEMPLATE_CONTENT,\n CREATE_TEMPLATE_APP,\n CREATE_TEMPLATE_VERSION,\n GET_DASHBOARD_TEMPLATE_DETAIL,\n IMPORT_DASHBOARD_TEMPLATE,\n} from '../graphql/storeTemplates';\n\n/** A column a widget expects from the sink bound to its slot. */\nexport interface TemplateExpectedField {\n name: string;\n role?: string;\n}\n\n/** A data-binding slot the template needs filled. */\nexport interface TemplateRequiredDataSink {\n slot: string;\n label: string;\n description?: string | null;\n expectedFields: TemplateExpectedField[];\n widgetTitles: string[];\n}\n\nexport interface TemplateRequiredWidgetType {\n widgetType: string;\n widgetTypeKey?: string | null;\n publishType: 'BUILTIN' | 'STORE' | 'CUSTOM';\n displayName?: string;\n requiresPurchase?: boolean;\n}\n\nexport interface TemplateRequiredInput {\n name: string;\n type: 'string' | 'number' | 'boolean' | 'select' | 'secret';\n label: string;\n placeholder?: string;\n required: boolean;\n defaultValue?: string | number | boolean | null;\n options?: { label: string; value: string }[];\n helpText?: string;\n}\n\nexport interface DashboardTemplateContent {\n definition: unknown;\n requiredDataSinks: TemplateRequiredDataSink[];\n requiredWidgetTypes: TemplateRequiredWidgetType[];\n requiredInputs: TemplateRequiredInput[];\n defaultDashboardName?: string;\n defaultCategory?: string | null;\n defaultTags?: string[];\n difficultyLevel?: 'easy' | 'medium' | 'hard';\n}\n\n/** A store product presented as a template card. */\nexport interface StoreDashboardTemplate {\n id: string;\n name: string;\n slug: string;\n description?: string | null;\n icon?: string | null;\n category?: string | null;\n tags: string[];\n featured: boolean;\n downloads: number;\n rating?: number | null;\n pricingModel: string;\n price: number;\n /** True for anything the workspace has to buy before it can be installed. */\n requiresPurchase: boolean;\n}\n\ninterface RawStoreProduct {\n id: string;\n name: string;\n slug: string;\n description?: string | null;\n icon?: string | null;\n category?: string | null;\n tags?: string[] | null;\n featured?: boolean | null;\n downloads?: number | null;\n rating?: number | null;\n pricingModel?: string | null;\n price?: number | null;\n}\n\nexport interface InstallTemplateArgs {\n templateId: string;\n templateName: string;\n content: DashboardTemplateContent;\n name?: string;\n visibility?: string;\n dataSinkBindings?: { slot: string; dataSinkId: string }[];\n inputValues?: Record<string, unknown>;\n}\n\nexport interface InstallTemplateResult {\n success: boolean;\n dashboardId?: string;\n errorMessage?: string;\n unresolvedSlots: string[];\n resolvedSlots: string[];\n widgetsNeedingData: number;\n warnings: string[];\n}\n\nfunction normalizeProduct(raw: RawStoreProduct): StoreDashboardTemplate {\n const pricingModel = raw.pricingModel ?? 'FREE';\n return {\n id: raw.id,\n name: raw.name,\n slug: raw.slug,\n description: raw.description ?? null,\n icon: raw.icon ?? null,\n category: raw.category ?? null,\n tags: raw.tags ?? [],\n featured: raw.featured ?? false,\n downloads: raw.downloads ?? 0,\n rating: raw.rating ?? null,\n pricingModel,\n price: raw.price ?? 0,\n requiresPurchase: pricingModel !== 'FREE' && (raw.price ?? 0) > 0,\n };\n}\n\nexport interface PublishToStoreArgs {\n dashboardId: string;\n name: string;\n slug: string;\n description: string;\n category?: string;\n tags?: string[];\n}\n\nexport interface PublishToStoreResult {\n success: boolean;\n /** The DRAFT devportal application created for this template. */\n applicationId?: string;\n status?: string;\n errorMessage?: string;\n /** Distinct data sinks the dashboard referenced, all tokenised into slots. */\n sinkCount: number;\n /** Sinks that could not be resolved; their slots fall back to positional names. */\n unknownSinkIds: string[];\n}\n\nexport interface UseStoreDashboardTemplatesResult {\n templates: StoreDashboardTemplate[];\n loading: boolean;\n error: string | null;\n totalCount: number;\n refetch: (search?: string) => Promise<void>;\n /** Fetch the full template document. Kept lazy: content is large. */\n fetchTemplateContent: (templateId: string) => Promise<DashboardTemplateContent | null>;\n installTemplate: (args: InstallTemplateArgs) => Promise<InstallTemplateResult>;\n publishToStore: (args: PublishToStoreArgs) => Promise<PublishToStoreResult>;\n}\n\nexport function useStoreDashboardTemplates(\n options: { search?: string; category?: string; autoLoad?: boolean } = {}\n): UseStoreDashboardTemplatesResult {\n const { search, category, autoLoad = true } = options;\n\n const globalClient = useGatewayClient('global');\n const workspaceClient = useApolloClient();\n\n const [templates, setTemplates] = useState<StoreDashboardTemplate[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const refetch = useCallback(\n async (overrideSearch?: string): Promise<void> => {\n if (!globalClient) return;\n setLoading(true);\n setError(null);\n try {\n const result = await globalClient.query<{\n browseStore?: { products?: RawStoreProduct[]; totalCount?: number };\n }>({\n query: BROWSE_DASHBOARD_TEMPLATES,\n variables: {\n input: {\n filter: {\n type: 'DASHBOARD',\n status: 'PUBLISHED',\n // Without this a template published from another product's\n // devportal listing would show up in BigConsole's gallery.\n compatibleWith: 'bigconsole',\n ...((overrideSearch ?? search) ? { search: overrideSearch ?? search } : {}),\n ...(category && category !== 'ALL' ? { category } : {}),\n },\n sortBy: 'RELEVANCE',\n pagination: { limit: 50, offset: 0 },\n },\n },\n fetchPolicy: 'network-only',\n });\n\n setTemplates((result.data?.browseStore?.products ?? []).map(normalizeProduct));\n setTotalCount(result.data?.browseStore?.totalCount ?? 0);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load templates from the store');\n setTemplates([]);\n setTotalCount(0);\n } finally {\n setLoading(false);\n }\n },\n [globalClient, search, category]\n );\n\n useEffect(() => {\n if (autoLoad) void refetch();\n }, [autoLoad, refetch]);\n\n const fetchTemplateContent = useCallback(\n async (templateId: string): Promise<DashboardTemplateContent | null> => {\n if (!globalClient) return null;\n try {\n const result = await globalClient.query<{ product?: { content?: unknown } }>({\n query: GET_DASHBOARD_TEMPLATE_DETAIL,\n variables: { id: templateId },\n fetchPolicy: 'network-only',\n });\n const content = result.data?.product?.content;\n if (!content || typeof content !== 'object') return null;\n const doc = content as Partial<DashboardTemplateContent>;\n if (!doc.definition) return null;\n return {\n definition: doc.definition,\n requiredDataSinks: doc.requiredDataSinks ?? [],\n requiredWidgetTypes: doc.requiredWidgetTypes ?? [],\n requiredInputs: doc.requiredInputs ?? [],\n defaultDashboardName: doc.defaultDashboardName,\n defaultCategory: doc.defaultCategory ?? null,\n defaultTags: doc.defaultTags ?? [],\n difficultyLevel: doc.difficultyLevel ?? 'medium',\n };\n } catch {\n return null;\n }\n },\n [globalClient]\n );\n\n const installTemplate = useCallback(\n async (args: InstallTemplateArgs): Promise<InstallTemplateResult> => {\n const result = await workspaceClient.mutate<{\n importDashboardTemplate?: InstallTemplateResult & { dashboard?: { id: string } | null };\n }>({\n mutation: IMPORT_DASHBOARD_TEMPLATE,\n variables: {\n input: {\n content: args.content,\n sourceTemplateId: args.templateId,\n sourceTemplateName: args.templateName,\n ...(args.name ? { name: args.name } : {}),\n ...(args.visibility ? { visibility: args.visibility } : {}),\n ...(args.dataSinkBindings?.length ? { dataSinkBindings: args.dataSinkBindings } : {}),\n ...(args.inputValues && Object.keys(args.inputValues).length ? { inputValues: args.inputValues } : {}),\n },\n },\n });\n\n const payload = result.data?.importDashboardTemplate;\n if (!payload) {\n return {\n success: false,\n errorMessage: 'No response from the server',\n unresolvedSlots: [],\n resolvedSlots: [],\n widgetsNeedingData: 0,\n warnings: [],\n };\n }\n\n return {\n success: payload.success,\n dashboardId: payload.dashboard?.id,\n errorMessage: payload.errorMessage,\n unresolvedSlots: payload.unresolvedSlots ?? [],\n resolvedSlots: payload.resolvedSlots ?? [],\n widgetsNeedingData: payload.widgetsNeedingData ?? 0,\n warnings: payload.warnings ?? [],\n };\n },\n [workspaceClient]\n );\n\n /**\n * Turn a live dashboard into a Dashboard Template application in the\n * developer portal.\n *\n * It deliberately does NOT create a StoreProduct directly. devportal already\n * owns dashboard-template publishing end to end — versioning, submit/review,\n * publisher identity, and the `metadata.sourceApplicationId` the store uses\n * for ownership on later updates. Writing a product straight to the store\n * would fork that pipeline and orphan the listing: a later devportal publish\n * of the same slug would hit the store's slug-collision guard and refuse.\n *\n * So this fills in the one thing devportal cannot do — produce the template\n * document from a live dashboard — and hands off to the existing flow. The\n * app is left in DRAFT so the author reviews the listing and submits it\n * themselves.\n *\n * Two hops: the workspace gateway tokenises the dashboard (it owns the data\n * sinks), the global gateway owns devportal. Brokering through the client\n * keeps both directions free of cross-layer M2M, exactly like install.\n */\n const publishToStore = useCallback(\n async (args: PublishToStoreArgs): Promise<PublishToStoreResult> => {\n const failure = (errorMessage: string): PublishToStoreResult => ({\n success: false,\n errorMessage,\n sinkCount: 0,\n unknownSinkIds: [],\n });\n\n if (!globalClient) return failure('Store is unavailable right now.');\n\n let content: unknown;\n let sinkCount = 0;\n let unknownSinkIds: string[] = [];\n try {\n const built = await workspaceClient.query<{\n buildDashboardTemplateContent?: {\n content: unknown;\n sinkCount: number;\n unknownSinkIds: string[];\n };\n }>({\n query: BUILD_DASHBOARD_TEMPLATE_CONTENT,\n variables: {\n dashboardId: args.dashboardId,\n defaultDashboardName: args.name,\n defaultCategory: args.category ?? null,\n defaultTags: args.tags ?? [],\n },\n fetchPolicy: 'network-only',\n });\n const payload = built.data?.buildDashboardTemplateContent;\n if (!payload?.content) return failure('Could not build a template from this dashboard.');\n content = payload.content;\n sinkCount = payload.sinkCount;\n unknownSinkIds = payload.unknownSinkIds ?? [];\n } catch (err) {\n return failure(err instanceof Error ? err.message : 'Failed to build the template.');\n }\n\n try {\n const app = await globalClient.mutate<{\n createApplication?: { id: string; status: string };\n }>({\n mutation: CREATE_TEMPLATE_APP,\n variables: {\n input: {\n appType: 'DASHBOARD',\n name: args.name,\n slug: args.slug,\n shortDescription: args.description.slice(0, 200),\n description: args.description,\n ...(args.category ? { category: args.category } : {}),\n tags: args.tags ?? [],\n compatibleProducts: ['bigconsole'],\n },\n },\n });\n\n const application = app.data?.createApplication;\n if (!application) {\n return { ...failure('The developer portal did not create the app.'), sinkCount, unknownSinkIds };\n }\n\n // The version manifest is what devportal maps onto StoreProduct.content\n // when the app is submitted and approved.\n await globalClient.mutate({\n mutation: CREATE_TEMPLATE_VERSION,\n variables: {\n applicationId: application.id,\n input: { version: '1.0.0', manifest: content },\n },\n });\n\n return {\n success: true,\n applicationId: application.id,\n status: application.status,\n sinkCount,\n unknownSinkIds,\n };\n } catch (err) {\n return {\n ...failure(err instanceof Error ? err.message : 'Failed to publish to the developer portal.'),\n sinkCount,\n unknownSinkIds,\n };\n }\n },\n [globalClient, workspaceClient]\n );\n\n return useMemo(\n () => ({\n templates,\n loading,\n error,\n totalCount,\n refetch,\n fetchTemplateContent,\n installTemplate,\n publishToStore,\n }),\n [templates, loading, error, totalCount, refetch, fetchTemplateContent, installTemplate, publishToStore]\n );\n}\n\nexport default useStoreDashboardTemplates;\n"],"mappings":"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"}
1
+ {"version":3,"file":"useStoreDashboardTemplates.js","names":[],"sources":["../../../src/bigconsole/hooks/useStoreDashboardTemplates.ts"],"sourcesContent":["/**\n * Store-distributed dashboard templates.\n *\n * Dashboard templates live in the global store as `StoreProduct` rows of type\n * `DASHBOARD` whose `content` column holds a `DashboardTemplateContent`\n * document. This mirrors how FluidGrids sources its workflow templates\n * (`browseStore(type: WORKFLOW)` → `content.definitionJson` →\n * `importWorkflowTemplate`).\n *\n * Two gateways are involved: the catalog is read from the **global** gateway,\n * and the install mutation runs against the **workspace** gateway, which is\n * where the dashboard is created.\n *\n * @module bigconsole/hooks/useStoreDashboardTemplates\n */\nimport { useApolloClient } from '@apollo/client/react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\n\nimport {\n BROWSE_DASHBOARD_TEMPLATES,\n BUILD_DASHBOARD_TEMPLATE_CONTENT,\n CREATE_TEMPLATE_APP,\n CREATE_TEMPLATE_VERSION,\n GET_DASHBOARD_TEMPLATE_DETAIL,\n IMPORT_DASHBOARD_TEMPLATE,\n} from '../graphql/storeTemplates';\n\n/** A column a widget expects from the sink bound to its slot. */\nexport interface TemplateExpectedField {\n name: string;\n role?: string;\n}\n\n/** A data-binding slot the template needs filled. */\nexport interface TemplateRequiredDataSink {\n slot: string;\n label: string;\n description?: string | null;\n expectedFields: TemplateExpectedField[];\n widgetTitles: string[];\n}\n\nexport interface TemplateRequiredWidgetType {\n widgetType: string;\n widgetTypeKey?: string | null;\n publishType: 'BUILTIN' | 'STORE' | 'CUSTOM';\n displayName?: string;\n requiresPurchase?: boolean;\n}\n\nexport interface TemplateRequiredInput {\n name: string;\n type: 'string' | 'number' | 'boolean' | 'select' | 'secret';\n label: string;\n placeholder?: string;\n required: boolean;\n defaultValue?: string | number | boolean | null;\n options?: { label: string; value: string }[];\n helpText?: string;\n}\n\nexport interface DashboardTemplateContent {\n definition: unknown;\n requiredDataSinks: TemplateRequiredDataSink[];\n requiredWidgetTypes: TemplateRequiredWidgetType[];\n requiredInputs: TemplateRequiredInput[];\n defaultDashboardName?: string;\n defaultCategory?: string | null;\n defaultTags?: string[];\n difficultyLevel?: 'easy' | 'medium' | 'hard';\n}\n\n/** A store product presented as a template card. */\nexport interface StoreDashboardTemplate {\n id: string;\n name: string;\n slug: string;\n description?: string | null;\n icon?: string | null;\n category?: string | null;\n tags: string[];\n featured: boolean;\n downloads: number;\n rating?: number | null;\n pricingModel: string;\n price: number;\n /** True for anything the workspace has to buy before it can be installed. */\n requiresPurchase: boolean;\n}\n\ninterface RawStoreProduct {\n id: string;\n name: string;\n slug: string;\n description?: string | null;\n icon?: string | null;\n category?: string | null;\n tags?: string[] | null;\n featured?: boolean | null;\n downloads?: number | null;\n rating?: number | null;\n pricingModel?: string | null;\n price?: number | null;\n}\n\nexport interface InstallTemplateArgs {\n templateId: string;\n templateName: string;\n content: DashboardTemplateContent;\n name?: string;\n visibility?: string;\n dataSinkBindings?: { slot: string; dataSinkId: string }[];\n inputValues?: Record<string, unknown>;\n}\n\nexport interface InstallTemplateResult {\n success: boolean;\n dashboardId?: string;\n errorMessage?: string;\n unresolvedSlots: string[];\n resolvedSlots: string[];\n widgetsNeedingData: number;\n warnings: string[];\n}\n\nfunction normalizeProduct(raw: RawStoreProduct): StoreDashboardTemplate {\n const pricingModel = raw.pricingModel ?? 'FREE';\n return {\n id: raw.id,\n name: raw.name,\n slug: raw.slug,\n description: raw.description ?? null,\n icon: raw.icon ?? null,\n category: raw.category ?? null,\n tags: raw.tags ?? [],\n featured: raw.featured ?? false,\n downloads: raw.downloads ?? 0,\n rating: raw.rating ?? null,\n pricingModel,\n price: raw.price ?? 0,\n requiresPurchase: pricingModel !== 'FREE' && (raw.price ?? 0) > 0,\n };\n}\n\nexport interface PublishToStoreArgs {\n dashboardId: string;\n name: string;\n slug: string;\n description: string;\n category?: string;\n tags?: string[];\n /** Embed a few real rows per data source so the board renders populated. */\n includeSampleData?: boolean;\n}\n\nexport interface PublishToStoreResult {\n success: boolean;\n /** The DRAFT devportal application created for this template. */\n applicationId?: string;\n status?: string;\n errorMessage?: string;\n /** Distinct data sinks the dashboard referenced, all tokenised into slots. */\n sinkCount: number;\n /** Sinks that could not be resolved; their slots fall back to positional names. */\n unknownSinkIds: string[];\n /** How many data sources contributed sample rows. */\n sampleDataSinkCount: number;\n}\n\nexport interface UseStoreDashboardTemplatesResult {\n templates: StoreDashboardTemplate[];\n loading: boolean;\n error: string | null;\n totalCount: number;\n refetch: (search?: string) => Promise<void>;\n /** Fetch the full template document. Kept lazy: content is large. */\n fetchTemplateContent: (templateId: string) => Promise<DashboardTemplateContent | null>;\n installTemplate: (args: InstallTemplateArgs) => Promise<InstallTemplateResult>;\n publishToStore: (args: PublishToStoreArgs) => Promise<PublishToStoreResult>;\n}\n\nexport function useStoreDashboardTemplates(\n options: { search?: string; category?: string; autoLoad?: boolean } = {}\n): UseStoreDashboardTemplatesResult {\n const { search, category, autoLoad = true } = options;\n\n const globalClient = useGatewayClient('global');\n const workspaceClient = useApolloClient();\n\n const [templates, setTemplates] = useState<StoreDashboardTemplate[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const refetch = useCallback(\n async (overrideSearch?: string): Promise<void> => {\n if (!globalClient) return;\n setLoading(true);\n setError(null);\n try {\n const result = await globalClient.query<{\n browseStore?: { products?: RawStoreProduct[]; totalCount?: number };\n }>({\n query: BROWSE_DASHBOARD_TEMPLATES,\n variables: {\n input: {\n filter: {\n type: 'DASHBOARD',\n status: 'PUBLISHED',\n // Without this a template published from another product's\n // devportal listing would show up in BigConsole's gallery.\n compatibleWith: 'bigconsole',\n ...((overrideSearch ?? search) ? { search: overrideSearch ?? search } : {}),\n ...(category && category !== 'ALL' ? { category } : {}),\n },\n sortBy: 'RELEVANCE',\n pagination: { limit: 50, offset: 0 },\n },\n },\n fetchPolicy: 'network-only',\n });\n\n setTemplates((result.data?.browseStore?.products ?? []).map(normalizeProduct));\n setTotalCount(result.data?.browseStore?.totalCount ?? 0);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load templates from the store');\n setTemplates([]);\n setTotalCount(0);\n } finally {\n setLoading(false);\n }\n },\n [globalClient, search, category]\n );\n\n useEffect(() => {\n if (autoLoad) void refetch();\n }, [autoLoad, refetch]);\n\n const fetchTemplateContent = useCallback(\n async (templateId: string): Promise<DashboardTemplateContent | null> => {\n if (!globalClient) return null;\n try {\n const result = await globalClient.query<{ product?: { content?: unknown } }>({\n query: GET_DASHBOARD_TEMPLATE_DETAIL,\n variables: { id: templateId },\n fetchPolicy: 'network-only',\n });\n const content = result.data?.product?.content;\n if (!content || typeof content !== 'object') return null;\n const doc = content as Partial<DashboardTemplateContent>;\n if (!doc.definition) return null;\n return {\n definition: doc.definition,\n requiredDataSinks: doc.requiredDataSinks ?? [],\n requiredWidgetTypes: doc.requiredWidgetTypes ?? [],\n requiredInputs: doc.requiredInputs ?? [],\n defaultDashboardName: doc.defaultDashboardName,\n defaultCategory: doc.defaultCategory ?? null,\n defaultTags: doc.defaultTags ?? [],\n difficultyLevel: doc.difficultyLevel ?? 'medium',\n };\n } catch {\n return null;\n }\n },\n [globalClient]\n );\n\n const installTemplate = useCallback(\n async (args: InstallTemplateArgs): Promise<InstallTemplateResult> => {\n const result = await workspaceClient.mutate<{\n importDashboardTemplate?: InstallTemplateResult & { dashboard?: { id: string } | null };\n }>({\n mutation: IMPORT_DASHBOARD_TEMPLATE,\n variables: {\n input: {\n content: args.content,\n sourceTemplateId: args.templateId,\n sourceTemplateName: args.templateName,\n ...(args.name ? { name: args.name } : {}),\n ...(args.visibility ? { visibility: args.visibility } : {}),\n ...(args.dataSinkBindings?.length ? { dataSinkBindings: args.dataSinkBindings } : {}),\n ...(args.inputValues && Object.keys(args.inputValues).length ? { inputValues: args.inputValues } : {}),\n },\n },\n });\n\n const payload = result.data?.importDashboardTemplate;\n if (!payload) {\n return {\n success: false,\n errorMessage: 'No response from the server',\n unresolvedSlots: [],\n resolvedSlots: [],\n widgetsNeedingData: 0,\n warnings: [],\n };\n }\n\n return {\n success: payload.success,\n dashboardId: payload.dashboard?.id,\n errorMessage: payload.errorMessage,\n unresolvedSlots: payload.unresolvedSlots ?? [],\n resolvedSlots: payload.resolvedSlots ?? [],\n widgetsNeedingData: payload.widgetsNeedingData ?? 0,\n warnings: payload.warnings ?? [],\n };\n },\n [workspaceClient]\n );\n\n /**\n * Turn a live dashboard into a Dashboard Template application in the\n * developer portal.\n *\n * It deliberately does NOT create a StoreProduct directly. devportal already\n * owns dashboard-template publishing end to end — versioning, submit/review,\n * publisher identity, and the `metadata.sourceApplicationId` the store uses\n * for ownership on later updates. Writing a product straight to the store\n * would fork that pipeline and orphan the listing: a later devportal publish\n * of the same slug would hit the store's slug-collision guard and refuse.\n *\n * So this fills in the one thing devportal cannot do — produce the template\n * document from a live dashboard — and hands off to the existing flow. The\n * app is left in DRAFT so the author reviews the listing and submits it\n * themselves.\n *\n * Two hops: the workspace gateway tokenises the dashboard (it owns the data\n * sinks), the global gateway owns devportal. Brokering through the client\n * keeps both directions free of cross-layer M2M, exactly like install.\n */\n const publishToStore = useCallback(\n async (args: PublishToStoreArgs): Promise<PublishToStoreResult> => {\n const failure = (errorMessage: string): PublishToStoreResult => ({\n success: false,\n errorMessage,\n sinkCount: 0,\n unknownSinkIds: [],\n sampleDataSinkCount: 0,\n });\n\n if (!globalClient) return failure('Store is unavailable right now.');\n\n let content: unknown;\n let sinkCount = 0;\n let unknownSinkIds: string[] = [];\n let sampleDataSinkCount = 0;\n try {\n const built = await workspaceClient.query<{\n buildDashboardTemplateContent?: {\n content: unknown;\n sinkCount: number;\n unknownSinkIds: string[];\n sampleDataSinkCount: number;\n };\n }>({\n query: BUILD_DASHBOARD_TEMPLATE_CONTENT,\n variables: {\n dashboardId: args.dashboardId,\n defaultDashboardName: args.name,\n defaultCategory: args.category ?? null,\n defaultTags: args.tags ?? [],\n includeSampleData: args.includeSampleData ?? false,\n },\n fetchPolicy: 'network-only',\n });\n const payload = built.data?.buildDashboardTemplateContent;\n if (!payload?.content) return failure('Could not build a template from this dashboard.');\n content = payload.content;\n sinkCount = payload.sinkCount;\n unknownSinkIds = payload.unknownSinkIds ?? [];\n sampleDataSinkCount = payload.sampleDataSinkCount ?? 0;\n } catch (err) {\n return failure(err instanceof Error ? err.message : 'Failed to build the template.');\n }\n\n try {\n const app = await globalClient.mutate<{\n createApplication?: { id: string; status: string };\n }>({\n mutation: CREATE_TEMPLATE_APP,\n variables: {\n input: {\n appType: 'DASHBOARD',\n name: args.name,\n slug: args.slug,\n shortDescription: args.description.slice(0, 200),\n description: args.description,\n ...(args.category ? { category: args.category } : {}),\n tags: args.tags ?? [],\n compatibleProducts: ['bigconsole'],\n },\n },\n });\n\n const application = app.data?.createApplication;\n if (!application) {\n return {\n ...failure('The developer portal did not create the app.'),\n sinkCount,\n unknownSinkIds,\n sampleDataSinkCount,\n };\n }\n\n // The version manifest is what devportal maps onto StoreProduct.content\n // when the app is submitted and approved.\n await globalClient.mutate({\n mutation: CREATE_TEMPLATE_VERSION,\n variables: {\n applicationId: application.id,\n input: { version: '1.0.0', manifest: content },\n },\n });\n\n return {\n success: true,\n applicationId: application.id,\n status: application.status,\n sinkCount,\n unknownSinkIds,\n sampleDataSinkCount,\n };\n } catch (err) {\n return {\n ...failure(err instanceof Error ? err.message : 'Failed to publish to the developer portal.'),\n sinkCount,\n unknownSinkIds,\n sampleDataSinkCount,\n };\n }\n },\n [globalClient, workspaceClient]\n );\n\n return useMemo(\n () => ({\n templates,\n loading,\n error,\n totalCount,\n refetch,\n fetchTemplateContent,\n installTemplate,\n publishToStore,\n }),\n [templates, loading, error, totalCount, refetch, fetchTemplateContent, installTemplate, publishToStore]\n );\n}\n\nexport default useStoreDashboardTemplates;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-bigconsole",
3
- "version": "2026.801.2",
3
+ "version": "2026.803.1",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [