@burdenoff/microfe-bigconsole 2026.613.6 → 2026.615.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,45 +1,60 @@
1
- import { memo as e, useCallback as t } from "react";
2
- import { jsx as n, jsxs as r } from "react/jsx-runtime";
1
+ import { memo as e, useCallback as t, useEffect as n, useRef as r, useState as i } from "react";
2
+ import { GripVertical as a } from "lucide-react";
3
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
3
4
  //#region src/bigconsole/components/widgets/table/TableHeader.tsx
4
- var i = e(function({ columns: e, sortState: i, onSort: a, sticky: o = !0, frozenLefts: s }) {
5
- let c = t((e) => {
6
- e.sortable !== !1 && a(e.key);
7
- }, [a]), l = (e) => {
5
+ var c = 60, l = e(function({ columns: e, sortState: l, onSort: u, sticky: d = !0, frozenLefts: f, editable: p = !1, onColumnResize: m, onColumnResizeEnd: h, onColumnReorder: g }) {
6
+ let _ = t((e) => {
7
+ e.sortable !== !1 && u(e.key);
8
+ }, [u]), v = r({}), [y, b] = i(null), [x, S] = i(null), C = r(null), w = r(null), T = t((e, t) => {
9
+ t.preventDefault(), t.stopPropagation();
10
+ let n = v.current[e], r = n ? n.offsetWidth : c, i = t.clientX;
11
+ b(e);
12
+ let a = r, o = (t) => {
13
+ a = Math.max(c, Math.round(r + (t.clientX - i))), m?.(e, a);
14
+ }, s = () => {
15
+ window.removeEventListener("pointermove", o), window.removeEventListener("pointerup", l), w.current = null;
16
+ }, l = () => {
17
+ s(), b(null), h?.(e, a);
18
+ };
19
+ w.current = s, window.addEventListener("pointermove", o), window.addEventListener("pointerup", l);
20
+ }, [m, h]);
21
+ n(() => () => w.current?.(), []);
22
+ let E = (e) => {
8
23
  if (e.sortable === !1) return null;
9
- let t = i.column === e.key ? i.direction : null;
10
- return /* @__PURE__ */ r("span", {
24
+ let t = l.column === e.key ? l.direction : null;
25
+ return /* @__PURE__ */ s("span", {
11
26
  className: "inline-flex ml-1",
12
27
  children: [
13
- t === "asc" && /* @__PURE__ */ n("svg", {
28
+ t === "asc" && /* @__PURE__ */ o("svg", {
14
29
  className: "w-4 h-4 text-action-primary-bg",
15
30
  fill: "none",
16
31
  stroke: "currentColor",
17
32
  viewBox: "0 0 24 24",
18
- children: /* @__PURE__ */ n("path", {
33
+ children: /* @__PURE__ */ o("path", {
19
34
  strokeLinecap: "round",
20
35
  strokeLinejoin: "round",
21
36
  strokeWidth: 2,
22
37
  d: "M5 15l7-7 7 7"
23
38
  })
24
39
  }),
25
- t === "desc" && /* @__PURE__ */ n("svg", {
40
+ t === "desc" && /* @__PURE__ */ o("svg", {
26
41
  className: "w-4 h-4 text-action-primary-bg",
27
42
  fill: "none",
28
43
  stroke: "currentColor",
29
44
  viewBox: "0 0 24 24",
30
- children: /* @__PURE__ */ n("path", {
45
+ children: /* @__PURE__ */ o("path", {
31
46
  strokeLinecap: "round",
32
47
  strokeLinejoin: "round",
33
48
  strokeWidth: 2,
34
49
  d: "M19 9l-7 7-7-7"
35
50
  })
36
51
  }),
37
- !t && /* @__PURE__ */ n("svg", {
52
+ !t && /* @__PURE__ */ o("svg", {
38
53
  className: "w-4 h-4 text-text-secondary opacity-0 group-hover:opacity-100",
39
54
  fill: "none",
40
55
  stroke: "currentColor",
41
56
  viewBox: "0 0 24 24",
42
- children: /* @__PURE__ */ n("path", {
57
+ children: /* @__PURE__ */ o("path", {
43
58
  strokeLinecap: "round",
44
59
  strokeLinejoin: "round",
45
60
  strokeWidth: 2,
@@ -49,16 +64,37 @@ var i = e(function({ columns: e, sortState: i, onSort: a, sticky: o = !0, frozen
49
64
  ]
50
65
  });
51
66
  };
52
- return /* @__PURE__ */ n("thead", {
67
+ return /* @__PURE__ */ o("thead", {
53
68
  className: `
54
69
  bg-bg-muted
55
- ${o ? "sticky top-0 z-10" : ""}
70
+ ${d ? "sticky top-0 z-10" : ""}
56
71
  `,
57
- children: /* @__PURE__ */ n("tr", { children: e.map((e) => {
58
- let t = s?.[e.key];
59
- return /* @__PURE__ */ n("th", {
72
+ children: /* @__PURE__ */ o("tr", { children: e.map((e) => {
73
+ let t = f?.[e.key], n = e.width, r = p && !!g && y === null, i = x === e.key && C.current !== e.key;
74
+ return /* @__PURE__ */ s("th", {
75
+ ref: (t) => {
76
+ v.current[e.key] = t;
77
+ },
78
+ draggable: r,
79
+ onDragStart: r ? (t) => {
80
+ C.current = e.key, t.dataTransfer.effectAllowed = "move", t.dataTransfer.setData("text/plain", e.key);
81
+ } : void 0,
82
+ onDragOver: p && g ? (t) => {
83
+ C.current && C.current !== e.key && (t.preventDefault(), t.dataTransfer.dropEffect = "move", x !== e.key && S(e.key));
84
+ } : void 0,
85
+ onDragLeave: p && g ? () => {
86
+ x === e.key && S(null);
87
+ } : void 0,
88
+ onDrop: p && g ? (t) => {
89
+ t.preventDefault();
90
+ let n = C.current;
91
+ C.current = null, S(null), n && n !== e.key && g(n, e.key);
92
+ } : void 0,
93
+ onDragEnd: r ? () => {
94
+ C.current = null, S(null);
95
+ } : void 0,
60
96
  className: `
61
- px-4 py-3
97
+ relative px-4 py-3
62
98
  text-xs font-semibold uppercase tracking-wider
63
99
  text-text-secondary
64
100
  border-b border-border-default
@@ -66,27 +102,51 @@ var i = e(function({ columns: e, sortState: i, onSort: a, sticky: o = !0, frozen
66
102
  ${e.align === "center" ? "text-center" : ""}
67
103
  ${e.align === "right" ? "text-right" : "text-left"}
68
104
  ${t == null ? "" : "sticky z-20 bg-bg-muted"}
105
+ ${i ? "border-l-2 border-l-action-primary-bg" : ""}
69
106
  `,
70
- style: t == null ? { width: e.width } : {
71
- width: e.width,
107
+ style: t == null ? { width: n } : {
108
+ width: n,
72
109
  left: t
73
110
  },
74
- onClick: () => c(e),
111
+ onClick: () => _(e),
75
112
  role: e.sortable === !1 ? void 0 : "button",
76
113
  tabIndex: e.sortable === !1 ? void 0 : 0,
77
114
  onKeyDown: (t) => {
78
- e.sortable !== !1 && (t.key === "Enter" || t.key === " ") && (t.preventDefault(), c(e));
115
+ e.sortable !== !1 && (t.key === "Enter" || t.key === " ") && (t.preventDefault(), _(e));
79
116
  },
80
- "aria-sort": i.column === e.key ? i.direction === "asc" ? "ascending" : "descending" : void 0,
81
- children: /* @__PURE__ */ r("div", {
117
+ "aria-sort": l.column === e.key ? l.direction === "asc" ? "ascending" : "descending" : void 0,
118
+ children: [/* @__PURE__ */ s("div", {
82
119
  className: "inline-flex items-center",
83
- children: [e.header, l(e)]
84
- })
120
+ children: [
121
+ p && g && /* @__PURE__ */ o("span", {
122
+ className: "mr-1 -ml-1 cursor-grab text-text-tertiary opacity-0 group-hover:opacity-100",
123
+ onClick: (e) => e.stopPropagation(),
124
+ "aria-hidden": "true",
125
+ title: "Drag to reorder column",
126
+ children: /* @__PURE__ */ o(a, { className: "w-3.5 h-3.5" })
127
+ }),
128
+ e.header,
129
+ E(e)
130
+ ]
131
+ }), p && m && /* @__PURE__ */ o("span", {
132
+ role: "separator",
133
+ "aria-orientation": "vertical",
134
+ "aria-label": `Resize ${e.header} column`,
135
+ className: `
136
+ absolute top-0 right-0 z-30 h-full w-1.5
137
+ cursor-col-resize select-none
138
+ hover:bg-action-primary-bg/40
139
+ ${y === e.key ? "bg-action-primary-bg/60" : ""}
140
+ `,
141
+ onPointerDown: (t) => T(e.key, t),
142
+ onClick: (e) => e.stopPropagation(),
143
+ draggable: !1
144
+ })]
85
145
  }, e.key);
86
146
  }) })
87
147
  });
88
148
  });
89
149
  //#endregion
90
- export { i as default };
150
+ export { l as default };
91
151
 
92
152
  //# sourceMappingURL=TableHeader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TableHeader.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/table/TableHeader.tsx"],"sourcesContent":["/**\n * TableHeader Component\n *\n * Sortable table header with column definitions.\n */\n\nimport { type FC, memo, useCallback } from 'react';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ColumnDefinition {\n /** Unique key for the column */\n key: string;\n /** Display header text */\n header: string;\n /** Whether column is sortable */\n sortable?: boolean;\n /** Column width (CSS value) */\n width?: string;\n /** Text alignment */\n align?: 'left' | 'center' | 'right';\n}\n\nexport type SortDirection = 'asc' | 'desc' | null;\n\nexport interface SortState {\n column: string | null;\n direction: SortDirection;\n}\n\nexport interface TableHeaderProps {\n /** Column definitions */\n columns: ColumnDefinition[];\n /** Current sort state */\n sortState: SortState;\n /** Callback when sort changes */\n onSort: (column: string) => void;\n /** Whether header is sticky */\n sticky?: boolean;\n /** column key → sticky-left px offset for frozen leading columns */\n frozenLefts?: Record<string, number>;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const TableHeader: FC<TableHeaderProps> = memo(function TableHeader({\n columns,\n sortState,\n onSort,\n sticky = true,\n frozenLefts,\n}) {\n const handleSort = useCallback(\n (column: ColumnDefinition) => {\n if (column.sortable !== false) {\n onSort(column.key);\n }\n },\n [onSort]\n );\n\n const getSortIcon = (column: ColumnDefinition) => {\n if (column.sortable === false) return null;\n\n const isActive = sortState.column === column.key;\n const direction = isActive ? sortState.direction : null;\n\n return (\n <span className=\"inline-flex ml-1\">\n {direction === 'asc' && (\n <svg className=\"w-4 h-4 text-action-primary-bg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 15l7-7 7 7\" />\n </svg>\n )}\n {direction === 'desc' && (\n <svg className=\"w-4 h-4 text-action-primary-bg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n )}\n {!direction && (\n <svg\n className=\"w-4 h-4 text-text-secondary opacity-0 group-hover:opacity-100\"\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=\"M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4\"\n />\n </svg>\n )}\n </span>\n );\n };\n\n return (\n <thead\n className={`\n bg-bg-muted\n ${sticky ? 'sticky top-0 z-10' : ''}\n `}\n >\n <tr>\n {columns.map((column) => {\n const frozen = frozenLefts?.[column.key];\n return (\n <th\n key={column.key}\n className={`\n px-4 py-3\n text-xs font-semibold uppercase tracking-wider\n text-text-secondary\n border-b border-border-default\n ${column.sortable !== false ? 'cursor-pointer select-none group hover:bg-bg-surface' : ''}\n ${column.align === 'center' ? 'text-center' : ''}\n ${column.align === 'right' ? 'text-right' : 'text-left'}\n ${frozen != null ? 'sticky z-20 bg-bg-muted' : ''}\n `}\n style={frozen != null ? { width: column.width, left: frozen } : { width: column.width }}\n onClick={() => handleSort(column)}\n role={column.sortable !== false ? 'button' : undefined}\n tabIndex={column.sortable !== false ? 0 : undefined}\n onKeyDown={(e) => {\n if (column.sortable !== false && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n handleSort(column);\n }\n }}\n aria-sort={\n sortState.column === column.key\n ? sortState.direction === 'asc'\n ? 'ascending'\n : 'descending'\n : undefined\n }\n >\n <div className=\"inline-flex items-center\">\n {column.header}\n {getSortIcon(column)}\n </div>\n </th>\n );\n })}\n </tr>\n </thead>\n );\n});\n\nexport default TableHeader;\n"],"mappings":";;;AAiDA,IAAa,IAAoC,EAAK,SAAqB,EACzE,YACA,cACA,WACA,YAAS,IACT,kBACC;CACD,IAAM,IAAa,GAChB,MAA6B;AAC5B,EAAI,EAAO,aAAa,MACtB,EAAO,EAAO,IAAI;IAGtB,CAAC,EAAO,CACT,EAEK,KAAe,MAA6B;AAChD,MAAI,EAAO,aAAa,GAAO,QAAO;EAGtC,IAAM,IADW,EAAU,WAAW,EAAO,MAChB,EAAU,YAAY;AAEnD,SACE,kBAAC,QAAD;GAAM,WAAU;aAAhB;IACG,MAAc,SACb,kBAAC,OAAD;KAAK,WAAU;KAAiC,MAAK;KAAO,QAAO;KAAe,SAAQ;eACxF,kBAAC,QAAD;MAAM,eAAc;MAAQ,gBAAe;MAAQ,aAAa;MAAG,GAAE;MAAkB,CAAA;KACnF,CAAA;IAEP,MAAc,UACb,kBAAC,OAAD;KAAK,WAAU;KAAiC,MAAK;KAAO,QAAO;KAAe,SAAQ;eACxF,kBAAC,QAAD;MAAM,eAAc;MAAQ,gBAAe;MAAQ,aAAa;MAAG,GAAE;MAAmB,CAAA;KACpF,CAAA;IAEP,CAAC,KACA,kBAAC,OAAD;KACE,WAAU;KACV,MAAK;KACL,QAAO;KACP,SAAQ;eAER,kBAAC,QAAD;MACE,eAAc;MACd,gBAAe;MACf,aAAa;MACb,GAAE;MACF,CAAA;KACE,CAAA;IAEH;;;AAIX,QACE,kBAAC,SAAD;EACE,WAAW;;UAEP,IAAS,sBAAsB,GAAG;;YAGtC,kBAAC,MAAD,EAAA,UACG,EAAQ,KAAK,MAAW;GACvB,IAAM,IAAS,IAAc,EAAO;AACpC,UACE,kBAAC,MAAD;IAEE,WAAW;;;;;gBAKT,EAAO,aAAa,KAAiE,KAAzD,uDAA4D;gBACxF,EAAO,UAAU,WAAW,gBAAgB,GAAG;gBAC/C,EAAO,UAAU,UAAU,eAAe,YAAY;gBACtD,KAAU,OAAmC,KAA5B,0BAA+B;;IAElD,OAAO,KAAU,OAA+C,EAAE,OAAO,EAAO,OAAO,GAA/D;KAAE,OAAO,EAAO;KAAO,MAAM;KAAQ;IAC7D,eAAe,EAAW,EAAO;IACjC,MAAM,EAAO,aAAa,KAAmB,KAAA,IAAX;IAClC,UAAU,EAAO,aAAa,KAAY,KAAA,IAAJ;IACtC,YAAY,MAAM;AAChB,KAAI,EAAO,aAAa,OAAU,EAAE,QAAQ,WAAW,EAAE,QAAQ,SAC/D,EAAE,gBAAgB,EAClB,EAAW,EAAO;;IAGtB,aACE,EAAU,WAAW,EAAO,MACxB,EAAU,cAAc,QACtB,cACA,eACF,KAAA;cAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAO,QACP,EAAY,EAAO,CAChB;;IACH,EAjCE,EAAO,IAiCT;IAEP,EACC,CAAA;EACC,CAAA;EAEV"}
1
+ {"version":3,"file":"TableHeader.js","names":[],"sources":["../../../../../src/bigconsole/components/widgets/table/TableHeader.tsx"],"sourcesContent":["/**\n * TableHeader Component\n *\n * Sortable table header with column definitions. In edit mode it also supports\n * interactive column resize (drag the right border) and reorder (drag the grip\n * handle), each surfaced through callbacks the parent persists to widget config.\n */\n\nimport { type FC, memo, useCallback, useEffect, useRef, useState } from 'react';\nimport { GripVertical } from 'lucide-react';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ColumnDefinition {\n /** Unique key for the column */\n key: string;\n /** Display header text */\n header: string;\n /** Whether column is sortable */\n sortable?: boolean;\n /** Column width (CSS value) */\n width?: string;\n /** Text alignment */\n align?: 'left' | 'center' | 'right';\n}\n\nexport type SortDirection = 'asc' | 'desc' | null;\n\nexport interface SortState {\n column: string | null;\n direction: SortDirection;\n}\n\nexport interface TableHeaderProps {\n /** Column definitions */\n columns: ColumnDefinition[];\n /** Current sort state */\n sortState: SortState;\n /** Callback when sort changes */\n onSort: (column: string) => void;\n /** Whether header is sticky */\n sticky?: boolean;\n /** column key → sticky-left px offset for frozen leading columns */\n frozenLefts?: Record<string, number>;\n /** Enable interactive resize + reorder (edit mode). */\n editable?: boolean;\n /** Called on every resize move with the in-progress width (px) for live feedback. */\n onColumnResize?: (key: string, widthPx: number) => void;\n /** Called once when a resize drag ends with the final width (px) — parent persists. */\n onColumnResizeEnd?: (key: string, widthPx: number) => void;\n /** Called when a column is dropped onto another to reorder — parent persists. */\n onColumnReorder?: (sourceKey: string, targetKey: string) => void;\n}\n\nconst MIN_COLUMN_WIDTH = 60;\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const TableHeader: FC<TableHeaderProps> = memo(function TableHeader({\n columns,\n sortState,\n onSort,\n sticky = true,\n frozenLefts,\n editable = false,\n onColumnResize,\n onColumnResizeEnd,\n onColumnReorder,\n}) {\n const handleSort = useCallback(\n (column: ColumnDefinition) => {\n if (column.sortable !== false) {\n onSort(column.key);\n }\n },\n [onSort]\n );\n\n // Refs to each <th> so a resize drag can read its current rendered width.\n const thRefs = useRef<Record<string, HTMLTableCellElement | null>>({});\n // Key being resized — disabled drag-reorder while resizing, and used for cursor.\n const [resizingKey, setResizingKey] = useState<string | null>(null);\n const [dragOverKey, setDragOverKey] = useState<string | null>(null);\n const dragSourceKey = useRef<string | null>(null);\n // Removes the active resize window-listeners; set during a drag, called on\n // pointer-up and on unmount so a drag interrupted by unmount can't leak.\n const teardownResizeRef = useRef<(() => void) | null>(null);\n\n // Start a resize drag from the right-border handle. Uses window listeners so\n // the pointer can leave the (narrow) handle without dropping the gesture.\n const startResize = useCallback(\n (key: string, startEvent: React.PointerEvent) => {\n startEvent.preventDefault();\n startEvent.stopPropagation();\n const th = thRefs.current[key];\n const startWidth = th ? th.offsetWidth : MIN_COLUMN_WIDTH;\n const startX = startEvent.clientX;\n setResizingKey(key);\n\n let lastWidth = startWidth;\n const onMove = (e: PointerEvent) => {\n lastWidth = Math.max(MIN_COLUMN_WIDTH, Math.round(startWidth + (e.clientX - startX)));\n onColumnResize?.(key, lastWidth);\n };\n const teardown = () => {\n window.removeEventListener('pointermove', onMove);\n window.removeEventListener('pointerup', onUp);\n teardownResizeRef.current = null;\n };\n const onUp = () => {\n teardown();\n setResizingKey(null);\n onColumnResizeEnd?.(key, lastWidth);\n };\n teardownResizeRef.current = teardown;\n window.addEventListener('pointermove', onMove);\n window.addEventListener('pointerup', onUp);\n },\n [onColumnResize, onColumnResizeEnd]\n );\n\n // Detach any in-progress resize listeners if the header unmounts mid-drag.\n useEffect(() => () => teardownResizeRef.current?.(), []);\n\n const getSortIcon = (column: ColumnDefinition) => {\n if (column.sortable === false) return null;\n\n const isActive = sortState.column === column.key;\n const direction = isActive ? sortState.direction : null;\n\n return (\n <span className=\"inline-flex ml-1\">\n {direction === 'asc' && (\n <svg className=\"w-4 h-4 text-action-primary-bg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M5 15l7-7 7 7\" />\n </svg>\n )}\n {direction === 'desc' && (\n <svg className=\"w-4 h-4 text-action-primary-bg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n )}\n {!direction && (\n <svg\n className=\"w-4 h-4 text-text-secondary opacity-0 group-hover:opacity-100\"\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=\"M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4\"\n />\n </svg>\n )}\n </span>\n );\n };\n\n return (\n <thead\n className={`\n bg-bg-muted\n ${sticky ? 'sticky top-0 z-10' : ''}\n `}\n >\n <tr>\n {columns.map((column) => {\n const frozen = frozenLefts?.[column.key];\n // Width (incl. live resize overrides) is baked into column.width by the parent.\n const width = column.width;\n const reorderable = editable && !!onColumnReorder && resizingKey === null;\n const isDropTarget = dragOverKey === column.key && dragSourceKey.current !== column.key;\n return (\n <th\n key={column.key}\n ref={(el) => {\n thRefs.current[column.key] = el;\n }}\n draggable={reorderable}\n onDragStart={\n reorderable\n ? (e) => {\n dragSourceKey.current = column.key;\n e.dataTransfer.effectAllowed = 'move';\n // Some browsers require data to be set for a drag to start.\n e.dataTransfer.setData('text/plain', column.key);\n }\n : undefined\n }\n onDragOver={\n editable && onColumnReorder\n ? (e) => {\n if (dragSourceKey.current && dragSourceKey.current !== column.key) {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n if (dragOverKey !== column.key) setDragOverKey(column.key);\n }\n }\n : undefined\n }\n onDragLeave={\n editable && onColumnReorder\n ? () => {\n if (dragOverKey === column.key) setDragOverKey(null);\n }\n : undefined\n }\n onDrop={\n editable && onColumnReorder\n ? (e) => {\n e.preventDefault();\n const source = dragSourceKey.current;\n dragSourceKey.current = null;\n setDragOverKey(null);\n if (source && source !== column.key) {\n onColumnReorder(source, column.key);\n }\n }\n : undefined\n }\n onDragEnd={\n reorderable\n ? () => {\n dragSourceKey.current = null;\n setDragOverKey(null);\n }\n : undefined\n }\n className={`\n relative px-4 py-3\n text-xs font-semibold uppercase tracking-wider\n text-text-secondary\n border-b border-border-default\n ${column.sortable !== false ? 'cursor-pointer select-none group hover:bg-bg-surface' : ''}\n ${column.align === 'center' ? 'text-center' : ''}\n ${column.align === 'right' ? 'text-right' : 'text-left'}\n ${frozen != null ? 'sticky z-20 bg-bg-muted' : ''}\n ${isDropTarget ? 'border-l-2 border-l-action-primary-bg' : ''}\n `}\n style={frozen != null ? { width, left: frozen } : { width }}\n onClick={() => handleSort(column)}\n role={column.sortable !== false ? 'button' : undefined}\n tabIndex={column.sortable !== false ? 0 : undefined}\n onKeyDown={(e) => {\n if (column.sortable !== false && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n handleSort(column);\n }\n }}\n aria-sort={\n sortState.column === column.key\n ? sortState.direction === 'asc'\n ? 'ascending'\n : 'descending'\n : undefined\n }\n >\n <div className=\"inline-flex items-center\">\n {editable && onColumnReorder && (\n // Reorder grip — its own drag surface so dragging it (not the\n // label) starts a reorder, leaving the label click free to sort.\n <span\n className=\"mr-1 -ml-1 cursor-grab text-text-tertiary opacity-0 group-hover:opacity-100\"\n onClick={(e) => e.stopPropagation()}\n aria-hidden=\"true\"\n title=\"Drag to reorder column\"\n >\n <GripVertical className=\"w-3.5 h-3.5\" />\n </span>\n )}\n {column.header}\n {getSortIcon(column)}\n </div>\n {editable && onColumnResize && (\n // Resize handle on the right border. Pointer-only; stops the sort\n // click and the reorder drag from firing.\n <span\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label={`Resize ${column.header} column`}\n className={`\n absolute top-0 right-0 z-30 h-full w-1.5\n cursor-col-resize select-none\n hover:bg-action-primary-bg/40\n ${resizingKey === column.key ? 'bg-action-primary-bg/60' : ''}\n `}\n onPointerDown={(e) => startResize(column.key, e)}\n onClick={(e) => e.stopPropagation()}\n draggable={false}\n />\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n );\n});\n\nexport default TableHeader;\n"],"mappings":"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"}
@@ -1,12 +1,16 @@
1
- import { useCrossFilterContext as e, useWidgetCrossFilter as t } from "../../../contexts/CrossFilterContext.js";
1
+ import { useIsEditMode as e } from "../../../store/dashboardStore.js";
2
+ import "../../../store/index.js";
3
+ import { useCrossFilterContext as t, useWidgetCrossFilter as n } from "../../../contexts/CrossFilterContext.js";
2
4
  import "../../../contexts/index.js";
3
- import n from "./TableHeader.js";
4
- import r from "./TableCell.js";
5
- import i from "./TablePagination.js";
6
- import { memo as a, useCallback as o, useEffect as s, useMemo as c, useRef as l, useState as u } from "react";
5
+ import r from "../../../hooks/useWidgetConfigPersist.js";
6
+ import "../../../hooks/index.js";
7
+ import i from "./TableHeader.js";
8
+ import a from "./TableCell.js";
9
+ import ee from "./TablePagination.js";
10
+ import { memo as o, useCallback as s, useEffect as c, useMemo as l, useRef as te, useState as u } from "react";
7
11
  import { jsx as d, jsxs as f } from "react/jsx-runtime";
8
12
  //#region src/bigconsole/components/widgets/table/TableWidget.tsx
9
- function ee(e, t) {
13
+ function ne(e, t) {
10
14
  return !t.column || !t.direction ? e : [...e].sort((e, n) => {
11
15
  let r = e[t.column], i = n[t.column];
12
16
  if (r == null && i == null) return 0;
@@ -16,7 +20,7 @@ function ee(e, t) {
16
20
  return a = typeof r == "number" && typeof i == "number" ? r - i : String(r).localeCompare(String(i)), t.direction === "asc" ? a : -a;
17
21
  });
18
22
  }
19
- function p(e, t, n) {
23
+ function re(e, t, n) {
20
24
  if (!t.trim()) return e;
21
25
  let r = t.toLowerCase();
22
26
  return e.filter((e) => n.some((t) => {
@@ -24,7 +28,7 @@ function p(e, t, n) {
24
28
  return n != null && String(n).toLowerCase().includes(r);
25
29
  }));
26
30
  }
27
- function m(e, t, n) {
31
+ function ie(e, t, n) {
28
32
  switch (t) {
29
33
  case "currency": return new Intl.NumberFormat("en-US", {
30
34
  style: "currency",
@@ -35,10 +39,10 @@ function m(e, t, n) {
35
39
  default: return Number.isInteger(e) ? String(e) : e.toFixed(2);
36
40
  }
37
41
  }
38
- var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
39
- let v = a.config, y = c(() => {
40
- if (Array.isArray(h)) return h;
41
- if (h && typeof h == "object") {
42
+ var p = o(function({ widget: o, data: p = [], onRowClick: m, onDrilldown: h }) {
43
+ let g = o.config, _ = l(() => {
44
+ if (Array.isArray(p)) return p;
45
+ if (p && typeof p == "object") {
42
46
  for (let e of [
43
47
  "data",
44
48
  "items",
@@ -49,20 +53,20 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
49
53
  "sales",
50
54
  "entries",
51
55
  "values"
52
- ]) if (Array.isArray(h[e])) return h[e];
53
- let e = Object.values(h);
56
+ ]) if (Array.isArray(p[e])) return p[e];
57
+ let e = Object.values(p);
54
58
  for (let t of e) if (Array.isArray(t)) return t;
55
- return [h];
59
+ return [p];
56
60
  }
57
61
  return [];
58
- }, [h]), { handleDataClick: b, enabled: x } = t(a.id, v?.crossFilterMappings), { clearAllCrossFilters: S, hasCrossFilters: C } = e(), [w, T] = u({
59
- column: v?.defaultSortColumn || null,
60
- direction: v?.defaultSortDirection || null
61
- }), [E, D] = u(""), [O, k] = u({
62
+ }, [p]), { handleDataClick: v, enabled: y } = n(o.id, g?.crossFilterMappings), { clearAllCrossFilters: b, hasCrossFilters: ae } = t(), [x, S] = u({
63
+ column: g?.defaultSortColumn || null,
64
+ direction: g?.defaultSortDirection || null
65
+ }), [C, w] = u(""), [T, E] = u({
62
66
  page: 0,
63
- pageSize: v?.pageSize || 10,
67
+ pageSize: g?.pageSize || 10,
64
68
  totalItems: 0
65
- }), A = c(() => v?.columns?.length ? v.columns.map((e) => {
69
+ }), D = e(), { persistConfig: O } = r(o), [k, A] = u({}), [j, oe] = u(null), M = l(() => g?.columns?.length ? g.columns.map((e) => {
66
70
  if (typeof e == "string") return {
67
71
  key: e,
68
72
  header: e.charAt(0).toUpperCase() + e.slice(1).replace(/_/g, " "),
@@ -76,118 +80,180 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
76
80
  width: e.width == null ? void 0 : String(e.width),
77
81
  align: e.align
78
82
  };
79
- }) : y.length > 0 ? Object.keys(y[0]).map((e) => ({
83
+ }) : _.length > 0 ? Object.keys(_[0]).map((e) => ({
80
84
  key: e,
81
85
  header: e.charAt(0).toUpperCase() + e.slice(1).replace(/_/g, " "),
82
86
  sortable: !0
83
- })) : [], [v?.columns, y]), j = c(() => {
87
+ })) : [], [g?.columns, _]), N = l(() => {
88
+ let e = M;
89
+ if (j) {
90
+ let t = new Map(M.map((e) => [e.key, e])), n = j.map((e) => t.get(e)).filter((e) => e !== void 0), r = M.filter((e) => !j.includes(e.key));
91
+ e = [...n, ...r];
92
+ }
93
+ return Object.keys(k).length > 0 && (e = e.map((e) => k[e.key] == null ? e : {
94
+ ...e,
95
+ width: `${k[e.key]}px`
96
+ })), e;
97
+ }, [
98
+ M,
99
+ j,
100
+ k
101
+ ]), P = s((e, t) => {
102
+ let n = /* @__PURE__ */ new Map();
103
+ g?.columns?.forEach((e) => {
104
+ let t = e.key || e.field;
105
+ t && n.set(t, e);
106
+ });
107
+ let r = M.map((e) => e.key);
108
+ O({ columns: [...e.filter((e) => r.includes(e)), ...r.filter((t) => !e.includes(t))].map((e) => {
109
+ let r = M.find((t) => t.key === e), i = {
110
+ ...n.get(e) ?? {},
111
+ key: e
112
+ };
113
+ !i.label && !i.header && r?.header && (i.label = r.header);
114
+ let a = t[e];
115
+ return a != null && (i.width = a), i;
116
+ }) });
117
+ }, [
118
+ M,
119
+ g?.columns,
120
+ O
121
+ ]), se = s((e, t) => {
122
+ A((n) => ({
123
+ ...n,
124
+ [e]: t
125
+ }));
126
+ }, []), ce = s((e, t) => {
127
+ let n = {
128
+ ...k,
129
+ [e]: t
130
+ };
131
+ A(n), P(j ?? M.map((e) => e.key), n);
132
+ }, [
133
+ k,
134
+ j,
135
+ M,
136
+ P
137
+ ]), le = s((e, t) => {
138
+ let n = j ?? M.map((e) => e.key), r = n.indexOf(e);
139
+ if (r < 0 || e === t) return;
140
+ let i = [...n];
141
+ i.splice(r, 1);
142
+ let a = i.indexOf(t);
143
+ a < 0 || (i.splice(a, 0, e), oe(i), P(i, k));
144
+ }, [
145
+ j,
146
+ M,
147
+ k,
148
+ P
149
+ ]), F = l(() => {
84
150
  let e = {};
85
- return v?.columns?.forEach((t) => {
151
+ return g?.columns?.forEach((t) => {
86
152
  t.format && (t.key || t.field) && (e[t.key || t.field || ""] = { type: t.format });
87
153
  }), e;
88
- }, [v?.columns]), M = c(() => {
154
+ }, [g?.columns]), ue = l(() => {
89
155
  let e = {};
90
- return v?.columns?.forEach((t) => {
156
+ return g?.columns?.forEach((t) => {
91
157
  let n = t.key || t.field;
92
158
  n && t.cellChart && (e[n] = t.cellChart);
93
159
  }), e;
94
- }, [v?.columns]), N = v?.conditionalRules, P = c(() => {
160
+ }, [g?.columns]), I = g?.conditionalRules, L = l(() => {
95
161
  let e = {};
96
- return v?.columns?.forEach((t) => {
162
+ return g?.columns?.forEach((t) => {
97
163
  let n = t.key || t.field;
98
164
  n && t.conditionalRules?.length && (e[n] = t.conditionalRules);
99
165
  }), e;
100
- }, [v?.columns]), F = c(() => {
166
+ }, [g?.columns]), R = l(() => {
101
167
  let e = {};
102
- return v?.columns?.forEach((t) => {
168
+ return g?.columns?.forEach((t) => {
103
169
  let n = t.key || t.field;
104
170
  n && t.total && t.total !== "none" && (e[n] = t.total);
105
171
  }), e;
106
- }, [v?.columns]), I = v?.showTotals === !0 || Object.keys(F).length > 0, L = c(() => {
107
- let e = y;
108
- return v?.showSearch !== !1 && (e = p(e, E, A)), e = ee(e, w), e;
172
+ }, [g?.columns]), z = g?.showTotals === !0 || Object.keys(R).length > 0, B = l(() => {
173
+ let e = _;
174
+ return g?.showSearch !== !1 && (e = re(e, C, N)), e = ne(e, x), e;
109
175
  }, [
110
- y,
111
- E,
112
- w,
113
- A,
114
- v?.showSearch
115
- ]), R = c(() => {
176
+ _,
177
+ C,
178
+ x,
179
+ N,
180
+ g?.showSearch
181
+ ]), V = l(() => {
116
182
  let e = {};
117
- for (let t of A) {
183
+ for (let t of N) {
118
184
  let n = 0;
119
- for (let e of L) {
185
+ for (let e of B) {
120
186
  let r = e[t.key];
121
187
  typeof r == "number" && Number.isFinite(r) && (n = Math.max(n, Math.abs(r)));
122
188
  }
123
189
  e[t.key] = n;
124
190
  }
125
191
  return e;
126
- }, [A, L]), z = c(() => {
127
- if (!I) return {};
192
+ }, [N, B]), H = l(() => {
193
+ if (!z) return {};
128
194
  let e = {};
129
- for (let t of A) {
130
- let n = F[t.key];
195
+ for (let t of N) {
196
+ let n = R[t.key];
131
197
  if (!n) {
132
198
  e[t.key] = null;
133
199
  continue;
134
200
  }
135
- let r = L.map((e) => e[t.key]).filter((e) => typeof e == "number" && Number.isFinite(e));
136
- n === "count" ? e[t.key] = L.length : r.length === 0 ? e[t.key] = null : n === "sum" ? e[t.key] = r.reduce((e, t) => e + t, 0) : n === "avg" ? e[t.key] = r.reduce((e, t) => e + t, 0) / r.length : n === "min" ? e[t.key] = Math.min(...r) : n === "max" ? e[t.key] = Math.max(...r) : e[t.key] = null;
201
+ let r = B.map((e) => e[t.key]).filter((e) => typeof e == "number" && Number.isFinite(e));
202
+ n === "count" ? e[t.key] = B.length : r.length === 0 ? e[t.key] = null : n === "sum" ? e[t.key] = r.reduce((e, t) => e + t, 0) : n === "avg" ? e[t.key] = r.reduce((e, t) => e + t, 0) / r.length : n === "min" ? e[t.key] = Math.min(...r) : n === "max" ? e[t.key] = Math.max(...r) : e[t.key] = null;
137
203
  }
138
204
  return e;
139
205
  }, [
140
- I,
141
- A,
142
- F,
143
- L
206
+ z,
207
+ N,
208
+ R,
209
+ B
144
210
  ]);
145
- c(() => {
146
- k((e) => ({
211
+ l(() => {
212
+ E((e) => ({
147
213
  ...e,
148
- totalItems: L.length
214
+ totalItems: B.length
149
215
  }));
150
- }, [L.length]);
151
- let B = c(() => {
152
- if (v?.showPagination === !1) return L;
153
- let e = O.page * O.pageSize;
154
- return L.slice(e, e + O.pageSize);
216
+ }, [B.length]);
217
+ let U = l(() => {
218
+ if (g?.showPagination === !1) return B;
219
+ let e = T.page * T.pageSize;
220
+ return B.slice(e, e + T.pageSize);
155
221
  }, [
156
- L,
157
- O,
158
- v?.showPagination
159
- ]), V = v?.virtualized === !0, H = v?.rowHeight && v.rowHeight > 0 ? v.rowHeight : 44, U = l(null), [W, G] = u(0), [K, q] = u(0);
160
- s(() => {
161
- if (!V) return;
162
- let e = U.current;
222
+ B,
223
+ T,
224
+ g?.showPagination
225
+ ]), W = g?.virtualized === !0, G = g?.rowHeight && g.rowHeight > 0 ? g.rowHeight : 44, K = te(null), [q, de] = u(0), [J, Y] = u(0);
226
+ c(() => {
227
+ if (!W) return;
228
+ let e = K.current;
163
229
  if (!e) return;
164
- q(e.clientHeight);
165
- let t = typeof ResizeObserver < "u" ? new ResizeObserver(() => q(e.clientHeight)) : void 0;
230
+ Y(e.clientHeight);
231
+ let t = typeof ResizeObserver < "u" ? new ResizeObserver(() => Y(e.clientHeight)) : void 0;
166
232
  return t?.observe(e), () => t?.disconnect();
167
- }, [V]);
168
- let J = c(() => {
169
- if (!V) return null;
170
- let e = L.length, t = K > 0 ? Math.ceil(K / H) : 20, n = Math.max(0, Math.floor(W / H) - 6), r = Math.min(e, n + t + 12);
233
+ }, [W]);
234
+ let X = l(() => {
235
+ if (!W) return null;
236
+ let e = B.length, t = J > 0 ? Math.ceil(J / G) : 20, n = Math.max(0, Math.floor(q / G) - 6), r = Math.min(e, n + t + 12);
171
237
  return {
172
- rows: L.slice(n, r),
173
- topSpacer: n * H,
174
- bottomSpacer: Math.max(0, (e - r) * H),
238
+ rows: B.slice(n, r),
239
+ topSpacer: n * G,
240
+ bottomSpacer: Math.max(0, (e - r) * G),
175
241
  startIndex: n
176
242
  };
177
243
  }, [
178
- V,
179
- L,
180
- K,
181
244
  W,
182
- H
183
- ]), Y = V && J ? J.rows : B, X = V && J ? J.startIndex : 0, Z = v?.frozenColumns && v.frozenColumns > 0 ? v.frozenColumns : 0, { displayColumns: Q, frozenLefts: $ } = c(() => {
245
+ B,
246
+ J,
247
+ q,
248
+ G
249
+ ]), fe = W && X ? X.rows : U, pe = W && X ? X.startIndex : 0, Z = g?.frozenColumns && g.frozenColumns > 0 ? g.frozenColumns : 0, { displayColumns: Q, frozenLefts: $ } = l(() => {
184
250
  if (Z <= 0) return {
185
- displayColumns: A,
251
+ displayColumns: N,
186
252
  frozenLefts: {}
187
253
  };
188
254
  let e = {}, t = 0;
189
255
  return {
190
- displayColumns: A.map((n, r) => {
256
+ displayColumns: N.map((n, r) => {
191
257
  if (r < Z) {
192
258
  let r = n.width && parseInt(n.width, 10) || 160;
193
259
  return e[n.key] = t, t += r, {
@@ -199,8 +265,8 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
199
265
  }),
200
266
  frozenLefts: e
201
267
  };
202
- }, [A, Z]), te = o((e) => {
203
- T((t) => {
268
+ }, [N, Z]), me = s((e) => {
269
+ S((t) => {
204
270
  if (t.column !== e) return {
205
271
  column: e,
206
272
  direction: "asc"
@@ -211,55 +277,55 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
211
277
  direction: n
212
278
  };
213
279
  });
214
- }, []), ne = o((e) => {
215
- k((t) => ({
280
+ }, []), he = s((e) => {
281
+ E((t) => ({
216
282
  ...t,
217
283
  page: e
218
284
  }));
219
- }, []), re = o((e) => {
220
- k((t) => ({
285
+ }, []), ge = s((e) => {
286
+ E((t) => ({
221
287
  ...t,
222
288
  pageSize: e,
223
289
  page: 0
224
290
  }));
225
- }, []), ie = o((e, t) => {
226
- let n = v?.crossFilterMappings?.length || v?.enableCrossFilter;
227
- if (x && n) {
228
- let t = A[0]?.key;
229
- b(e, t ? String(e[t]) : void 0);
291
+ }, []), _e = s((e, t) => {
292
+ let n = g?.crossFilterMappings?.length || g?.enableCrossFilter;
293
+ if (y && n) {
294
+ let t = N[0]?.key;
295
+ v(e, t ? String(e[t]) : void 0);
230
296
  }
231
- _ ? _(e) : g && a.drilldowns?.length && g(e, t);
297
+ h ? h(e) : m && o.drilldowns?.length && m(e, t);
232
298
  }, [
233
- g,
234
- _,
235
- a.drilldowns,
236
- x,
237
- b,
238
- A,
239
- v?.crossFilterMappings,
240
- v?.enableCrossFilter
241
- ]), ae = o(() => {
242
- let e = [A.map((e) => e.header).join(","), ...L.map((e) => A.map((t) => {
299
+ m,
300
+ h,
301
+ o.drilldowns,
302
+ y,
303
+ v,
304
+ N,
305
+ g?.crossFilterMappings,
306
+ g?.enableCrossFilter
307
+ ]), ve = s(() => {
308
+ let e = [N.map((e) => e.header).join(","), ...B.map((e) => N.map((t) => {
243
309
  let n = e[t.key];
244
310
  if (n == null) return "";
245
311
  let r = String(n);
246
312
  return r.includes(",") || r.includes("\"") ? `"${r.replace(/"/g, "\"\"")}"` : r;
247
313
  }).join(","))].join("\n"), t = new Blob([e], { type: "text/csv" }), n = URL.createObjectURL(t), r = document.createElement("a");
248
- r.href = n, r.download = `${a.title || "table"}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`, r.click(), URL.revokeObjectURL(n);
314
+ r.href = n, r.download = `${o.title || "table"}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`, r.click(), URL.revokeObjectURL(n);
249
315
  }, [
250
- A,
251
- L,
252
- a.title
253
- ]), oe = v?.crossFilterMappings?.length || v?.enableCrossFilter, se = !!(_ || g && a.drilldowns?.length || oe);
316
+ N,
317
+ B,
318
+ o.title
319
+ ]), ye = g?.crossFilterMappings?.length || g?.enableCrossFilter, be = !!(h || m && o.drilldowns?.length || ye);
254
320
  return /* @__PURE__ */ f("div", {
255
321
  className: "flex flex-col h-full",
256
322
  children: [
257
- C && /* @__PURE__ */ d("div", {
323
+ ae && /* @__PURE__ */ d("div", {
258
324
  className: "flex items-center gap-2 px-3 py-2 border-b border-border-default",
259
325
  children: /* @__PURE__ */ f("button", {
260
326
  type: "button",
261
327
  onClick: (e) => {
262
- e.stopPropagation(), S();
328
+ e.stopPropagation(), b();
263
329
  },
264
330
  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 ",
265
331
  children: [/* @__PURE__ */ d("svg", {
@@ -276,9 +342,9 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
276
342
  }), "Clear Filter"]
277
343
  })
278
344
  }),
279
- (v?.showSearch !== !1 || v?.showExport !== !1) && /* @__PURE__ */ f("div", {
345
+ (g?.showSearch !== !1 || g?.showExport !== !1) && /* @__PURE__ */ f("div", {
280
346
  className: "flex items-center justify-between p-3 border-b border-border-default",
281
- children: [v?.showSearch !== !1 && /* @__PURE__ */ f("div", {
347
+ children: [g?.showSearch !== !1 && /* @__PURE__ */ f("div", {
282
348
  className: "relative flex items-center",
283
349
  children: [/* @__PURE__ */ d("svg", {
284
350
  className: "absolute left-3 w-4 h-4 text-text-secondary pointer-events-none",
@@ -294,13 +360,13 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
294
360
  }), /* @__PURE__ */ d("input", {
295
361
  type: "text",
296
362
  placeholder: "Search products...",
297
- value: E,
298
- onChange: (e) => D(e.target.value),
363
+ value: C,
364
+ onChange: (e) => w(e.target.value),
299
365
  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 "
300
366
  })]
301
- }), v?.showExport !== !1 && /* @__PURE__ */ f("button", {
367
+ }), g?.showExport !== !1 && /* @__PURE__ */ f("button", {
302
368
  type: "button",
303
- onClick: ae,
369
+ onClick: ve,
304
370
  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-muted\n transition-colors\n ",
305
371
  children: [/* @__PURE__ */ d("svg", {
306
372
  className: "w-4 h-4",
@@ -317,22 +383,26 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
317
383
  })]
318
384
  }),
319
385
  /* @__PURE__ */ f("div", {
320
- ref: U,
386
+ ref: K,
321
387
  className: "flex-1 overflow-auto",
322
- onScroll: V ? (e) => G(e.target.scrollTop) : void 0,
388
+ onScroll: W ? (e) => de(e.target.scrollTop) : void 0,
323
389
  children: [/* @__PURE__ */ f("table", {
324
390
  className: "w-full",
325
391
  children: [
326
- /* @__PURE__ */ d(n, {
392
+ /* @__PURE__ */ d(i, {
327
393
  columns: Q,
328
- sortState: w,
329
- onSort: te,
330
- frozenLefts: Z > 0 ? $ : void 0
394
+ sortState: x,
395
+ onSort: me,
396
+ frozenLefts: Z > 0 ? $ : void 0,
397
+ editable: D,
398
+ onColumnResize: D ? se : void 0,
399
+ onColumnResizeEnd: D ? ce : void 0,
400
+ onColumnReorder: D ? le : void 0
331
401
  }),
332
402
  /* @__PURE__ */ f("tbody", { children: [
333
- V && J && J.topSpacer > 0 && /* @__PURE__ */ d("tr", {
403
+ W && X && X.topSpacer > 0 && /* @__PURE__ */ d("tr", {
334
404
  "aria-hidden": "true",
335
- style: { height: J.topSpacer },
405
+ style: { height: X.topSpacer },
336
406
  children: /* @__PURE__ */ d("td", {
337
407
  colSpan: Q.length,
338
408
  style: {
@@ -341,30 +411,30 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
341
411
  }
342
412
  })
343
413
  }),
344
- Y.map((e, t) => {
345
- let n = X + t;
414
+ fe.map((e, t) => {
415
+ let n = pe + t;
346
416
  return /* @__PURE__ */ d("tr", {
347
417
  className: `
348
- ${se ? "cursor-pointer hover:bg-bg-muted/50" : ""}
418
+ ${be ? "cursor-pointer hover:bg-bg-muted/50" : ""}
349
419
  ${n % 2 == 0 ? "bg-bg-surface" : "bg-bg-muted/30"}
350
420
  `,
351
- style: V ? { height: H } : void 0,
352
- onClick: () => ie(e, n),
353
- children: Q.map((t) => /* @__PURE__ */ d(r, {
421
+ style: W ? { height: G } : void 0,
422
+ onClick: () => _e(e, n),
423
+ children: Q.map((t) => /* @__PURE__ */ d(a, {
354
424
  value: e[t.key],
355
425
  column: t,
356
- format: j[t.key],
426
+ format: F[t.key],
357
427
  rowData: e,
358
- conditionalRules: P[t.key] ?? N,
359
- dataBarMax: R[t.key],
428
+ conditionalRules: L[t.key] ?? I,
429
+ dataBarMax: V[t.key],
360
430
  stickyLeft: $[t.key],
361
- cellChart: M[t.key]
431
+ cellChart: ue[t.key]
362
432
  }, t.key))
363
433
  }, n);
364
434
  }),
365
- V && J && J.bottomSpacer > 0 && /* @__PURE__ */ d("tr", {
435
+ W && X && X.bottomSpacer > 0 && /* @__PURE__ */ d("tr", {
366
436
  "aria-hidden": "true",
367
- style: { height: J.bottomSpacer },
437
+ style: { height: X.bottomSpacer },
368
438
  children: /* @__PURE__ */ d("td", {
369
439
  colSpan: Q.length,
370
440
  style: {
@@ -374,10 +444,10 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
374
444
  })
375
445
  })
376
446
  ] }),
377
- I && L.length > 0 && /* @__PURE__ */ d("tfoot", { children: /* @__PURE__ */ d("tr", {
447
+ z && B.length > 0 && /* @__PURE__ */ d("tfoot", { children: /* @__PURE__ */ d("tr", {
378
448
  className: "bg-bg-muted/50 font-semibold border-t-2 border-border-default",
379
449
  children: Q.map((e, t) => {
380
- let n = z[e.key], r = j[e.key], i = $[e.key];
450
+ let n = H[e.key], r = F[e.key], i = $[e.key];
381
451
  return /* @__PURE__ */ d("td", {
382
452
  className: `
383
453
  px-4 py-3 text-sm text-text-primary
@@ -386,28 +456,28 @@ var h = a(function({ widget: a, data: h = [], onRowClick: g, onDrilldown: _ }) {
386
456
  ${i == null ? "" : "sticky z-10 bg-bg-muted"}
387
457
  `,
388
458
  style: i == null ? void 0 : { left: i },
389
- children: n == null ? t === 0 ? "Total" : "" : m(n, r?.type, r?.currency)
459
+ children: n == null ? t === 0 ? "Total" : "" : ie(n, r?.type, r?.currency)
390
460
  }, e.key);
391
461
  })
392
462
  }) })
393
463
  ]
394
- }), L.length === 0 && /* @__PURE__ */ d("div", {
464
+ }), B.length === 0 && /* @__PURE__ */ d("div", {
395
465
  className: "flex items-center justify-center py-12 text-text-secondary",
396
- children: E ? "No matching records found" : "No data available"
466
+ children: C ? "No matching records found" : "No data available"
397
467
  })]
398
468
  }),
399
- !V && v?.showPagination !== !1 && L.length > 0 && /* @__PURE__ */ d(i, {
469
+ !W && g?.showPagination !== !1 && B.length > 0 && /* @__PURE__ */ d(ee, {
400
470
  pagination: {
401
- ...O,
402
- totalItems: L.length
471
+ ...T,
472
+ totalItems: B.length
403
473
  },
404
- onPageChange: ne,
405
- onPageSizeChange: re
474
+ onPageChange: he,
475
+ onPageSizeChange: ge
406
476
  })
407
477
  ]
408
478
  });
409
479
  });
410
480
  //#endregion
411
- export { h as default };
481
+ export { p as default };
412
482
 
413
483
  //# sourceMappingURL=TableWidget.js.map
@@ -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 type { ConditionalRule } from '../utils/conditionalFormat';\n// Phase 2: Cross-filtering support\nimport { useWidgetCrossFilter, useCrossFilterContext } from '../../../contexts';\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 // Build column definitions from config or data\n const columns: 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 // 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 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-muted\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 />\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-muted/50' : ''}\n ${rowIndex % 2 === 0 ? 'bg-bg-surface' : 'bg-bg-muted/30'}\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-muted/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-muted' : ''}\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 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 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-muted\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-muted/50' : ''}\n ${rowIndex % 2 === 0 ? 'bg-bg-surface' : 'bg-bg-muted/30'}\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-muted/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-muted' : ''}\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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"}
@@ -4,6 +4,7 @@ import "./useWidgetData.js";
4
4
  import "./useWidgetCatalog.js";
5
5
  import "./useAutoRefresh.js";
6
6
  import "./useWidgetOperations.js";
7
+ import "./useWidgetConfigPersist.js";
7
8
  import "./useDashboardOperations.js";
8
9
  import "./useWidgetExport.js";
9
10
  import "./useDashboardImageExport.js";
@@ -0,0 +1,32 @@
1
+ import { useWidgetStore as e } from "../store/widgetStore.js";
2
+ import { useIsEditMode as t } from "../store/dashboardStore.js";
3
+ import "../store/index.js";
4
+ import n from "./useWidgetOperations.js";
5
+ import { useCallback as r } from "react";
6
+ //#region src/bigconsole/hooks/useWidgetConfigPersist.ts
7
+ function i(i) {
8
+ let a = t(), { updateWidget: o } = n(void 0, void 0, { skipFetch: !0 });
9
+ return {
10
+ canPersist: a,
11
+ persistConfig: r(async (t) => {
12
+ if (!a) return !1;
13
+ let n = e.getState().widgets.get(i.id)?.config ?? i.config ?? {};
14
+ return await o({
15
+ id: i.id,
16
+ config: {
17
+ ...n,
18
+ ...t
19
+ }
20
+ }) != null;
21
+ }, [
22
+ a,
23
+ i.id,
24
+ i.config,
25
+ o
26
+ ])
27
+ };
28
+ }
29
+ //#endregion
30
+ export { i as default };
31
+
32
+ //# sourceMappingURL=useWidgetConfigPersist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWidgetConfigPersist.js","names":[],"sources":["../../../src/bigconsole/hooks/useWidgetConfigPersist.ts"],"sourcesContent":["/**\n * useWidgetConfigPersist Hook\n *\n * Persists in-place edits to a widget's `config` (e.g. column widths/order from\n * a table drag, or pivot field zones) back to the backend via `updateWidget`,\n * so the change survives a reload.\n *\n * Persistence is gated on edit mode: a viewer dragging a column gets a live,\n * local-only change (handled by the caller's own state) and nothing is written;\n * an editor's change is saved. The hook merges the patch onto the *latest* stored\n * config to avoid clobbering concurrent edits from the properties panel.\n */\n\nimport { useCallback } from 'react';\nimport type { Widget } from '../types';\nimport { useWidgetStore } from '../store';\nimport { useIsEditMode } from '../store';\nimport { useWidgetOperations } from './useWidgetOperations';\n\nexport interface UseWidgetConfigPersistResult {\n /** True when edits to this widget's config will be saved (edit mode). */\n canPersist: boolean;\n /**\n * Merge `patch` into the widget's config and save it. No-op (returns false)\n * when not in edit mode. Resolves to true when the save succeeds.\n */\n persistConfig: (patch: Record<string, unknown>) => Promise<boolean>;\n}\n\nexport function useWidgetConfigPersist(widget: Widget): UseWidgetConfigPersistResult {\n const isEditMode = useIsEditMode();\n // skipFetch: this hook never owns the widget list — it only issues updates.\n const { updateWidget } = useWidgetOperations(undefined, undefined, { skipFetch: true });\n\n const canPersist = isEditMode;\n\n const persistConfig = useCallback(\n async (patch: Record<string, unknown>): Promise<boolean> => {\n if (!isEditMode) return false;\n // Read the freshest config from the store rather than the closure's copy,\n // so we don't overwrite an edit made elsewhere since this component rendered.\n const latest = useWidgetStore.getState().widgets.get(widget.id);\n const baseConfig = (latest?.config ?? widget.config ?? {}) as Record<string, unknown>;\n const result = await updateWidget({\n id: widget.id,\n config: { ...baseConfig, ...patch },\n });\n return result != null;\n },\n [isEditMode, widget.id, widget.config, updateWidget]\n );\n\n return { canPersist, persistConfig };\n}\n\nexport default useWidgetConfigPersist;\n"],"mappings":";;;;;;AA6BA,SAAgB,EAAuB,GAA8C;CACnF,IAAM,IAAa,GAAe,EAE5B,EAAE,oBAAiB,EAAoB,KAAA,GAAW,KAAA,GAAW,EAAE,WAAW,IAAM,CAAC;AAoBvF,QAAO;EAAE,YAlBU;EAkBE,eAhBC,EACpB,OAAO,MAAqD;AAC1D,OAAI,CAAC,EAAY,QAAO;GAIxB,IAAM,IADS,EAAe,UAAU,CAAC,QAAQ,IAAI,EAAO,GAAG,EACnC,UAAU,EAAO,UAAU,EAAE;AAKzD,UAJe,MAAM,EAAa;IAChC,IAAI,EAAO;IACX,QAAQ;KAAE,GAAG;KAAY,GAAG;KAAO;IACpC,CAAC,IACe;KAEnB;GAAC;GAAY,EAAO;GAAI,EAAO;GAAQ;GAAa,CACrD;EAEmC"}
@@ -239,8 +239,8 @@ var i = 30, a = {
239
239
  sidebarCollapsed: e.sidebarCollapsed,
240
240
  filtersCollapsed: e.filtersCollapsed
241
241
  })
242
- }), { name: "DashboardStore" })), c = () => s((e) => e.globalFilterValues);
242
+ }), { name: "DashboardStore" })), c = () => s((e) => e.viewMode === "edit"), l = () => s((e) => e.globalFilterValues);
243
243
  //#endregion
244
- export { s as useDashboardStore, c as useGlobalFilterValues };
244
+ export { s as useDashboardStore, l as useGlobalFilterValues, c as useIsEditMode };
245
245
 
246
246
  //# sourceMappingURL=dashboardStore.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dashboardStore.js","names":[],"sources":["../../../src/bigconsole/store/dashboardStore.ts"],"sourcesContent":["/**\n * Dashboard Store - Zustand state management for dashboards\n *\n * Manages dashboard UI state, active page, and layout configuration.\n * Server state is managed by Apollo Client.\n */\n\nimport { create } from 'zustand';\nimport { devtools, persist } from 'zustand/middleware';\nimport { createProfileScopedStorage } from '@burdenoff/fe-libs/shared/utils';\nimport type { Dashboard, DashboardPage, DashboardLayoutConfig, DashboardTheme } from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype ViewMode = 'view' | 'edit' | 'preview';\ntype ZoomLevel = number; // 50-200 range\n\n// Undo/redo history — lightweight snapshots of mutable dashboard state\ninterface DashboardSnapshot {\n currentDashboard: Dashboard | null;\n pages: Map<string, DashboardPage>;\n activePageId: string | null;\n layoutConfig: DashboardLayoutConfig;\n}\n\nconst MAX_HISTORY = 30;\n\ninterface DashboardState {\n // Dashboard data\n currentDashboard: Dashboard | null;\n dashboards: Map<string, Dashboard>;\n\n // Page state\n activePageId: string | null;\n pages: Map<string, DashboardPage>;\n\n // View state\n viewMode: ViewMode;\n zoomLevel: ZoomLevel;\n showGrid: boolean;\n snapToGrid: boolean;\n\n // Layout config\n layoutConfig: DashboardLayoutConfig;\n\n // Theme\n theme: DashboardTheme | null;\n\n // Global Filters (Phase 2)\n globalFilterValues: Record<string, unknown>;\n filtersCollapsed: boolean;\n\n // Toolbar state\n toolbarCollapsed: boolean;\n sidebarCollapsed: boolean;\n\n // Undo/Redo history\n canUndo: boolean;\n canRedo: boolean;\n _history: DashboardSnapshot[];\n _historyIndex: number;\n\n // Loading states\n isLoading: boolean;\n isSaving: boolean;\n hasUnsavedChanges: boolean;\n\n // Actions\n setCurrentDashboard: (dashboard: Dashboard | null) => void;\n setDashboards: (dashboards: Dashboard[]) => void;\n addDashboard: (dashboard: Dashboard) => void;\n updateDashboard: (id: string, updates: Partial<Dashboard>) => void;\n removeDashboard: (id: string) => void;\n\n // Page actions\n setActivePageId: (pageId: string | null) => void;\n setPages: (pages: DashboardPage[]) => void;\n addPage: (page: DashboardPage) => void;\n updatePage: (id: string, updates: Partial<DashboardPage>) => void;\n removePage: (id: string) => void;\n reorderPages: (pageIds: string[]) => void;\n\n // View actions\n setViewMode: (mode: ViewMode) => void;\n toggleViewMode: () => void;\n setZoomLevel: (level: number) => void;\n zoomIn: () => void;\n zoomOut: () => void;\n resetZoom: () => void;\n toggleGrid: () => void;\n toggleSnapToGrid: () => void;\n\n // Layout actions\n setLayoutConfig: (config: Partial<DashboardLayoutConfig>) => void;\n\n // Theme actions\n setTheme: (theme: DashboardTheme | null) => void;\n\n // Toolbar actions\n toggleToolbar: () => void;\n toggleSidebar: () => void;\n\n // State actions\n setLoading: (loading: boolean) => void;\n setSaving: (saving: boolean) => void;\n setHasUnsavedChanges: (hasChanges: boolean) => void;\n markAsSaved: () => void;\n\n // Global Filter actions (Phase 2)\n setGlobalFilterValues: (values: Record<string, unknown>) => void;\n setGlobalFilterValue: (filterId: string, value: unknown) => void;\n clearGlobalFilterValue: (filterId: string) => void;\n clearAllGlobalFilters: () => void;\n toggleFiltersCollapsed: () => void;\n\n // Undo/Redo actions (placeholders)\n undo: () => void;\n redo: () => void;\n\n // Reset\n reset: () => void;\n}\n\n// ============================================================================\n// Default Layout Config\n// ============================================================================\n\nconst defaultLayoutConfig: DashboardLayoutConfig = {\n columns: 12,\n rowHeight: 60,\n gap: 16,\n margin: [16, 16],\n containerPadding: [16, 16],\n};\n\n// ============================================================================\n// Initial State\n// ============================================================================\n\nconst initialState = {\n currentDashboard: null,\n dashboards: new Map<string, Dashboard>(),\n activePageId: null,\n pages: new Map<string, DashboardPage>(),\n viewMode: 'view' as ViewMode,\n zoomLevel: 100 as ZoomLevel,\n showGrid: true,\n snapToGrid: true,\n layoutConfig: defaultLayoutConfig,\n theme: null,\n // Global Filters (Phase 2)\n globalFilterValues: {} as Record<string, unknown>,\n filtersCollapsed: false,\n // Toolbar\n toolbarCollapsed: false,\n sidebarCollapsed: false,\n // Undo/redo history\n canUndo: false,\n canRedo: false,\n _history: [] as DashboardSnapshot[],\n _historyIndex: -1,\n isLoading: false,\n isSaving: false,\n hasUnsavedChanges: false,\n};\n\n// ============================================================================\n// Store\n// ============================================================================\n\nexport const useDashboardStore = create<DashboardState>()(\n devtools(\n persist(\n (set, _get) => ({\n ...initialState,\n\n // Dashboard actions\n setCurrentDashboard: (dashboard) => {\n const pages = dashboard ? new Map(dashboard.pages.map((p) => [p.id, p])) : new Map();\n const activePageId = dashboard?.pages?.[0]?.id || null;\n const layoutConfig = dashboard?.layoutConfig || defaultLayoutConfig;\n // Initialize undo history with the loaded state\n const snapshot: DashboardSnapshot = { currentDashboard: dashboard, pages, activePageId, layoutConfig };\n set({\n currentDashboard: dashboard,\n activePageId,\n pages,\n layoutConfig,\n theme: dashboard?.theme || null,\n _history: [snapshot],\n _historyIndex: 0,\n canUndo: false,\n canRedo: false,\n });\n },\n\n setDashboards: (dashboards) =>\n set({\n dashboards: new Map(dashboards.map((d) => [d.id, d])),\n }),\n\n addDashboard: (dashboard) =>\n set((state) => {\n const newDashboards = new Map(state.dashboards);\n newDashboards.set(dashboard.id, dashboard);\n return { dashboards: newDashboards };\n }),\n\n updateDashboard: (id, updates) =>\n set((state) => {\n const newDashboards = new Map(state.dashboards);\n const existing = newDashboards.get(id);\n if (existing) {\n newDashboards.set(id, { ...existing, ...updates });\n }\n // Also update currentDashboard if it's the one being updated\n const currentDashboard =\n state.currentDashboard?.id === id ? { ...state.currentDashboard, ...updates } : state.currentDashboard;\n return {\n dashboards: newDashboards,\n currentDashboard,\n hasUnsavedChanges: true,\n };\n }),\n\n removeDashboard: (id) =>\n set((state) => {\n const newDashboards = new Map(state.dashboards);\n newDashboards.delete(id);\n return {\n dashboards: newDashboards,\n currentDashboard: state.currentDashboard?.id === id ? null : state.currentDashboard,\n };\n }),\n\n // Page actions\n setActivePageId: (pageId) => set({ activePageId: pageId }),\n\n setPages: (pages) =>\n set({\n pages: new Map(pages.map((p) => [p.id, p])),\n activePageId: pages[0]?.id || null,\n }),\n\n addPage: (page) =>\n set((state) => {\n const newPages = new Map(state.pages);\n newPages.set(page.id, page);\n return { pages: newPages, hasUnsavedChanges: true };\n }),\n\n updatePage: (id, updates) =>\n set((state) => {\n const newPages = new Map(state.pages);\n const existing = newPages.get(id);\n if (existing) {\n newPages.set(id, { ...existing, ...updates });\n }\n return { pages: newPages, hasUnsavedChanges: true };\n }),\n\n removePage: (id) =>\n set((state) => {\n const newPages = new Map(state.pages);\n newPages.delete(id);\n const pagesArray = Array.from(newPages.values());\n return {\n pages: newPages,\n activePageId: state.activePageId === id ? pagesArray[0]?.id || null : state.activePageId,\n hasUnsavedChanges: true,\n };\n }),\n\n reorderPages: (pageIds) =>\n set((state) => {\n const newPages = new Map<string, DashboardPage>();\n pageIds.forEach((id, index) => {\n const page = state.pages.get(id);\n if (page) {\n newPages.set(id, { ...page, order: index });\n }\n });\n return { pages: newPages, hasUnsavedChanges: true };\n }),\n\n // View actions\n setViewMode: (mode) => set({ viewMode: mode }),\n\n toggleViewMode: () =>\n set((state) => ({\n viewMode: state.viewMode === 'edit' ? 'view' : 'edit',\n })),\n\n setZoomLevel: (level) => {\n // Clamp to valid zoom levels\n const clampedLevel = Math.max(50, Math.min(200, level)) as ZoomLevel;\n set({ zoomLevel: clampedLevel });\n },\n\n zoomIn: () =>\n set((state) => {\n const levels: ZoomLevel[] = [50, 75, 100, 125, 150];\n const currentIndex = levels.indexOf(state.zoomLevel);\n const nextIndex = Math.min(currentIndex + 1, levels.length - 1);\n return { zoomLevel: levels[nextIndex] };\n }),\n\n zoomOut: () =>\n set((state) => {\n const levels: ZoomLevel[] = [50, 75, 100, 125, 150];\n const currentIndex = levels.indexOf(state.zoomLevel);\n const prevIndex = Math.max(currentIndex - 1, 0);\n return { zoomLevel: levels[prevIndex] };\n }),\n\n resetZoom: () => set({ zoomLevel: 100 }),\n\n toggleGrid: () => set((state) => ({ showGrid: !state.showGrid })),\n\n toggleSnapToGrid: () => set((state) => ({ snapToGrid: !state.snapToGrid })),\n\n // Layout actions\n setLayoutConfig: (config) =>\n set((state) => ({\n layoutConfig: { ...state.layoutConfig, ...config },\n hasUnsavedChanges: true,\n })),\n\n // Theme actions\n setTheme: (theme) => set({ theme, hasUnsavedChanges: true }),\n\n // Toolbar actions\n toggleToolbar: () => set((state) => ({ toolbarCollapsed: !state.toolbarCollapsed })),\n toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),\n\n // State actions\n setLoading: (loading) => set({ isLoading: loading }),\n setSaving: (saving) => set({ isSaving: saving }),\n setHasUnsavedChanges: (hasChanges) =>\n set((state) => {\n if (!hasChanges) return { hasUnsavedChanges: false };\n // Push snapshot to history on mutation\n const snapshot: DashboardSnapshot = {\n currentDashboard: state.currentDashboard,\n pages: new Map(state.pages),\n activePageId: state.activePageId,\n layoutConfig: state.layoutConfig,\n };\n // Trim future history (discard redo stack on new mutation)\n const history = state._history.slice(0, state._historyIndex + 1);\n history.push(snapshot);\n // Cap history size\n if (history.length > MAX_HISTORY) history.shift();\n const newIndex = history.length - 1;\n return {\n hasUnsavedChanges: true,\n _history: history,\n _historyIndex: newIndex,\n canUndo: newIndex > 0,\n canRedo: false,\n };\n }),\n markAsSaved: () => set({ hasUnsavedChanges: false, isSaving: false }),\n\n // Global Filter actions (Phase 2)\n setGlobalFilterValues: (values) => set({ globalFilterValues: values }),\n\n setGlobalFilterValue: (filterId, value) =>\n set((state) => ({\n globalFilterValues: {\n ...state.globalFilterValues,\n [filterId]: value,\n },\n })),\n\n clearGlobalFilterValue: (filterId) =>\n set((state) => {\n const newValues = { ...state.globalFilterValues };\n delete newValues[filterId];\n return { globalFilterValues: newValues };\n }),\n\n clearAllGlobalFilters: () => set({ globalFilterValues: {} }),\n\n toggleFiltersCollapsed: () => set((state) => ({ filtersCollapsed: !state.filtersCollapsed })),\n\n // Undo/Redo — snapshot-based history\n undo: () =>\n set((state) => {\n if (state._historyIndex <= 0) return state;\n const newIndex = state._historyIndex - 1;\n const snapshot = state._history[newIndex];\n if (!snapshot) return state;\n return {\n currentDashboard: snapshot.currentDashboard,\n pages: new Map(snapshot.pages),\n activePageId: snapshot.activePageId,\n layoutConfig: snapshot.layoutConfig,\n _historyIndex: newIndex,\n canUndo: newIndex > 0,\n canRedo: true,\n hasUnsavedChanges: true,\n };\n }),\n redo: () =>\n set((state) => {\n if (state._historyIndex >= state._history.length - 1) return state;\n const newIndex = state._historyIndex + 1;\n const snapshot = state._history[newIndex];\n if (!snapshot) return state;\n return {\n currentDashboard: snapshot.currentDashboard,\n pages: new Map(snapshot.pages),\n activePageId: snapshot.activePageId,\n layoutConfig: snapshot.layoutConfig,\n _historyIndex: newIndex,\n canUndo: true,\n canRedo: newIndex < state._history.length - 1,\n hasUnsavedChanges: true,\n };\n }),\n\n // Reset\n reset: () => set(initialState),\n }),\n {\n name: 'bigconsole-dashboard-store',\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n // Only persist UI preferences\n viewMode: state.viewMode,\n zoomLevel: state.zoomLevel,\n showGrid: state.showGrid,\n snapToGrid: state.snapToGrid,\n toolbarCollapsed: state.toolbarCollapsed,\n sidebarCollapsed: state.sidebarCollapsed,\n filtersCollapsed: state.filtersCollapsed,\n }),\n }\n ),\n { name: 'DashboardStore' }\n )\n);\n\n// ============================================================================\n// Selectors\n// ============================================================================\n\nexport const useCurrentDashboard = () => {\n return useDashboardStore((state) => state.currentDashboard);\n};\n\nexport const useActivePage = () => {\n const activePageId = useDashboardStore((state) => state.activePageId);\n const pages = useDashboardStore((state) => state.pages);\n return activePageId ? pages.get(activePageId) : null;\n};\n\nexport const usePagesArray = () => {\n return useDashboardStore((state) => Array.from(state.pages.values()).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)));\n};\n\nexport const useDashboardsArray = () => {\n return useDashboardStore((state) => Array.from(state.dashboards.values()));\n};\n\nexport const useIsEditMode = () => {\n return useDashboardStore((state) => state.viewMode === 'edit');\n};\n\nexport const useLayoutConfig = () => {\n return useDashboardStore((state) => state.layoutConfig);\n};\n\nexport const useHasUnsavedChanges = () => {\n return useDashboardStore((state) => state.hasUnsavedChanges);\n};\n\n// ============================================================================\n// Global Filter Selectors (Phase 2)\n// ============================================================================\n\n/**\n * Get all global filter values\n */\nexport const useGlobalFilterValues = () => {\n return useDashboardStore((state) => state.globalFilterValues);\n};\n\n/**\n * Get a specific filter value by ID\n */\nexport const useGlobalFilterValue = (filterId: string) => {\n return useDashboardStore((state) => state.globalFilterValues[filterId]);\n};\n\n/**\n * Check if any filters are active\n */\nexport const useHasActiveFilters = () => {\n return useDashboardStore((state) => Object.keys(state.globalFilterValues).length > 0);\n};\n\n/**\n * Get filters collapsed state\n */\nexport const useFiltersCollapsed = () => {\n return useDashboardStore((state) => state.filtersCollapsed);\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"dashboardStore.js","names":[],"sources":["../../../src/bigconsole/store/dashboardStore.ts"],"sourcesContent":["/**\n * Dashboard Store - Zustand state management for dashboards\n *\n * Manages dashboard UI state, active page, and layout configuration.\n * Server state is managed by Apollo Client.\n */\n\nimport { create } from 'zustand';\nimport { devtools, persist } from 'zustand/middleware';\nimport { createProfileScopedStorage } from '@burdenoff/fe-libs/shared/utils';\nimport type { Dashboard, DashboardPage, DashboardLayoutConfig, DashboardTheme } from '../types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype ViewMode = 'view' | 'edit' | 'preview';\ntype ZoomLevel = number; // 50-200 range\n\n// Undo/redo history — lightweight snapshots of mutable dashboard state\ninterface DashboardSnapshot {\n currentDashboard: Dashboard | null;\n pages: Map<string, DashboardPage>;\n activePageId: string | null;\n layoutConfig: DashboardLayoutConfig;\n}\n\nconst MAX_HISTORY = 30;\n\ninterface DashboardState {\n // Dashboard data\n currentDashboard: Dashboard | null;\n dashboards: Map<string, Dashboard>;\n\n // Page state\n activePageId: string | null;\n pages: Map<string, DashboardPage>;\n\n // View state\n viewMode: ViewMode;\n zoomLevel: ZoomLevel;\n showGrid: boolean;\n snapToGrid: boolean;\n\n // Layout config\n layoutConfig: DashboardLayoutConfig;\n\n // Theme\n theme: DashboardTheme | null;\n\n // Global Filters (Phase 2)\n globalFilterValues: Record<string, unknown>;\n filtersCollapsed: boolean;\n\n // Toolbar state\n toolbarCollapsed: boolean;\n sidebarCollapsed: boolean;\n\n // Undo/Redo history\n canUndo: boolean;\n canRedo: boolean;\n _history: DashboardSnapshot[];\n _historyIndex: number;\n\n // Loading states\n isLoading: boolean;\n isSaving: boolean;\n hasUnsavedChanges: boolean;\n\n // Actions\n setCurrentDashboard: (dashboard: Dashboard | null) => void;\n setDashboards: (dashboards: Dashboard[]) => void;\n addDashboard: (dashboard: Dashboard) => void;\n updateDashboard: (id: string, updates: Partial<Dashboard>) => void;\n removeDashboard: (id: string) => void;\n\n // Page actions\n setActivePageId: (pageId: string | null) => void;\n setPages: (pages: DashboardPage[]) => void;\n addPage: (page: DashboardPage) => void;\n updatePage: (id: string, updates: Partial<DashboardPage>) => void;\n removePage: (id: string) => void;\n reorderPages: (pageIds: string[]) => void;\n\n // View actions\n setViewMode: (mode: ViewMode) => void;\n toggleViewMode: () => void;\n setZoomLevel: (level: number) => void;\n zoomIn: () => void;\n zoomOut: () => void;\n resetZoom: () => void;\n toggleGrid: () => void;\n toggleSnapToGrid: () => void;\n\n // Layout actions\n setLayoutConfig: (config: Partial<DashboardLayoutConfig>) => void;\n\n // Theme actions\n setTheme: (theme: DashboardTheme | null) => void;\n\n // Toolbar actions\n toggleToolbar: () => void;\n toggleSidebar: () => void;\n\n // State actions\n setLoading: (loading: boolean) => void;\n setSaving: (saving: boolean) => void;\n setHasUnsavedChanges: (hasChanges: boolean) => void;\n markAsSaved: () => void;\n\n // Global Filter actions (Phase 2)\n setGlobalFilterValues: (values: Record<string, unknown>) => void;\n setGlobalFilterValue: (filterId: string, value: unknown) => void;\n clearGlobalFilterValue: (filterId: string) => void;\n clearAllGlobalFilters: () => void;\n toggleFiltersCollapsed: () => void;\n\n // Undo/Redo actions (placeholders)\n undo: () => void;\n redo: () => void;\n\n // Reset\n reset: () => void;\n}\n\n// ============================================================================\n// Default Layout Config\n// ============================================================================\n\nconst defaultLayoutConfig: DashboardLayoutConfig = {\n columns: 12,\n rowHeight: 60,\n gap: 16,\n margin: [16, 16],\n containerPadding: [16, 16],\n};\n\n// ============================================================================\n// Initial State\n// ============================================================================\n\nconst initialState = {\n currentDashboard: null,\n dashboards: new Map<string, Dashboard>(),\n activePageId: null,\n pages: new Map<string, DashboardPage>(),\n viewMode: 'view' as ViewMode,\n zoomLevel: 100 as ZoomLevel,\n showGrid: true,\n snapToGrid: true,\n layoutConfig: defaultLayoutConfig,\n theme: null,\n // Global Filters (Phase 2)\n globalFilterValues: {} as Record<string, unknown>,\n filtersCollapsed: false,\n // Toolbar\n toolbarCollapsed: false,\n sidebarCollapsed: false,\n // Undo/redo history\n canUndo: false,\n canRedo: false,\n _history: [] as DashboardSnapshot[],\n _historyIndex: -1,\n isLoading: false,\n isSaving: false,\n hasUnsavedChanges: false,\n};\n\n// ============================================================================\n// Store\n// ============================================================================\n\nexport const useDashboardStore = create<DashboardState>()(\n devtools(\n persist(\n (set, _get) => ({\n ...initialState,\n\n // Dashboard actions\n setCurrentDashboard: (dashboard) => {\n const pages = dashboard ? new Map(dashboard.pages.map((p) => [p.id, p])) : new Map();\n const activePageId = dashboard?.pages?.[0]?.id || null;\n const layoutConfig = dashboard?.layoutConfig || defaultLayoutConfig;\n // Initialize undo history with the loaded state\n const snapshot: DashboardSnapshot = { currentDashboard: dashboard, pages, activePageId, layoutConfig };\n set({\n currentDashboard: dashboard,\n activePageId,\n pages,\n layoutConfig,\n theme: dashboard?.theme || null,\n _history: [snapshot],\n _historyIndex: 0,\n canUndo: false,\n canRedo: false,\n });\n },\n\n setDashboards: (dashboards) =>\n set({\n dashboards: new Map(dashboards.map((d) => [d.id, d])),\n }),\n\n addDashboard: (dashboard) =>\n set((state) => {\n const newDashboards = new Map(state.dashboards);\n newDashboards.set(dashboard.id, dashboard);\n return { dashboards: newDashboards };\n }),\n\n updateDashboard: (id, updates) =>\n set((state) => {\n const newDashboards = new Map(state.dashboards);\n const existing = newDashboards.get(id);\n if (existing) {\n newDashboards.set(id, { ...existing, ...updates });\n }\n // Also update currentDashboard if it's the one being updated\n const currentDashboard =\n state.currentDashboard?.id === id ? { ...state.currentDashboard, ...updates } : state.currentDashboard;\n return {\n dashboards: newDashboards,\n currentDashboard,\n hasUnsavedChanges: true,\n };\n }),\n\n removeDashboard: (id) =>\n set((state) => {\n const newDashboards = new Map(state.dashboards);\n newDashboards.delete(id);\n return {\n dashboards: newDashboards,\n currentDashboard: state.currentDashboard?.id === id ? null : state.currentDashboard,\n };\n }),\n\n // Page actions\n setActivePageId: (pageId) => set({ activePageId: pageId }),\n\n setPages: (pages) =>\n set({\n pages: new Map(pages.map((p) => [p.id, p])),\n activePageId: pages[0]?.id || null,\n }),\n\n addPage: (page) =>\n set((state) => {\n const newPages = new Map(state.pages);\n newPages.set(page.id, page);\n return { pages: newPages, hasUnsavedChanges: true };\n }),\n\n updatePage: (id, updates) =>\n set((state) => {\n const newPages = new Map(state.pages);\n const existing = newPages.get(id);\n if (existing) {\n newPages.set(id, { ...existing, ...updates });\n }\n return { pages: newPages, hasUnsavedChanges: true };\n }),\n\n removePage: (id) =>\n set((state) => {\n const newPages = new Map(state.pages);\n newPages.delete(id);\n const pagesArray = Array.from(newPages.values());\n return {\n pages: newPages,\n activePageId: state.activePageId === id ? pagesArray[0]?.id || null : state.activePageId,\n hasUnsavedChanges: true,\n };\n }),\n\n reorderPages: (pageIds) =>\n set((state) => {\n const newPages = new Map<string, DashboardPage>();\n pageIds.forEach((id, index) => {\n const page = state.pages.get(id);\n if (page) {\n newPages.set(id, { ...page, order: index });\n }\n });\n return { pages: newPages, hasUnsavedChanges: true };\n }),\n\n // View actions\n setViewMode: (mode) => set({ viewMode: mode }),\n\n toggleViewMode: () =>\n set((state) => ({\n viewMode: state.viewMode === 'edit' ? 'view' : 'edit',\n })),\n\n setZoomLevel: (level) => {\n // Clamp to valid zoom levels\n const clampedLevel = Math.max(50, Math.min(200, level)) as ZoomLevel;\n set({ zoomLevel: clampedLevel });\n },\n\n zoomIn: () =>\n set((state) => {\n const levels: ZoomLevel[] = [50, 75, 100, 125, 150];\n const currentIndex = levels.indexOf(state.zoomLevel);\n const nextIndex = Math.min(currentIndex + 1, levels.length - 1);\n return { zoomLevel: levels[nextIndex] };\n }),\n\n zoomOut: () =>\n set((state) => {\n const levels: ZoomLevel[] = [50, 75, 100, 125, 150];\n const currentIndex = levels.indexOf(state.zoomLevel);\n const prevIndex = Math.max(currentIndex - 1, 0);\n return { zoomLevel: levels[prevIndex] };\n }),\n\n resetZoom: () => set({ zoomLevel: 100 }),\n\n toggleGrid: () => set((state) => ({ showGrid: !state.showGrid })),\n\n toggleSnapToGrid: () => set((state) => ({ snapToGrid: !state.snapToGrid })),\n\n // Layout actions\n setLayoutConfig: (config) =>\n set((state) => ({\n layoutConfig: { ...state.layoutConfig, ...config },\n hasUnsavedChanges: true,\n })),\n\n // Theme actions\n setTheme: (theme) => set({ theme, hasUnsavedChanges: true }),\n\n // Toolbar actions\n toggleToolbar: () => set((state) => ({ toolbarCollapsed: !state.toolbarCollapsed })),\n toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),\n\n // State actions\n setLoading: (loading) => set({ isLoading: loading }),\n setSaving: (saving) => set({ isSaving: saving }),\n setHasUnsavedChanges: (hasChanges) =>\n set((state) => {\n if (!hasChanges) return { hasUnsavedChanges: false };\n // Push snapshot to history on mutation\n const snapshot: DashboardSnapshot = {\n currentDashboard: state.currentDashboard,\n pages: new Map(state.pages),\n activePageId: state.activePageId,\n layoutConfig: state.layoutConfig,\n };\n // Trim future history (discard redo stack on new mutation)\n const history = state._history.slice(0, state._historyIndex + 1);\n history.push(snapshot);\n // Cap history size\n if (history.length > MAX_HISTORY) history.shift();\n const newIndex = history.length - 1;\n return {\n hasUnsavedChanges: true,\n _history: history,\n _historyIndex: newIndex,\n canUndo: newIndex > 0,\n canRedo: false,\n };\n }),\n markAsSaved: () => set({ hasUnsavedChanges: false, isSaving: false }),\n\n // Global Filter actions (Phase 2)\n setGlobalFilterValues: (values) => set({ globalFilterValues: values }),\n\n setGlobalFilterValue: (filterId, value) =>\n set((state) => ({\n globalFilterValues: {\n ...state.globalFilterValues,\n [filterId]: value,\n },\n })),\n\n clearGlobalFilterValue: (filterId) =>\n set((state) => {\n const newValues = { ...state.globalFilterValues };\n delete newValues[filterId];\n return { globalFilterValues: newValues };\n }),\n\n clearAllGlobalFilters: () => set({ globalFilterValues: {} }),\n\n toggleFiltersCollapsed: () => set((state) => ({ filtersCollapsed: !state.filtersCollapsed })),\n\n // Undo/Redo — snapshot-based history\n undo: () =>\n set((state) => {\n if (state._historyIndex <= 0) return state;\n const newIndex = state._historyIndex - 1;\n const snapshot = state._history[newIndex];\n if (!snapshot) return state;\n return {\n currentDashboard: snapshot.currentDashboard,\n pages: new Map(snapshot.pages),\n activePageId: snapshot.activePageId,\n layoutConfig: snapshot.layoutConfig,\n _historyIndex: newIndex,\n canUndo: newIndex > 0,\n canRedo: true,\n hasUnsavedChanges: true,\n };\n }),\n redo: () =>\n set((state) => {\n if (state._historyIndex >= state._history.length - 1) return state;\n const newIndex = state._historyIndex + 1;\n const snapshot = state._history[newIndex];\n if (!snapshot) return state;\n return {\n currentDashboard: snapshot.currentDashboard,\n pages: new Map(snapshot.pages),\n activePageId: snapshot.activePageId,\n layoutConfig: snapshot.layoutConfig,\n _historyIndex: newIndex,\n canUndo: true,\n canRedo: newIndex < state._history.length - 1,\n hasUnsavedChanges: true,\n };\n }),\n\n // Reset\n reset: () => set(initialState),\n }),\n {\n name: 'bigconsole-dashboard-store',\n storage: createProfileScopedStorage(),\n partialize: (state) => ({\n // Only persist UI preferences\n viewMode: state.viewMode,\n zoomLevel: state.zoomLevel,\n showGrid: state.showGrid,\n snapToGrid: state.snapToGrid,\n toolbarCollapsed: state.toolbarCollapsed,\n sidebarCollapsed: state.sidebarCollapsed,\n filtersCollapsed: state.filtersCollapsed,\n }),\n }\n ),\n { name: 'DashboardStore' }\n )\n);\n\n// ============================================================================\n// Selectors\n// ============================================================================\n\nexport const useCurrentDashboard = () => {\n return useDashboardStore((state) => state.currentDashboard);\n};\n\nexport const useActivePage = () => {\n const activePageId = useDashboardStore((state) => state.activePageId);\n const pages = useDashboardStore((state) => state.pages);\n return activePageId ? pages.get(activePageId) : null;\n};\n\nexport const usePagesArray = () => {\n return useDashboardStore((state) => Array.from(state.pages.values()).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)));\n};\n\nexport const useDashboardsArray = () => {\n return useDashboardStore((state) => Array.from(state.dashboards.values()));\n};\n\nexport const useIsEditMode = () => {\n return useDashboardStore((state) => state.viewMode === 'edit');\n};\n\nexport const useLayoutConfig = () => {\n return useDashboardStore((state) => state.layoutConfig);\n};\n\nexport const useHasUnsavedChanges = () => {\n return useDashboardStore((state) => state.hasUnsavedChanges);\n};\n\n// ============================================================================\n// Global Filter Selectors (Phase 2)\n// ============================================================================\n\n/**\n * Get all global filter values\n */\nexport const useGlobalFilterValues = () => {\n return useDashboardStore((state) => state.globalFilterValues);\n};\n\n/**\n * Get a specific filter value by ID\n */\nexport const useGlobalFilterValue = (filterId: string) => {\n return useDashboardStore((state) => state.globalFilterValues[filterId]);\n};\n\n/**\n * Check if any filters are active\n */\nexport const useHasActiveFilters = () => {\n return useDashboardStore((state) => Object.keys(state.globalFilterValues).length > 0);\n};\n\n/**\n * Get filters collapsed state\n */\nexport const useFiltersCollapsed = () => {\n return useDashboardStore((state) => state.filtersCollapsed);\n};\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.613.6",
3
+ "version": "2026.615.1",
4
4
  "description": "BigConsole - AI-powered analytics and dashboard platform",
5
5
  "type": "module",
6
6
  "files": [