@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 +286 -160
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -9
- package/dist/index.d.ts +25 -9
- package/dist/index.js +287 -162
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, useState, useEffect, useRef,
|
|
1
|
+
import { createContext, useContext, useMemo, useState, useEffect, useRef, useCallback, Suspense } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { ResponsiveBar } from '@nivo/bar';
|
|
4
4
|
import { ResponsivePie } from '@nivo/pie';
|
|
@@ -7,10 +7,12 @@ import { ResponsiveScatterPlot } from '@nivo/scatterplot';
|
|
|
7
7
|
import { ResponsiveHeatMap } from '@nivo/heatmap';
|
|
8
8
|
import PivotTableUI from 'react-pivottable/PivotTableUI';
|
|
9
9
|
import 'react-pivottable/pivottable.css';
|
|
10
|
-
import {
|
|
10
|
+
import { TerminalSlider, TerminalInput, TerminalToggle, TerminalSelect } from '@quantumwake/terminal-ux-components';
|
|
11
|
+
import { Loader2, Play, ChevronDown, AlertCircle, Rows3, BarChart3, PieChart, TrendingUp, ScatterChart, LayoutGrid, Code, Sparkles, Terminal, RefreshCw, Minimize2, Maximize2, Plus, Save, FolderOpen, Trash2, X, Filter, Database, Send, GripVertical } from 'lucide-react';
|
|
11
12
|
import CodeMirror, { EditorView, Prec, keymap } from '@uiw/react-codemirror';
|
|
12
13
|
import { sql, PostgreSQL, schemaCompletionSource, keywordCompletionSource } from '@codemirror/lang-sql';
|
|
13
14
|
import { snippetCompletion, completeFromList, autocompletion, acceptCompletion } from '@codemirror/autocomplete';
|
|
15
|
+
import GridLayout, { WidthProvider } from 'react-grid-layout';
|
|
14
16
|
|
|
15
17
|
// src/context/DashboardContext.tsx
|
|
16
18
|
var DashboardContext = createContext(null);
|
|
@@ -39,7 +41,9 @@ function useCapabilities() {
|
|
|
39
41
|
canAddPanel: !!c.addPanel,
|
|
40
42
|
canEditPanels: !!c.removePanel,
|
|
41
43
|
// Studio can recompute + persist a panel's precomputed result.
|
|
42
|
-
canRefresh: !!c.persistPanelData
|
|
44
|
+
canRefresh: !!c.persistPanelData,
|
|
45
|
+
// Studio can drag/resize panels and persist the grid layout.
|
|
46
|
+
canEditLayout: !!c.persistLayout
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -224,8 +228,15 @@ var DEFAULT_CHART_STYLE = {
|
|
|
224
228
|
legendBold: false,
|
|
225
229
|
legendHighlight: "",
|
|
226
230
|
// Tick overflow handling.
|
|
227
|
-
xTickRotation:
|
|
231
|
+
xTickRotation: 0,
|
|
232
|
+
yTickRotation: 0,
|
|
228
233
|
tickTruncate: 0,
|
|
234
|
+
tickWrap: true,
|
|
235
|
+
tickWrapWidth: 14,
|
|
236
|
+
// Table cells.
|
|
237
|
+
cellWrap: true,
|
|
238
|
+
cellPrecision: 2,
|
|
239
|
+
cellTruncate: 0,
|
|
229
240
|
// Series legend placement (charts that have one: pie, grouped bar).
|
|
230
241
|
legendAnchor: "right",
|
|
231
242
|
// Panel title (rendered by DashboardRenderer's panel header).
|
|
@@ -300,7 +311,7 @@ var axisLegend = (style, axis, columnName, opts2 = {}) => {
|
|
|
300
311
|
out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
|
|
301
312
|
out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
|
|
302
313
|
}
|
|
303
|
-
|
|
314
|
+
out.tickRotation = isX ? s.xTickRotation : s.yTickRotation;
|
|
304
315
|
if (opts2.numeric) out.format = (v) => Number(v).toLocaleString();
|
|
305
316
|
else if (s.tickTruncate > 0) out.format = (v) => truncate(v, s.tickTruncate);
|
|
306
317
|
return out;
|
|
@@ -350,6 +361,78 @@ function MetricView({ records, config, value: presetValue }) {
|
|
|
350
361
|
function InsightView({ config }) {
|
|
351
362
|
return /* @__PURE__ */ jsx("div", { className: "h-full p-4 overflow-auto", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-midnight-text-body leading-relaxed whitespace-pre-wrap", children: config.text }) });
|
|
352
363
|
}
|
|
364
|
+
function formatVal(value, s, numeric) {
|
|
365
|
+
let str = numeric ? Number(value).toLocaleString() : String(value);
|
|
366
|
+
if (s.tickTruncate > 0 && str.length > s.tickTruncate) str = `${str.slice(0, s.tickTruncate)}\u2026`;
|
|
367
|
+
return str;
|
|
368
|
+
}
|
|
369
|
+
function wrapText(str, width) {
|
|
370
|
+
if (width <= 0 || str.length <= width) return [str];
|
|
371
|
+
const words = str.split(/\s+/);
|
|
372
|
+
const lines = [];
|
|
373
|
+
let cur = "";
|
|
374
|
+
const push = (t) => {
|
|
375
|
+
if (t) lines.push(t);
|
|
376
|
+
};
|
|
377
|
+
for (const w of words) {
|
|
378
|
+
if (w.length > width) {
|
|
379
|
+
push(cur);
|
|
380
|
+
cur = "";
|
|
381
|
+
for (let i = 0; i < w.length; i += width) lines.push(w.slice(i, i + width));
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (!cur) cur = w;
|
|
385
|
+
else if (cur.length + 1 + w.length <= width) cur += ` ${w}`;
|
|
386
|
+
else {
|
|
387
|
+
push(cur);
|
|
388
|
+
cur = w;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
push(cur);
|
|
392
|
+
if (lines.length > 4) {
|
|
393
|
+
const kept = lines.slice(0, 4);
|
|
394
|
+
kept[3] = `${kept[3].slice(0, Math.max(0, width - 1))}\u2026`;
|
|
395
|
+
return kept;
|
|
396
|
+
}
|
|
397
|
+
return lines;
|
|
398
|
+
}
|
|
399
|
+
function makeAxis(style, axis, columnName, opts2 = {}) {
|
|
400
|
+
const s = withStyleDefaults(style);
|
|
401
|
+
const isX = axis === "x";
|
|
402
|
+
const numeric = !!opts2.numeric;
|
|
403
|
+
const show = isX ? s.showXLegend : s.showYLegend;
|
|
404
|
+
const label = (isX ? s.xAxisLabel : s.yAxisLabel) || columnName;
|
|
405
|
+
const rotate = isX ? s.xTickRotation : s.yTickRotation;
|
|
406
|
+
const out = { tickSize: 5, tickPadding: 5 };
|
|
407
|
+
if (show && label) {
|
|
408
|
+
out.legend = label;
|
|
409
|
+
out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
|
|
410
|
+
out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
|
|
411
|
+
}
|
|
412
|
+
if (s.tickWrap) {
|
|
413
|
+
out.renderTick = (tick) => {
|
|
414
|
+
const lines = wrapText(formatVal(tick.value, s, numeric), s.tickWrapWidth);
|
|
415
|
+
const firstDy = isX ? "0" : `${-((lines.length - 1) * 0.55)}em`;
|
|
416
|
+
return /* @__PURE__ */ jsxs("g", { transform: `translate(${tick.x},${tick.y})`, children: [
|
|
417
|
+
/* @__PURE__ */ jsx("line", { x2: isX ? 0 : -5, y2: isX ? 5 : 0, style: { stroke: s.textColor, strokeWidth: 1, opacity: 0.3 } }),
|
|
418
|
+
/* @__PURE__ */ jsx(
|
|
419
|
+
"text",
|
|
420
|
+
{
|
|
421
|
+
transform: `translate(${tick.textX},${tick.textY}) rotate(${rotate})`,
|
|
422
|
+
textAnchor: tick.textAnchor,
|
|
423
|
+
dominantBaseline: tick.textBaseline,
|
|
424
|
+
style: { fill: s.textColor, fontSize: s.fontSize },
|
|
425
|
+
children: lines.map((ln, i) => /* @__PURE__ */ jsx("tspan", { x: 0, dy: i === 0 ? firstDy : "1.1em", children: ln }, i))
|
|
426
|
+
}
|
|
427
|
+
)
|
|
428
|
+
] });
|
|
429
|
+
};
|
|
430
|
+
} else {
|
|
431
|
+
out.tickRotation = rotate;
|
|
432
|
+
out.format = (v) => formatVal(v, s, numeric);
|
|
433
|
+
}
|
|
434
|
+
return out;
|
|
435
|
+
}
|
|
353
436
|
function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: presetData, style }) {
|
|
354
437
|
const computed = useMemo(() => {
|
|
355
438
|
if (presetData || !records) return [];
|
|
@@ -357,7 +440,7 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
|
|
|
357
440
|
return Object.entries(groups).map(([key, recs]) => ({ group: key, value: aggregate(recs, valueColumn, aggFn) })).sort((a, b) => b.value - a.value).slice(0, 50);
|
|
358
441
|
}, [records, groupColumn, valueColumn, aggFn, presetData]);
|
|
359
442
|
const data = presetData || computed;
|
|
360
|
-
return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[
|
|
443
|
+
return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
|
|
361
444
|
ResponsiveBar,
|
|
362
445
|
{
|
|
363
446
|
data,
|
|
@@ -367,8 +450,8 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
|
|
|
367
450
|
padding: 0.3,
|
|
368
451
|
colors: ["rgba(74, 222, 128, 0.8)"],
|
|
369
452
|
borderColor: { from: "color", modifiers: [["darker", 1.6]] },
|
|
370
|
-
axisBottom:
|
|
371
|
-
axisLeft:
|
|
453
|
+
axisBottom: makeAxis(style, "x", groupColumn),
|
|
454
|
+
axisLeft: makeAxis(style, "y", valueColumn, { numeric: true }),
|
|
372
455
|
labelSkipWidth: 12,
|
|
373
456
|
labelSkipHeight: 12,
|
|
374
457
|
labelTextColor: { from: "color", modifiers: [["darker", 3]] },
|
|
@@ -389,7 +472,7 @@ function PieView({ records, groupColumn, data: presetData, style }) {
|
|
|
389
472
|
}, [records, groupColumn, presetData]);
|
|
390
473
|
const data = presetData || computed;
|
|
391
474
|
const legend = legendConfig(style);
|
|
392
|
-
return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[
|
|
475
|
+
return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
|
|
393
476
|
ResponsivePie,
|
|
394
477
|
{
|
|
395
478
|
data,
|
|
@@ -425,7 +508,7 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
425
508
|
}];
|
|
426
509
|
}, [records, xColumn, yColumn, presetData]);
|
|
427
510
|
const data = presetData || computed;
|
|
428
|
-
return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[
|
|
511
|
+
return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
|
|
429
512
|
ResponsiveLine,
|
|
430
513
|
{
|
|
431
514
|
data,
|
|
@@ -441,8 +524,8 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
441
524
|
pointBorderWidth: 2,
|
|
442
525
|
pointBorderColor: { from: "serieColor" },
|
|
443
526
|
enableGridX: false,
|
|
444
|
-
axisBottom:
|
|
445
|
-
axisLeft:
|
|
527
|
+
axisBottom: makeAxis(style, "x", xColumn),
|
|
528
|
+
axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
|
|
446
529
|
useMesh: true,
|
|
447
530
|
theme: buildNivoTheme(style)
|
|
448
531
|
}
|
|
@@ -455,7 +538,7 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
455
538
|
return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
|
|
456
539
|
}, [records, xColumn, yColumn, presetData]);
|
|
457
540
|
const data = presetData || computed;
|
|
458
|
-
return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[
|
|
541
|
+
return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
|
|
459
542
|
ResponsiveScatterPlot,
|
|
460
543
|
{
|
|
461
544
|
data,
|
|
@@ -464,8 +547,8 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
464
547
|
yScale: { type: "linear", min: "auto", max: "auto" },
|
|
465
548
|
colors: ["rgba(167, 139, 250, 0.7)"],
|
|
466
549
|
nodeSize: 6,
|
|
467
|
-
axisBottom:
|
|
468
|
-
axisLeft:
|
|
550
|
+
axisBottom: makeAxis(style, "x", xColumn, { numeric: true }),
|
|
551
|
+
axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
|
|
469
552
|
useMesh: true,
|
|
470
553
|
theme: buildNivoTheme(style)
|
|
471
554
|
}
|
|
@@ -559,7 +642,7 @@ function HeatmapView({
|
|
|
559
642
|
return /* @__PURE__ */ jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
|
|
560
643
|
}
|
|
561
644
|
const margin = showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 };
|
|
562
|
-
return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[
|
|
645
|
+
return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
|
|
563
646
|
ResponsiveHeatMap,
|
|
564
647
|
{
|
|
565
648
|
data,
|
|
@@ -609,120 +692,80 @@ function PivotView({ records }) {
|
|
|
609
692
|
)
|
|
610
693
|
] });
|
|
611
694
|
}
|
|
695
|
+
function Section({ title, children }) {
|
|
696
|
+
return /* @__PURE__ */ jsxs("div", { className: "border-t border-midnight-border font-mono", style: { paddingTop: 12, paddingBottom: 12 }, children: [
|
|
697
|
+
/* @__PURE__ */ jsx("div", { className: "uppercase tracking-wider text-midnight-text-subdued", style: { fontSize: 10, marginBottom: 8 }, children: title }),
|
|
698
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", rowGap: 6 }, children })
|
|
699
|
+
] });
|
|
700
|
+
}
|
|
612
701
|
function Row({ label, children }) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
/* @__PURE__ */ jsx("
|
|
616
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center", children })
|
|
702
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gridTemplateColumns: "84px minmax(0, 1fr)", alignItems: "center", columnGap: 8, height: 32 }, children: [
|
|
703
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted truncate", title: label, children: label }),
|
|
704
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", justifyContent: "flex-end", width: "100%", minWidth: 0 }, children })
|
|
617
705
|
] });
|
|
618
706
|
}
|
|
619
707
|
function ColorControl({ value, onChange }) {
|
|
620
|
-
const { theme } = useDashboard();
|
|
621
708
|
return /* @__PURE__ */ jsx(
|
|
622
709
|
"input",
|
|
623
710
|
{
|
|
624
711
|
type: "color",
|
|
625
712
|
value: /^#/.test(value) ? value : "#94a3b8",
|
|
626
713
|
onChange: (e) => onChange(e.target.value),
|
|
627
|
-
className:
|
|
628
|
-
}
|
|
629
|
-
);
|
|
630
|
-
}
|
|
631
|
-
function NumberControl({ value, onChange, min, max }) {
|
|
632
|
-
const { theme } = useDashboard();
|
|
633
|
-
return /* @__PURE__ */ jsx(
|
|
634
|
-
"input",
|
|
635
|
-
{
|
|
636
|
-
type: "number",
|
|
637
|
-
value,
|
|
638
|
-
min,
|
|
639
|
-
max,
|
|
640
|
-
onChange: (e) => onChange(Number(e.target.value)),
|
|
641
|
-
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`
|
|
642
|
-
}
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
function TextControl({ value, onChange, placeholder }) {
|
|
646
|
-
const { theme } = useDashboard();
|
|
647
|
-
return /* @__PURE__ */ jsx(
|
|
648
|
-
"input",
|
|
649
|
-
{
|
|
650
|
-
type: "text",
|
|
651
|
-
value,
|
|
652
|
-
placeholder,
|
|
653
|
-
onChange: (e) => onChange(e.target.value),
|
|
654
|
-
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`
|
|
655
|
-
}
|
|
656
|
-
);
|
|
657
|
-
}
|
|
658
|
-
function Toggle({ value, onChange }) {
|
|
659
|
-
return /* @__PURE__ */ jsx(
|
|
660
|
-
"input",
|
|
661
|
-
{
|
|
662
|
-
type: "checkbox",
|
|
663
|
-
checked: value,
|
|
664
|
-
onChange: (e) => onChange(e.target.checked),
|
|
665
|
-
className: "w-4 h-4 accent-midnight-accent cursor-pointer"
|
|
714
|
+
className: "w-9 h-6 bg-transparent border border-midnight-border cursor-pointer p-0"
|
|
666
715
|
}
|
|
667
716
|
);
|
|
668
717
|
}
|
|
669
718
|
function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
|
|
670
719
|
const on = !!value;
|
|
671
720
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
672
|
-
/* @__PURE__ */ jsx(
|
|
673
|
-
|
|
721
|
+
on && /* @__PURE__ */ jsx(ColorControl, { value, onChange }),
|
|
722
|
+
/* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: on, onChange: (v) => onChange(v ? fallback : "") })
|
|
674
723
|
] });
|
|
675
724
|
}
|
|
676
|
-
function Choice({ value, options, onChange }) {
|
|
677
|
-
const { theme } = useDashboard();
|
|
678
|
-
return /* @__PURE__ */ jsx("div", { className: "w-28", children: /* @__PURE__ */ jsx(
|
|
679
|
-
"select",
|
|
680
|
-
{
|
|
681
|
-
value,
|
|
682
|
-
onChange: (e) => onChange(e.target.value),
|
|
683
|
-
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`,
|
|
684
|
-
children: options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o))
|
|
685
|
-
}
|
|
686
|
-
) });
|
|
687
|
-
}
|
|
688
|
-
function Group({ children, last }) {
|
|
689
|
-
const { theme } = useDashboard();
|
|
690
|
-
return /* @__PURE__ */ jsx("div", { className: `space-y-2 pb-3 ${last ? "" : `mb-3 border-b ${theme.border}`}`, children });
|
|
691
|
-
}
|
|
692
725
|
function ChartStyleControls({ style, onChange }) {
|
|
693
726
|
const s = withStyleDefaults(style);
|
|
694
727
|
const set = (key, value) => onChange({ ...s, [key]: value });
|
|
695
728
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
696
|
-
/* @__PURE__ */ jsxs(
|
|
729
|
+
/* @__PURE__ */ jsxs(Section, { title: "Canvas", children: [
|
|
697
730
|
/* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
|
|
698
|
-
/* @__PURE__ */ jsx(Row, { label: "Text
|
|
699
|
-
/* @__PURE__ */ jsx(Row, { label: "Grid
|
|
700
|
-
/* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(
|
|
731
|
+
/* @__PURE__ */ jsx(Row, { label: "Text", children: /* @__PURE__ */ jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
|
|
732
|
+
/* @__PURE__ */ jsx(Row, { label: "Grid", children: /* @__PURE__ */ jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
|
|
733
|
+
/* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.fontSize, min: 6, max: 24, unit: "px", onChange: (v) => set("fontSize", v) }) })
|
|
701
734
|
] }),
|
|
702
|
-
/* @__PURE__ */ jsxs(
|
|
703
|
-
/* @__PURE__ */ jsx(Row, { label: "X
|
|
704
|
-
/* @__PURE__ */ jsx(Row, { label: "Show X
|
|
705
|
-
/* @__PURE__ */ jsx(Row, { label: "Y
|
|
706
|
-
/* @__PURE__ */ jsx(Row, { label: "Show Y
|
|
707
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
708
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
709
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
710
|
-
/* @__PURE__ */ jsx(Row, { label: "X title pos", children: /* @__PURE__ */ jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
|
|
711
|
-
/* @__PURE__ */ jsx(Row, { label: "X title offset", children: /* @__PURE__ */ jsx(NumberControl, { value: s.xLegendOffset, onChange: (v) => set("xLegendOffset", v) }) }),
|
|
712
|
-
/* @__PURE__ */ jsx(Row, { label: "Y title pos", children: /* @__PURE__ */ jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
|
|
713
|
-
/* @__PURE__ */ jsx(Row, { label: "Y title offset", children: /* @__PURE__ */ jsx(NumberControl, { value: s.yLegendOffset, onChange: (v) => set("yLegendOffset", v) }) })
|
|
735
|
+
/* @__PURE__ */ jsxs(Section, { title: "Axis titles", children: [
|
|
736
|
+
/* @__PURE__ */ jsx(Row, { label: "X title", children: /* @__PURE__ */ jsx(TerminalInput, { size: "small", value: s.xAxisLabel, placeholder: "(column)", onChange: (e) => set("xAxisLabel", e.target.value) }) }),
|
|
737
|
+
/* @__PURE__ */ jsx(Row, { label: "Show X", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
|
|
738
|
+
/* @__PURE__ */ jsx(Row, { label: "Y title", children: /* @__PURE__ */ jsx(TerminalInput, { size: "small", value: s.yAxisLabel, placeholder: "(column)", onChange: (e) => set("yAxisLabel", e.target.value) }) }),
|
|
739
|
+
/* @__PURE__ */ jsx(Row, { label: "Show Y", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
|
|
740
|
+
/* @__PURE__ */ jsx(Row, { label: "Color", children: /* @__PURE__ */ jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
|
|
741
|
+
/* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
|
|
742
|
+
/* @__PURE__ */ jsx(Row, { label: "Highlight", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) })
|
|
714
743
|
] }),
|
|
715
|
-
/* @__PURE__ */ jsxs(
|
|
716
|
-
/* @__PURE__ */ jsx(Row, { label: "X
|
|
717
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
744
|
+
/* @__PURE__ */ jsxs(Section, { title: "Axis placement", children: [
|
|
745
|
+
/* @__PURE__ */ jsx(Row, { label: "X pos", children: /* @__PURE__ */ jsx(TerminalSelect, { size: "small", value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
|
|
746
|
+
/* @__PURE__ */ jsx(Row, { label: "X offset", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.xLegendOffset, min: -80, max: 80, onChange: (v) => set("xLegendOffset", v) }) }),
|
|
747
|
+
/* @__PURE__ */ jsx(Row, { label: "Y pos", children: /* @__PURE__ */ jsx(TerminalSelect, { size: "small", value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
|
|
748
|
+
/* @__PURE__ */ jsx(Row, { label: "Y offset", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.yLegendOffset, min: -80, max: 80, onChange: (v) => set("yLegendOffset", v) }) })
|
|
718
749
|
] }),
|
|
719
|
-
/* @__PURE__ */ jsxs(
|
|
720
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
721
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
722
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
723
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
724
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
725
|
-
] })
|
|
750
|
+
/* @__PURE__ */ jsxs(Section, { title: "Ticks", children: [
|
|
751
|
+
/* @__PURE__ */ jsx(Row, { label: "Wrap", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.tickWrap, onChange: (v) => set("tickWrap", v) }) }),
|
|
752
|
+
/* @__PURE__ */ jsx(Row, { label: "Wrap width", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.tickWrapWidth, min: 4, max: 40, onChange: (v) => set("tickWrapWidth", v) }) }),
|
|
753
|
+
/* @__PURE__ */ jsx(Row, { label: "X angle", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.xTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("xTickRotation", v) }) }),
|
|
754
|
+
/* @__PURE__ */ jsx(Row, { label: "Y angle", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.yTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("yTickRotation", v) }) }),
|
|
755
|
+
/* @__PURE__ */ jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
|
|
756
|
+
] }),
|
|
757
|
+
/* @__PURE__ */ jsxs(Section, { title: "Table cells", children: [
|
|
758
|
+
/* @__PURE__ */ jsx(Row, { label: "Wrap", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.cellWrap, onChange: (v) => set("cellWrap", v) }) }),
|
|
759
|
+
/* @__PURE__ */ jsx(Row, { label: "Decimals", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.cellPrecision, min: 0, max: 8, onChange: (v) => set("cellPrecision", v) }) }),
|
|
760
|
+
/* @__PURE__ */ jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsx(TerminalSlider, { value: s.cellTruncate, min: 0, max: 80, onChange: (v) => set("cellTruncate", v) }) })
|
|
761
|
+
] }),
|
|
762
|
+
/* @__PURE__ */ jsxs(Section, { title: "Panel title", children: [
|
|
763
|
+
/* @__PURE__ */ jsx(Row, { label: "Align", children: /* @__PURE__ */ jsx(TerminalSelect, { size: "small", value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
|
|
764
|
+
/* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
|
|
765
|
+
/* @__PURE__ */ jsx(Row, { label: "Color", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
|
|
766
|
+
/* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
|
|
767
|
+
] }),
|
|
768
|
+
/* @__PURE__ */ jsx(Section, { title: "Series legend", children: /* @__PURE__ */ jsx(Row, { label: "Position", children: /* @__PURE__ */ jsx(TerminalSelect, { size: "small", value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }) })
|
|
726
769
|
] });
|
|
727
770
|
}
|
|
728
771
|
var AGGREGATES = [
|
|
@@ -1330,7 +1373,7 @@ var GroupedBarChart = ({ records, xFields = [], yFields = [], groupField, data:
|
|
|
1330
1373
|
const keys = presetKeys || yFields.map((yf) => `${yf.agg}(${yf.name})`);
|
|
1331
1374
|
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)"];
|
|
1332
1375
|
const legend = legendConfig(style);
|
|
1333
|
-
return /* @__PURE__ */ jsx("div", { className: "h-full min-h-[
|
|
1376
|
+
return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
|
|
1334
1377
|
ResponsiveBar,
|
|
1335
1378
|
{
|
|
1336
1379
|
data,
|
|
@@ -1340,8 +1383,8 @@ var GroupedBarChart = ({ records, xFields = [], yFields = [], groupField, data:
|
|
|
1340
1383
|
margin: { top: 20, right: 120, bottom: 60, left: 60 },
|
|
1341
1384
|
padding: 0.3,
|
|
1342
1385
|
colors: colors.slice(0, keys.length),
|
|
1343
|
-
axisBottom:
|
|
1344
|
-
axisLeft:
|
|
1386
|
+
axisBottom: makeAxis(style, "x", xFields[0]?.name || ""),
|
|
1387
|
+
axisLeft: makeAxis(style, "y", "", { numeric: true }),
|
|
1345
1388
|
labelSkipWidth: 12,
|
|
1346
1389
|
labelSkipHeight: 12,
|
|
1347
1390
|
labelTextColor: { from: "color", modifiers: [["darker", 3]] },
|
|
@@ -1447,6 +1490,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1447
1490
|
const [style, setStyle] = useState(DEFAULT_CHART_STYLE);
|
|
1448
1491
|
const [showStyle, setShowStyle] = useState(false);
|
|
1449
1492
|
const [showFields, setShowFields] = useState(false);
|
|
1493
|
+
const [fillContainer, setFillContainer] = useState(false);
|
|
1450
1494
|
const [sqlRows, setSqlRows] = useState(null);
|
|
1451
1495
|
const [sqlLoading, setSqlLoading] = useState(false);
|
|
1452
1496
|
const [sqlError, setSqlError] = useState(null);
|
|
@@ -1561,12 +1605,13 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1561
1605
|
}
|
|
1562
1606
|
}
|
|
1563
1607
|
config.style = style;
|
|
1608
|
+
config.fill = fillContainer;
|
|
1564
1609
|
onSave({
|
|
1565
1610
|
title: title || `${chartType} chart`,
|
|
1566
1611
|
type: chartType === "grouped-bar" ? "bar" : chartType,
|
|
1567
1612
|
config,
|
|
1568
|
-
width: 6,
|
|
1569
|
-
height: 2
|
|
1613
|
+
width: fillContainer ? 12 : 6,
|
|
1614
|
+
height: fillContainer ? 8 : 2
|
|
1570
1615
|
});
|
|
1571
1616
|
};
|
|
1572
1617
|
return /* @__PURE__ */ jsxs("div", { className: "flex h-full", children: [
|
|
@@ -1722,6 +1767,10 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1722
1767
|
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"
|
|
1723
1768
|
}
|
|
1724
1769
|
),
|
|
1770
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
1771
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", title: "Span the full dashboard width and height", children: "Maximize size" }),
|
|
1772
|
+
/* @__PURE__ */ jsx(TerminalToggle, { size: "small", checked: fillContainer, onChange: setFillContainer })
|
|
1773
|
+
] }),
|
|
1725
1774
|
/* @__PURE__ */ jsx(
|
|
1726
1775
|
"button",
|
|
1727
1776
|
{
|
|
@@ -1818,14 +1867,26 @@ function panelSql(type, config) {
|
|
|
1818
1867
|
if (SQL_CHART_TYPES.has(type)) return buildChartSQL(panelToChartConfig(type, config)) || "";
|
|
1819
1868
|
return "";
|
|
1820
1869
|
}
|
|
1821
|
-
function
|
|
1870
|
+
function formatCell(value, s) {
|
|
1871
|
+
if (value == null) return "";
|
|
1872
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
1873
|
+
let str;
|
|
1874
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
1875
|
+
if (value !== "" && typeof value !== "boolean" && Number.isFinite(n)) {
|
|
1876
|
+
str = Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { minimumFractionDigits: s.cellPrecision, maximumFractionDigits: s.cellPrecision });
|
|
1877
|
+
} else {
|
|
1878
|
+
str = String(value);
|
|
1879
|
+
}
|
|
1880
|
+
if (s.cellTruncate > 0 && str.length > s.cellTruncate) str = `${str.slice(0, s.cellTruncate)}\u2026`;
|
|
1881
|
+
return str;
|
|
1882
|
+
}
|
|
1883
|
+
function DataTable({ rows, columns, style }) {
|
|
1884
|
+
const s = withStyleDefaults(style);
|
|
1822
1885
|
const colNames = columns?.length ? columns : rows[0] ? Object.keys(rows[0]) : [];
|
|
1886
|
+
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]";
|
|
1823
1887
|
return /* @__PURE__ */ jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
1824
1888
|
/* @__PURE__ */ jsx("thead", { className: "sticky top-0 bg-midnight-elevated", children: /* @__PURE__ */ jsx("tr", { children: colNames.map((n) => /* @__PURE__ */ jsx("th", { className: "px-2 py-1 text-left text-midnight-text-muted font-mono border-b border-midnight-border", children: n }, n)) }) }),
|
|
1825
|
-
/* @__PURE__ */ jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => {
|
|
1826
|
-
const cellValue = r[n];
|
|
1827
|
-
return /* @__PURE__ */ 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);
|
|
1828
|
-
}) }, i)) })
|
|
1889
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => /* @__PURE__ */ jsx("td", { className: cellCls, children: formatCell(r[n], s) }, n)) }, i)) })
|
|
1829
1890
|
] }) });
|
|
1830
1891
|
}
|
|
1831
1892
|
function SqlPanel({ panel }) {
|
|
@@ -1920,7 +1981,7 @@ function SqlPanel({ panel }) {
|
|
|
1920
1981
|
const v = first ? Object.values(first)[0] : 0;
|
|
1921
1982
|
chart = /* @__PURE__ */ jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1922
1983
|
} else if (chartType === "table") {
|
|
1923
|
-
chart = /* @__PURE__ */ jsx(DataTable, { rows, columns: config.columns });
|
|
1984
|
+
chart = /* @__PURE__ */ jsx(DataTable, { rows, columns: config.columns, style: config.style });
|
|
1924
1985
|
} else {
|
|
1925
1986
|
const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
|
|
1926
1987
|
switch (chartType) {
|
|
@@ -1984,7 +2045,7 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1984
2045
|
case "insight":
|
|
1985
2046
|
return /* @__PURE__ */ jsx(InsightView, { config: { text: config.text } });
|
|
1986
2047
|
case "table":
|
|
1987
|
-
return /* @__PURE__ */ jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name) });
|
|
2048
|
+
return /* @__PURE__ */ jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name), style: config.style });
|
|
1988
2049
|
default:
|
|
1989
2050
|
return /* @__PURE__ */ jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1990
2051
|
"Unknown panel type: ",
|
|
@@ -1992,57 +2053,121 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1992
2053
|
] });
|
|
1993
2054
|
}
|
|
1994
2055
|
}
|
|
1995
|
-
var
|
|
2056
|
+
var GRID_COLS = 12;
|
|
2057
|
+
var ROW_HEIGHT = 80;
|
|
2058
|
+
var GRID_MARGIN = 10;
|
|
2059
|
+
var DRAG_HANDLE = "panel-drag-handle";
|
|
2060
|
+
var RGL_CSS = `
|
|
2061
|
+
.react-grid-layout { position: relative; transition: height 200ms ease; }
|
|
2062
|
+
.react-grid-item { transition: all 200ms ease; transition-property: left, top, width, height; box-sizing: border-box; }
|
|
2063
|
+
.react-grid-item.cssTransforms { transition-property: transform, width, height; }
|
|
2064
|
+
.react-grid-item.resizing { transition: none; z-index: 3; will-change: width, height; }
|
|
2065
|
+
.react-grid-item.react-draggable-dragging { transition: none; z-index: 3; will-change: transform; }
|
|
2066
|
+
.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; }
|
|
2067
|
+
.react-grid-item > .react-resizable-handle { position: absolute; width: 18px; height: 18px; bottom: 0; right: 0; cursor: se-resize; }
|
|
2068
|
+
.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); }
|
|
2069
|
+
.${DRAG_HANDLE} { cursor: grab; }
|
|
2070
|
+
.react-grid-item.react-draggable-dragging .${DRAG_HANDLE} { cursor: grabbing; }
|
|
2071
|
+
`;
|
|
2072
|
+
var rglCssInjected = false;
|
|
2073
|
+
function useInjectRglCss() {
|
|
2074
|
+
useEffect(() => {
|
|
2075
|
+
if (rglCssInjected || typeof document === "undefined") return;
|
|
2076
|
+
const el = document.createElement("style");
|
|
2077
|
+
el.setAttribute("data-rgl", "dashboard-renderer");
|
|
2078
|
+
el.textContent = RGL_CSS;
|
|
2079
|
+
document.head.appendChild(el);
|
|
2080
|
+
rglCssInjected = true;
|
|
2081
|
+
}, []);
|
|
2082
|
+
}
|
|
2083
|
+
var GridLayoutWithWidth = WidthProvider(GridLayout);
|
|
2084
|
+
function buildLayout(panels) {
|
|
2085
|
+
let cx = 0, cy = 0, rowH = 0;
|
|
2086
|
+
return panels.map((p) => {
|
|
2087
|
+
const fill = !!p.config?.fill;
|
|
2088
|
+
const w = fill ? GRID_COLS : Math.min(Math.max(p.width || 6, 1), GRID_COLS);
|
|
2089
|
+
const h = fill ? Math.max(p.height || 8, 6) : Math.max(p.height || 4, 1);
|
|
2090
|
+
if (typeof p.x === "number" && typeof p.y === "number") {
|
|
2091
|
+
return { i: p.id, x: fill ? 0 : p.x, y: p.y, w, h, minW: 2, minH: 2 };
|
|
2092
|
+
}
|
|
2093
|
+
if (cx + w > GRID_COLS) {
|
|
2094
|
+
cx = 0;
|
|
2095
|
+
cy += rowH;
|
|
2096
|
+
rowH = 0;
|
|
2097
|
+
}
|
|
2098
|
+
const item = { i: p.id, x: cx, y: cy, w, h, minW: 2, minH: 2 };
|
|
2099
|
+
cx += w;
|
|
2100
|
+
rowH = Math.max(rowH, h);
|
|
2101
|
+
return item;
|
|
2102
|
+
});
|
|
2103
|
+
}
|
|
1996
2104
|
function DashboardRenderer({ dashboard, records, columns }) {
|
|
1997
|
-
const { theme, removePanel } = useDashboard();
|
|
1998
|
-
const { canEditPanels } = useCapabilities();
|
|
2105
|
+
const { theme, removePanel, persistLayout } = useDashboard();
|
|
2106
|
+
const { canEditPanels, canEditLayout } = useCapabilities();
|
|
2107
|
+
useInjectRglCss();
|
|
2108
|
+
const panels = dashboard?.panels ?? [];
|
|
2109
|
+
const layout = useMemo(() => buildLayout(panels), [panels]);
|
|
2110
|
+
const onLayoutChange = useCallback((next) => {
|
|
2111
|
+
if (!persistLayout) return;
|
|
2112
|
+
persistLayout(next.map((l) => ({ id: l.i, x: l.x, y: l.y, w: l.w, h: l.h })));
|
|
2113
|
+
}, [persistLayout]);
|
|
1999
2114
|
if (!dashboard) return null;
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
/* @__PURE__ */ jsx(
|
|
2003
|
-
|
|
2004
|
-
] }),
|
|
2005
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-12 gap-3", children: dashboard.panels?.map((panel) => {
|
|
2006
|
-
const colSpan = Math.min(Math.max(panel.width || 6, 1), 12);
|
|
2007
|
-
const rowSpan = Math.min(Math.max(panel.height || 2, 1), 4);
|
|
2008
|
-
return /* @__PURE__ */ jsxs(
|
|
2009
|
-
"div",
|
|
2115
|
+
const panelFrame = (panel, draggable) => /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-surface flex flex-col overflow-hidden h-full`, children: [
|
|
2116
|
+
/* @__PURE__ */ jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated ${draggable ? DRAG_HANDLE : ""}`, children: [
|
|
2117
|
+
/* @__PURE__ */ jsx(
|
|
2118
|
+
"span",
|
|
2010
2119
|
{
|
|
2011
|
-
className:
|
|
2120
|
+
className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
|
|
2012
2121
|
style: {
|
|
2013
|
-
|
|
2014
|
-
|
|
2122
|
+
textAlign: panel.config?.style?.titleAlign || "left",
|
|
2123
|
+
fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
|
|
2124
|
+
background: panel.config?.style?.titleBackground || void 0,
|
|
2125
|
+
color: panel.config?.style?.titleColor || void 0
|
|
2015
2126
|
},
|
|
2016
|
-
children:
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2127
|
+
children: panel.title || panel.type
|
|
2128
|
+
}
|
|
2129
|
+
),
|
|
2130
|
+
canEditPanels && removePanel && /* @__PURE__ */ jsx(
|
|
2131
|
+
"button",
|
|
2132
|
+
{
|
|
2133
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
2134
|
+
onClick: () => removePanel(panel.id),
|
|
2135
|
+
className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
|
|
2136
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
|
|
2137
|
+
}
|
|
2138
|
+
)
|
|
2139
|
+
] }),
|
|
2140
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ViewLoading, {}), children: /* @__PURE__ */ jsx(PanelContent, { panel, records, columns }) }) })
|
|
2141
|
+
] }, panel.id);
|
|
2142
|
+
const insightsBanner = dashboard.insights ? /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
|
|
2143
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
|
|
2144
|
+
/* @__PURE__ */ jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
|
|
2145
|
+
] }) : null;
|
|
2146
|
+
if (panels.length === 1) {
|
|
2147
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-2 h-full min-h-[60vh]", children: [
|
|
2148
|
+
insightsBanner,
|
|
2149
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: panelFrame(panels[0], false) })
|
|
2150
|
+
] });
|
|
2151
|
+
}
|
|
2152
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
|
|
2153
|
+
insightsBanner,
|
|
2154
|
+
/* @__PURE__ */ jsx(
|
|
2155
|
+
GridLayoutWithWidth,
|
|
2156
|
+
{
|
|
2157
|
+
className: "layout",
|
|
2158
|
+
layout,
|
|
2159
|
+
cols: GRID_COLS,
|
|
2160
|
+
rowHeight: ROW_HEIGHT,
|
|
2161
|
+
margin: [GRID_MARGIN, GRID_MARGIN],
|
|
2162
|
+
isDraggable: canEditLayout,
|
|
2163
|
+
isResizable: canEditLayout,
|
|
2164
|
+
draggableHandle: `.${DRAG_HANDLE}`,
|
|
2165
|
+
onDragStop: onLayoutChange,
|
|
2166
|
+
onResizeStop: onLayoutChange,
|
|
2167
|
+
compactType: canEditLayout ? "vertical" : null,
|
|
2168
|
+
children: panels.map((panel) => panelFrame(panel, canEditLayout))
|
|
2169
|
+
}
|
|
2170
|
+
)
|
|
2046
2171
|
] });
|
|
2047
2172
|
}
|
|
2048
2173
|
function ProfileSummary({ profile, theme }) {
|