@quantumwake/terminal-ux-dashboard-components 0.1.13 → 0.1.17

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/index.cjs CHANGED
@@ -9,15 +9,18 @@ var scatterplot = require('@nivo/scatterplot');
9
9
  var heatmap = require('@nivo/heatmap');
10
10
  var PivotTableUI = require('react-pivottable/PivotTableUI');
11
11
  require('react-pivottable/pivottable.css');
12
+ var terminalUxComponents = require('@quantumwake/terminal-ux-components');
12
13
  var lucideReact = require('lucide-react');
13
14
  var CodeMirror = require('@uiw/react-codemirror');
14
15
  var langSql = require('@codemirror/lang-sql');
15
16
  var autocomplete = require('@codemirror/autocomplete');
17
+ var GridLayout = require('react-grid-layout');
16
18
 
17
19
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
20
 
19
21
  var PivotTableUI__default = /*#__PURE__*/_interopDefault(PivotTableUI);
20
22
  var CodeMirror__default = /*#__PURE__*/_interopDefault(CodeMirror);
23
+ var GridLayout__default = /*#__PURE__*/_interopDefault(GridLayout);
21
24
 
22
25
  // src/context/DashboardContext.tsx
23
26
  var DashboardContext = react.createContext(null);
@@ -46,7 +49,9 @@ function useCapabilities() {
46
49
  canAddPanel: !!c.addPanel,
47
50
  canEditPanels: !!c.removePanel,
48
51
  // Studio can recompute + persist a panel's precomputed result.
49
- canRefresh: !!c.persistPanelData
52
+ canRefresh: !!c.persistPanelData,
53
+ // Studio can drag/resize panels and persist the grid layout.
54
+ canEditLayout: !!c.persistLayout
50
55
  };
51
56
  }
52
57
 
@@ -231,8 +236,15 @@ var DEFAULT_CHART_STYLE = {
231
236
  legendBold: false,
232
237
  legendHighlight: "",
233
238
  // Tick overflow handling.
234
- xTickRotation: -35,
239
+ xTickRotation: 0,
240
+ yTickRotation: 0,
235
241
  tickTruncate: 0,
242
+ tickWrap: true,
243
+ tickWrapWidth: 14,
244
+ // Table cells.
245
+ cellWrap: true,
246
+ cellPrecision: 2,
247
+ cellTruncate: 0,
236
248
  // Series legend placement (charts that have one: pie, grouped bar).
237
249
  legendAnchor: "right",
238
250
  // Panel title (rendered by DashboardRenderer's panel header).
@@ -307,7 +319,7 @@ var axisLegend = (style, axis, columnName, opts2 = {}) => {
307
319
  out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
308
320
  out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
309
321
  }
310
- if (isX) out.tickRotation = s.xTickRotation;
322
+ out.tickRotation = isX ? s.xTickRotation : s.yTickRotation;
311
323
  if (opts2.numeric) out.format = (v) => Number(v).toLocaleString();
312
324
  else if (s.tickTruncate > 0) out.format = (v) => truncate(v, s.tickTruncate);
313
325
  return out;
@@ -357,6 +369,78 @@ function MetricView({ records, config, value: presetValue }) {
357
369
  function InsightView({ config }) {
358
370
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full p-4 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-midnight-text-body leading-relaxed whitespace-pre-wrap", children: config.text }) });
359
371
  }
372
+ function formatVal(value, s, numeric) {
373
+ let str = numeric ? Number(value).toLocaleString() : String(value);
374
+ if (s.tickTruncate > 0 && str.length > s.tickTruncate) str = `${str.slice(0, s.tickTruncate)}\u2026`;
375
+ return str;
376
+ }
377
+ function wrapText(str, width) {
378
+ if (width <= 0 || str.length <= width) return [str];
379
+ const words = str.split(/\s+/);
380
+ const lines = [];
381
+ let cur = "";
382
+ const push = (t) => {
383
+ if (t) lines.push(t);
384
+ };
385
+ for (const w of words) {
386
+ if (w.length > width) {
387
+ push(cur);
388
+ cur = "";
389
+ for (let i = 0; i < w.length; i += width) lines.push(w.slice(i, i + width));
390
+ continue;
391
+ }
392
+ if (!cur) cur = w;
393
+ else if (cur.length + 1 + w.length <= width) cur += ` ${w}`;
394
+ else {
395
+ push(cur);
396
+ cur = w;
397
+ }
398
+ }
399
+ push(cur);
400
+ if (lines.length > 4) {
401
+ const kept = lines.slice(0, 4);
402
+ kept[3] = `${kept[3].slice(0, Math.max(0, width - 1))}\u2026`;
403
+ return kept;
404
+ }
405
+ return lines;
406
+ }
407
+ function makeAxis(style, axis, columnName, opts2 = {}) {
408
+ const s = withStyleDefaults(style);
409
+ const isX = axis === "x";
410
+ const numeric = !!opts2.numeric;
411
+ const show = isX ? s.showXLegend : s.showYLegend;
412
+ const label = (isX ? s.xAxisLabel : s.yAxisLabel) || columnName;
413
+ const rotate = isX ? s.xTickRotation : s.yTickRotation;
414
+ const out = { tickSize: 5, tickPadding: 5 };
415
+ if (show && label) {
416
+ out.legend = label;
417
+ out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
418
+ out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
419
+ }
420
+ if (s.tickWrap) {
421
+ out.renderTick = (tick) => {
422
+ const lines = wrapText(formatVal(tick.value, s, numeric), s.tickWrapWidth);
423
+ const firstDy = isX ? "0" : `${-((lines.length - 1) * 0.55)}em`;
424
+ return /* @__PURE__ */ jsxRuntime.jsxs("g", { transform: `translate(${tick.x},${tick.y})`, children: [
425
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x2: isX ? 0 : -5, y2: isX ? 5 : 0, style: { stroke: s.textColor, strokeWidth: 1, opacity: 0.3 } }),
426
+ /* @__PURE__ */ jsxRuntime.jsx(
427
+ "text",
428
+ {
429
+ transform: `translate(${tick.textX},${tick.textY}) rotate(${rotate})`,
430
+ textAnchor: tick.textAnchor,
431
+ dominantBaseline: tick.textBaseline,
432
+ style: { fill: s.textColor, fontSize: s.fontSize },
433
+ children: lines.map((ln, i) => /* @__PURE__ */ jsxRuntime.jsx("tspan", { x: 0, dy: i === 0 ? firstDy : "1.1em", children: ln }, i))
434
+ }
435
+ )
436
+ ] });
437
+ };
438
+ } else {
439
+ out.tickRotation = rotate;
440
+ out.format = (v) => formatVal(v, s, numeric);
441
+ }
442
+ return out;
443
+ }
360
444
  function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: presetData, style }) {
361
445
  const computed = react.useMemo(() => {
362
446
  if (presetData || !records) return [];
@@ -364,7 +448,7 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
364
448
  return Object.entries(groups).map(([key, recs]) => ({ group: key, value: aggregate(recs, valueColumn, aggFn) })).sort((a, b) => b.value - a.value).slice(0, 50);
365
449
  }, [records, groupColumn, valueColumn, aggFn, presetData]);
366
450
  const data = presetData || computed;
367
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
451
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
368
452
  bar.ResponsiveBar,
369
453
  {
370
454
  data,
@@ -374,8 +458,8 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
374
458
  padding: 0.3,
375
459
  colors: ["rgba(74, 222, 128, 0.8)"],
376
460
  borderColor: { from: "color", modifiers: [["darker", 1.6]] },
377
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", groupColumn) },
378
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", valueColumn, { numeric: true }) },
461
+ axisBottom: makeAxis(style, "x", groupColumn),
462
+ axisLeft: makeAxis(style, "y", valueColumn, { numeric: true }),
379
463
  labelSkipWidth: 12,
380
464
  labelSkipHeight: 12,
381
465
  labelTextColor: { from: "color", modifiers: [["darker", 3]] },
@@ -396,7 +480,7 @@ function PieView({ records, groupColumn, data: presetData, style }) {
396
480
  }, [records, groupColumn, presetData]);
397
481
  const data = presetData || computed;
398
482
  const legend = legendConfig(style);
399
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
483
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
400
484
  pie.ResponsivePie,
401
485
  {
402
486
  data,
@@ -432,7 +516,7 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
432
516
  }];
433
517
  }, [records, xColumn, yColumn, presetData]);
434
518
  const data = presetData || computed;
435
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
519
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
436
520
  line.ResponsiveLine,
437
521
  {
438
522
  data,
@@ -448,8 +532,8 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
448
532
  pointBorderWidth: 2,
449
533
  pointBorderColor: { from: "serieColor" },
450
534
  enableGridX: false,
451
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
452
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
535
+ axisBottom: makeAxis(style, "x", xColumn),
536
+ axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
453
537
  useMesh: true,
454
538
  theme: buildNivoTheme(style)
455
539
  }
@@ -462,7 +546,7 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
462
546
  return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
463
547
  }, [records, xColumn, yColumn, presetData]);
464
548
  const data = presetData || computed;
465
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
549
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
466
550
  scatterplot.ResponsiveScatterPlot,
467
551
  {
468
552
  data,
@@ -471,8 +555,8 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
471
555
  yScale: { type: "linear", min: "auto", max: "auto" },
472
556
  colors: ["rgba(167, 139, 250, 0.7)"],
473
557
  nodeSize: 6,
474
- axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn, { numeric: true }) },
475
- axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
558
+ axisBottom: makeAxis(style, "x", xColumn, { numeric: true }),
559
+ axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
476
560
  useMesh: true,
477
561
  theme: buildNivoTheme(style)
478
562
  }
@@ -566,7 +650,7 @@ function HeatmapView({
566
650
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
567
651
  }
568
652
  const margin = showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 };
569
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
653
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
570
654
  heatmap.ResponsiveHeatMap,
571
655
  {
572
656
  data,
@@ -616,120 +700,80 @@ function PivotView({ records }) {
616
700
  )
617
701
  ] });
618
702
  }
703
+ function Section({ title, children }) {
704
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-midnight-border font-mono", style: { paddingTop: 12, paddingBottom: 12 }, children: [
705
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "uppercase tracking-wider text-midnight-text-subdued", style: { fontSize: 10, marginBottom: 8 }, children: title }),
706
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", rowGap: 6 }, children })
707
+ ] });
708
+ }
619
709
  function Row({ label, children }) {
620
- const { theme } = useDashboard();
621
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
622
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-xs ${theme.font} text-midnight-text-muted`, children: label }),
623
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center", children })
710
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gridTemplateColumns: "84px minmax(0, 1fr)", alignItems: "center", columnGap: 8, height: 32 }, children: [
711
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted truncate", title: label, children: label }),
712
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "flex-end", width: "100%", minWidth: 0 }, children })
624
713
  ] });
625
714
  }
626
715
  function ColorControl({ value, onChange }) {
627
- const { theme } = useDashboard();
628
716
  return /* @__PURE__ */ jsxRuntime.jsx(
629
717
  "input",
630
718
  {
631
719
  type: "color",
632
720
  value: /^#/.test(value) ? value : "#94a3b8",
633
721
  onChange: (e) => onChange(e.target.value),
634
- className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer`
635
- }
636
- );
637
- }
638
- function NumberControl({ value, onChange, min, max }) {
639
- const { theme } = useDashboard();
640
- return /* @__PURE__ */ jsxRuntime.jsx(
641
- "input",
642
- {
643
- type: "number",
644
- value,
645
- min,
646
- max,
647
- onChange: (e) => onChange(Number(e.target.value)),
648
- className: `w-20 px-2 py-1 text-xs ${theme.font} bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent`
649
- }
650
- );
651
- }
652
- function TextControl({ value, onChange, placeholder }) {
653
- const { theme } = useDashboard();
654
- return /* @__PURE__ */ jsxRuntime.jsx(
655
- "input",
656
- {
657
- type: "text",
658
- value,
659
- placeholder,
660
- onChange: (e) => onChange(e.target.value),
661
- className: `w-28 px-2 py-1 text-xs ${theme.font} bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent`
662
- }
663
- );
664
- }
665
- function Toggle({ value, onChange }) {
666
- return /* @__PURE__ */ jsxRuntime.jsx(
667
- "input",
668
- {
669
- type: "checkbox",
670
- checked: value,
671
- onChange: (e) => onChange(e.target.checked),
672
- className: "w-4 h-4 accent-midnight-accent cursor-pointer"
722
+ className: "w-9 h-6 bg-transparent border border-midnight-border cursor-pointer p-0"
673
723
  }
674
724
  );
675
725
  }
676
726
  function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
677
727
  const on = !!value;
678
728
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
679
- /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: on, onChange: (v) => onChange(v ? fallback : "") }),
680
- on && /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value, onChange })
729
+ on && /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value, onChange }),
730
+ /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: on, onChange: (v) => onChange(v ? fallback : "") })
681
731
  ] });
682
732
  }
683
- function Choice({ value, options, onChange }) {
684
- const { theme } = useDashboard();
685
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-28", children: /* @__PURE__ */ jsxRuntime.jsx(
686
- "select",
687
- {
688
- value,
689
- onChange: (e) => onChange(e.target.value),
690
- className: `w-full px-2 py-1 text-xs ${theme.font} bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent`,
691
- children: options.map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o, children: o }, o))
692
- }
693
- ) });
694
- }
695
- function Group({ children, last }) {
696
- const { theme } = useDashboard();
697
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `space-y-2 pb-3 ${last ? "" : `mb-3 border-b ${theme.border}`}`, children });
698
- }
699
733
  function ChartStyleControls({ style, onChange }) {
700
734
  const s = withStyleDefaults(style);
701
735
  const set = (key, value) => onChange({ ...s, [key]: value });
702
736
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
703
- /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
737
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Canvas", children: [
704
738
  /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Background", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
705
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Text color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
706
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Grid color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
707
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.fontSize, min: 6, max: 24, onChange: (v) => set("fontSize", v) }) })
739
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Text", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
740
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Grid", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
741
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.fontSize, min: 6, max: 24, unit: "px", onChange: (v) => set("fontSize", v) }) })
708
742
  ] }),
709
- /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
710
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X axis title", children: /* @__PURE__ */ jsxRuntime.jsx(TextControl, { value: s.xAxisLabel, placeholder: "(column)", onChange: (v) => set("xAxisLabel", v) }) }),
711
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show X title", children: /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
712
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y axis title", children: /* @__PURE__ */ jsxRuntime.jsx(TextControl, { value: s.yAxisLabel, placeholder: "(column)", onChange: (v) => set("yAxisLabel", v) }) }),
713
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show Y title", children: /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
714
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Title color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
715
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Title bold", children: /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
716
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Title highlight", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) }),
717
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X title pos", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
718
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X title offset", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.xLegendOffset, onChange: (v) => set("xLegendOffset", v) }) }),
719
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y title pos", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
720
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y title offset", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.yLegendOffset, onChange: (v) => set("yLegendOffset", v) }) })
743
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Axis titles", children: [
744
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X title", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalInput, { size: "small", value: s.xAxisLabel, placeholder: "(column)", onChange: (e) => set("xAxisLabel", e.target.value) }) }),
745
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show X", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
746
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y title", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalInput, { size: "small", value: s.yAxisLabel, placeholder: "(column)", onChange: (e) => set("yAxisLabel", e.target.value) }) }),
747
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Show Y", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
748
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
749
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
750
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Highlight", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) })
721
751
  ] }),
722
- /* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
723
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X tick angle", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.xTickRotation, min: -90, max: 90, onChange: (v) => set("xTickRotation", v) }) }),
724
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Truncate ticks", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
752
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Axis placement", children: [
753
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X pos", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSelect, { size: "small", value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
754
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X offset", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.xLegendOffset, min: -80, max: 80, onChange: (v) => set("xLegendOffset", v) }) }),
755
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y pos", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSelect, { size: "small", value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
756
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y offset", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.yLegendOffset, min: -80, max: 80, onChange: (v) => set("yLegendOffset", v) }) })
725
757
  ] }),
726
- /* @__PURE__ */ jsxRuntime.jsxs(Group, { last: true, children: [
727
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Series legend", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }),
728
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Panel title align", children: /* @__PURE__ */ jsxRuntime.jsx(Choice, { value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
729
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Panel title bold", children: /* @__PURE__ */ jsxRuntime.jsx(Toggle, { value: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
730
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Panel title color", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
731
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Panel title bg", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
732
- ] })
758
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Ticks", children: [
759
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Wrap", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.tickWrap, onChange: (v) => set("tickWrap", v) }) }),
760
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Wrap width", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.tickWrapWidth, min: 4, max: 40, onChange: (v) => set("tickWrapWidth", v) }) }),
761
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X angle", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.xTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("xTickRotation", v) }) }),
762
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y angle", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.yTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("yTickRotation", v) }) }),
763
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
764
+ ] }),
765
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Table cells", children: [
766
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Wrap", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.cellWrap, onChange: (v) => set("cellWrap", v) }) }),
767
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Decimals", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.cellPrecision, min: 0, max: 8, onChange: (v) => set("cellPrecision", v) }) }),
768
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSlider, { value: s.cellTruncate, min: 0, max: 80, onChange: (v) => set("cellTruncate", v) }) })
769
+ ] }),
770
+ /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: "Panel title", children: [
771
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Align", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSelect, { size: "small", value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
772
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
773
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Color", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
774
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Background", children: /* @__PURE__ */ jsxRuntime.jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
775
+ ] }),
776
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Series legend", children: /* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Position", children: /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalSelect, { size: "small", value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }) })
733
777
  ] });
734
778
  }
735
779
  var AGGREGATES = [
@@ -1337,7 +1381,7 @@ var GroupedBarChart = ({ records, xFields = [], yFields = [], groupField, data:
1337
1381
  const keys = presetKeys || yFields.map((yf) => `${yf.agg}(${yf.name})`);
1338
1382
  const colors = ["rgba(74,222,128,0.8)", "rgba(96,165,250,0.8)", "rgba(251,146,60,0.8)", "rgba(167,139,250,0.8)", "rgba(248,113,113,0.8)"];
1339
1383
  const legend = legendConfig(style);
1340
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full min-h-[400px]", children: /* @__PURE__ */ jsxRuntime.jsx(
1384
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
1341
1385
  bar.ResponsiveBar,
1342
1386
  {
1343
1387
  data,
@@ -1347,8 +1391,8 @@ var GroupedBarChart = ({ records, xFields = [], yFields = [], groupField, data:
1347
1391
  margin: { top: 20, right: 120, bottom: 60, left: 60 },
1348
1392
  padding: 0.3,
1349
1393
  colors: colors.slice(0, keys.length),
1350
- axisBottom: { tickSize: 5, tickPadding: 5, tickRotation: -35 },
1351
- axisLeft: { tickSize: 5, tickPadding: 5, format: (v) => Number(v).toLocaleString() },
1394
+ axisBottom: makeAxis(style, "x", xFields[0]?.name || ""),
1395
+ axisLeft: makeAxis(style, "y", "", { numeric: true }),
1352
1396
  labelSkipWidth: 12,
1353
1397
  labelSkipHeight: 12,
1354
1398
  labelTextColor: { from: "color", modifiers: [["darker", 3]] },
@@ -1454,6 +1498,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1454
1498
  const [style, setStyle] = react.useState(DEFAULT_CHART_STYLE);
1455
1499
  const [showStyle, setShowStyle] = react.useState(false);
1456
1500
  const [showFields, setShowFields] = react.useState(false);
1501
+ const [fillContainer, setFillContainer] = react.useState(false);
1457
1502
  const [sqlRows, setSqlRows] = react.useState(null);
1458
1503
  const [sqlLoading, setSqlLoading] = react.useState(false);
1459
1504
  const [sqlError, setSqlError] = react.useState(null);
@@ -1568,12 +1613,13 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1568
1613
  }
1569
1614
  }
1570
1615
  config.style = style;
1616
+ config.fill = fillContainer;
1571
1617
  onSave({
1572
1618
  title: title || `${chartType} chart`,
1573
1619
  type: chartType === "grouped-bar" ? "bar" : chartType,
1574
1620
  config,
1575
- width: 6,
1576
- height: 2
1621
+ width: fillContainer ? 12 : 6,
1622
+ height: fillContainer ? 8 : 2
1577
1623
  });
1578
1624
  };
1579
1625
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full", children: [
@@ -1729,6 +1775,10 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1729
1775
  className: "w-full mb-2 px-2 py-1 text-xs font-mono bg-midnight-surface border border-midnight-border text-midnight-text-body outline-none focus:border-midnight-accent transition-colors"
1730
1776
  }
1731
1777
  ),
1778
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1779
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", title: "Span the full dashboard width and height", children: "Maximize size" }),
1780
+ /* @__PURE__ */ jsxRuntime.jsx(terminalUxComponents.TerminalToggle, { size: "small", checked: fillContainer, onChange: setFillContainer })
1781
+ ] }),
1732
1782
  /* @__PURE__ */ jsxRuntime.jsx(
1733
1783
  "button",
1734
1784
  {
@@ -1825,14 +1875,26 @@ function panelSql(type, config) {
1825
1875
  if (SQL_CHART_TYPES.has(type)) return buildChartSQL(panelToChartConfig(type, config)) || "";
1826
1876
  return "";
1827
1877
  }
1828
- function DataTable({ rows, columns }) {
1878
+ function formatCell(value, s) {
1879
+ if (value == null) return "";
1880
+ if (typeof value === "object") return JSON.stringify(value);
1881
+ let str;
1882
+ const n = typeof value === "number" ? value : Number(value);
1883
+ if (value !== "" && typeof value !== "boolean" && Number.isFinite(n)) {
1884
+ str = Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { minimumFractionDigits: s.cellPrecision, maximumFractionDigits: s.cellPrecision });
1885
+ } else {
1886
+ str = String(value);
1887
+ }
1888
+ if (s.cellTruncate > 0 && str.length > s.cellTruncate) str = `${str.slice(0, s.cellTruncate)}\u2026`;
1889
+ return str;
1890
+ }
1891
+ function DataTable({ rows, columns, style }) {
1892
+ const s = withStyleDefaults(style);
1829
1893
  const colNames = columns?.length ? columns : rows[0] ? Object.keys(rows[0]) : [];
1894
+ const cellCls = s.cellWrap ? "px-2 py-1 text-midnight-text-body align-top whitespace-normal break-words max-w-[280px]" : "px-2 py-1 text-midnight-text-body truncate max-w-[200px]";
1830
1895
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full", children: [
1831
1896
  /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "sticky top-0 bg-midnight-elevated", children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: colNames.map((n) => /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-2 py-1 text-left text-midnight-text-muted font-mono border-b border-midnight-border", children: n }, n)) }) }),
1832
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => {
1833
- const cellValue = r[n];
1834
- return /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-2 py-1 text-midnight-text-body truncate max-w-[200px]", children: cellValue == null ? "" : typeof cellValue === "object" ? JSON.stringify(cellValue) : String(cellValue) }, n);
1835
- }) }, i)) })
1897
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: cellCls, children: formatCell(r[n], s) }, n)) }, i)) })
1836
1898
  ] }) });
1837
1899
  }
1838
1900
  function SqlPanel({ panel }) {
@@ -1927,7 +1989,7 @@ function SqlPanel({ panel }) {
1927
1989
  const v = first ? Object.values(first)[0] : 0;
1928
1990
  chart = /* @__PURE__ */ jsxRuntime.jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
1929
1991
  } else if (chartType === "table") {
1930
- chart = /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows, columns: config.columns });
1992
+ chart = /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows, columns: config.columns, style: config.style });
1931
1993
  } else {
1932
1994
  const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
1933
1995
  switch (chartType) {
@@ -1991,7 +2053,7 @@ function PanelContent({ panel, records, columns }) {
1991
2053
  case "insight":
1992
2054
  return /* @__PURE__ */ jsxRuntime.jsx(InsightView, { config: { text: config.text } });
1993
2055
  case "table":
1994
- return /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name) });
2056
+ return /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name), style: config.style });
1995
2057
  default:
1996
2058
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
1997
2059
  "Unknown panel type: ",
@@ -1999,57 +2061,121 @@ function PanelContent({ panel, records, columns }) {
1999
2061
  ] });
2000
2062
  }
2001
2063
  }
2002
- var ROW_HEIGHT = 180;
2064
+ var GRID_COLS = 12;
2065
+ var ROW_HEIGHT = 80;
2066
+ var GRID_MARGIN = 10;
2067
+ var DRAG_HANDLE = "panel-drag-handle";
2068
+ var RGL_CSS = `
2069
+ .react-grid-layout { position: relative; transition: height 200ms ease; }
2070
+ .react-grid-item { transition: all 200ms ease; transition-property: left, top, width, height; box-sizing: border-box; }
2071
+ .react-grid-item.cssTransforms { transition-property: transform, width, height; }
2072
+ .react-grid-item.resizing { transition: none; z-index: 3; will-change: width, height; }
2073
+ .react-grid-item.react-draggable-dragging { transition: none; z-index: 3; will-change: transform; }
2074
+ .react-grid-item.react-grid-placeholder { background: rgba(99,102,241,0.18); border: 1px dashed #6366f1; border-radius: 2px; transition-duration: 100ms; z-index: 2; user-select: none; }
2075
+ .react-grid-item > .react-resizable-handle { position: absolute; width: 18px; height: 18px; bottom: 0; right: 0; cursor: se-resize; }
2076
+ .react-grid-item > .react-resizable-handle::after { content: ''; position: absolute; right: 4px; bottom: 4px; width: 6px; height: 6px; border-right: 2px solid rgba(148,163,184,0.7); border-bottom: 2px solid rgba(148,163,184,0.7); }
2077
+ .${DRAG_HANDLE} { cursor: grab; }
2078
+ .react-grid-item.react-draggable-dragging .${DRAG_HANDLE} { cursor: grabbing; }
2079
+ `;
2080
+ var rglCssInjected = false;
2081
+ function useInjectRglCss() {
2082
+ react.useEffect(() => {
2083
+ if (rglCssInjected || typeof document === "undefined") return;
2084
+ const el = document.createElement("style");
2085
+ el.setAttribute("data-rgl", "dashboard-renderer");
2086
+ el.textContent = RGL_CSS;
2087
+ document.head.appendChild(el);
2088
+ rglCssInjected = true;
2089
+ }, []);
2090
+ }
2091
+ var GridLayoutWithWidth = GridLayout.WidthProvider(GridLayout__default.default);
2092
+ function buildLayout(panels) {
2093
+ let cx = 0, cy = 0, rowH = 0;
2094
+ return panels.map((p) => {
2095
+ const fill = !!p.config?.fill;
2096
+ const w = fill ? GRID_COLS : Math.min(Math.max(p.width || 6, 1), GRID_COLS);
2097
+ const h = fill ? Math.max(p.height || 8, 6) : Math.max(p.height || 4, 1);
2098
+ if (typeof p.x === "number" && typeof p.y === "number") {
2099
+ return { i: p.id, x: fill ? 0 : p.x, y: p.y, w, h, minW: 2, minH: 2 };
2100
+ }
2101
+ if (cx + w > GRID_COLS) {
2102
+ cx = 0;
2103
+ cy += rowH;
2104
+ rowH = 0;
2105
+ }
2106
+ const item = { i: p.id, x: cx, y: cy, w, h, minW: 2, minH: 2 };
2107
+ cx += w;
2108
+ rowH = Math.max(rowH, h);
2109
+ return item;
2110
+ });
2111
+ }
2003
2112
  function DashboardRenderer({ dashboard, records, columns }) {
2004
- const { theme, removePanel } = useDashboard();
2005
- const { canEditPanels } = useCapabilities();
2113
+ const { theme, removePanel, persistLayout } = useDashboard();
2114
+ const { canEditPanels, canEditLayout } = useCapabilities();
2115
+ useInjectRglCss();
2116
+ const panels = dashboard?.panels ?? [];
2117
+ const layout = react.useMemo(() => buildLayout(panels), [panels]);
2118
+ const onLayoutChange = react.useCallback((next) => {
2119
+ if (!persistLayout) return;
2120
+ persistLayout(next.map((l) => ({ id: l.i, x: l.x, y: l.y, w: l.w, h: l.h })));
2121
+ }, [persistLayout]);
2006
2122
  if (!dashboard) return null;
2007
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
2008
- dashboard.insights && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
2009
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
2010
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
2011
- ] }),
2012
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-12 gap-3", children: dashboard.panels?.map((panel) => {
2013
- const colSpan = Math.min(Math.max(panel.width || 6, 1), 12);
2014
- const rowSpan = Math.min(Math.max(panel.height || 2, 1), 4);
2015
- return /* @__PURE__ */ jsxRuntime.jsxs(
2016
- "div",
2123
+ const panelFrame = (panel, draggable) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `border ${theme.border} bg-midnight-surface flex flex-col overflow-hidden h-full`, children: [
2124
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated ${draggable ? DRAG_HANDLE : ""}`, children: [
2125
+ /* @__PURE__ */ jsxRuntime.jsx(
2126
+ "span",
2017
2127
  {
2018
- className: `border ${theme.border} bg-midnight-surface flex flex-col`,
2128
+ className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
2019
2129
  style: {
2020
- gridColumn: `span ${colSpan}`,
2021
- minHeight: `${rowSpan * ROW_HEIGHT}px`
2130
+ textAlign: panel.config?.style?.titleAlign || "left",
2131
+ fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
2132
+ background: panel.config?.style?.titleBackground || void 0,
2133
+ color: panel.config?.style?.titleColor || void 0
2022
2134
  },
2023
- children: [
2024
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated`, children: [
2025
- /* @__PURE__ */ jsxRuntime.jsx(
2026
- "span",
2027
- {
2028
- className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
2029
- style: {
2030
- textAlign: panel.config?.style?.titleAlign || "left",
2031
- fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
2032
- background: panel.config?.style?.titleBackground || void 0,
2033
- color: panel.config?.style?.titleColor || void 0
2034
- },
2035
- children: panel.title || panel.type
2036
- }
2037
- ),
2038
- canEditPanels && removePanel && /* @__PURE__ */ jsxRuntime.jsx(
2039
- "button",
2040
- {
2041
- onClick: () => removePanel(panel.id),
2042
- className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
2043
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
2044
- }
2045
- )
2046
- ] }),
2047
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ViewLoading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(PanelContent, { panel, records, columns }) }) })
2048
- ]
2049
- },
2050
- panel.id
2051
- );
2052
- }) })
2135
+ children: panel.title || panel.type
2136
+ }
2137
+ ),
2138
+ canEditPanels && removePanel && /* @__PURE__ */ jsxRuntime.jsx(
2139
+ "button",
2140
+ {
2141
+ onMouseDown: (e) => e.stopPropagation(),
2142
+ onClick: () => removePanel(panel.id),
2143
+ className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
2144
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "w-3 h-3" })
2145
+ }
2146
+ )
2147
+ ] }),
2148
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: /* @__PURE__ */ jsxRuntime.jsx(ViewLoading, {}), children: /* @__PURE__ */ jsxRuntime.jsx(PanelContent, { panel, records, columns }) }) })
2149
+ ] }, panel.id);
2150
+ const insightsBanner = dashboard.insights ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
2151
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
2152
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
2153
+ ] }) : null;
2154
+ if (panels.length === 1) {
2155
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-2 h-full min-h-[60vh]", children: [
2156
+ insightsBanner,
2157
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0", children: panelFrame(panels[0], false) })
2158
+ ] });
2159
+ }
2160
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
2161
+ insightsBanner,
2162
+ /* @__PURE__ */ jsxRuntime.jsx(
2163
+ GridLayoutWithWidth,
2164
+ {
2165
+ className: "layout",
2166
+ layout,
2167
+ cols: GRID_COLS,
2168
+ rowHeight: ROW_HEIGHT,
2169
+ margin: [GRID_MARGIN, GRID_MARGIN],
2170
+ isDraggable: canEditLayout,
2171
+ isResizable: canEditLayout,
2172
+ draggableHandle: `.${DRAG_HANDLE}`,
2173
+ onDragStop: onLayoutChange,
2174
+ onResizeStop: onLayoutChange,
2175
+ compactType: canEditLayout ? "vertical" : null,
2176
+ children: panels.map((panel) => panelFrame(panel, canEditLayout))
2177
+ }
2178
+ )
2053
2179
  ] });
2054
2180
  }
2055
2181
  function ProfileSummary({ profile, theme }) {