@quantumwake/terminal-ux-dashboard-components 0.1.10 → 0.1.13
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 +201 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -5
- package/dist/index.d.ts +17 -5
- package/dist/index.js +201 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -142,8 +142,10 @@ var buildChartSQL = ({
|
|
|
142
142
|
}
|
|
143
143
|
case "scatter": {
|
|
144
144
|
if (!x || !y) return null;
|
|
145
|
-
const
|
|
146
|
-
|
|
145
|
+
const nx = `TRY_CAST(${qIdent(x)} AS DOUBLE)`, ny = `TRY_CAST(${qIdent(y)} AS DOUBLE)`;
|
|
146
|
+
const extra = `${nx} IS NOT NULL AND ${ny} IS NOT NULL`;
|
|
147
|
+
const w = where ? `${where} AND ${extra}` : ` WHERE ${extra}`;
|
|
148
|
+
return `SELECT ${nx} AS x, ${ny} AS y FROM ${TABLE}${w} LIMIT ${MAX_POINTS}`;
|
|
147
149
|
}
|
|
148
150
|
case "heatmap": {
|
|
149
151
|
if (!x || !y) return null;
|
|
@@ -175,7 +177,10 @@ var shapeChartData = (chartType, rows, { yFields = [] } = {}) => {
|
|
|
175
177
|
case "line":
|
|
176
178
|
return [{ id: "series", data: rows.map((r) => ({ x: String(r.x), y: Number(r.y) || 0 })) }];
|
|
177
179
|
case "scatter":
|
|
178
|
-
return [{
|
|
180
|
+
return [{
|
|
181
|
+
id: "points",
|
|
182
|
+
data: rows.map((r) => ({ x: Number(r.x), y: Number(r.y) })).filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y))
|
|
183
|
+
}];
|
|
179
184
|
case "heatmap": {
|
|
180
185
|
const rowKeys = [];
|
|
181
186
|
const colKeys = [];
|
|
@@ -217,10 +222,24 @@ var DEFAULT_CHART_STYLE = {
|
|
|
217
222
|
xLegendOffset: 46,
|
|
218
223
|
yLegendPosition: "middle",
|
|
219
224
|
yLegendOffset: -50,
|
|
225
|
+
// Custom axis title text + visibility.
|
|
226
|
+
xAxisLabel: "",
|
|
227
|
+
yAxisLabel: "",
|
|
228
|
+
showXLegend: true,
|
|
229
|
+
showYLegend: true,
|
|
230
|
+
// Axis title emphasis.
|
|
231
|
+
legendBold: false,
|
|
232
|
+
legendHighlight: "",
|
|
233
|
+
// Tick overflow handling.
|
|
234
|
+
xTickRotation: -35,
|
|
235
|
+
tickTruncate: 0,
|
|
220
236
|
// Series legend placement (charts that have one: pie, grouped bar).
|
|
221
237
|
legendAnchor: "right",
|
|
222
|
-
// Panel title
|
|
223
|
-
titleAlign: "left"
|
|
238
|
+
// Panel title (rendered by DashboardRenderer's panel header).
|
|
239
|
+
titleAlign: "left",
|
|
240
|
+
titleBold: false,
|
|
241
|
+
titleBackground: "",
|
|
242
|
+
titleColor: ""
|
|
224
243
|
};
|
|
225
244
|
var LEGEND_ANCHORS = ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"];
|
|
226
245
|
var withStyleDefaults = (style) => ({
|
|
@@ -234,7 +253,17 @@ var buildNivoTheme = (style) => {
|
|
|
234
253
|
text: { fill: s.textColor, fontSize: s.fontSize },
|
|
235
254
|
axis: {
|
|
236
255
|
ticks: { text: { fill: s.textColor, fontSize: s.fontSize } },
|
|
237
|
-
legend: {
|
|
256
|
+
legend: {
|
|
257
|
+
text: {
|
|
258
|
+
fill: s.legendColor,
|
|
259
|
+
fontSize: s.fontSize + 2,
|
|
260
|
+
fontWeight: s.legendBold ? 700 : 400,
|
|
261
|
+
// A highlight is drawn as a text outline (halo) — the clean,
|
|
262
|
+
// SVG-native way to make an axis title stand out.
|
|
263
|
+
outlineWidth: s.legendHighlight ? 3 : 0,
|
|
264
|
+
outlineColor: s.legendHighlight || "transparent"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
238
267
|
},
|
|
239
268
|
grid: { line: { stroke: s.gridColor } },
|
|
240
269
|
crosshair: { line: { stroke: s.textColor, strokeDasharray: "6 6" } },
|
|
@@ -263,14 +292,25 @@ var legendConfig = (style) => {
|
|
|
263
292
|
const ty = s.legendAnchor.includes("top") ? 10 : -10;
|
|
264
293
|
return { ...base, translateX: tx, translateY: ty };
|
|
265
294
|
};
|
|
266
|
-
var
|
|
295
|
+
var truncate = (v, n) => {
|
|
296
|
+
const str = String(v);
|
|
297
|
+
return n > 0 && str.length > n ? `${str.slice(0, n)}\u2026` : str;
|
|
298
|
+
};
|
|
299
|
+
var axisLegend = (style, axis, columnName, opts2 = {}) => {
|
|
267
300
|
const s = withStyleDefaults(style);
|
|
268
301
|
const isX = axis === "x";
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
302
|
+
const show = isX ? s.showXLegend : s.showYLegend;
|
|
303
|
+
const label = (isX ? s.xAxisLabel : s.yAxisLabel) || columnName;
|
|
304
|
+
const out = {};
|
|
305
|
+
if (show && label) {
|
|
306
|
+
out.legend = label;
|
|
307
|
+
out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
|
|
308
|
+
out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
|
|
309
|
+
}
|
|
310
|
+
if (isX) out.tickRotation = s.xTickRotation;
|
|
311
|
+
if (opts2.numeric) out.format = (v) => Number(v).toLocaleString();
|
|
312
|
+
else if (s.tickTruncate > 0) out.format = (v) => truncate(v, s.tickTruncate);
|
|
313
|
+
return out;
|
|
274
314
|
};
|
|
275
315
|
|
|
276
316
|
// src/dataShape.ts
|
|
@@ -334,8 +374,8 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
|
|
|
334
374
|
padding: 0.3,
|
|
335
375
|
colors: ["rgba(74, 222, 128, 0.8)"],
|
|
336
376
|
borderColor: { from: "color", modifiers: [["darker", 1.6]] },
|
|
337
|
-
axisBottom: { tickSize: 5, tickPadding: 5,
|
|
338
|
-
axisLeft: { tickSize: 5, tickPadding: 5,
|
|
377
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", groupColumn) },
|
|
378
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", valueColumn, { numeric: true }) },
|
|
339
379
|
labelSkipWidth: 12,
|
|
340
380
|
labelSkipHeight: 12,
|
|
341
381
|
labelTextColor: { from: "color", modifiers: [["darker", 3]] },
|
|
@@ -408,8 +448,8 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
408
448
|
pointBorderWidth: 2,
|
|
409
449
|
pointBorderColor: { from: "serieColor" },
|
|
410
450
|
enableGridX: false,
|
|
411
|
-
axisBottom: { tickSize: 5, tickPadding: 5,
|
|
412
|
-
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn) },
|
|
451
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
|
|
452
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
|
|
413
453
|
useMesh: true,
|
|
414
454
|
theme: buildNivoTheme(style)
|
|
415
455
|
}
|
|
@@ -418,7 +458,7 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
418
458
|
function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
419
459
|
const computed = react.useMemo(() => {
|
|
420
460
|
if (presetData || !records) return [];
|
|
421
|
-
const points = records.
|
|
461
|
+
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);
|
|
422
462
|
return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
|
|
423
463
|
}, [records, xColumn, yColumn, presetData]);
|
|
424
464
|
const data = presetData || computed;
|
|
@@ -431,8 +471,8 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
431
471
|
yScale: { type: "linear", min: "auto", max: "auto" },
|
|
432
472
|
colors: ["rgba(167, 139, 250, 0.7)"],
|
|
433
473
|
nodeSize: 6,
|
|
434
|
-
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
|
|
435
|
-
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn) },
|
|
474
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn, { numeric: true }) },
|
|
475
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
|
|
436
476
|
useMesh: true,
|
|
437
477
|
theme: buildNivoTheme(style)
|
|
438
478
|
}
|
|
@@ -531,8 +571,8 @@ function HeatmapView({
|
|
|
531
571
|
{
|
|
532
572
|
data,
|
|
533
573
|
margin,
|
|
534
|
-
axisTop: { tickSize: 5, tickPadding: 5, tickRotation:
|
|
535
|
-
axisLeft: { tickSize: 5, tickPadding: 5, legend: rowColumn, legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
574
|
+
axisTop: { tickSize: 5, tickPadding: 5, tickRotation: s.xTickRotation, legend: s.showXLegend ? s.xAxisLabel || colColumn : "", legendPosition: s.xLegendPosition, legendOffset: -50 },
|
|
575
|
+
axisLeft: { tickSize: 5, tickPadding: 5, legend: s.showYLegend ? s.yAxisLabel || rowColumn : "", legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
536
576
|
axisRight: showTotals ? { tickSize: 5, tickPadding: 5, format: (id) => fmtNum(rowTotals[id]), legend: `${marginAgg} \u25B8`, legendOffset: 60 } : null,
|
|
537
577
|
axisBottom: showTotals ? { tickSize: 5, tickPadding: 5, tickRotation: -35, format: (id) => fmtNum(colTotals[id]) } : null,
|
|
538
578
|
colors: { type: "sequential", scheme: "blue_green", minValue: 0 },
|
|
@@ -609,6 +649,37 @@ function NumberControl({ value, onChange, min, max }) {
|
|
|
609
649
|
}
|
|
610
650
|
);
|
|
611
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"
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
|
|
677
|
+
const on = !!value;
|
|
678
|
+
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 })
|
|
681
|
+
] });
|
|
682
|
+
}
|
|
612
683
|
function Choice({ value, options, onChange }) {
|
|
613
684
|
const { theme } = useDashboard();
|
|
614
685
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-28", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -632,19 +703,32 @@ function ChartStyleControls({ style, onChange }) {
|
|
|
632
703
|
/* @__PURE__ */ jsxRuntime.jsxs(Group, { children: [
|
|
633
704
|
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Background", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
|
|
634
705
|
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Text color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
|
|
635
|
-
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Title color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
|
|
636
706
|
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Grid color", children: /* @__PURE__ */ jsxRuntime.jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
|
|
637
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) }) })
|
|
638
708
|
] }),
|
|
639
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) }) }),
|
|
640
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) }) }),
|
|
641
718
|
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "X title offset", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.xLegendOffset, onChange: (v) => set("xLegendOffset", v) }) }),
|
|
642
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) }) }),
|
|
643
720
|
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "Y title offset", children: /* @__PURE__ */ jsxRuntime.jsx(NumberControl, { value: s.yLegendOffset, onChange: (v) => set("yLegendOffset", v) }) })
|
|
644
721
|
] }),
|
|
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) }) })
|
|
725
|
+
] }),
|
|
645
726
|
/* @__PURE__ */ jsxRuntime.jsxs(Group, { last: true, children: [
|
|
646
|
-
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "
|
|
647
|
-
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: "
|
|
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" }) })
|
|
648
732
|
] })
|
|
649
733
|
] });
|
|
650
734
|
}
|
|
@@ -1369,6 +1453,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1369
1453
|
const [showTotals, setShowTotals] = react.useState(false);
|
|
1370
1454
|
const [style, setStyle] = react.useState(DEFAULT_CHART_STYLE);
|
|
1371
1455
|
const [showStyle, setShowStyle] = react.useState(false);
|
|
1456
|
+
const [showFields, setShowFields] = react.useState(false);
|
|
1372
1457
|
const [sqlRows, setSqlRows] = react.useState(null);
|
|
1373
1458
|
const [sqlLoading, setSqlLoading] = react.useState(false);
|
|
1374
1459
|
const [sqlError, setSqlError] = react.useState(null);
|
|
@@ -1654,8 +1739,23 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1654
1739
|
)
|
|
1655
1740
|
] }),
|
|
1656
1741
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-4 border-t border-midnight-border", children: [
|
|
1657
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1658
|
-
|
|
1742
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1743
|
+
"button",
|
|
1744
|
+
{
|
|
1745
|
+
onClick: () => setShowFields((s) => !s),
|
|
1746
|
+
className: "flex items-center gap-1 text-xs uppercase text-midnight-text-muted font-mono hover:text-midnight-text-body transition-colors",
|
|
1747
|
+
children: [
|
|
1748
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `w-3 h-3 transition-transform ${showFields ? "" : "-rotate-90"}` }),
|
|
1749
|
+
"Available Fields",
|
|
1750
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-midnight-text-muted/60 normal-case", children: [
|
|
1751
|
+
"(",
|
|
1752
|
+
columns.length,
|
|
1753
|
+
")"
|
|
1754
|
+
] })
|
|
1755
|
+
]
|
|
1756
|
+
}
|
|
1757
|
+
),
|
|
1758
|
+
showFields && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 mt-2", children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs font-mono px-1 py-0.5", children: [
|
|
1659
1759
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-midnight-text-body", children: col.name }),
|
|
1660
1760
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-midnight-text-muted", children: col.type })
|
|
1661
1761
|
] }, col.name)) })
|
|
@@ -1684,6 +1784,57 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1684
1784
|
function ViewLoading() {
|
|
1685
1785
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
|
|
1686
1786
|
}
|
|
1787
|
+
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap"]);
|
|
1788
|
+
var SQL_DATA_TYPES = /* @__PURE__ */ new Set(["table", "metric"]);
|
|
1789
|
+
var TABLE_PREVIEW_ROWS = 200;
|
|
1790
|
+
function tableSql(config) {
|
|
1791
|
+
const cols = config.columns?.length ? config.columns.map(qIdent).join(", ") : "*";
|
|
1792
|
+
return `SELECT ${cols} FROM data LIMIT ${TABLE_PREVIEW_ROWS}`;
|
|
1793
|
+
}
|
|
1794
|
+
function metricSql(config) {
|
|
1795
|
+
return `SELECT ${aggExpr(config.agg, config.column)} AS v FROM data`;
|
|
1796
|
+
}
|
|
1797
|
+
function panelToChartConfig(chartType, config) {
|
|
1798
|
+
const cfg = { chartType, xFields: [], yFields: [], filters: config.filters || [] };
|
|
1799
|
+
switch (chartType) {
|
|
1800
|
+
case "bar":
|
|
1801
|
+
case "grouped-bar":
|
|
1802
|
+
if (config.group) cfg.xFields = [{ name: config.group }];
|
|
1803
|
+
cfg.yFields = config.yFields?.length ? config.yFields : config.value ? [{ name: config.value, agg: config.agg }] : [];
|
|
1804
|
+
break;
|
|
1805
|
+
case "pie":
|
|
1806
|
+
if (config.group) cfg.xFields = [{ name: config.group }];
|
|
1807
|
+
break;
|
|
1808
|
+
case "line":
|
|
1809
|
+
case "scatter":
|
|
1810
|
+
if (config.x) cfg.xFields = [{ name: config.x }];
|
|
1811
|
+
if (config.y) cfg.yFields = [{ name: config.y }];
|
|
1812
|
+
break;
|
|
1813
|
+
case "heatmap":
|
|
1814
|
+
if (config.row) cfg.xFields = [{ name: config.row }];
|
|
1815
|
+
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
1816
|
+
if (config.value) cfg.valueField = { name: config.value, agg: config.agg };
|
|
1817
|
+
break;
|
|
1818
|
+
}
|
|
1819
|
+
return cfg;
|
|
1820
|
+
}
|
|
1821
|
+
function panelSql(type, config) {
|
|
1822
|
+
if (config.sql) return config.sql;
|
|
1823
|
+
if (type === "table") return tableSql(config);
|
|
1824
|
+
if (type === "metric") return metricSql(config);
|
|
1825
|
+
if (SQL_CHART_TYPES.has(type)) return buildChartSQL(panelToChartConfig(type, config)) || "";
|
|
1826
|
+
return "";
|
|
1827
|
+
}
|
|
1828
|
+
function DataTable({ rows, columns }) {
|
|
1829
|
+
const colNames = columns?.length ? columns : rows[0] ? Object.keys(rows[0]) : [];
|
|
1830
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full", children: [
|
|
1831
|
+
/* @__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)) })
|
|
1836
|
+
] }) });
|
|
1837
|
+
}
|
|
1687
1838
|
function SqlPanel({ panel }) {
|
|
1688
1839
|
const { runQuery, persistPanelData } = useDashboard();
|
|
1689
1840
|
const { canRefresh } = useCapabilities();
|
|
@@ -1691,6 +1842,7 @@ function SqlPanel({ panel }) {
|
|
|
1691
1842
|
runQueryRef.current = runQuery;
|
|
1692
1843
|
const config = panel.config || {};
|
|
1693
1844
|
const chartType = config.chartType || panel.type;
|
|
1845
|
+
const sql = panelSql(chartType, config);
|
|
1694
1846
|
const precomputed = config.data;
|
|
1695
1847
|
const [rows, setRows] = react.useState(precomputed ?? null);
|
|
1696
1848
|
const [error, setError] = react.useState(null);
|
|
@@ -1709,7 +1861,7 @@ function SqlPanel({ panel }) {
|
|
|
1709
1861
|
let cancelled = false;
|
|
1710
1862
|
setLoading(true);
|
|
1711
1863
|
setError(null);
|
|
1712
|
-
runQueryRef.current(
|
|
1864
|
+
runQueryRef.current(sql).then(
|
|
1713
1865
|
(res) => {
|
|
1714
1866
|
if (!cancelled) {
|
|
1715
1867
|
setLoading(false);
|
|
@@ -1727,12 +1879,12 @@ function SqlPanel({ panel }) {
|
|
|
1727
1879
|
return () => {
|
|
1728
1880
|
cancelled = true;
|
|
1729
1881
|
};
|
|
1730
|
-
}, [
|
|
1882
|
+
}, [sql, canRefresh]);
|
|
1731
1883
|
const refresh = react.useCallback(async () => {
|
|
1732
1884
|
setRefreshing(true);
|
|
1733
1885
|
setError(null);
|
|
1734
1886
|
try {
|
|
1735
|
-
const res = await runQueryRef.current(
|
|
1887
|
+
const res = await runQueryRef.current(sql);
|
|
1736
1888
|
const r = res?.rows || [];
|
|
1737
1889
|
setRows(r);
|
|
1738
1890
|
persistPanelData?.(panel.id, r, (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -1741,7 +1893,7 @@ function SqlPanel({ panel }) {
|
|
|
1741
1893
|
} finally {
|
|
1742
1894
|
setRefreshing(false);
|
|
1743
1895
|
}
|
|
1744
|
-
}, [
|
|
1896
|
+
}, [sql, panel.id, persistPanelData]);
|
|
1745
1897
|
const refreshBtn = canRefresh ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1746
1898
|
"button",
|
|
1747
1899
|
{
|
|
@@ -1774,6 +1926,8 @@ function SqlPanel({ panel }) {
|
|
|
1774
1926
|
const first = rows[0];
|
|
1775
1927
|
const v = first ? Object.values(first)[0] : 0;
|
|
1776
1928
|
chart = /* @__PURE__ */ jsxRuntime.jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1929
|
+
} else if (chartType === "table") {
|
|
1930
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows, columns: config.columns });
|
|
1777
1931
|
} else {
|
|
1778
1932
|
const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
|
|
1779
1933
|
switch (chartType) {
|
|
@@ -1812,7 +1966,8 @@ function SqlPanel({ panel }) {
|
|
|
1812
1966
|
function PanelContent({ panel, records, columns }) {
|
|
1813
1967
|
const { type } = panel;
|
|
1814
1968
|
const config = panel.config || {};
|
|
1815
|
-
|
|
1969
|
+
const effType = config.chartType || type;
|
|
1970
|
+
if (config.sql || SQL_CHART_TYPES.has(effType) || SQL_DATA_TYPES.has(effType)) {
|
|
1816
1971
|
return /* @__PURE__ */ jsxRuntime.jsx(SqlPanel, { panel });
|
|
1817
1972
|
}
|
|
1818
1973
|
if (!records?.length && type !== "insight") {
|
|
@@ -1835,17 +1990,8 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1835
1990
|
return /* @__PURE__ */ jsxRuntime.jsx(MetricView, { records, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1836
1991
|
case "insight":
|
|
1837
1992
|
return /* @__PURE__ */ jsxRuntime.jsx(InsightView, { config: { text: config.text } });
|
|
1838
|
-
case "table":
|
|
1839
|
-
|
|
1840
|
-
const colNames = cols?.map((c) => c.name) || (records && records[0] ? Object.keys(records[0]) : []);
|
|
1841
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full", children: [
|
|
1842
|
-
/* @__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)) }) }),
|
|
1843
|
-
/* @__PURE__ */ jsxRuntime.jsx("tbody", { children: (records || []).slice(0, 50).map((r, i) => /* @__PURE__ */ jsxRuntime.jsx("tr", { className: "border-b border-dashed border-midnight-border hover:bg-midnight-raised", children: colNames.map((n) => {
|
|
1844
|
-
const cellValue = r[n];
|
|
1845
|
-
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);
|
|
1846
|
-
}) }, i)) })
|
|
1847
|
-
] }) });
|
|
1848
|
-
}
|
|
1993
|
+
case "table":
|
|
1994
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name) });
|
|
1849
1995
|
default:
|
|
1850
1996
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1851
1997
|
"Unknown panel type: ",
|
|
@@ -1876,7 +2022,19 @@ function DashboardRenderer({ dashboard, records, columns }) {
|
|
|
1876
2022
|
},
|
|
1877
2023
|
children: [
|
|
1878
2024
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated`, children: [
|
|
1879
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
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
|
+
),
|
|
1880
2038
|
canEditPanels && removePanel && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1881
2039
|
"button",
|
|
1882
2040
|
{
|