@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.
- package/dist/bigconsole/components/widgets/table/TableHeader.js +89 -29
- package/dist/bigconsole/components/widgets/table/TableHeader.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableWidget.js +218 -148
- package/dist/bigconsole/components/widgets/table/TableWidget.js.map +1 -1
- package/dist/bigconsole/hooks/index.js +1 -0
- package/dist/bigconsole/hooks/useWidgetConfigPersist.js +32 -0
- package/dist/bigconsole/hooks/useWidgetConfigPersist.js.map +1 -0
- package/dist/bigconsole/store/dashboardStore.js +2 -2
- package/dist/bigconsole/store/dashboardStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,45 +1,60 @@
|
|
|
1
|
-
import { memo as e, useCallback as t } from "react";
|
|
2
|
-
import {
|
|
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
|
|
5
|
-
let
|
|
6
|
-
e.sortable !== !1 &&
|
|
7
|
-
}, [
|
|
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 =
|
|
10
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
67
|
+
return /* @__PURE__ */ o("thead", {
|
|
53
68
|
className: `
|
|
54
69
|
bg-bg-muted
|
|
55
|
-
${
|
|
70
|
+
${d ? "sticky top-0 z-10" : ""}
|
|
56
71
|
`,
|
|
57
|
-
children: /* @__PURE__ */
|
|
58
|
-
let t =
|
|
59
|
-
return /* @__PURE__ */
|
|
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:
|
|
71
|
-
width:
|
|
107
|
+
style: t == null ? { width: n } : {
|
|
108
|
+
width: n,
|
|
72
109
|
left: t
|
|
73
110
|
},
|
|
74
|
-
onClick: () =>
|
|
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(),
|
|
115
|
+
e.sortable !== !1 && (t.key === "Enter" || t.key === " ") && (t.preventDefault(), _(e));
|
|
79
116
|
},
|
|
80
|
-
"aria-sort":
|
|
81
|
-
children: /* @__PURE__ */
|
|
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: [
|
|
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 {
|
|
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 {
|
|
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
|
|
4
|
-
import
|
|
5
|
-
import i from "./
|
|
6
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
39
|
-
let
|
|
40
|
-
if (Array.isArray(
|
|
41
|
-
if (
|
|
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(
|
|
53
|
-
let e = Object.values(
|
|
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 [
|
|
59
|
+
return [p];
|
|
56
60
|
}
|
|
57
61
|
return [];
|
|
58
|
-
}, [
|
|
59
|
-
column:
|
|
60
|
-
direction:
|
|
61
|
-
}), [
|
|
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:
|
|
67
|
+
pageSize: g?.pageSize || 10,
|
|
64
68
|
totalItems: 0
|
|
65
|
-
}), A =
|
|
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
|
-
}) :
|
|
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
|
-
})) : [], [
|
|
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
|
|
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
|
-
}, [
|
|
154
|
+
}, [g?.columns]), ue = l(() => {
|
|
89
155
|
let e = {};
|
|
90
|
-
return
|
|
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
|
-
}, [
|
|
160
|
+
}, [g?.columns]), I = g?.conditionalRules, L = l(() => {
|
|
95
161
|
let e = {};
|
|
96
|
-
return
|
|
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
|
-
}, [
|
|
166
|
+
}, [g?.columns]), R = l(() => {
|
|
101
167
|
let e = {};
|
|
102
|
-
return
|
|
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
|
-
}, [
|
|
107
|
-
let e =
|
|
108
|
-
return
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
]),
|
|
176
|
+
_,
|
|
177
|
+
C,
|
|
178
|
+
x,
|
|
179
|
+
N,
|
|
180
|
+
g?.showSearch
|
|
181
|
+
]), V = l(() => {
|
|
116
182
|
let e = {};
|
|
117
|
-
for (let t of
|
|
183
|
+
for (let t of N) {
|
|
118
184
|
let n = 0;
|
|
119
|
-
for (let e of
|
|
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
|
-
}, [
|
|
127
|
-
if (!
|
|
192
|
+
}, [N, B]), H = l(() => {
|
|
193
|
+
if (!z) return {};
|
|
128
194
|
let e = {};
|
|
129
|
-
for (let t of
|
|
130
|
-
let n =
|
|
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 =
|
|
136
|
-
n === "count" ? e[t.key] =
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
206
|
+
z,
|
|
207
|
+
N,
|
|
208
|
+
R,
|
|
209
|
+
B
|
|
144
210
|
]);
|
|
145
|
-
|
|
146
|
-
|
|
211
|
+
l(() => {
|
|
212
|
+
E((e) => ({
|
|
147
213
|
...e,
|
|
148
|
-
totalItems:
|
|
214
|
+
totalItems: B.length
|
|
149
215
|
}));
|
|
150
|
-
}, [
|
|
151
|
-
let
|
|
152
|
-
if (
|
|
153
|
-
let e =
|
|
154
|
-
return
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
]),
|
|
160
|
-
|
|
161
|
-
if (!
|
|
162
|
-
let e =
|
|
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
|
-
|
|
165
|
-
let t = typeof ResizeObserver < "u" ? new ResizeObserver(() =>
|
|
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
|
-
}, [
|
|
168
|
-
let
|
|
169
|
-
if (!
|
|
170
|
-
let e =
|
|
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:
|
|
173
|
-
topSpacer: n *
|
|
174
|
-
bottomSpacer: Math.max(0, (e - r) *
|
|
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
|
-
|
|
183
|
-
|
|
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:
|
|
251
|
+
displayColumns: N,
|
|
186
252
|
frozenLefts: {}
|
|
187
253
|
};
|
|
188
254
|
let e = {}, t = 0;
|
|
189
255
|
return {
|
|
190
|
-
displayColumns:
|
|
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
|
-
}, [
|
|
203
|
-
|
|
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
|
-
}, []),
|
|
215
|
-
|
|
280
|
+
}, []), he = s((e) => {
|
|
281
|
+
E((t) => ({
|
|
216
282
|
...t,
|
|
217
283
|
page: e
|
|
218
284
|
}));
|
|
219
|
-
}, []),
|
|
220
|
-
|
|
285
|
+
}, []), ge = s((e) => {
|
|
286
|
+
E((t) => ({
|
|
221
287
|
...t,
|
|
222
288
|
pageSize: e,
|
|
223
289
|
page: 0
|
|
224
290
|
}));
|
|
225
|
-
}, []),
|
|
226
|
-
let n =
|
|
227
|
-
if (
|
|
228
|
-
let t =
|
|
229
|
-
|
|
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
|
-
|
|
297
|
+
h ? h(e) : m && o.drilldowns?.length && m(e, t);
|
|
232
298
|
}, [
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
]),
|
|
242
|
-
let e = [
|
|
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 = `${
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
]),
|
|
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
|
-
|
|
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(),
|
|
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
|
-
(
|
|
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: [
|
|
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:
|
|
298
|
-
onChange: (e) =>
|
|
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
|
-
}),
|
|
367
|
+
}), g?.showExport !== !1 && /* @__PURE__ */ f("button", {
|
|
302
368
|
type: "button",
|
|
303
|
-
onClick:
|
|
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:
|
|
386
|
+
ref: K,
|
|
321
387
|
className: "flex-1 overflow-auto",
|
|
322
|
-
onScroll:
|
|
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(
|
|
392
|
+
/* @__PURE__ */ d(i, {
|
|
327
393
|
columns: Q,
|
|
328
|
-
sortState:
|
|
329
|
-
onSort:
|
|
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
|
-
|
|
403
|
+
W && X && X.topSpacer > 0 && /* @__PURE__ */ d("tr", {
|
|
334
404
|
"aria-hidden": "true",
|
|
335
|
-
style: { height:
|
|
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
|
-
|
|
345
|
-
let n =
|
|
414
|
+
fe.map((e, t) => {
|
|
415
|
+
let n = pe + t;
|
|
346
416
|
return /* @__PURE__ */ d("tr", {
|
|
347
417
|
className: `
|
|
348
|
-
${
|
|
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:
|
|
352
|
-
onClick: () =>
|
|
353
|
-
children: Q.map((t) => /* @__PURE__ */ d(
|
|
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:
|
|
426
|
+
format: F[t.key],
|
|
357
427
|
rowData: e,
|
|
358
|
-
conditionalRules:
|
|
359
|
-
dataBarMax:
|
|
428
|
+
conditionalRules: L[t.key] ?? I,
|
|
429
|
+
dataBarMax: V[t.key],
|
|
360
430
|
stickyLeft: $[t.key],
|
|
361
|
-
cellChart:
|
|
431
|
+
cellChart: ue[t.key]
|
|
362
432
|
}, t.key))
|
|
363
433
|
}, n);
|
|
364
434
|
}),
|
|
365
|
-
|
|
435
|
+
W && X && X.bottomSpacer > 0 && /* @__PURE__ */ d("tr", {
|
|
366
436
|
"aria-hidden": "true",
|
|
367
|
-
style: { height:
|
|
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
|
-
|
|
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 =
|
|
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" : "" :
|
|
459
|
+
children: n == null ? t === 0 ? "Total" : "" : ie(n, r?.type, r?.currency)
|
|
390
460
|
}, e.key);
|
|
391
461
|
})
|
|
392
462
|
}) })
|
|
393
463
|
]
|
|
394
|
-
}),
|
|
464
|
+
}), B.length === 0 && /* @__PURE__ */ d("div", {
|
|
395
465
|
className: "flex items-center justify-center py-12 text-text-secondary",
|
|
396
|
-
children:
|
|
466
|
+
children: C ? "No matching records found" : "No data available"
|
|
397
467
|
})]
|
|
398
468
|
}),
|
|
399
|
-
!
|
|
469
|
+
!W && g?.showPagination !== !1 && B.length > 0 && /* @__PURE__ */ d(ee, {
|
|
400
470
|
pagination: {
|
|
401
|
-
...
|
|
402
|
-
totalItems:
|
|
471
|
+
...T,
|
|
472
|
+
totalItems: B.length
|
|
403
473
|
},
|
|
404
|
-
onPageChange:
|
|
405
|
-
onPageSizeChange:
|
|
474
|
+
onPageChange: he,
|
|
475
|
+
onPageSizeChange: ge
|
|
406
476
|
})
|
|
407
477
|
]
|
|
408
478
|
});
|
|
409
479
|
});
|
|
410
480
|
//#endregion
|
|
411
|
-
export {
|
|
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,
|
|
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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"}
|