@quantumwake/terminal-ux-dashboard-components 0.1.12 → 0.1.15
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 +301 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +36 -14
- package/dist/index.d.ts +36 -14
- package/dist/index.js +301 -103
- package/dist/index.js.map +1 -1
- package/package.json +4 -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';
|
|
@@ -11,6 +11,7 @@ import { Loader2, Play, ChevronDown, AlertCircle, Rows3, BarChart3, PieChart, Tr
|
|
|
11
11
|
import CodeMirror, { EditorView, Prec, keymap } from '@uiw/react-codemirror';
|
|
12
12
|
import { sql, PostgreSQL, schemaCompletionSource, keywordCompletionSource } from '@codemirror/lang-sql';
|
|
13
13
|
import { snippetCompletion, completeFromList, autocompletion, acceptCompletion } from '@codemirror/autocomplete';
|
|
14
|
+
import GridLayout, { WidthProvider } from 'react-grid-layout';
|
|
14
15
|
|
|
15
16
|
// src/context/DashboardContext.tsx
|
|
16
17
|
var DashboardContext = createContext(null);
|
|
@@ -39,7 +40,9 @@ function useCapabilities() {
|
|
|
39
40
|
canAddPanel: !!c.addPanel,
|
|
40
41
|
canEditPanels: !!c.removePanel,
|
|
41
42
|
// Studio can recompute + persist a panel's precomputed result.
|
|
42
|
-
canRefresh: !!c.persistPanelData
|
|
43
|
+
canRefresh: !!c.persistPanelData,
|
|
44
|
+
// Studio can drag/resize panels and persist the grid layout.
|
|
45
|
+
canEditLayout: !!c.persistLayout
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -135,8 +138,10 @@ var buildChartSQL = ({
|
|
|
135
138
|
}
|
|
136
139
|
case "scatter": {
|
|
137
140
|
if (!x || !y) return null;
|
|
138
|
-
const
|
|
139
|
-
|
|
141
|
+
const nx = `TRY_CAST(${qIdent(x)} AS DOUBLE)`, ny = `TRY_CAST(${qIdent(y)} AS DOUBLE)`;
|
|
142
|
+
const extra = `${nx} IS NOT NULL AND ${ny} IS NOT NULL`;
|
|
143
|
+
const w = where ? `${where} AND ${extra}` : ` WHERE ${extra}`;
|
|
144
|
+
return `SELECT ${nx} AS x, ${ny} AS y FROM ${TABLE}${w} LIMIT ${MAX_POINTS}`;
|
|
140
145
|
}
|
|
141
146
|
case "heatmap": {
|
|
142
147
|
if (!x || !y) return null;
|
|
@@ -168,7 +173,10 @@ var shapeChartData = (chartType, rows, { yFields = [] } = {}) => {
|
|
|
168
173
|
case "line":
|
|
169
174
|
return [{ id: "series", data: rows.map((r) => ({ x: String(r.x), y: Number(r.y) || 0 })) }];
|
|
170
175
|
case "scatter":
|
|
171
|
-
return [{
|
|
176
|
+
return [{
|
|
177
|
+
id: "points",
|
|
178
|
+
data: rows.map((r) => ({ x: Number(r.x), y: Number(r.y) })).filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y))
|
|
179
|
+
}];
|
|
172
180
|
case "heatmap": {
|
|
173
181
|
const rowKeys = [];
|
|
174
182
|
const colKeys = [];
|
|
@@ -210,10 +218,25 @@ var DEFAULT_CHART_STYLE = {
|
|
|
210
218
|
xLegendOffset: 46,
|
|
211
219
|
yLegendPosition: "middle",
|
|
212
220
|
yLegendOffset: -50,
|
|
221
|
+
// Custom axis title text + visibility.
|
|
222
|
+
xAxisLabel: "",
|
|
223
|
+
yAxisLabel: "",
|
|
224
|
+
showXLegend: true,
|
|
225
|
+
showYLegend: true,
|
|
226
|
+
// Axis title emphasis.
|
|
227
|
+
legendBold: false,
|
|
228
|
+
legendHighlight: "",
|
|
229
|
+
// Tick overflow handling.
|
|
230
|
+
xTickRotation: -35,
|
|
231
|
+
yTickRotation: 0,
|
|
232
|
+
tickTruncate: 0,
|
|
213
233
|
// Series legend placement (charts that have one: pie, grouped bar).
|
|
214
234
|
legendAnchor: "right",
|
|
215
|
-
// Panel title
|
|
216
|
-
titleAlign: "left"
|
|
235
|
+
// Panel title (rendered by DashboardRenderer's panel header).
|
|
236
|
+
titleAlign: "left",
|
|
237
|
+
titleBold: false,
|
|
238
|
+
titleBackground: "",
|
|
239
|
+
titleColor: ""
|
|
217
240
|
};
|
|
218
241
|
var LEGEND_ANCHORS = ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"];
|
|
219
242
|
var withStyleDefaults = (style) => ({
|
|
@@ -227,7 +250,17 @@ var buildNivoTheme = (style) => {
|
|
|
227
250
|
text: { fill: s.textColor, fontSize: s.fontSize },
|
|
228
251
|
axis: {
|
|
229
252
|
ticks: { text: { fill: s.textColor, fontSize: s.fontSize } },
|
|
230
|
-
legend: {
|
|
253
|
+
legend: {
|
|
254
|
+
text: {
|
|
255
|
+
fill: s.legendColor,
|
|
256
|
+
fontSize: s.fontSize + 2,
|
|
257
|
+
fontWeight: s.legendBold ? 700 : 400,
|
|
258
|
+
// A highlight is drawn as a text outline (halo) — the clean,
|
|
259
|
+
// SVG-native way to make an axis title stand out.
|
|
260
|
+
outlineWidth: s.legendHighlight ? 3 : 0,
|
|
261
|
+
outlineColor: s.legendHighlight || "transparent"
|
|
262
|
+
}
|
|
263
|
+
}
|
|
231
264
|
},
|
|
232
265
|
grid: { line: { stroke: s.gridColor } },
|
|
233
266
|
crosshair: { line: { stroke: s.textColor, strokeDasharray: "6 6" } },
|
|
@@ -256,14 +289,25 @@ var legendConfig = (style) => {
|
|
|
256
289
|
const ty = s.legendAnchor.includes("top") ? 10 : -10;
|
|
257
290
|
return { ...base, translateX: tx, translateY: ty };
|
|
258
291
|
};
|
|
259
|
-
var
|
|
292
|
+
var truncate = (v, n) => {
|
|
293
|
+
const str = String(v);
|
|
294
|
+
return n > 0 && str.length > n ? `${str.slice(0, n)}\u2026` : str;
|
|
295
|
+
};
|
|
296
|
+
var axisLegend = (style, axis, columnName, opts2 = {}) => {
|
|
260
297
|
const s = withStyleDefaults(style);
|
|
261
298
|
const isX = axis === "x";
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
299
|
+
const show = isX ? s.showXLegend : s.showYLegend;
|
|
300
|
+
const label = (isX ? s.xAxisLabel : s.yAxisLabel) || columnName;
|
|
301
|
+
const out = {};
|
|
302
|
+
if (show && label) {
|
|
303
|
+
out.legend = label;
|
|
304
|
+
out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
|
|
305
|
+
out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
|
|
306
|
+
}
|
|
307
|
+
out.tickRotation = isX ? s.xTickRotation : s.yTickRotation;
|
|
308
|
+
if (opts2.numeric) out.format = (v) => Number(v).toLocaleString();
|
|
309
|
+
else if (s.tickTruncate > 0) out.format = (v) => truncate(v, s.tickTruncate);
|
|
310
|
+
return out;
|
|
267
311
|
};
|
|
268
312
|
|
|
269
313
|
// src/dataShape.ts
|
|
@@ -327,8 +371,8 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
|
|
|
327
371
|
padding: 0.3,
|
|
328
372
|
colors: ["rgba(74, 222, 128, 0.8)"],
|
|
329
373
|
borderColor: { from: "color", modifiers: [["darker", 1.6]] },
|
|
330
|
-
axisBottom: { tickSize: 5, tickPadding: 5,
|
|
331
|
-
axisLeft: { tickSize: 5, tickPadding: 5,
|
|
374
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", groupColumn) },
|
|
375
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", valueColumn, { numeric: true }) },
|
|
332
376
|
labelSkipWidth: 12,
|
|
333
377
|
labelSkipHeight: 12,
|
|
334
378
|
labelTextColor: { from: "color", modifiers: [["darker", 3]] },
|
|
@@ -401,8 +445,8 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
401
445
|
pointBorderWidth: 2,
|
|
402
446
|
pointBorderColor: { from: "serieColor" },
|
|
403
447
|
enableGridX: false,
|
|
404
|
-
axisBottom: { tickSize: 5, tickPadding: 5,
|
|
405
|
-
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn) },
|
|
448
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
|
|
449
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
|
|
406
450
|
useMesh: true,
|
|
407
451
|
theme: buildNivoTheme(style)
|
|
408
452
|
}
|
|
@@ -411,7 +455,7 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
411
455
|
function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
412
456
|
const computed = useMemo(() => {
|
|
413
457
|
if (presetData || !records) return [];
|
|
414
|
-
const points = records.
|
|
458
|
+
const points = records.map((r) => ({ x: Number(r[xColumn]), y: Number(r[yColumn]) })).filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y)).slice(0, 1e3);
|
|
415
459
|
return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
|
|
416
460
|
}, [records, xColumn, yColumn, presetData]);
|
|
417
461
|
const data = presetData || computed;
|
|
@@ -424,8 +468,8 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
424
468
|
yScale: { type: "linear", min: "auto", max: "auto" },
|
|
425
469
|
colors: ["rgba(167, 139, 250, 0.7)"],
|
|
426
470
|
nodeSize: 6,
|
|
427
|
-
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
|
|
428
|
-
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn) },
|
|
471
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn, { numeric: true }) },
|
|
472
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
|
|
429
473
|
useMesh: true,
|
|
430
474
|
theme: buildNivoTheme(style)
|
|
431
475
|
}
|
|
@@ -524,8 +568,8 @@ function HeatmapView({
|
|
|
524
568
|
{
|
|
525
569
|
data,
|
|
526
570
|
margin,
|
|
527
|
-
axisTop: { tickSize: 5, tickPadding: 5, tickRotation:
|
|
528
|
-
axisLeft: { tickSize: 5, tickPadding: 5, legend: rowColumn, legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
571
|
+
axisTop: { tickSize: 5, tickPadding: 5, tickRotation: s.xTickRotation, legend: s.showXLegend ? s.xAxisLabel || colColumn : "", legendPosition: s.xLegendPosition, legendOffset: -50 },
|
|
572
|
+
axisLeft: { tickSize: 5, tickPadding: 5, legend: s.showYLegend ? s.yAxisLabel || rowColumn : "", legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
529
573
|
axisRight: showTotals ? { tickSize: 5, tickPadding: 5, format: (id) => fmtNum(rowTotals[id]), legend: `${marginAgg} \u25B8`, legendOffset: 60 } : null,
|
|
530
574
|
axisBottom: showTotals ? { tickSize: 5, tickPadding: 5, tickRotation: -35, format: (id) => fmtNum(colTotals[id]) } : null,
|
|
531
575
|
colors: { type: "sequential", scheme: "blue_green", minValue: 0 },
|
|
@@ -569,11 +613,17 @@ function PivotView({ records }) {
|
|
|
569
613
|
)
|
|
570
614
|
] });
|
|
571
615
|
}
|
|
572
|
-
function
|
|
616
|
+
function Section({ title, children }) {
|
|
573
617
|
const { theme } = useDashboard();
|
|
574
|
-
return /* @__PURE__ */ jsxs("div", { className:
|
|
575
|
-
/* @__PURE__ */ jsx("
|
|
576
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
618
|
+
return /* @__PURE__ */ jsxs("div", { className: `py-3 border-t ${theme.border} first:border-t-0 first:pt-0`, children: [
|
|
619
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wider text-midnight-text-muted/70 font-mono mb-2", children: title }),
|
|
620
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1.5", children })
|
|
621
|
+
] });
|
|
622
|
+
}
|
|
623
|
+
function Row({ label, children }) {
|
|
624
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[88px_1fr] items-center gap-2 min-h-[30px]", children: [
|
|
625
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted truncate", title: label, children: label }),
|
|
626
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-end", children })
|
|
577
627
|
] });
|
|
578
628
|
}
|
|
579
629
|
function ColorControl({ value, onChange }) {
|
|
@@ -584,61 +634,112 @@ function ColorControl({ value, onChange }) {
|
|
|
584
634
|
type: "color",
|
|
585
635
|
value: /^#/.test(value) ? value : "#94a3b8",
|
|
586
636
|
onChange: (e) => onChange(e.target.value),
|
|
587
|
-
className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer`
|
|
637
|
+
className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer p-0`
|
|
588
638
|
}
|
|
589
639
|
);
|
|
590
640
|
}
|
|
591
|
-
function
|
|
641
|
+
function SliderControl({ value, onChange, min, max, step = 1, unit = "" }) {
|
|
642
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
|
|
643
|
+
/* @__PURE__ */ jsx(
|
|
644
|
+
"input",
|
|
645
|
+
{
|
|
646
|
+
type: "range",
|
|
647
|
+
min,
|
|
648
|
+
max,
|
|
649
|
+
step,
|
|
650
|
+
value,
|
|
651
|
+
onChange: (e) => onChange(Number(e.target.value)),
|
|
652
|
+
className: "flex-1 h-1 accent-midnight-accent cursor-pointer"
|
|
653
|
+
}
|
|
654
|
+
),
|
|
655
|
+
/* @__PURE__ */ jsxs("span", { className: "w-10 shrink-0 text-right text-[11px] font-mono tabular-nums text-midnight-text-body", children: [
|
|
656
|
+
value,
|
|
657
|
+
unit
|
|
658
|
+
] })
|
|
659
|
+
] });
|
|
660
|
+
}
|
|
661
|
+
function TextControl({ value, onChange, placeholder }) {
|
|
592
662
|
const { theme } = useDashboard();
|
|
593
663
|
return /* @__PURE__ */ jsx(
|
|
594
664
|
"input",
|
|
595
665
|
{
|
|
596
|
-
type: "
|
|
666
|
+
type: "text",
|
|
597
667
|
value,
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
668
|
+
placeholder,
|
|
669
|
+
onChange: (e) => onChange(e.target.value),
|
|
670
|
+
className: `w-full px-2 py-1 text-xs font-mono bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent transition-colors`
|
|
671
|
+
}
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
function Switch({ value, onChange }) {
|
|
675
|
+
return /* @__PURE__ */ jsx(
|
|
676
|
+
"button",
|
|
677
|
+
{
|
|
678
|
+
type: "button",
|
|
679
|
+
role: "switch",
|
|
680
|
+
"aria-checked": value,
|
|
681
|
+
onClick: () => onChange(!value),
|
|
682
|
+
className: `relative inline-flex h-4 w-7 shrink-0 items-center rounded-full transition-colors ${value ? "bg-midnight-accent" : "bg-midnight-border"}`,
|
|
683
|
+
children: /* @__PURE__ */ jsx("span", { className: `inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${value ? "translate-x-3.5" : "translate-x-0.5"}` })
|
|
602
684
|
}
|
|
603
685
|
);
|
|
604
686
|
}
|
|
687
|
+
function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
|
|
688
|
+
const on = !!value;
|
|
689
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
690
|
+
on && /* @__PURE__ */ jsx(ColorControl, { value, onChange }),
|
|
691
|
+
/* @__PURE__ */ jsx(Switch, { value: on, onChange: (v) => onChange(v ? fallback : "") })
|
|
692
|
+
] });
|
|
693
|
+
}
|
|
605
694
|
function Choice({ value, options, onChange }) {
|
|
606
695
|
const { theme } = useDashboard();
|
|
607
|
-
return /* @__PURE__ */ jsx(
|
|
696
|
+
return /* @__PURE__ */ jsx(
|
|
608
697
|
"select",
|
|
609
698
|
{
|
|
610
699
|
value,
|
|
611
700
|
onChange: (e) => onChange(e.target.value),
|
|
612
|
-
className: `w-full px-2 py-1 text-xs
|
|
701
|
+
className: `w-full px-2 py-1 text-xs font-mono bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent transition-colors`,
|
|
613
702
|
children: options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o))
|
|
614
703
|
}
|
|
615
|
-
)
|
|
616
|
-
}
|
|
617
|
-
function Group({ children, last }) {
|
|
618
|
-
const { theme } = useDashboard();
|
|
619
|
-
return /* @__PURE__ */ jsx("div", { className: `space-y-2 pb-3 ${last ? "" : `mb-3 border-b ${theme.border}`}`, children });
|
|
704
|
+
);
|
|
620
705
|
}
|
|
621
706
|
function ChartStyleControls({ style, onChange }) {
|
|
622
707
|
const s = withStyleDefaults(style);
|
|
623
708
|
const set = (key, value) => onChange({ ...s, [key]: value });
|
|
624
709
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
625
|
-
/* @__PURE__ */ jsxs(
|
|
710
|
+
/* @__PURE__ */ jsxs(Section, { title: "Canvas", children: [
|
|
626
711
|
/* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
|
|
627
|
-
/* @__PURE__ */ jsx(Row, { label: "Text
|
|
628
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
629
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
630
|
-
/* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(NumberControl, { value: s.fontSize, min: 6, max: 24, onChange: (v) => set("fontSize", v) }) })
|
|
712
|
+
/* @__PURE__ */ jsx(Row, { label: "Text", children: /* @__PURE__ */ jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
|
|
713
|
+
/* @__PURE__ */ jsx(Row, { label: "Grid", children: /* @__PURE__ */ jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
|
|
714
|
+
/* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(SliderControl, { value: s.fontSize, min: 6, max: 24, unit: "px", onChange: (v) => set("fontSize", v) }) })
|
|
631
715
|
] }),
|
|
632
|
-
/* @__PURE__ */ jsxs(
|
|
633
|
-
/* @__PURE__ */ jsx(Row, { label: "X title
|
|
634
|
-
/* @__PURE__ */ jsx(Row, { label: "X
|
|
635
|
-
/* @__PURE__ */ jsx(Row, { label: "Y title
|
|
636
|
-
/* @__PURE__ */ jsx(Row, { label: "Y
|
|
716
|
+
/* @__PURE__ */ jsxs(Section, { title: "Axis titles", children: [
|
|
717
|
+
/* @__PURE__ */ jsx(Row, { label: "X title", children: /* @__PURE__ */ jsx(TextControl, { value: s.xAxisLabel, placeholder: "(column)", onChange: (v) => set("xAxisLabel", v) }) }),
|
|
718
|
+
/* @__PURE__ */ jsx(Row, { label: "Show X", children: /* @__PURE__ */ jsx(Switch, { value: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
|
|
719
|
+
/* @__PURE__ */ jsx(Row, { label: "Y title", children: /* @__PURE__ */ jsx(TextControl, { value: s.yAxisLabel, placeholder: "(column)", onChange: (v) => set("yAxisLabel", v) }) }),
|
|
720
|
+
/* @__PURE__ */ jsx(Row, { label: "Show Y", children: /* @__PURE__ */ jsx(Switch, { value: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
|
|
721
|
+
/* @__PURE__ */ jsx(Row, { label: "Color", children: /* @__PURE__ */ jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
|
|
722
|
+
/* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(Switch, { value: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
|
|
723
|
+
/* @__PURE__ */ jsx(Row, { label: "Highlight", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) })
|
|
637
724
|
] }),
|
|
638
|
-
/* @__PURE__ */ jsxs(
|
|
639
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
640
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
641
|
-
|
|
725
|
+
/* @__PURE__ */ jsxs(Section, { title: "Axis placement", children: [
|
|
726
|
+
/* @__PURE__ */ jsx(Row, { label: "X pos", children: /* @__PURE__ */ jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
|
|
727
|
+
/* @__PURE__ */ jsx(Row, { label: "X offset", children: /* @__PURE__ */ jsx(SliderControl, { value: s.xLegendOffset, min: -80, max: 80, onChange: (v) => set("xLegendOffset", v) }) }),
|
|
728
|
+
/* @__PURE__ */ jsx(Row, { label: "Y pos", children: /* @__PURE__ */ jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
|
|
729
|
+
/* @__PURE__ */ jsx(Row, { label: "Y offset", children: /* @__PURE__ */ jsx(SliderControl, { value: s.yLegendOffset, min: -80, max: 80, onChange: (v) => set("yLegendOffset", v) }) })
|
|
730
|
+
] }),
|
|
731
|
+
/* @__PURE__ */ jsxs(Section, { title: "Ticks", children: [
|
|
732
|
+
/* @__PURE__ */ jsx(Row, { label: "X angle", children: /* @__PURE__ */ jsx(SliderControl, { value: s.xTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("xTickRotation", v) }) }),
|
|
733
|
+
/* @__PURE__ */ jsx(Row, { label: "Y angle", children: /* @__PURE__ */ jsx(SliderControl, { value: s.yTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("yTickRotation", v) }) }),
|
|
734
|
+
/* @__PURE__ */ jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsx(SliderControl, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
|
|
735
|
+
] }),
|
|
736
|
+
/* @__PURE__ */ jsxs(Section, { title: "Panel title", children: [
|
|
737
|
+
/* @__PURE__ */ jsx(Row, { label: "Align", children: /* @__PURE__ */ jsx(Choice, { value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
|
|
738
|
+
/* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(Switch, { value: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
|
|
739
|
+
/* @__PURE__ */ jsx(Row, { label: "Color", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
|
|
740
|
+
/* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
|
|
741
|
+
] }),
|
|
742
|
+
/* @__PURE__ */ jsx(Section, { title: "Series legend", children: /* @__PURE__ */ jsx(Row, { label: "Position", children: /* @__PURE__ */ jsx(Choice, { value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }) })
|
|
642
743
|
] });
|
|
643
744
|
}
|
|
644
745
|
var AGGREGATES = [
|
|
@@ -1362,6 +1463,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1362
1463
|
const [showTotals, setShowTotals] = useState(false);
|
|
1363
1464
|
const [style, setStyle] = useState(DEFAULT_CHART_STYLE);
|
|
1364
1465
|
const [showStyle, setShowStyle] = useState(false);
|
|
1466
|
+
const [showFields, setShowFields] = useState(false);
|
|
1365
1467
|
const [sqlRows, setSqlRows] = useState(null);
|
|
1366
1468
|
const [sqlLoading, setSqlLoading] = useState(false);
|
|
1367
1469
|
const [sqlError, setSqlError] = useState(null);
|
|
@@ -1647,8 +1749,23 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1647
1749
|
)
|
|
1648
1750
|
] }),
|
|
1649
1751
|
/* @__PURE__ */ jsxs("div", { className: "pt-4 border-t border-midnight-border", children: [
|
|
1650
|
-
/* @__PURE__ */
|
|
1651
|
-
|
|
1752
|
+
/* @__PURE__ */ jsxs(
|
|
1753
|
+
"button",
|
|
1754
|
+
{
|
|
1755
|
+
onClick: () => setShowFields((s) => !s),
|
|
1756
|
+
className: "flex items-center gap-1 text-xs uppercase text-midnight-text-muted font-mono hover:text-midnight-text-body transition-colors",
|
|
1757
|
+
children: [
|
|
1758
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: `w-3 h-3 transition-transform ${showFields ? "" : "-rotate-90"}` }),
|
|
1759
|
+
"Available Fields",
|
|
1760
|
+
/* @__PURE__ */ jsxs("span", { className: "text-midnight-text-muted/60 normal-case", children: [
|
|
1761
|
+
"(",
|
|
1762
|
+
columns.length,
|
|
1763
|
+
")"
|
|
1764
|
+
] })
|
|
1765
|
+
]
|
|
1766
|
+
}
|
|
1767
|
+
),
|
|
1768
|
+
showFields && /* @__PURE__ */ jsx("div", { className: "space-y-1 mt-2", children: columns.map((col) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs font-mono px-1 py-0.5", children: [
|
|
1652
1769
|
/* @__PURE__ */ jsx("span", { className: "text-midnight-text-body", children: col.name }),
|
|
1653
1770
|
/* @__PURE__ */ jsx("span", { className: "text-midnight-text-muted", children: col.type })
|
|
1654
1771
|
] }, col.name)) })
|
|
@@ -1678,6 +1795,15 @@ function ViewLoading() {
|
|
|
1678
1795
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
|
|
1679
1796
|
}
|
|
1680
1797
|
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap"]);
|
|
1798
|
+
var SQL_DATA_TYPES = /* @__PURE__ */ new Set(["table", "metric"]);
|
|
1799
|
+
var TABLE_PREVIEW_ROWS = 200;
|
|
1800
|
+
function tableSql(config) {
|
|
1801
|
+
const cols = config.columns?.length ? config.columns.map(qIdent).join(", ") : "*";
|
|
1802
|
+
return `SELECT ${cols} FROM data LIMIT ${TABLE_PREVIEW_ROWS}`;
|
|
1803
|
+
}
|
|
1804
|
+
function metricSql(config) {
|
|
1805
|
+
return `SELECT ${aggExpr(config.agg, config.column)} AS v FROM data`;
|
|
1806
|
+
}
|
|
1681
1807
|
function panelToChartConfig(chartType, config) {
|
|
1682
1808
|
const cfg = { chartType, xFields: [], yFields: [], filters: config.filters || [] };
|
|
1683
1809
|
switch (chartType) {
|
|
@@ -1702,11 +1828,23 @@ function panelToChartConfig(chartType, config) {
|
|
|
1702
1828
|
}
|
|
1703
1829
|
return cfg;
|
|
1704
1830
|
}
|
|
1705
|
-
function panelSql(
|
|
1831
|
+
function panelSql(type, config) {
|
|
1706
1832
|
if (config.sql) return config.sql;
|
|
1707
|
-
if (
|
|
1833
|
+
if (type === "table") return tableSql(config);
|
|
1834
|
+
if (type === "metric") return metricSql(config);
|
|
1835
|
+
if (SQL_CHART_TYPES.has(type)) return buildChartSQL(panelToChartConfig(type, config)) || "";
|
|
1708
1836
|
return "";
|
|
1709
1837
|
}
|
|
1838
|
+
function DataTable({ rows, columns }) {
|
|
1839
|
+
const colNames = columns?.length ? columns : rows[0] ? Object.keys(rows[0]) : [];
|
|
1840
|
+
return /* @__PURE__ */ jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
1841
|
+
/* @__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)) }) }),
|
|
1842
|
+
/* @__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) => {
|
|
1843
|
+
const cellValue = r[n];
|
|
1844
|
+
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);
|
|
1845
|
+
}) }, i)) })
|
|
1846
|
+
] }) });
|
|
1847
|
+
}
|
|
1710
1848
|
function SqlPanel({ panel }) {
|
|
1711
1849
|
const { runQuery, persistPanelData } = useDashboard();
|
|
1712
1850
|
const { canRefresh } = useCapabilities();
|
|
@@ -1798,6 +1936,8 @@ function SqlPanel({ panel }) {
|
|
|
1798
1936
|
const first = rows[0];
|
|
1799
1937
|
const v = first ? Object.values(first)[0] : 0;
|
|
1800
1938
|
chart = /* @__PURE__ */ jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1939
|
+
} else if (chartType === "table") {
|
|
1940
|
+
chart = /* @__PURE__ */ jsx(DataTable, { rows, columns: config.columns });
|
|
1801
1941
|
} else {
|
|
1802
1942
|
const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
|
|
1803
1943
|
switch (chartType) {
|
|
@@ -1837,7 +1977,7 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1837
1977
|
const { type } = panel;
|
|
1838
1978
|
const config = panel.config || {};
|
|
1839
1979
|
const effType = config.chartType || type;
|
|
1840
|
-
if (config.sql || SQL_CHART_TYPES.has(effType)) {
|
|
1980
|
+
if (config.sql || SQL_CHART_TYPES.has(effType) || SQL_DATA_TYPES.has(effType)) {
|
|
1841
1981
|
return /* @__PURE__ */ jsx(SqlPanel, { panel });
|
|
1842
1982
|
}
|
|
1843
1983
|
if (!records?.length && type !== "insight") {
|
|
@@ -1860,17 +2000,8 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1860
2000
|
return /* @__PURE__ */ jsx(MetricView, { records, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1861
2001
|
case "insight":
|
|
1862
2002
|
return /* @__PURE__ */ jsx(InsightView, { config: { text: config.text } });
|
|
1863
|
-
case "table":
|
|
1864
|
-
|
|
1865
|
-
const colNames = cols?.map((c) => c.name) || (records && records[0] ? Object.keys(records[0]) : []);
|
|
1866
|
-
return /* @__PURE__ */ jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
1867
|
-
/* @__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)) }) }),
|
|
1868
|
-
/* @__PURE__ */ jsx("tbody", { children: (records || []).slice(0, 50).map((r, i) => /* @__PURE__ */ jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => {
|
|
1869
|
-
const cellValue = r[n];
|
|
1870
|
-
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);
|
|
1871
|
-
}) }, i)) })
|
|
1872
|
-
] }) });
|
|
1873
|
-
}
|
|
2003
|
+
case "table":
|
|
2004
|
+
return /* @__PURE__ */ jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name) });
|
|
1874
2005
|
default:
|
|
1875
2006
|
return /* @__PURE__ */ jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1876
2007
|
"Unknown panel type: ",
|
|
@@ -1878,45 +2009,112 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1878
2009
|
] });
|
|
1879
2010
|
}
|
|
1880
2011
|
}
|
|
1881
|
-
var
|
|
2012
|
+
var GRID_COLS = 12;
|
|
2013
|
+
var ROW_HEIGHT = 80;
|
|
2014
|
+
var GRID_MARGIN = 10;
|
|
2015
|
+
var DRAG_HANDLE = "panel-drag-handle";
|
|
2016
|
+
var RGL_CSS = `
|
|
2017
|
+
.react-grid-layout { position: relative; transition: height 200ms ease; }
|
|
2018
|
+
.react-grid-item { transition: all 200ms ease; transition-property: left, top, width, height; box-sizing: border-box; }
|
|
2019
|
+
.react-grid-item.cssTransforms { transition-property: transform, width, height; }
|
|
2020
|
+
.react-grid-item.resizing { transition: none; z-index: 3; will-change: width, height; }
|
|
2021
|
+
.react-grid-item.react-draggable-dragging { transition: none; z-index: 3; will-change: transform; }
|
|
2022
|
+
.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; }
|
|
2023
|
+
.react-grid-item > .react-resizable-handle { position: absolute; width: 18px; height: 18px; bottom: 0; right: 0; cursor: se-resize; }
|
|
2024
|
+
.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); }
|
|
2025
|
+
.${DRAG_HANDLE} { cursor: grab; }
|
|
2026
|
+
.react-grid-item.react-draggable-dragging .${DRAG_HANDLE} { cursor: grabbing; }
|
|
2027
|
+
`;
|
|
2028
|
+
var rglCssInjected = false;
|
|
2029
|
+
function useInjectRglCss() {
|
|
2030
|
+
useEffect(() => {
|
|
2031
|
+
if (rglCssInjected || typeof document === "undefined") return;
|
|
2032
|
+
const el = document.createElement("style");
|
|
2033
|
+
el.setAttribute("data-rgl", "dashboard-renderer");
|
|
2034
|
+
el.textContent = RGL_CSS;
|
|
2035
|
+
document.head.appendChild(el);
|
|
2036
|
+
rglCssInjected = true;
|
|
2037
|
+
}, []);
|
|
2038
|
+
}
|
|
2039
|
+
var GridLayoutWithWidth = WidthProvider(GridLayout);
|
|
2040
|
+
function buildLayout(panels) {
|
|
2041
|
+
let cx = 0, cy = 0, rowH = 0;
|
|
2042
|
+
return panels.map((p) => {
|
|
2043
|
+
const w = Math.min(Math.max(p.width || 6, 1), GRID_COLS);
|
|
2044
|
+
const h = Math.max(p.height || 4, 1);
|
|
2045
|
+
if (typeof p.x === "number" && typeof p.y === "number") {
|
|
2046
|
+
return { i: p.id, x: p.x, y: p.y, w, h, minW: 2, minH: 2 };
|
|
2047
|
+
}
|
|
2048
|
+
if (cx + w > GRID_COLS) {
|
|
2049
|
+
cx = 0;
|
|
2050
|
+
cy += rowH;
|
|
2051
|
+
rowH = 0;
|
|
2052
|
+
}
|
|
2053
|
+
const item = { i: p.id, x: cx, y: cy, w, h, minW: 2, minH: 2 };
|
|
2054
|
+
cx += w;
|
|
2055
|
+
rowH = Math.max(rowH, h);
|
|
2056
|
+
return item;
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
1882
2059
|
function DashboardRenderer({ dashboard, records, columns }) {
|
|
1883
|
-
const { theme, removePanel } = useDashboard();
|
|
1884
|
-
const { canEditPanels } = useCapabilities();
|
|
2060
|
+
const { theme, removePanel, persistLayout } = useDashboard();
|
|
2061
|
+
const { canEditPanels, canEditLayout } = useCapabilities();
|
|
2062
|
+
useInjectRglCss();
|
|
2063
|
+
const panels = dashboard?.panels ?? [];
|
|
2064
|
+
const layout = useMemo(() => buildLayout(panels), [panels]);
|
|
2065
|
+
const onLayoutChange = useCallback((next) => {
|
|
2066
|
+
if (!persistLayout) return;
|
|
2067
|
+
persistLayout(next.map((l) => ({ id: l.i, x: l.x, y: l.y, w: l.w, h: l.h })));
|
|
2068
|
+
}, [persistLayout]);
|
|
1885
2069
|
if (!dashboard) return null;
|
|
1886
2070
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
|
|
1887
2071
|
dashboard.insights && /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
|
|
1888
2072
|
/* @__PURE__ */ jsx(Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
|
|
1889
2073
|
/* @__PURE__ */ jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
|
|
1890
2074
|
] }),
|
|
1891
|
-
/* @__PURE__ */ jsx(
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2075
|
+
/* @__PURE__ */ jsx(
|
|
2076
|
+
GridLayoutWithWidth,
|
|
2077
|
+
{
|
|
2078
|
+
className: "layout",
|
|
2079
|
+
layout,
|
|
2080
|
+
cols: GRID_COLS,
|
|
2081
|
+
rowHeight: ROW_HEIGHT,
|
|
2082
|
+
margin: [GRID_MARGIN, GRID_MARGIN],
|
|
2083
|
+
isDraggable: canEditLayout,
|
|
2084
|
+
isResizable: canEditLayout,
|
|
2085
|
+
draggableHandle: `.${DRAG_HANDLE}`,
|
|
2086
|
+
onDragStop: onLayoutChange,
|
|
2087
|
+
onResizeStop: onLayoutChange,
|
|
2088
|
+
compactType: canEditLayout ? "vertical" : null,
|
|
2089
|
+
children: panels.map((panel) => /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-surface flex flex-col overflow-hidden`, children: [
|
|
2090
|
+
/* @__PURE__ */ jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated ${canEditLayout ? DRAG_HANDLE : ""}`, children: [
|
|
2091
|
+
/* @__PURE__ */ jsx(
|
|
2092
|
+
"span",
|
|
2093
|
+
{
|
|
2094
|
+
className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
|
|
2095
|
+
style: {
|
|
2096
|
+
textAlign: panel.config?.style?.titleAlign || "left",
|
|
2097
|
+
fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
|
|
2098
|
+
background: panel.config?.style?.titleBackground || void 0,
|
|
2099
|
+
color: panel.config?.style?.titleColor || void 0
|
|
2100
|
+
},
|
|
2101
|
+
children: panel.title || panel.type
|
|
2102
|
+
}
|
|
2103
|
+
),
|
|
2104
|
+
canEditPanels && removePanel && /* @__PURE__ */ jsx(
|
|
2105
|
+
"button",
|
|
2106
|
+
{
|
|
2107
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
2108
|
+
onClick: () => removePanel(panel.id),
|
|
2109
|
+
className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
|
|
2110
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
|
|
2111
|
+
}
|
|
2112
|
+
)
|
|
2113
|
+
] }),
|
|
2114
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ViewLoading, {}), children: /* @__PURE__ */ jsx(PanelContent, { panel, records, columns }) }) })
|
|
2115
|
+
] }, panel.id))
|
|
2116
|
+
}
|
|
2117
|
+
)
|
|
1920
2118
|
] });
|
|
1921
2119
|
}
|
|
1922
2120
|
function ProfileSummary({ profile, theme }) {
|