@burdenoff/microfe-bigconsole 2026.617.1 → 2026.622.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.
@@ -14,41 +14,42 @@ import l from "./DashboardGrid.js";
14
14
  import ee from "./DashboardToolbar.js";
15
15
  import te from "./GlobalFiltersBar/GlobalFiltersBar.js";
16
16
  import "./GlobalFiltersBar/index.js";
17
- import u from "./PageTabsManager/PageTabsManager.js";
17
+ import ne from "./LiveBoardBanner.js";
18
+ import re from "./PageTabsManager/PageTabsManager.js";
18
19
  import "./PageTabsManager/index.js";
19
- import ne from "./ShareDialog/ShareDialog.js";
20
+ import ie from "./ShareDialog/ShareDialog.js";
20
21
  import "./ShareDialog/index.js";
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";
22
+ import ae from "./WidgetPalette.js";
23
+ import oe from "../widgets/WidgetConfigDialog.js";
24
+ import se from "../widgets/PropertiesPanel.js";
25
+ import { calculatePositionFromDrop as ce, findNextAvailablePosition as le } from "../../utils/widgetPositioning.js";
26
+ import { memo as u, useCallback as d, useEffect as ue, useMemo as de, useRef as f, useState as p } from "react";
27
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
27
28
  //#region src/bigconsole/components/dashboard/DashboardCanvas.tsx
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({
29
+ var g = u(function({ dashboardId: u, pageId: g, title: _, onPaste: fe, onSave: pe, isSaving: me = !1, onExport: he, pages: v = [], activePageId: y, onPageChange: ge, onPagesReorder: b, onPageAdd: x, onPageRename: _e, onPageDuplicate: ve, onPageDelete: ye, showFilters: be = !0, showPageTabs: xe = !0, enableCrossFilter: Se = !0 }) {
30
+ let S = f(null), C = f(null), [w, Ce] = p(1200), [we, T] = p(!1), [Te, E] = p(!1), [D, O] = p(!1), [k, A] = p(!1), [Ee, j] = p(null), [M, N] = p(null), [P, F] = p(null), [I, L] = p(null), R = t((e) => e.viewMode === "edit"), { workspaceId: z, apiGatewayUrl: B, authToken: V } = r(), { installedWidgets: H, loading: De } = c({
30
31
  workspaceId: z,
31
32
  apiGatewayUrl: B,
32
33
  authToken: V,
33
34
  enabled: R
34
- }), { definitions: De } = a({
35
+ }), { definitions: Oe } = a({
35
36
  workspaceId: z,
36
37
  apiGatewayUrl: B,
37
38
  authToken: V,
38
39
  enabled: R
39
- }), U = t((e) => e.zoomLevel), W = t((e) => e.setGlobalFilterValues), Oe = f((e) => {
40
+ }), U = t((e) => e.zoomLevel), W = t((e) => e.setGlobalFilterValues), ke = d((e) => {
40
41
  W(e);
41
- }, [W]), { createWidget: G } = o(v, d, { skipFetch: !0 }), K = e((e) => e.widgets), q = ce(() => {
42
+ }, [W]), { createWidget: G } = o(g, u, { skipFetch: !0 }), K = e((e) => e.widgets), q = de(() => {
42
43
  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(() => {
44
+ return g ? e.filter((e) => e.pageId === g) : e;
45
+ }, [K, g]), Ae = e((e) => e.selectedWidgetId), J = e((e) => e.selectWidget), Y = e((e) => e.addWidget), X = e((e) => e.updateWidgetPosition), je = e((e) => e.configPanelOpen), Me = e((e) => e.closeConfigPanel);
46
+ ue(() => {
46
47
  let e = () => {
47
- C.current && xe(C.current.offsetWidth);
48
+ S.current && Ce(S.current.offsetWidth);
48
49
  };
49
50
  return e(), window.addEventListener("resize", e), () => window.removeEventListener("resize", e);
50
51
  }, []);
51
- let Me = f((e) => {
52
+ let Ne = d((e) => {
52
53
  e.forEach((e) => {
53
54
  X(e.i, {
54
55
  x: e.x,
@@ -57,16 +58,16 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
57
58
  height: e.h
58
59
  });
59
60
  });
60
- }, [X]), Ne = f((e) => {
61
+ }, [X]), Pe = d((e) => {
61
62
  J(e);
62
- }, [J]), Z = f(async (e) => {
63
+ }, [J]), Z = d(async (e) => {
63
64
  let t = I || (e.type ? i(e.type) : null), n = {
64
65
  width: t?.defaultSize?.width ?? 4,
65
66
  height: t?.defaultSize?.height ?? 4
66
67
  }, r;
67
- if (N) r = N;
68
+ if (M) r = M;
68
69
  else {
69
- let { x: e, y: t } = se(q, n, { cols: 12 });
70
+ let { x: e, y: t } = le(q, n, { cols: 12 });
70
71
  r = {
71
72
  x: e,
72
73
  y: t,
@@ -75,10 +76,10 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
75
76
  };
76
77
  }
77
78
  let a = e;
78
- if (v) {
79
+ if (g) {
79
80
  let t = await G({
80
- pageId: v,
81
- dashboardId: d,
81
+ pageId: g,
82
+ dashboardId: u,
82
83
  type: e.type,
83
84
  title: e.title || "Untitled Widget",
84
85
  description: e.description,
@@ -91,14 +92,14 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
91
92
  parserId: a.parserId
92
93
  });
93
94
  if (t) {
94
- J(t.id), E(!1), P(null), F(null), L(null);
95
+ J(t.id), T(!1), N(null), F(null), L(null);
95
96
  return;
96
97
  }
97
98
  }
98
99
  let o = {
99
100
  id: crypto.randomUUID(),
100
- dashboardId: d,
101
- pageId: v || "local",
101
+ dashboardId: u,
102
+ pageId: g || "local",
102
103
  type: e.type,
103
104
  title: e.title || "Untitled Widget",
104
105
  description: e.description,
@@ -117,22 +118,22 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
117
118
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
118
119
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
119
120
  };
120
- Y(o), J(o.id), E(!1), P(null), F(null), L(null);
121
+ Y(o), J(o.id), T(!1), N(null), F(null), L(null);
121
122
  }, [
122
- d,
123
- v,
123
+ u,
124
+ g,
124
125
  G,
125
126
  Y,
126
127
  J,
127
128
  q,
128
- N,
129
+ M,
129
130
  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);
131
+ ]), Fe = d((e) => {
132
+ A(!0), j(e);
133
+ }, []), Ie = d(() => {
134
+ A(!1), j(null);
135
+ }, []), Le = d((e) => {
136
+ e.preventDefault(), A(!1), j(null);
136
137
  try {
137
138
  let t = e.dataTransfer.getData("application/json");
138
139
  if (!t) return;
@@ -144,102 +145,106 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
144
145
  defaultConfig: n.defaultConfig,
145
146
  defaultSize: n.defaultSize || r.defaultSize
146
147
  });
147
- let o = C.current?.getBoundingClientRect();
148
+ let o = S.current?.getBoundingClientRect();
148
149
  if (!o) return;
149
150
  let s = {
150
151
  width: r.defaultSize.width,
151
152
  height: r.defaultSize.height
152
- }, { x: c, y: l } = oe(e.clientX, e.clientY, o, T, s, { cols: 12 });
153
- P({
153
+ }, { x: c, y: l } = ce(e.clientX, e.clientY, o, w, s, { cols: 12 });
154
+ N({
154
155
  x: c,
155
156
  y: l,
156
157
  width: s.width,
157
158
  height: s.height
158
- }), F(n.type), L(a), E(!0);
159
+ }), F(n.type), L(a), T(!0);
159
160
  } catch (e) {
160
161
  console.error("Failed to handle widget drop:", e);
161
162
  }
162
- }, [T]), Le = f((e) => {
163
+ }, [w]), Re = d((e) => {
163
164
  e.preventDefault(), e.dataTransfer.dropEffect = "copy";
164
- }, []), Re = f((e, t) => {
165
+ }, []), ze = d((e, t) => {
165
166
  let n = t || i(e);
166
167
  Z({
167
168
  type: e,
168
169
  title: n.name,
169
170
  config: n.defaultConfig
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"
171
+ }), O(!1);
172
+ }, [Z]), Be = d(() => {
173
+ O((e) => !e);
174
+ }, []), { exportPNG: Q, exportPDF: $, isExporting: Ve } = s({
175
+ containerRef: C,
176
+ name: _ || "dashboard"
176
177
  });
177
- return /* @__PURE__ */ _("div", {
178
+ return /* @__PURE__ */ h("div", {
178
179
  className: "flex flex-col h-full min-w-0 overflow-hidden",
179
180
  children: [
180
- /* @__PURE__ */ g(ee, {
181
- dashboardId: d,
182
- title: y,
183
- onAddWidget: () => E(!0),
184
- onTogglePalette: ze,
185
- isPaletteOpen: O,
186
- onPaste: le,
187
- onSave: ue,
188
- isSaving: de,
189
- onExport: fe,
190
- onExportImage: f(() => {
181
+ /* @__PURE__ */ m(ee, {
182
+ dashboardId: u,
183
+ title: _,
184
+ onAddWidget: () => T(!0),
185
+ onTogglePalette: Be,
186
+ isPaletteOpen: D,
187
+ onPaste: fe,
188
+ onSave: pe,
189
+ isSaving: me,
190
+ onExport: he,
191
+ onExportImage: d(() => {
191
192
  Q();
192
193
  }, [Q]),
193
- onExportPdf: f(() => {
194
+ onExportPdf: d(() => {
194
195
  $();
195
196
  }, [$]),
196
- isExportingImage: Be,
197
- onShare: () => D(!0)
197
+ isExportingImage: Ve,
198
+ onShare: () => E(!0)
198
199
  }),
199
- ve && /* @__PURE__ */ g(te, {
200
- dashboardId: d,
201
- onFilterChange: Oe,
200
+ /* @__PURE__ */ m(ne, {
201
+ widgets: q,
202
+ isEditMode: R
203
+ }),
204
+ be && /* @__PURE__ */ m(te, {
205
+ dashboardId: u,
206
+ onFilterChange: ke,
202
207
  collapsible: !0
203
208
  }),
204
- ye && (R || b.length > 0) && /* @__PURE__ */ g(u, {
205
- pages: b,
206
- activePageId: pe || b[0]?.id,
209
+ xe && (R || v.length > 0) && /* @__PURE__ */ m(re, {
210
+ pages: v,
211
+ activePageId: y || v[0]?.id,
207
212
  editable: R,
208
- onPageSelect: me || (() => {}),
209
- onPagesReorder: x ? (e) => {
210
- x(e.map((e) => b.find((t) => t.id === e)).filter(Boolean));
213
+ onPageSelect: ge || (() => {}),
214
+ onPagesReorder: b ? (e) => {
215
+ b(e.map((e) => v.find((t) => t.id === e)).filter(Boolean));
211
216
  } : void 0,
212
- onPageCreate: he,
213
- onPageRename: ge,
214
- onPageDuplicate: S,
215
- onPageDelete: _e
217
+ onPageCreate: x,
218
+ onPageRename: _e,
219
+ onPageDuplicate: ve,
220
+ onPageDelete: ye
216
221
  }),
217
- /* @__PURE__ */ _("div", {
222
+ /* @__PURE__ */ h("div", {
218
223
  className: "flex flex-1 min-w-0 overflow-hidden",
219
- children: [R && /* @__PURE__ */ g(re, {
220
- isOpen: O,
221
- onClose: () => k(!1),
222
- onWidgetSelect: Re,
223
- onDragStart: Pe,
224
- onDragEnd: Fe,
224
+ children: [R && /* @__PURE__ */ m(ae, {
225
+ isOpen: D,
226
+ onClose: () => O(!1),
227
+ onWidgetSelect: ze,
228
+ onDragStart: Fe,
229
+ onDragEnd: Ie,
225
230
  installedWidgets: H,
226
- installedWidgetsLoading: Ee,
227
- definitions: De
228
- }), /* @__PURE__ */ _("div", {
229
- ref: C,
231
+ installedWidgetsLoading: De,
232
+ definitions: Oe
233
+ }), /* @__PURE__ */ h("div", {
234
+ ref: S,
230
235
  className: "flex-1 min-w-0 overflow-auto bg-bg-muted",
231
- onDrop: Ie,
232
- onDragOver: Le,
236
+ onDrop: Le,
237
+ onDragOver: Re,
233
238
  onClick: () => {
234
239
  R && J(null);
235
240
  },
236
241
  style: {
237
242
  position: "relative",
238
- border: A ? "2px dashed var(--color-action-primary-bg)" : "none",
239
- backgroundColor: A ? "var(--color-bg-hover)" : void 0,
243
+ border: k ? "2px dashed var(--color-action-primary-bg)" : "none",
244
+ backgroundColor: k ? "var(--color-bg-hover)" : void 0,
240
245
  transition: "border-color 0.2s, background-color 0.2s"
241
246
  },
242
- children: [A && /* @__PURE__ */ g("div", {
247
+ children: [k && /* @__PURE__ */ m("div", {
243
248
  style: {
244
249
  position: "absolute",
245
250
  top: "50%",
@@ -253,7 +258,7 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
253
258
  pointerEvents: "none",
254
259
  zIndex: 100
255
260
  },
256
- children: /* @__PURE__ */ _("p", {
261
+ children: /* @__PURE__ */ h("p", {
257
262
  style: {
258
263
  fontSize: "var(--font-size-body-md)",
259
264
  color: "var(--color-text-primary)",
@@ -261,39 +266,39 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
261
266
  },
262
267
  children: [
263
268
  "Drop to add ",
264
- /* @__PURE__ */ g("strong", { children: we }),
269
+ /* @__PURE__ */ m("strong", { children: Ee }),
265
270
  " widget"
266
271
  ]
267
272
  })
268
- }), /* @__PURE__ */ g("div", {
269
- ref: w,
273
+ }), /* @__PURE__ */ m("div", {
274
+ ref: C,
270
275
  className: "min-h-full origin-top-left transition-transform",
271
276
  style: {
272
277
  transform: `scale(${U / 100})`,
273
278
  width: `${100 / (U / 100)}%`
274
279
  },
275
- children: q.length > 0 ? /* @__PURE__ */ g(n, {
276
- enabled: be,
280
+ children: q.length > 0 ? /* @__PURE__ */ m(n, {
281
+ enabled: Se,
277
282
  onCrossFilter: (e) => {},
278
- children: /* @__PURE__ */ g(l, {
283
+ children: /* @__PURE__ */ m(l, {
279
284
  widgets: q,
280
285
  isEditMode: R,
281
- width: T,
282
- onLayoutChange: Me,
283
- onWidgetSelect: Ne,
284
- selectedWidgetId: ke
286
+ width: w,
287
+ onLayoutChange: Ne,
288
+ onWidgetSelect: Pe,
289
+ selectedWidgetId: Ae
285
290
  })
286
- }) : /* @__PURE__ */ _("div", {
291
+ }) : /* @__PURE__ */ h("div", {
287
292
  className: "flex flex-col items-center justify-center min-h-[400px] p-8",
288
293
  children: [
289
- /* @__PURE__ */ g("div", {
294
+ /* @__PURE__ */ m("div", {
290
295
  className: "w-16 h-16 mb-4 flex items-center justify-center rounded-full bg-bg-surface",
291
- children: /* @__PURE__ */ g("svg", {
296
+ children: /* @__PURE__ */ m("svg", {
292
297
  className: "w-8 h-8 text-text-secondary",
293
298
  fill: "none",
294
299
  stroke: "currentColor",
295
300
  viewBox: "0 0 24 24",
296
- children: /* @__PURE__ */ g("path", {
301
+ children: /* @__PURE__ */ m("path", {
297
302
  strokeLinecap: "round",
298
303
  strokeLinejoin: "round",
299
304
  strokeWidth: 2,
@@ -301,24 +306,24 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
301
306
  })
302
307
  })
303
308
  }),
304
- /* @__PURE__ */ g("h3", {
309
+ /* @__PURE__ */ m("h3", {
305
310
  className: "text-lg font-medium text-text-primary mb-2",
306
311
  children: "No widgets yet"
307
312
  }),
308
- /* @__PURE__ */ g("p", {
313
+ /* @__PURE__ */ m("p", {
309
314
  className: "text-sm text-text-secondary mb-4 text-center max-w-[300px]",
310
315
  children: "Add widgets to build your dashboard. Click the \"Add Widget\" button to get started."
311
316
  }),
312
- R && /* @__PURE__ */ _("button", {
317
+ R && /* @__PURE__ */ h("button", {
313
318
  type: "button",
314
- onClick: () => E(!0),
319
+ onClick: () => T(!0),
315
320
  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 ",
316
- children: [/* @__PURE__ */ g("svg", {
321
+ children: [/* @__PURE__ */ m("svg", {
317
322
  className: "w-4 h-4",
318
323
  fill: "none",
319
324
  stroke: "currentColor",
320
325
  viewBox: "0 0 24 24",
321
- children: /* @__PURE__ */ g("path", {
326
+ children: /* @__PURE__ */ m("path", {
322
327
  strokeLinecap: "round",
323
328
  strokeLinejoin: "round",
324
329
  strokeWidth: 2,
@@ -331,31 +336,31 @@ var v = d(function({ dashboardId: d, pageId: v, title: y, onPaste: le, onSave: u
331
336
  })]
332
337
  })]
333
338
  }),
334
- /* @__PURE__ */ g(ae, {
335
- isOpen: Ae,
336
- onClose: je
339
+ /* @__PURE__ */ m(se, {
340
+ isOpen: je,
341
+ onClose: Me
337
342
  }),
338
- /* @__PURE__ */ g(ie, {
339
- isOpen: Se,
343
+ /* @__PURE__ */ m(oe, {
344
+ isOpen: we,
340
345
  onClose: () => {
341
- E(!1), P(null), F(null), L(null);
346
+ T(!1), N(null), F(null), L(null);
342
347
  },
343
348
  onSave: Z,
344
- preselectedType: Te || void 0,
349
+ preselectedType: P || void 0,
345
350
  preselectedDefinition: I || void 0,
346
- dashboardId: d,
351
+ dashboardId: u,
347
352
  installedWidgets: H
348
353
  }),
349
- /* @__PURE__ */ g(ne, {
350
- dashboardId: d,
351
- dashboardName: y || "Dashboard",
352
- isOpen: Ce,
353
- onClose: () => D(!1)
354
+ /* @__PURE__ */ m(ie, {
355
+ dashboardId: u,
356
+ dashboardName: _ || "Dashboard",
357
+ isOpen: Te,
358
+ onClose: () => E(!1)
354
359
  })
355
360
  ]
356
361
  });
357
362
  });
358
363
  //#endregion
359
- export { v as default };
364
+ export { g as default };
360
365
 
361
366
  //# 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 { 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 &quot;Add Widget&quot; 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 { LiveBoardBanner } from './LiveBoardBanner';\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 {/* Live wall-board freshness banner — renders only when the page has\n auto-refreshing widgets (e.g. the Live Bed Board) and not in edit mode. */}\n <LiveBoardBanner widgets={widgetsArray} isEditMode={isEditMode} />\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 &quot;Add Widget&quot; 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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"}