@burdenoff/microfe-bigconsole 2026.613.4 → 2026.613.6
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/dashboard/DashboardCanvas.js +163 -151
- package/dist/bigconsole/components/dashboard/DashboardCanvas.js.map +1 -1
- package/dist/bigconsole/components/dashboard/DashboardToolbar.js +89 -69
- package/dist/bigconsole/components/dashboard/DashboardToolbar.js.map +1 -1
- package/dist/bigconsole/components/widgets/map-widget/MapWidget.js +65 -46
- package/dist/bigconsole/components/widgets/map-widget/MapWidget.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableCell.js +50 -30
- package/dist/bigconsole/components/widgets/table/TableCell.js.map +1 -1
- package/dist/bigconsole/components/widgets/table/TableWidget.js +95 -88
- package/dist/bigconsole/components/widgets/table/TableWidget.js.map +1 -1
- package/dist/bigconsole/hooks/useDashboardImageExport.js +92 -1
- package/dist/bigconsole/hooks/useDashboardImageExport.js.map +1 -0
- package/package.json +1 -1
|
@@ -8,64 +8,65 @@ import "../../context/index.js";
|
|
|
8
8
|
import { getWidgetDefinition as i } from "../widgets/WidgetRegistry.js";
|
|
9
9
|
import a from "../../hooks/useWidgetCatalog.js";
|
|
10
10
|
import o from "../../hooks/useWidgetOperations.js";
|
|
11
|
-
import
|
|
12
|
-
import c from "
|
|
13
|
-
import l from "./
|
|
14
|
-
import ee from "./
|
|
11
|
+
import s from "../../hooks/useDashboardImageExport.js";
|
|
12
|
+
import { useInstalledWidgets as c } from "../../hooks/useInstalledWidgets.js";
|
|
13
|
+
import l from "./DashboardGrid.js";
|
|
14
|
+
import ee from "./DashboardToolbar.js";
|
|
15
|
+
import te from "./GlobalFiltersBar/GlobalFiltersBar.js";
|
|
15
16
|
import "./GlobalFiltersBar/index.js";
|
|
16
17
|
import u from "./PageTabsManager/PageTabsManager.js";
|
|
17
18
|
import "./PageTabsManager/index.js";
|
|
18
|
-
import
|
|
19
|
+
import ne from "./ShareDialog/ShareDialog.js";
|
|
19
20
|
import "./ShareDialog/index.js";
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import { calculatePositionFromDrop as
|
|
24
|
-
import { memo as
|
|
25
|
-
import { jsx as
|
|
21
|
+
import re from "./WidgetPalette.js";
|
|
22
|
+
import ie from "../widgets/WidgetConfigDialog.js";
|
|
23
|
+
import ae from "../widgets/PropertiesPanel.js";
|
|
24
|
+
import { calculatePositionFromDrop as oe, findNextAvailablePosition as se } from "../../utils/widgetPositioning.js";
|
|
25
|
+
import { memo as d, useCallback as f, useEffect as p, useMemo as ce, useRef as m, useState as h } from "react";
|
|
26
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
26
27
|
//#region src/bigconsole/components/dashboard/DashboardCanvas.tsx
|
|
27
|
-
var
|
|
28
|
-
let
|
|
29
|
-
workspaceId:
|
|
30
|
-
apiGatewayUrl:
|
|
31
|
-
authToken:
|
|
32
|
-
enabled:
|
|
33
|
-
}), { definitions:
|
|
34
|
-
workspaceId:
|
|
35
|
-
apiGatewayUrl:
|
|
36
|
-
authToken:
|
|
37
|
-
enabled:
|
|
38
|
-
}),
|
|
39
|
-
|
|
40
|
-
}, [
|
|
41
|
-
let e = Array.from(
|
|
42
|
-
return
|
|
43
|
-
}, [
|
|
44
|
-
|
|
28
|
+
var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: ue, isSaving: de = !1, onExport: fe, pages: b = [], activePageId: pe, onPageChange: me, onPagesReorder: x, onPageAdd: he, onPageRename: ge, onPageDuplicate: S, onPageDelete: _e, showFilters: ve = !0, showPageTabs: ye = !0, enableCrossFilter: be = !0 }) {
|
|
29
|
+
let C = m(null), w = m(null), [T, xe] = h(1200), [Se, E] = h(!1), [Ce, D] = h(!1), [O, k] = h(!1), [A, j] = h(!1), [we, M] = h(null), [N, P] = h(null), [Te, F] = h(null), [I, L] = h(null), R = t((e) => e.viewMode === "edit"), { workspaceId: z, apiGatewayUrl: B, authToken: V } = r(), { installedWidgets: H, loading: Ee } = c({
|
|
30
|
+
workspaceId: z,
|
|
31
|
+
apiGatewayUrl: B,
|
|
32
|
+
authToken: V,
|
|
33
|
+
enabled: R
|
|
34
|
+
}), { definitions: De } = a({
|
|
35
|
+
workspaceId: z,
|
|
36
|
+
apiGatewayUrl: B,
|
|
37
|
+
authToken: V,
|
|
38
|
+
enabled: R
|
|
39
|
+
}), U = t((e) => e.zoomLevel), W = t((e) => e.setGlobalFilterValues), Oe = f((e) => {
|
|
40
|
+
W(e);
|
|
41
|
+
}, [W]), { createWidget: G } = o(v, d, { skipFetch: !0 }), K = e((e) => e.widgets), q = ce(() => {
|
|
42
|
+
let e = Array.from(K.values());
|
|
43
|
+
return v ? e.filter((e) => e.pageId === v) : e;
|
|
44
|
+
}, [K, v]), ke = e((e) => e.selectedWidgetId), J = e((e) => e.selectWidget), Y = e((e) => e.addWidget), X = e((e) => e.updateWidgetPosition), Ae = e((e) => e.configPanelOpen), je = e((e) => e.closeConfigPanel);
|
|
45
|
+
p(() => {
|
|
45
46
|
let e = () => {
|
|
46
|
-
|
|
47
|
+
C.current && xe(C.current.offsetWidth);
|
|
47
48
|
};
|
|
48
49
|
return e(), window.addEventListener("resize", e), () => window.removeEventListener("resize", e);
|
|
49
50
|
}, []);
|
|
50
|
-
let
|
|
51
|
+
let Me = f((e) => {
|
|
51
52
|
e.forEach((e) => {
|
|
52
|
-
|
|
53
|
+
X(e.i, {
|
|
53
54
|
x: e.x,
|
|
54
55
|
y: e.y,
|
|
55
56
|
width: e.w,
|
|
56
57
|
height: e.h
|
|
57
58
|
});
|
|
58
59
|
});
|
|
59
|
-
}, [
|
|
60
|
-
|
|
61
|
-
}, [
|
|
62
|
-
let t =
|
|
60
|
+
}, [X]), Ne = f((e) => {
|
|
61
|
+
J(e);
|
|
62
|
+
}, [J]), Z = f(async (e) => {
|
|
63
|
+
let t = I || (e.type ? i(e.type) : null), n = {
|
|
63
64
|
width: t?.defaultSize?.width ?? 4,
|
|
64
65
|
height: t?.defaultSize?.height ?? 4
|
|
65
66
|
}, r;
|
|
66
|
-
if (
|
|
67
|
+
if (N) r = N;
|
|
67
68
|
else {
|
|
68
|
-
let { x: e, y: t } =
|
|
69
|
+
let { x: e, y: t } = se(q, n, { cols: 12 });
|
|
69
70
|
r = {
|
|
70
71
|
x: e,
|
|
71
72
|
y: t,
|
|
@@ -74,10 +75,10 @@ var _ = f(function({ dashboardId: f, pageId: _, title: v, onPaste: le, onSave: u
|
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
let a = e;
|
|
77
|
-
if (
|
|
78
|
-
let t = await
|
|
79
|
-
pageId:
|
|
80
|
-
dashboardId:
|
|
78
|
+
if (v) {
|
|
79
|
+
let t = await G({
|
|
80
|
+
pageId: v,
|
|
81
|
+
dashboardId: d,
|
|
81
82
|
type: e.type,
|
|
82
83
|
title: e.title || "Untitled Widget",
|
|
83
84
|
description: e.description,
|
|
@@ -90,14 +91,14 @@ var _ = f(function({ dashboardId: f, pageId: _, title: v, onPaste: le, onSave: u
|
|
|
90
91
|
parserId: a.parserId
|
|
91
92
|
});
|
|
92
93
|
if (t) {
|
|
93
|
-
|
|
94
|
+
J(t.id), E(!1), P(null), F(null), L(null);
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
98
|
let o = {
|
|
98
99
|
id: crypto.randomUUID(),
|
|
99
|
-
dashboardId:
|
|
100
|
-
pageId:
|
|
100
|
+
dashboardId: d,
|
|
101
|
+
pageId: v || "local",
|
|
101
102
|
type: e.type,
|
|
102
103
|
title: e.title || "Untitled Widget",
|
|
103
104
|
description: e.description,
|
|
@@ -116,22 +117,22 @@ var _ = f(function({ dashboardId: f, pageId: _, title: v, onPaste: le, onSave: u
|
|
|
116
117
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
117
118
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
118
119
|
};
|
|
119
|
-
|
|
120
|
+
Y(o), J(o.id), E(!1), P(null), F(null), L(null);
|
|
120
121
|
}, [
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Z,
|
|
125
|
-
X,
|
|
122
|
+
d,
|
|
123
|
+
v,
|
|
124
|
+
G,
|
|
126
125
|
Y,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}, []),
|
|
134
|
-
|
|
126
|
+
J,
|
|
127
|
+
q,
|
|
128
|
+
N,
|
|
129
|
+
I
|
|
130
|
+
]), Pe = f((e) => {
|
|
131
|
+
j(!0), M(e);
|
|
132
|
+
}, []), Fe = f(() => {
|
|
133
|
+
j(!1), M(null);
|
|
134
|
+
}, []), Ie = f((e) => {
|
|
135
|
+
e.preventDefault(), j(!1), M(null);
|
|
135
136
|
try {
|
|
136
137
|
let t = e.dataTransfer.getData("application/json");
|
|
137
138
|
if (!t) return;
|
|
@@ -143,92 +144,102 @@ var _ = f(function({ dashboardId: f, pageId: _, title: v, onPaste: le, onSave: u
|
|
|
143
144
|
defaultConfig: n.defaultConfig,
|
|
144
145
|
defaultSize: n.defaultSize || r.defaultSize
|
|
145
146
|
});
|
|
146
|
-
let o =
|
|
147
|
+
let o = C.current?.getBoundingClientRect();
|
|
147
148
|
if (!o) return;
|
|
148
149
|
let s = {
|
|
149
150
|
width: r.defaultSize.width,
|
|
150
151
|
height: r.defaultSize.height
|
|
151
|
-
}, { x: c, y: l } =
|
|
152
|
-
|
|
152
|
+
}, { x: c, y: l } = oe(e.clientX, e.clientY, o, T, s, { cols: 12 });
|
|
153
|
+
P({
|
|
153
154
|
x: c,
|
|
154
155
|
y: l,
|
|
155
156
|
width: s.width,
|
|
156
157
|
height: s.height
|
|
157
|
-
}),
|
|
158
|
+
}), F(n.type), L(a), E(!0);
|
|
158
159
|
} catch (e) {
|
|
159
160
|
console.error("Failed to handle widget drop:", e);
|
|
160
161
|
}
|
|
161
|
-
}, [
|
|
162
|
+
}, [T]), Le = f((e) => {
|
|
162
163
|
e.preventDefault(), e.dataTransfer.dropEffect = "copy";
|
|
163
|
-
}, []),
|
|
164
|
+
}, []), Re = f((e, t) => {
|
|
164
165
|
let n = t || i(e);
|
|
165
|
-
|
|
166
|
+
Z({
|
|
166
167
|
type: e,
|
|
167
168
|
title: n.name,
|
|
168
169
|
config: n.defaultConfig
|
|
169
|
-
}),
|
|
170
|
-
}, [
|
|
171
|
-
|
|
170
|
+
}), k(!1);
|
|
171
|
+
}, [Z]), ze = f(() => {
|
|
172
|
+
k((e) => !e);
|
|
173
|
+
}, []), { exportPNG: Q, exportPDF: $, isExporting: Be } = s({
|
|
174
|
+
containerRef: w,
|
|
175
|
+
name: y || "dashboard"
|
|
176
|
+
});
|
|
177
|
+
return /* @__PURE__ */ _("div", {
|
|
172
178
|
className: "flex flex-col h-full min-w-0 overflow-hidden",
|
|
173
179
|
children: [
|
|
174
|
-
/* @__PURE__ */
|
|
175
|
-
dashboardId:
|
|
176
|
-
title:
|
|
177
|
-
onAddWidget: () =>
|
|
178
|
-
onTogglePalette:
|
|
179
|
-
|
|
180
|
-
}, []),
|
|
181
|
-
isPaletteOpen: k,
|
|
180
|
+
/* @__PURE__ */ g(ee, {
|
|
181
|
+
dashboardId: d,
|
|
182
|
+
title: y,
|
|
183
|
+
onAddWidget: () => E(!0),
|
|
184
|
+
onTogglePalette: ze,
|
|
185
|
+
isPaletteOpen: O,
|
|
182
186
|
onPaste: le,
|
|
183
187
|
onSave: ue,
|
|
184
188
|
isSaving: de,
|
|
185
|
-
onExport:
|
|
186
|
-
|
|
189
|
+
onExport: fe,
|
|
190
|
+
onExportImage: f(() => {
|
|
191
|
+
Q();
|
|
192
|
+
}, [Q]),
|
|
193
|
+
onExportPdf: f(() => {
|
|
194
|
+
$();
|
|
195
|
+
}, [$]),
|
|
196
|
+
isExportingImage: Be,
|
|
197
|
+
onShare: () => D(!0)
|
|
187
198
|
}),
|
|
188
|
-
|
|
189
|
-
dashboardId:
|
|
190
|
-
onFilterChange:
|
|
199
|
+
ve && /* @__PURE__ */ g(te, {
|
|
200
|
+
dashboardId: d,
|
|
201
|
+
onFilterChange: Oe,
|
|
191
202
|
collapsible: !0
|
|
192
203
|
}),
|
|
193
|
-
|
|
204
|
+
ye && (R || b.length > 0) && /* @__PURE__ */ g(u, {
|
|
194
205
|
pages: b,
|
|
195
|
-
activePageId:
|
|
196
|
-
editable:
|
|
197
|
-
onPageSelect:
|
|
198
|
-
onPagesReorder:
|
|
199
|
-
|
|
206
|
+
activePageId: pe || b[0]?.id,
|
|
207
|
+
editable: R,
|
|
208
|
+
onPageSelect: me || (() => {}),
|
|
209
|
+
onPagesReorder: x ? (e) => {
|
|
210
|
+
x(e.map((e) => b.find((t) => t.id === e)).filter(Boolean));
|
|
200
211
|
} : void 0,
|
|
201
|
-
onPageCreate:
|
|
202
|
-
onPageRename:
|
|
203
|
-
onPageDuplicate:
|
|
204
|
-
onPageDelete:
|
|
212
|
+
onPageCreate: he,
|
|
213
|
+
onPageRename: ge,
|
|
214
|
+
onPageDuplicate: S,
|
|
215
|
+
onPageDelete: _e
|
|
205
216
|
}),
|
|
206
|
-
/* @__PURE__ */
|
|
217
|
+
/* @__PURE__ */ _("div", {
|
|
207
218
|
className: "flex flex-1 min-w-0 overflow-hidden",
|
|
208
|
-
children: [
|
|
209
|
-
isOpen:
|
|
210
|
-
onClose: () =>
|
|
211
|
-
onWidgetSelect:
|
|
212
|
-
onDragStart:
|
|
213
|
-
onDragEnd:
|
|
214
|
-
installedWidgets:
|
|
215
|
-
installedWidgetsLoading:
|
|
216
|
-
definitions:
|
|
217
|
-
}), /* @__PURE__ */
|
|
218
|
-
ref:
|
|
219
|
+
children: [R && /* @__PURE__ */ g(re, {
|
|
220
|
+
isOpen: O,
|
|
221
|
+
onClose: () => k(!1),
|
|
222
|
+
onWidgetSelect: Re,
|
|
223
|
+
onDragStart: Pe,
|
|
224
|
+
onDragEnd: Fe,
|
|
225
|
+
installedWidgets: H,
|
|
226
|
+
installedWidgetsLoading: Ee,
|
|
227
|
+
definitions: De
|
|
228
|
+
}), /* @__PURE__ */ _("div", {
|
|
229
|
+
ref: C,
|
|
219
230
|
className: "flex-1 min-w-0 overflow-auto bg-bg-muted",
|
|
220
|
-
onDrop:
|
|
221
|
-
onDragOver:
|
|
231
|
+
onDrop: Ie,
|
|
232
|
+
onDragOver: Le,
|
|
222
233
|
onClick: () => {
|
|
223
|
-
|
|
234
|
+
R && J(null);
|
|
224
235
|
},
|
|
225
236
|
style: {
|
|
226
237
|
position: "relative",
|
|
227
|
-
border:
|
|
228
|
-
backgroundColor:
|
|
238
|
+
border: A ? "2px dashed var(--color-action-primary-bg)" : "none",
|
|
239
|
+
backgroundColor: A ? "var(--color-bg-hover)" : void 0,
|
|
229
240
|
transition: "border-color 0.2s, background-color 0.2s"
|
|
230
241
|
},
|
|
231
|
-
children: [
|
|
242
|
+
children: [A && /* @__PURE__ */ g("div", {
|
|
232
243
|
style: {
|
|
233
244
|
position: "absolute",
|
|
234
245
|
top: "50%",
|
|
@@ -242,7 +253,7 @@ var _ = f(function({ dashboardId: f, pageId: _, title: v, onPaste: le, onSave: u
|
|
|
242
253
|
pointerEvents: "none",
|
|
243
254
|
zIndex: 100
|
|
244
255
|
},
|
|
245
|
-
children: /* @__PURE__ */
|
|
256
|
+
children: /* @__PURE__ */ _("p", {
|
|
246
257
|
style: {
|
|
247
258
|
fontSize: "var(--font-size-body-md)",
|
|
248
259
|
color: "var(--color-text-primary)",
|
|
@@ -250,38 +261,39 @@ var _ = f(function({ dashboardId: f, pageId: _, title: v, onPaste: le, onSave: u
|
|
|
250
261
|
},
|
|
251
262
|
children: [
|
|
252
263
|
"Drop to add ",
|
|
253
|
-
/* @__PURE__ */
|
|
264
|
+
/* @__PURE__ */ g("strong", { children: we }),
|
|
254
265
|
" widget"
|
|
255
266
|
]
|
|
256
267
|
})
|
|
257
|
-
}), /* @__PURE__ */
|
|
268
|
+
}), /* @__PURE__ */ g("div", {
|
|
269
|
+
ref: w,
|
|
258
270
|
className: "min-h-full origin-top-left transition-transform",
|
|
259
271
|
style: {
|
|
260
|
-
transform: `scale(${
|
|
261
|
-
width: `${100 / (
|
|
272
|
+
transform: `scale(${U / 100})`,
|
|
273
|
+
width: `${100 / (U / 100)}%`
|
|
262
274
|
},
|
|
263
|
-
children:
|
|
264
|
-
enabled:
|
|
275
|
+
children: q.length > 0 ? /* @__PURE__ */ g(n, {
|
|
276
|
+
enabled: be,
|
|
265
277
|
onCrossFilter: (e) => {},
|
|
266
|
-
children: /* @__PURE__ */
|
|
267
|
-
widgets:
|
|
268
|
-
isEditMode:
|
|
269
|
-
width:
|
|
270
|
-
onLayoutChange:
|
|
271
|
-
onWidgetSelect:
|
|
272
|
-
selectedWidgetId:
|
|
278
|
+
children: /* @__PURE__ */ g(l, {
|
|
279
|
+
widgets: q,
|
|
280
|
+
isEditMode: R,
|
|
281
|
+
width: T,
|
|
282
|
+
onLayoutChange: Me,
|
|
283
|
+
onWidgetSelect: Ne,
|
|
284
|
+
selectedWidgetId: ke
|
|
273
285
|
})
|
|
274
|
-
}) : /* @__PURE__ */
|
|
286
|
+
}) : /* @__PURE__ */ _("div", {
|
|
275
287
|
className: "flex flex-col items-center justify-center min-h-[400px] p-8",
|
|
276
288
|
children: [
|
|
277
|
-
/* @__PURE__ */
|
|
289
|
+
/* @__PURE__ */ g("div", {
|
|
278
290
|
className: "w-16 h-16 mb-4 flex items-center justify-center rounded-full bg-bg-surface",
|
|
279
|
-
children: /* @__PURE__ */
|
|
291
|
+
children: /* @__PURE__ */ g("svg", {
|
|
280
292
|
className: "w-8 h-8 text-text-secondary",
|
|
281
293
|
fill: "none",
|
|
282
294
|
stroke: "currentColor",
|
|
283
295
|
viewBox: "0 0 24 24",
|
|
284
|
-
children: /* @__PURE__ */
|
|
296
|
+
children: /* @__PURE__ */ g("path", {
|
|
285
297
|
strokeLinecap: "round",
|
|
286
298
|
strokeLinejoin: "round",
|
|
287
299
|
strokeWidth: 2,
|
|
@@ -289,24 +301,24 @@ var _ = f(function({ dashboardId: f, pageId: _, title: v, onPaste: le, onSave: u
|
|
|
289
301
|
})
|
|
290
302
|
})
|
|
291
303
|
}),
|
|
292
|
-
/* @__PURE__ */
|
|
304
|
+
/* @__PURE__ */ g("h3", {
|
|
293
305
|
className: "text-lg font-medium text-text-primary mb-2",
|
|
294
306
|
children: "No widgets yet"
|
|
295
307
|
}),
|
|
296
|
-
/* @__PURE__ */
|
|
308
|
+
/* @__PURE__ */ g("p", {
|
|
297
309
|
className: "text-sm text-text-secondary mb-4 text-center max-w-[300px]",
|
|
298
310
|
children: "Add widgets to build your dashboard. Click the \"Add Widget\" button to get started."
|
|
299
311
|
}),
|
|
300
|
-
|
|
312
|
+
R && /* @__PURE__ */ _("button", {
|
|
301
313
|
type: "button",
|
|
302
|
-
onClick: () =>
|
|
314
|
+
onClick: () => E(!0),
|
|
303
315
|
className: "\n inline-flex items-center gap-2\n px-4 py-2\n text-sm font-medium\n text-action-primary-fg\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-md\n transition-colors\n ",
|
|
304
|
-
children: [/* @__PURE__ */
|
|
316
|
+
children: [/* @__PURE__ */ g("svg", {
|
|
305
317
|
className: "w-4 h-4",
|
|
306
318
|
fill: "none",
|
|
307
319
|
stroke: "currentColor",
|
|
308
320
|
viewBox: "0 0 24 24",
|
|
309
|
-
children: /* @__PURE__ */
|
|
321
|
+
children: /* @__PURE__ */ g("path", {
|
|
310
322
|
strokeLinecap: "round",
|
|
311
323
|
strokeLinejoin: "round",
|
|
312
324
|
strokeWidth: 2,
|
|
@@ -319,31 +331,31 @@ var _ = f(function({ dashboardId: f, pageId: _, title: v, onPaste: le, onSave: u
|
|
|
319
331
|
})]
|
|
320
332
|
})]
|
|
321
333
|
}),
|
|
322
|
-
/* @__PURE__ */
|
|
323
|
-
isOpen:
|
|
324
|
-
onClose:
|
|
334
|
+
/* @__PURE__ */ g(ae, {
|
|
335
|
+
isOpen: Ae,
|
|
336
|
+
onClose: je
|
|
325
337
|
}),
|
|
326
|
-
/* @__PURE__ */
|
|
327
|
-
isOpen:
|
|
338
|
+
/* @__PURE__ */ g(ie, {
|
|
339
|
+
isOpen: Se,
|
|
328
340
|
onClose: () => {
|
|
329
|
-
|
|
341
|
+
E(!1), P(null), F(null), L(null);
|
|
330
342
|
},
|
|
331
|
-
onSave:
|
|
332
|
-
preselectedType:
|
|
333
|
-
preselectedDefinition:
|
|
334
|
-
dashboardId:
|
|
335
|
-
installedWidgets:
|
|
343
|
+
onSave: Z,
|
|
344
|
+
preselectedType: Te || void 0,
|
|
345
|
+
preselectedDefinition: I || void 0,
|
|
346
|
+
dashboardId: d,
|
|
347
|
+
installedWidgets: H
|
|
336
348
|
}),
|
|
337
|
-
/* @__PURE__ */
|
|
338
|
-
dashboardId:
|
|
339
|
-
dashboardName:
|
|
340
|
-
isOpen:
|
|
341
|
-
onClose: () =>
|
|
349
|
+
/* @__PURE__ */ g(ne, {
|
|
350
|
+
dashboardId: d,
|
|
351
|
+
dashboardName: y || "Dashboard",
|
|
352
|
+
isOpen: Ce,
|
|
353
|
+
onClose: () => D(!1)
|
|
342
354
|
})
|
|
343
355
|
]
|
|
344
356
|
});
|
|
345
357
|
});
|
|
346
358
|
//#endregion
|
|
347
|
-
export {
|
|
359
|
+
export { v as default };
|
|
348
360
|
|
|
349
361
|
//# sourceMappingURL=DashboardCanvas.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardCanvas.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardCanvas.tsx"],"sourcesContent":["/**\n * DashboardCanvas Component\n *\n * Main canvas for dashboard with grid layout and widget rendering.\n * Phase 2: Added GlobalFiltersBar and PageTabsManager integration.\n * Phase 3: Added WidgetPalette sidebar for drag-and-drop widget creation.\n */\n\nimport { type FC, memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport type { Layout } from 'react-grid-layout';\nimport type { Widget, WidgetType } from '../../types';\nimport { useDashboardStore, useWidgetStore } from '../../store';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { useInstalledWidgets } from '../../hooks/useInstalledWidgets';\nimport { useWidgetCatalog } from '../../hooks/useWidgetCatalog';\nimport { useBigConsole } from '../../context';\nimport { DashboardGrid } from './DashboardGrid';\nimport { DashboardToolbar } from './DashboardToolbar';\nimport { GlobalFiltersBar } from './GlobalFiltersBar';\nimport { PageTabsManager, type DashboardPageTab } from './PageTabsManager';\nimport { ShareDialog } from './ShareDialog';\nimport { WidgetPalette } from './WidgetPalette';\nimport { WidgetConfigDialog } from '../widgets/WidgetConfigDialog';\nimport { PropertiesPanel } from '../widgets/PropertiesPanel';\nimport { getWidgetDefinition, type WidgetDefinition } from '../widgets/WidgetRegistry';\n// Phase 2: Cross-filtering context\nimport { CrossFilterProvider } from '../../contexts';\n// Smart widget positioning\nimport { findNextAvailablePosition, calculatePositionFromDrop } from '../../utils/widgetPositioning';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardCanvasProps {\n /** Dashboard ID */\n dashboardId: string;\n /** Page ID (for loading widgets) */\n pageId?: string;\n /** Dashboard title */\n title?: string;\n /** Callback to paste widget */\n onPaste?: () => void;\n /** Callback when dashboard is saved */\n onSave?: () => void;\n /** Whether save is in progress */\n isSaving?: boolean;\n /** Callback to export dashboard */\n onExport?: () => void;\n /** Dashboard pages for PageTabsManager */\n pages?: DashboardPageTab[];\n /** Active page ID */\n activePageId?: string;\n /** Callback when page changes */\n onPageChange?: (pageId: string) => void;\n /** Callback when pages are reordered */\n onPagesReorder?: (pages: DashboardPageTab[]) => void;\n /** Callback when page is added */\n onPageAdd?: () => void;\n /** Callback when page is renamed */\n onPageRename?: (pageId: string, newName: string) => void;\n /** Callback when page is duplicated */\n onPageDuplicate?: (pageId: string) => void;\n /** Callback when page is deleted */\n onPageDelete?: (pageId: string) => void;\n /** Whether to show global filters bar */\n showFilters?: boolean;\n /** Whether to show page tabs */\n showPageTabs?: boolean;\n /** Whether to enable cross-filtering (widget clicks update filters) */\n enableCrossFilter?: boolean;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardCanvas: FC<DashboardCanvasProps> = memo(function DashboardCanvas({\n dashboardId,\n pageId,\n title,\n onPaste,\n onSave,\n isSaving = false,\n onExport,\n pages = [],\n activePageId,\n onPageChange,\n onPagesReorder,\n onPageAdd,\n onPageRename,\n onPageDuplicate,\n onPageDelete,\n showFilters = true,\n showPageTabs = true,\n enableCrossFilter = true,\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(1200);\n const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);\n const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);\n const [isPaletteOpen, setIsPaletteOpen] = useState(false);\n const [isDraggingWidget, setIsDraggingWidget] = useState(false);\n const [dragWidgetType, setDragWidgetType] = useState<WidgetType | null>(null);\n // State for drag & drop widget creation (stores pending position and type)\n const [pendingDropPosition, setPendingDropPosition] = useState<{\n x: number;\n y: number;\n width: number;\n height: number;\n } | null>(null);\n const [pendingDropType, setPendingDropType] = useState<WidgetType | null>(null);\n // Stores the full WidgetDefinition for installed/store widgets dropped from the palette,\n // so the config dialog can use the store metadata instead of the generic 'custom' definition.\n const [pendingDropDefinition, setPendingDropDefinition] = useState<WidgetDefinition | null>(null);\n\n // Store state\n const isEditMode = useDashboardStore((state) => state.viewMode === 'edit');\n\n // Fetch installed widgets only in edit mode — the palette and add-widget dialog\n // are not usable in read-only view, so skip the request for viewer-only sessions.\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n const { installedWidgets, loading: installedWidgetsLoading } = useInstalledWidgets({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n const zoomLevel = useDashboardStore((state) => state.zoomLevel);\n const setGlobalFilterValues = useDashboardStore((state) => state.setGlobalFilterValues);\n\n // Handle filter changes - update store for widget access\n const handleFilterChange = useCallback(\n (filterValues: Record<string, unknown>) => {\n // Update global filter values in dashboard store\n setGlobalFilterValues(filterValues);\n\n // Widgets can now access filter values via useGlobalFilterValues() hook\n // When backend supports filter parameters, widget queries will include these values\n },\n [setGlobalFilterValues]\n );\n\n // Widget operations hook (connects to backend) - skipFetch since parent page fetches\n const { createWidget: createWidgetApi } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n const widgetsMap = useWidgetStore((state) => state.widgets);\n // Filter widgets to only show widgets for the current page\n const widgetsArray = useMemo(() => {\n const allWidgets = Array.from(widgetsMap.values());\n if (!pageId) return allWidgets;\n return allWidgets.filter((widget) => widget.pageId === pageId);\n }, [widgetsMap, pageId]);\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetPosition = useWidgetStore((state) => state.updateWidgetPosition);\n const configPanelOpen = useWidgetStore((state) => state.configPanelOpen);\n const closeConfigPanel = useWidgetStore((state) => state.closeConfigPanel);\n\n // Handle resize\n useEffect(() => {\n const updateWidth = () => {\n if (containerRef.current) {\n setContainerWidth(containerRef.current.offsetWidth);\n }\n };\n\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, []);\n\n // Handle layout change\n const handleLayoutChange = useCallback(\n (layout: Layout[]) => {\n layout.forEach((item) => {\n updateWidgetPosition(item.i, {\n x: item.x,\n y: item.y,\n width: item.w,\n height: item.h,\n });\n });\n },\n [updateWidgetPosition]\n );\n\n // Handle widget select\n const handleWidgetSelect = useCallback(\n (widgetId: string) => {\n selectWidget(widgetId);\n },\n [selectWidget]\n );\n\n // Handle add widget - tries API first, falls back to local store\n const handleAddWidget = useCallback(\n async (widgetData: Partial<Widget>) => {\n // Get widget definition for default size; prefer the pending drop definition\n // which preserves installed/store widget metadata over the generic registry lookup.\n const definition = pendingDropDefinition || (widgetData.type ? getWidgetDefinition(widgetData.type) : null);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n // Use pending drop position if available (from drag & drop), otherwise use smart placement\n let position: { x: number; y: number; width: number; height: number };\n if (pendingDropPosition) {\n position = pendingDropPosition;\n } else {\n // Use smart placement algorithm - horizontal first, then next row\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n position = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n }\n\n // Extract dataSinkId and parserId from widgetData (passed from WidgetConfigDialog)\n const extendedData = widgetData as Partial<Widget> & { dataSinkId?: string; parserId?: string };\n\n // Try to create via API first\n if (pageId) {\n const widget = await createWidgetApi({\n pageId,\n dashboardId,\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n config: widgetData.config || {},\n position,\n refreshInterval: widgetData.refreshInterval ?? undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n });\n\n if (widget) {\n selectWidget(widget.id);\n setIsAddDialogOpen(false);\n // Clear pending drop state\n setPendingDropPosition(null);\n setPendingDropType(null);\n setPendingDropDefinition(null);\n return;\n }\n }\n\n // Fallback to local store if API fails or no pageId\n const newWidget: Widget = {\n id: crypto.randomUUID(),\n dashboardId,\n pageId: pageId || 'local',\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n refreshInterval: widgetData.refreshInterval,\n config: widgetData.config || {},\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n addWidgetToStore(newWidget);\n selectWidget(newWidget.id);\n setIsAddDialogOpen(false);\n // Clear pending drop state\n setPendingDropPosition(null);\n setPendingDropType(null);\n setPendingDropDefinition(null);\n },\n [\n dashboardId,\n pageId,\n createWidgetApi,\n addWidgetToStore,\n selectWidget,\n widgetsArray,\n pendingDropPosition,\n pendingDropDefinition,\n ]\n );\n\n // Handle drag start from palette\n const handlePaletteDragStart = useCallback((type: WidgetType) => {\n setIsDraggingWidget(true);\n setDragWidgetType(type);\n }, []);\n\n // Handle drag end from palette\n const handlePaletteDragEnd = useCallback(() => {\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n }, []);\n\n // Handle drop on canvas - opens config dialog instead of creating widget directly\n const handleCanvasDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n\n try {\n const data = e.dataTransfer.getData('application/json');\n if (!data) return;\n\n const widgetData = JSON.parse(data);\n const definition = getWidgetDefinition(widgetData.type);\n\n // If the dropped data includes defaultConfig (from WidgetPalette drag serialization),\n // reconstruct a full WidgetDefinition so installed/store widget identity is preserved.\n let droppedDefinition: WidgetDefinition | null = null;\n if (widgetData.defaultConfig && widgetData.category) {\n droppedDefinition = {\n ...definition,\n name: widgetData.name || definition.name,\n category: widgetData.category,\n defaultConfig: widgetData.defaultConfig,\n defaultSize: widgetData.defaultSize || definition.defaultSize,\n };\n }\n\n // Calculate drop position relative to canvas\n const canvasRect = containerRef.current?.getBoundingClientRect();\n if (!canvasRect) return;\n\n // Use utility to calculate grid position from drop coordinates\n const widgetSize = {\n width: definition.defaultSize.width,\n height: definition.defaultSize.height,\n };\n\n const { x: gridX, y: gridY } = calculatePositionFromDrop(\n e.clientX,\n e.clientY,\n canvasRect,\n containerWidth,\n widgetSize,\n { cols: 12 }\n );\n\n const position = {\n x: gridX,\n y: gridY,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n // Store the drop position, type, and full definition, then open the config dialog\n setPendingDropPosition(position);\n setPendingDropType(widgetData.type as WidgetType);\n setPendingDropDefinition(droppedDefinition);\n setIsAddDialogOpen(true);\n } catch (error) {\n console.error('Failed to handle widget drop:', error);\n }\n },\n [containerWidth]\n );\n\n // Handle drag over to allow drop\n const handleCanvasDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }, []);\n\n // Handle widget select from palette (quick add without drag)\n const handlePaletteWidgetSelect = useCallback(\n (type: WidgetType, paletteDefinition?: WidgetDefinition) => {\n const definition = paletteDefinition || getWidgetDefinition(type);\n handleAddWidget({\n type,\n title: definition.name,\n config: definition.defaultConfig,\n });\n setIsPaletteOpen(false);\n },\n [handleAddWidget]\n );\n\n // Toggle palette\n const togglePalette = useCallback(() => {\n setIsPaletteOpen((prev) => !prev);\n }, []);\n\n return (\n <div className=\"flex flex-col h-full min-w-0 overflow-hidden\">\n {/* Toolbar */}\n <DashboardToolbar\n dashboardId={dashboardId}\n title={title}\n onAddWidget={() => setIsAddDialogOpen(true)}\n onTogglePalette={togglePalette}\n isPaletteOpen={isPaletteOpen}\n onPaste={onPaste}\n onSave={onSave}\n isSaving={isSaving}\n onExport={onExport}\n onShare={() => setIsShareDialogOpen(true)}\n />\n\n {/* Global Filters Bar */}\n {showFilters && <GlobalFiltersBar dashboardId={dashboardId} onFilterChange={handleFilterChange} collapsible />}\n\n {/* Page Tabs - always show in edit mode so user can add first page */}\n {showPageTabs && (isEditMode || pages.length > 0) && (\n <PageTabsManager\n pages={pages}\n activePageId={activePageId || pages[0]?.id}\n editable={isEditMode}\n onPageSelect={onPageChange || (() => {})}\n onPagesReorder={\n onPagesReorder\n ? (pageIds) => {\n // Convert string[] to DashboardPageTab[] for the callback\n const reorderedPages = pageIds.map((id) => pages.find((p) => p.id === id)!).filter(Boolean);\n onPagesReorder(reorderedPages);\n }\n : undefined\n }\n onPageCreate={onPageAdd}\n onPageRename={onPageRename}\n onPageDuplicate={onPageDuplicate}\n onPageDelete={onPageDelete}\n />\n )}\n\n {/* Main content area with palette and canvas */}\n <div className=\"flex flex-1 min-w-0 overflow-hidden\">\n {/* Widget Palette Sidebar */}\n {isEditMode && (\n <WidgetPalette\n isOpen={isPaletteOpen}\n onClose={() => setIsPaletteOpen(false)}\n onWidgetSelect={handlePaletteWidgetSelect}\n onDragStart={handlePaletteDragStart}\n onDragEnd={handlePaletteDragEnd}\n installedWidgets={installedWidgets}\n installedWidgetsLoading={installedWidgetsLoading}\n definitions={widgetCatalog}\n />\n )}\n\n {/* Canvas area */}\n <div\n ref={containerRef}\n className=\"flex-1 min-w-0 overflow-auto bg-bg-muted\"\n onDrop={handleCanvasDrop}\n onDragOver={handleCanvasDragOver}\n onClick={() => {\n if (isEditMode) {\n selectWidget(null);\n }\n }}\n style={{\n position: 'relative',\n border: isDraggingWidget ? '2px dashed var(--color-action-primary-bg)' : 'none',\n backgroundColor: isDraggingWidget ? 'var(--color-bg-hover)' : undefined,\n transition: 'border-color 0.2s, background-color 0.2s',\n }}\n >\n {/* Drop zone indicator */}\n {isDraggingWidget && (\n <div\n style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n padding: 'var(--space-pagePadding)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-2)',\n textAlign: 'center',\n pointerEvents: 'none',\n zIndex: 100,\n }}\n >\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-primary)', margin: 0 }}>\n Drop to add <strong>{dragWidgetType}</strong> widget\n </p>\n </div>\n )}\n\n <div\n className=\"min-h-full origin-top-left transition-transform\"\n style={{\n transform: `scale(${zoomLevel / 100})`,\n width: `${100 / (zoomLevel / 100)}%`,\n }}\n >\n {widgetsArray.length > 0 ? (\n <CrossFilterProvider\n enabled={enableCrossFilter}\n onCrossFilter={(_filters) => {\n // Cross-filter applied - widget queries can access filter values\n }}\n >\n <DashboardGrid\n widgets={widgetsArray}\n isEditMode={isEditMode}\n width={containerWidth}\n onLayoutChange={handleLayoutChange}\n onWidgetSelect={handleWidgetSelect}\n selectedWidgetId={selectedWidgetId}\n />\n </CrossFilterProvider>\n ) : (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] p-8\">\n <div className=\"w-16 h-16 mb-4 flex items-center justify-center rounded-full bg-bg-surface\">\n <svg className=\"w-8 h-8 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary mb-2\">No widgets yet</h3>\n <p className=\"text-sm text-text-secondary mb-4 text-center max-w-[300px]\">\n Add widgets to build your dashboard. Click the "Add Widget" button to get started.\n </p>\n {isEditMode && (\n <button\n type=\"button\"\n onClick={() => setIsAddDialogOpen(true)}\n className=\"\n inline-flex items-center gap-2\n px-4 py-2\n text-sm font-medium\n text-action-primary-fg\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-md\n transition-colors\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 4v16m8-8H4\" />\n </svg>\n Add Widget\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Properties Panel */}\n <PropertiesPanel isOpen={configPanelOpen} onClose={closeConfigPanel} />\n\n {/* Add Widget Dialog */}\n <WidgetConfigDialog\n isOpen={isAddDialogOpen}\n onClose={() => {\n setIsAddDialogOpen(false);\n // Clear pending drop state when dialog is closed\n setPendingDropPosition(null);\n setPendingDropType(null);\n setPendingDropDefinition(null);\n }}\n onSave={handleAddWidget}\n preselectedType={pendingDropType || undefined}\n preselectedDefinition={pendingDropDefinition || undefined}\n dashboardId={dashboardId}\n installedWidgets={installedWidgets}\n />\n\n {/* Share Dialog */}\n <ShareDialog\n dashboardId={dashboardId}\n dashboardName={title || 'Dashboard'}\n isOpen={isShareDialogOpen}\n onClose={() => setIsShareDialogOpen(false)}\n />\n </div>\n );\n});\n\nexport default DashboardCanvas;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardCanvas.js","names":[],"sources":["../../../../src/bigconsole/components/dashboard/DashboardCanvas.tsx"],"sourcesContent":["/**\n * DashboardCanvas Component\n *\n * Main canvas for dashboard with grid layout and widget rendering.\n * Phase 2: Added GlobalFiltersBar and PageTabsManager integration.\n * Phase 3: Added WidgetPalette sidebar for drag-and-drop widget creation.\n */\n\nimport { type FC, memo, useRef, useState, useCallback, useEffect, useMemo } from 'react';\nimport type { Layout } from 'react-grid-layout';\nimport type { Widget, WidgetType } from '../../types';\nimport { useDashboardStore, useWidgetStore } from '../../store';\nimport { useWidgetOperations } from '../../hooks/useWidgetOperations';\nimport { useInstalledWidgets } from '../../hooks/useInstalledWidgets';\nimport { useWidgetCatalog } from '../../hooks/useWidgetCatalog';\nimport { useDashboardImageExport } from '../../hooks/useDashboardImageExport';\nimport { useBigConsole } from '../../context';\nimport { DashboardGrid } from './DashboardGrid';\nimport { DashboardToolbar } from './DashboardToolbar';\nimport { GlobalFiltersBar } from './GlobalFiltersBar';\nimport { PageTabsManager, type DashboardPageTab } from './PageTabsManager';\nimport { ShareDialog } from './ShareDialog';\nimport { WidgetPalette } from './WidgetPalette';\nimport { WidgetConfigDialog } from '../widgets/WidgetConfigDialog';\nimport { PropertiesPanel } from '../widgets/PropertiesPanel';\nimport { getWidgetDefinition, type WidgetDefinition } from '../widgets/WidgetRegistry';\n// Phase 2: Cross-filtering context\nimport { CrossFilterProvider } from '../../contexts';\n// Smart widget positioning\nimport { findNextAvailablePosition, calculatePositionFromDrop } from '../../utils/widgetPositioning';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface DashboardCanvasProps {\n /** Dashboard ID */\n dashboardId: string;\n /** Page ID (for loading widgets) */\n pageId?: string;\n /** Dashboard title */\n title?: string;\n /** Callback to paste widget */\n onPaste?: () => void;\n /** Callback when dashboard is saved */\n onSave?: () => void;\n /** Whether save is in progress */\n isSaving?: boolean;\n /** Callback to export dashboard */\n onExport?: () => void;\n /** Dashboard pages for PageTabsManager */\n pages?: DashboardPageTab[];\n /** Active page ID */\n activePageId?: string;\n /** Callback when page changes */\n onPageChange?: (pageId: string) => void;\n /** Callback when pages are reordered */\n onPagesReorder?: (pages: DashboardPageTab[]) => void;\n /** Callback when page is added */\n onPageAdd?: () => void;\n /** Callback when page is renamed */\n onPageRename?: (pageId: string, newName: string) => void;\n /** Callback when page is duplicated */\n onPageDuplicate?: (pageId: string) => void;\n /** Callback when page is deleted */\n onPageDelete?: (pageId: string) => void;\n /** Whether to show global filters bar */\n showFilters?: boolean;\n /** Whether to show page tabs */\n showPageTabs?: boolean;\n /** Whether to enable cross-filtering (widget clicks update filters) */\n enableCrossFilter?: boolean;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\nexport const DashboardCanvas: FC<DashboardCanvasProps> = memo(function DashboardCanvas({\n dashboardId,\n pageId,\n title,\n onPaste,\n onSave,\n isSaving = false,\n onExport,\n pages = [],\n activePageId,\n onPageChange,\n onPagesReorder,\n onPageAdd,\n onPageRename,\n onPageDuplicate,\n onPageDelete,\n showFilters = true,\n showPageTabs = true,\n enableCrossFilter = true,\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n // Inner board element (the grid surface) — rasterized for PNG/PDF board-pack export.\n const boardRef = useRef<HTMLDivElement>(null);\n const [containerWidth, setContainerWidth] = useState(1200);\n const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);\n const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);\n const [isPaletteOpen, setIsPaletteOpen] = useState(false);\n const [isDraggingWidget, setIsDraggingWidget] = useState(false);\n const [dragWidgetType, setDragWidgetType] = useState<WidgetType | null>(null);\n // State for drag & drop widget creation (stores pending position and type)\n const [pendingDropPosition, setPendingDropPosition] = useState<{\n x: number;\n y: number;\n width: number;\n height: number;\n } | null>(null);\n const [pendingDropType, setPendingDropType] = useState<WidgetType | null>(null);\n // Stores the full WidgetDefinition for installed/store widgets dropped from the palette,\n // so the config dialog can use the store metadata instead of the generic 'custom' definition.\n const [pendingDropDefinition, setPendingDropDefinition] = useState<WidgetDefinition | null>(null);\n\n // Store state\n const isEditMode = useDashboardStore((state) => state.viewMode === 'edit');\n\n // Fetch installed widgets only in edit mode — the palette and add-widget dialog\n // are not usable in read-only view, so skip the request for viewer-only sessions.\n const { workspaceId, apiGatewayUrl, authToken } = useBigConsole();\n const { installedWidgets, loading: installedWidgetsLoading } = useInstalledWidgets({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n // Built-in widget catalog from the backend (getWidgetTypes); falls back to the\n // bundled registry while loading or if the supergraph hasn't adopted it yet.\n const { definitions: widgetCatalog } = useWidgetCatalog({\n workspaceId,\n apiGatewayUrl,\n authToken,\n enabled: isEditMode,\n });\n const zoomLevel = useDashboardStore((state) => state.zoomLevel);\n const setGlobalFilterValues = useDashboardStore((state) => state.setGlobalFilterValues);\n\n // Handle filter changes - update store for widget access\n const handleFilterChange = useCallback(\n (filterValues: Record<string, unknown>) => {\n // Update global filter values in dashboard store\n setGlobalFilterValues(filterValues);\n\n // Widgets can now access filter values via useGlobalFilterValues() hook\n // When backend supports filter parameters, widget queries will include these values\n },\n [setGlobalFilterValues]\n );\n\n // Widget operations hook (connects to backend) - skipFetch since parent page fetches\n const { createWidget: createWidgetApi } = useWidgetOperations(pageId, dashboardId, { skipFetch: true });\n\n const widgetsMap = useWidgetStore((state) => state.widgets);\n // Filter widgets to only show widgets for the current page\n const widgetsArray = useMemo(() => {\n const allWidgets = Array.from(widgetsMap.values());\n if (!pageId) return allWidgets;\n return allWidgets.filter((widget) => widget.pageId === pageId);\n }, [widgetsMap, pageId]);\n const selectedWidgetId = useWidgetStore((state) => state.selectedWidgetId);\n const selectWidget = useWidgetStore((state) => state.selectWidget);\n const addWidgetToStore = useWidgetStore((state) => state.addWidget);\n const updateWidgetPosition = useWidgetStore((state) => state.updateWidgetPosition);\n const configPanelOpen = useWidgetStore((state) => state.configPanelOpen);\n const closeConfigPanel = useWidgetStore((state) => state.closeConfigPanel);\n\n // Handle resize\n useEffect(() => {\n const updateWidth = () => {\n if (containerRef.current) {\n setContainerWidth(containerRef.current.offsetWidth);\n }\n };\n\n updateWidth();\n window.addEventListener('resize', updateWidth);\n return () => window.removeEventListener('resize', updateWidth);\n }, []);\n\n // Handle layout change\n const handleLayoutChange = useCallback(\n (layout: Layout[]) => {\n layout.forEach((item) => {\n updateWidgetPosition(item.i, {\n x: item.x,\n y: item.y,\n width: item.w,\n height: item.h,\n });\n });\n },\n [updateWidgetPosition]\n );\n\n // Handle widget select\n const handleWidgetSelect = useCallback(\n (widgetId: string) => {\n selectWidget(widgetId);\n },\n [selectWidget]\n );\n\n // Handle add widget - tries API first, falls back to local store\n const handleAddWidget = useCallback(\n async (widgetData: Partial<Widget>) => {\n // Get widget definition for default size; prefer the pending drop definition\n // which preserves installed/store widget metadata over the generic registry lookup.\n const definition = pendingDropDefinition || (widgetData.type ? getWidgetDefinition(widgetData.type) : null);\n const widgetSize = {\n width: definition?.defaultSize?.width ?? 4,\n height: definition?.defaultSize?.height ?? 4,\n };\n\n // Use pending drop position if available (from drag & drop), otherwise use smart placement\n let position: { x: number; y: number; width: number; height: number };\n if (pendingDropPosition) {\n position = pendingDropPosition;\n } else {\n // Use smart placement algorithm - horizontal first, then next row\n const { x, y } = findNextAvailablePosition(widgetsArray, widgetSize, { cols: 12 });\n position = {\n x,\n y,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n }\n\n // Extract dataSinkId and parserId from widgetData (passed from WidgetConfigDialog)\n const extendedData = widgetData as Partial<Widget> & { dataSinkId?: string; parserId?: string };\n\n // Try to create via API first\n if (pageId) {\n const widget = await createWidgetApi({\n pageId,\n dashboardId,\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n config: widgetData.config || {},\n position,\n refreshInterval: widgetData.refreshInterval ?? undefined,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n });\n\n if (widget) {\n selectWidget(widget.id);\n setIsAddDialogOpen(false);\n // Clear pending drop state\n setPendingDropPosition(null);\n setPendingDropType(null);\n setPendingDropDefinition(null);\n return;\n }\n }\n\n // Fallback to local store if API fails or no pageId\n const newWidget: Widget = {\n id: crypto.randomUUID(),\n dashboardId,\n pageId: pageId || 'local',\n type: widgetData.type!,\n title: widgetData.title || 'Untitled Widget',\n description: widgetData.description,\n // v2.0: Renderer and template support\n renderer: widgetData.renderer,\n template: widgetData.template,\n refreshInterval: widgetData.refreshInterval,\n config: widgetData.config || {},\n position,\n positionX: position.x,\n positionY: position.y,\n positionWidth: position.width,\n positionHeight: position.height,\n // v2.0: Data source fields (DataSink → Parser → Widget)\n dataSinkId: extendedData.dataSinkId,\n parserId: extendedData.parserId,\n translations: [],\n createdAt: new Date().toISOString(),\n updatedAt: new Date().toISOString(),\n };\n\n addWidgetToStore(newWidget);\n selectWidget(newWidget.id);\n setIsAddDialogOpen(false);\n // Clear pending drop state\n setPendingDropPosition(null);\n setPendingDropType(null);\n setPendingDropDefinition(null);\n },\n [\n dashboardId,\n pageId,\n createWidgetApi,\n addWidgetToStore,\n selectWidget,\n widgetsArray,\n pendingDropPosition,\n pendingDropDefinition,\n ]\n );\n\n // Handle drag start from palette\n const handlePaletteDragStart = useCallback((type: WidgetType) => {\n setIsDraggingWidget(true);\n setDragWidgetType(type);\n }, []);\n\n // Handle drag end from palette\n const handlePaletteDragEnd = useCallback(() => {\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n }, []);\n\n // Handle drop on canvas - opens config dialog instead of creating widget directly\n const handleCanvasDrop = useCallback(\n (e: React.DragEvent) => {\n e.preventDefault();\n setIsDraggingWidget(false);\n setDragWidgetType(null);\n\n try {\n const data = e.dataTransfer.getData('application/json');\n if (!data) return;\n\n const widgetData = JSON.parse(data);\n const definition = getWidgetDefinition(widgetData.type);\n\n // If the dropped data includes defaultConfig (from WidgetPalette drag serialization),\n // reconstruct a full WidgetDefinition so installed/store widget identity is preserved.\n let droppedDefinition: WidgetDefinition | null = null;\n if (widgetData.defaultConfig && widgetData.category) {\n droppedDefinition = {\n ...definition,\n name: widgetData.name || definition.name,\n category: widgetData.category,\n defaultConfig: widgetData.defaultConfig,\n defaultSize: widgetData.defaultSize || definition.defaultSize,\n };\n }\n\n // Calculate drop position relative to canvas\n const canvasRect = containerRef.current?.getBoundingClientRect();\n if (!canvasRect) return;\n\n // Use utility to calculate grid position from drop coordinates\n const widgetSize = {\n width: definition.defaultSize.width,\n height: definition.defaultSize.height,\n };\n\n const { x: gridX, y: gridY } = calculatePositionFromDrop(\n e.clientX,\n e.clientY,\n canvasRect,\n containerWidth,\n widgetSize,\n { cols: 12 }\n );\n\n const position = {\n x: gridX,\n y: gridY,\n width: widgetSize.width,\n height: widgetSize.height,\n };\n\n // Store the drop position, type, and full definition, then open the config dialog\n setPendingDropPosition(position);\n setPendingDropType(widgetData.type as WidgetType);\n setPendingDropDefinition(droppedDefinition);\n setIsAddDialogOpen(true);\n } catch (error) {\n console.error('Failed to handle widget drop:', error);\n }\n },\n [containerWidth]\n );\n\n // Handle drag over to allow drop\n const handleCanvasDragOver = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'copy';\n }, []);\n\n // Handle widget select from palette (quick add without drag)\n const handlePaletteWidgetSelect = useCallback(\n (type: WidgetType, paletteDefinition?: WidgetDefinition) => {\n const definition = paletteDefinition || getWidgetDefinition(type);\n handleAddWidget({\n type,\n title: definition.name,\n config: definition.defaultConfig,\n });\n setIsPaletteOpen(false);\n },\n [handleAddWidget]\n );\n\n // Toggle palette\n const togglePalette = useCallback(() => {\n setIsPaletteOpen((prev) => !prev);\n }, []);\n\n // Board-pack export (PNG + PDF) — rasterizes the inner board element.\n const {\n exportPNG,\n exportPDF,\n isExporting: isExportingImage,\n } = useDashboardImageExport({ containerRef: boardRef, name: title || 'dashboard' });\n const handleExportImage = useCallback(() => {\n void exportPNG();\n }, [exportPNG]);\n const handleExportPdf = useCallback(() => {\n void exportPDF();\n }, [exportPDF]);\n\n return (\n <div className=\"flex flex-col h-full min-w-0 overflow-hidden\">\n {/* Toolbar */}\n <DashboardToolbar\n dashboardId={dashboardId}\n title={title}\n onAddWidget={() => setIsAddDialogOpen(true)}\n onTogglePalette={togglePalette}\n isPaletteOpen={isPaletteOpen}\n onPaste={onPaste}\n onSave={onSave}\n isSaving={isSaving}\n onExport={onExport}\n onExportImage={handleExportImage}\n onExportPdf={handleExportPdf}\n isExportingImage={isExportingImage}\n onShare={() => setIsShareDialogOpen(true)}\n />\n\n {/* Global Filters Bar */}\n {showFilters && <GlobalFiltersBar dashboardId={dashboardId} onFilterChange={handleFilterChange} collapsible />}\n\n {/* Page Tabs - always show in edit mode so user can add first page */}\n {showPageTabs && (isEditMode || pages.length > 0) && (\n <PageTabsManager\n pages={pages}\n activePageId={activePageId || pages[0]?.id}\n editable={isEditMode}\n onPageSelect={onPageChange || (() => {})}\n onPagesReorder={\n onPagesReorder\n ? (pageIds) => {\n // Convert string[] to DashboardPageTab[] for the callback\n const reorderedPages = pageIds.map((id) => pages.find((p) => p.id === id)!).filter(Boolean);\n onPagesReorder(reorderedPages);\n }\n : undefined\n }\n onPageCreate={onPageAdd}\n onPageRename={onPageRename}\n onPageDuplicate={onPageDuplicate}\n onPageDelete={onPageDelete}\n />\n )}\n\n {/* Main content area with palette and canvas */}\n <div className=\"flex flex-1 min-w-0 overflow-hidden\">\n {/* Widget Palette Sidebar */}\n {isEditMode && (\n <WidgetPalette\n isOpen={isPaletteOpen}\n onClose={() => setIsPaletteOpen(false)}\n onWidgetSelect={handlePaletteWidgetSelect}\n onDragStart={handlePaletteDragStart}\n onDragEnd={handlePaletteDragEnd}\n installedWidgets={installedWidgets}\n installedWidgetsLoading={installedWidgetsLoading}\n definitions={widgetCatalog}\n />\n )}\n\n {/* Canvas area */}\n <div\n ref={containerRef}\n className=\"flex-1 min-w-0 overflow-auto bg-bg-muted\"\n onDrop={handleCanvasDrop}\n onDragOver={handleCanvasDragOver}\n onClick={() => {\n if (isEditMode) {\n selectWidget(null);\n }\n }}\n style={{\n position: 'relative',\n border: isDraggingWidget ? '2px dashed var(--color-action-primary-bg)' : 'none',\n backgroundColor: isDraggingWidget ? 'var(--color-bg-hover)' : undefined,\n transition: 'border-color 0.2s, background-color 0.2s',\n }}\n >\n {/* Drop zone indicator */}\n {isDraggingWidget && (\n <div\n style={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n padding: 'var(--space-pagePadding)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n boxShadow: 'var(--shadow-elevation-2)',\n textAlign: 'center',\n pointerEvents: 'none',\n zIndex: 100,\n }}\n >\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-primary)', margin: 0 }}>\n Drop to add <strong>{dragWidgetType}</strong> widget\n </p>\n </div>\n )}\n\n <div\n ref={boardRef}\n className=\"min-h-full origin-top-left transition-transform\"\n style={{\n transform: `scale(${zoomLevel / 100})`,\n width: `${100 / (zoomLevel / 100)}%`,\n }}\n >\n {widgetsArray.length > 0 ? (\n <CrossFilterProvider\n enabled={enableCrossFilter}\n onCrossFilter={(_filters) => {\n // Cross-filter applied - widget queries can access filter values\n }}\n >\n <DashboardGrid\n widgets={widgetsArray}\n isEditMode={isEditMode}\n width={containerWidth}\n onLayoutChange={handleLayoutChange}\n onWidgetSelect={handleWidgetSelect}\n selectedWidgetId={selectedWidgetId}\n />\n </CrossFilterProvider>\n ) : (\n <div className=\"flex flex-col items-center justify-center min-h-[400px] p-8\">\n <div className=\"w-16 h-16 mb-4 flex items-center justify-center rounded-full bg-bg-surface\">\n <svg className=\"w-8 h-8 text-text-secondary\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z\"\n />\n </svg>\n </div>\n <h3 className=\"text-lg font-medium text-text-primary mb-2\">No widgets yet</h3>\n <p className=\"text-sm text-text-secondary mb-4 text-center max-w-[300px]\">\n Add widgets to build your dashboard. Click the "Add Widget" button to get started.\n </p>\n {isEditMode && (\n <button\n type=\"button\"\n onClick={() => setIsAddDialogOpen(true)}\n className=\"\n inline-flex items-center gap-2\n px-4 py-2\n text-sm font-medium\n text-action-primary-fg\n bg-action-primary-bg\n hover:bg-action-primary-bgHover\n rounded-md\n transition-colors\n \"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 4v16m8-8H4\" />\n </svg>\n Add Widget\n </button>\n )}\n </div>\n )}\n </div>\n </div>\n </div>\n\n {/* Properties Panel */}\n <PropertiesPanel isOpen={configPanelOpen} onClose={closeConfigPanel} />\n\n {/* Add Widget Dialog */}\n <WidgetConfigDialog\n isOpen={isAddDialogOpen}\n onClose={() => {\n setIsAddDialogOpen(false);\n // Clear pending drop state when dialog is closed\n setPendingDropPosition(null);\n setPendingDropType(null);\n setPendingDropDefinition(null);\n }}\n onSave={handleAddWidget}\n preselectedType={pendingDropType || undefined}\n preselectedDefinition={pendingDropDefinition || undefined}\n dashboardId={dashboardId}\n installedWidgets={installedWidgets}\n />\n\n {/* Share Dialog */}\n <ShareDialog\n dashboardId={dashboardId}\n dashboardName={title || 'Dashboard'}\n isOpen={isShareDialogOpen}\n onClose={() => setIsShareDialogOpen(false)}\n />\n </div>\n );\n});\n\nexport default DashboardCanvas;\n"],"mappings":"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"}
|