@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.d.cts
CHANGED
|
@@ -98,8 +98,19 @@ interface ChartStyle {
|
|
|
98
98
|
xLegendOffset: number;
|
|
99
99
|
yLegendPosition: LegendPosition;
|
|
100
100
|
yLegendOffset: number;
|
|
101
|
+
xAxisLabel: string;
|
|
102
|
+
yAxisLabel: string;
|
|
103
|
+
showXLegend: boolean;
|
|
104
|
+
showYLegend: boolean;
|
|
105
|
+
legendBold: boolean;
|
|
106
|
+
legendHighlight: string;
|
|
107
|
+
xTickRotation: number;
|
|
108
|
+
tickTruncate: number;
|
|
101
109
|
legendAnchor: LegendAnchor;
|
|
102
110
|
titleAlign: TitleAlign;
|
|
111
|
+
titleBold: boolean;
|
|
112
|
+
titleBackground: string;
|
|
113
|
+
titleColor: string;
|
|
103
114
|
}
|
|
104
115
|
declare const DEFAULT_CHART_STYLE: ChartStyle;
|
|
105
116
|
declare const LEGEND_ANCHORS: LegendAnchor[];
|
|
@@ -121,6 +132,9 @@ declare const buildNivoTheme: (style?: Partial<ChartStyle> | null) => {
|
|
|
121
132
|
text: {
|
|
122
133
|
fill: string;
|
|
123
134
|
fontSize: number;
|
|
135
|
+
fontWeight: number;
|
|
136
|
+
outlineWidth: number;
|
|
137
|
+
outlineColor: string;
|
|
124
138
|
};
|
|
125
139
|
};
|
|
126
140
|
};
|
|
@@ -176,11 +190,9 @@ declare const legendConfig: (style?: Partial<ChartStyle> | null) => {
|
|
|
176
190
|
symbolShape: "circle";
|
|
177
191
|
itemTextColor: string;
|
|
178
192
|
} | null;
|
|
179
|
-
declare const axisLegend: (style: Partial<ChartStyle> | null | undefined, axis: "x" | "y",
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
legendOffset: number;
|
|
183
|
-
};
|
|
193
|
+
declare const axisLegend: (style: Partial<ChartStyle> | null | undefined, axis: "x" | "y", columnName: string, opts?: {
|
|
194
|
+
numeric?: boolean;
|
|
195
|
+
}) => Record<string, unknown>;
|
|
184
196
|
|
|
185
197
|
type AggFn = 'count' | 'distinct' | 'sum' | 'avg' | 'min' | 'max';
|
|
186
198
|
declare const groupBy: (records: Row[], column: string) => Record<string, Row[]>;
|
package/dist/index.d.ts
CHANGED
|
@@ -98,8 +98,19 @@ interface ChartStyle {
|
|
|
98
98
|
xLegendOffset: number;
|
|
99
99
|
yLegendPosition: LegendPosition;
|
|
100
100
|
yLegendOffset: number;
|
|
101
|
+
xAxisLabel: string;
|
|
102
|
+
yAxisLabel: string;
|
|
103
|
+
showXLegend: boolean;
|
|
104
|
+
showYLegend: boolean;
|
|
105
|
+
legendBold: boolean;
|
|
106
|
+
legendHighlight: string;
|
|
107
|
+
xTickRotation: number;
|
|
108
|
+
tickTruncate: number;
|
|
101
109
|
legendAnchor: LegendAnchor;
|
|
102
110
|
titleAlign: TitleAlign;
|
|
111
|
+
titleBold: boolean;
|
|
112
|
+
titleBackground: string;
|
|
113
|
+
titleColor: string;
|
|
103
114
|
}
|
|
104
115
|
declare const DEFAULT_CHART_STYLE: ChartStyle;
|
|
105
116
|
declare const LEGEND_ANCHORS: LegendAnchor[];
|
|
@@ -121,6 +132,9 @@ declare const buildNivoTheme: (style?: Partial<ChartStyle> | null) => {
|
|
|
121
132
|
text: {
|
|
122
133
|
fill: string;
|
|
123
134
|
fontSize: number;
|
|
135
|
+
fontWeight: number;
|
|
136
|
+
outlineWidth: number;
|
|
137
|
+
outlineColor: string;
|
|
124
138
|
};
|
|
125
139
|
};
|
|
126
140
|
};
|
|
@@ -176,11 +190,9 @@ declare const legendConfig: (style?: Partial<ChartStyle> | null) => {
|
|
|
176
190
|
symbolShape: "circle";
|
|
177
191
|
itemTextColor: string;
|
|
178
192
|
} | null;
|
|
179
|
-
declare const axisLegend: (style: Partial<ChartStyle> | null | undefined, axis: "x" | "y",
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
legendOffset: number;
|
|
183
|
-
};
|
|
193
|
+
declare const axisLegend: (style: Partial<ChartStyle> | null | undefined, axis: "x" | "y", columnName: string, opts?: {
|
|
194
|
+
numeric?: boolean;
|
|
195
|
+
}) => Record<string, unknown>;
|
|
184
196
|
|
|
185
197
|
type AggFn = 'count' | 'distinct' | 'sum' | 'avg' | 'min' | 'max';
|
|
186
198
|
declare const groupBy: (records: Row[], column: string) => Record<string, Row[]>;
|
package/dist/index.js
CHANGED
|
@@ -135,8 +135,10 @@ var buildChartSQL = ({
|
|
|
135
135
|
}
|
|
136
136
|
case "scatter": {
|
|
137
137
|
if (!x || !y) return null;
|
|
138
|
-
const
|
|
139
|
-
|
|
138
|
+
const nx = `TRY_CAST(${qIdent(x)} AS DOUBLE)`, ny = `TRY_CAST(${qIdent(y)} AS DOUBLE)`;
|
|
139
|
+
const extra = `${nx} IS NOT NULL AND ${ny} IS NOT NULL`;
|
|
140
|
+
const w = where ? `${where} AND ${extra}` : ` WHERE ${extra}`;
|
|
141
|
+
return `SELECT ${nx} AS x, ${ny} AS y FROM ${TABLE}${w} LIMIT ${MAX_POINTS}`;
|
|
140
142
|
}
|
|
141
143
|
case "heatmap": {
|
|
142
144
|
if (!x || !y) return null;
|
|
@@ -168,7 +170,10 @@ var shapeChartData = (chartType, rows, { yFields = [] } = {}) => {
|
|
|
168
170
|
case "line":
|
|
169
171
|
return [{ id: "series", data: rows.map((r) => ({ x: String(r.x), y: Number(r.y) || 0 })) }];
|
|
170
172
|
case "scatter":
|
|
171
|
-
return [{
|
|
173
|
+
return [{
|
|
174
|
+
id: "points",
|
|
175
|
+
data: rows.map((r) => ({ x: Number(r.x), y: Number(r.y) })).filter((p) => Number.isFinite(p.x) && Number.isFinite(p.y))
|
|
176
|
+
}];
|
|
172
177
|
case "heatmap": {
|
|
173
178
|
const rowKeys = [];
|
|
174
179
|
const colKeys = [];
|
|
@@ -210,10 +215,24 @@ var DEFAULT_CHART_STYLE = {
|
|
|
210
215
|
xLegendOffset: 46,
|
|
211
216
|
yLegendPosition: "middle",
|
|
212
217
|
yLegendOffset: -50,
|
|
218
|
+
// Custom axis title text + visibility.
|
|
219
|
+
xAxisLabel: "",
|
|
220
|
+
yAxisLabel: "",
|
|
221
|
+
showXLegend: true,
|
|
222
|
+
showYLegend: true,
|
|
223
|
+
// Axis title emphasis.
|
|
224
|
+
legendBold: false,
|
|
225
|
+
legendHighlight: "",
|
|
226
|
+
// Tick overflow handling.
|
|
227
|
+
xTickRotation: -35,
|
|
228
|
+
tickTruncate: 0,
|
|
213
229
|
// Series legend placement (charts that have one: pie, grouped bar).
|
|
214
230
|
legendAnchor: "right",
|
|
215
|
-
// Panel title
|
|
216
|
-
titleAlign: "left"
|
|
231
|
+
// Panel title (rendered by DashboardRenderer's panel header).
|
|
232
|
+
titleAlign: "left",
|
|
233
|
+
titleBold: false,
|
|
234
|
+
titleBackground: "",
|
|
235
|
+
titleColor: ""
|
|
217
236
|
};
|
|
218
237
|
var LEGEND_ANCHORS = ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"];
|
|
219
238
|
var withStyleDefaults = (style) => ({
|
|
@@ -227,7 +246,17 @@ var buildNivoTheme = (style) => {
|
|
|
227
246
|
text: { fill: s.textColor, fontSize: s.fontSize },
|
|
228
247
|
axis: {
|
|
229
248
|
ticks: { text: { fill: s.textColor, fontSize: s.fontSize } },
|
|
230
|
-
legend: {
|
|
249
|
+
legend: {
|
|
250
|
+
text: {
|
|
251
|
+
fill: s.legendColor,
|
|
252
|
+
fontSize: s.fontSize + 2,
|
|
253
|
+
fontWeight: s.legendBold ? 700 : 400,
|
|
254
|
+
// A highlight is drawn as a text outline (halo) — the clean,
|
|
255
|
+
// SVG-native way to make an axis title stand out.
|
|
256
|
+
outlineWidth: s.legendHighlight ? 3 : 0,
|
|
257
|
+
outlineColor: s.legendHighlight || "transparent"
|
|
258
|
+
}
|
|
259
|
+
}
|
|
231
260
|
},
|
|
232
261
|
grid: { line: { stroke: s.gridColor } },
|
|
233
262
|
crosshair: { line: { stroke: s.textColor, strokeDasharray: "6 6" } },
|
|
@@ -256,14 +285,25 @@ var legendConfig = (style) => {
|
|
|
256
285
|
const ty = s.legendAnchor.includes("top") ? 10 : -10;
|
|
257
286
|
return { ...base, translateX: tx, translateY: ty };
|
|
258
287
|
};
|
|
259
|
-
var
|
|
288
|
+
var truncate = (v, n) => {
|
|
289
|
+
const str = String(v);
|
|
290
|
+
return n > 0 && str.length > n ? `${str.slice(0, n)}\u2026` : str;
|
|
291
|
+
};
|
|
292
|
+
var axisLegend = (style, axis, columnName, opts2 = {}) => {
|
|
260
293
|
const s = withStyleDefaults(style);
|
|
261
294
|
const isX = axis === "x";
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
295
|
+
const show = isX ? s.showXLegend : s.showYLegend;
|
|
296
|
+
const label = (isX ? s.xAxisLabel : s.yAxisLabel) || columnName;
|
|
297
|
+
const out = {};
|
|
298
|
+
if (show && label) {
|
|
299
|
+
out.legend = label;
|
|
300
|
+
out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
|
|
301
|
+
out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
|
|
302
|
+
}
|
|
303
|
+
if (isX) out.tickRotation = s.xTickRotation;
|
|
304
|
+
if (opts2.numeric) out.format = (v) => Number(v).toLocaleString();
|
|
305
|
+
else if (s.tickTruncate > 0) out.format = (v) => truncate(v, s.tickTruncate);
|
|
306
|
+
return out;
|
|
267
307
|
};
|
|
268
308
|
|
|
269
309
|
// src/dataShape.ts
|
|
@@ -327,8 +367,8 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
|
|
|
327
367
|
padding: 0.3,
|
|
328
368
|
colors: ["rgba(74, 222, 128, 0.8)"],
|
|
329
369
|
borderColor: { from: "color", modifiers: [["darker", 1.6]] },
|
|
330
|
-
axisBottom: { tickSize: 5, tickPadding: 5,
|
|
331
|
-
axisLeft: { tickSize: 5, tickPadding: 5,
|
|
370
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", groupColumn) },
|
|
371
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", valueColumn, { numeric: true }) },
|
|
332
372
|
labelSkipWidth: 12,
|
|
333
373
|
labelSkipHeight: 12,
|
|
334
374
|
labelTextColor: { from: "color", modifiers: [["darker", 3]] },
|
|
@@ -401,8 +441,8 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
401
441
|
pointBorderWidth: 2,
|
|
402
442
|
pointBorderColor: { from: "serieColor" },
|
|
403
443
|
enableGridX: false,
|
|
404
|
-
axisBottom: { tickSize: 5, tickPadding: 5,
|
|
405
|
-
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn) },
|
|
444
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
|
|
445
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
|
|
406
446
|
useMesh: true,
|
|
407
447
|
theme: buildNivoTheme(style)
|
|
408
448
|
}
|
|
@@ -411,7 +451,7 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
411
451
|
function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
412
452
|
const computed = useMemo(() => {
|
|
413
453
|
if (presetData || !records) return [];
|
|
414
|
-
const points = records.
|
|
454
|
+
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
455
|
return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
|
|
416
456
|
}, [records, xColumn, yColumn, presetData]);
|
|
417
457
|
const data = presetData || computed;
|
|
@@ -424,8 +464,8 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
424
464
|
yScale: { type: "linear", min: "auto", max: "auto" },
|
|
425
465
|
colors: ["rgba(167, 139, 250, 0.7)"],
|
|
426
466
|
nodeSize: 6,
|
|
427
|
-
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn) },
|
|
428
|
-
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn) },
|
|
467
|
+
axisBottom: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "x", xColumn, { numeric: true }) },
|
|
468
|
+
axisLeft: { tickSize: 5, tickPadding: 5, ...axisLegend(style, "y", yColumn, { numeric: true }) },
|
|
429
469
|
useMesh: true,
|
|
430
470
|
theme: buildNivoTheme(style)
|
|
431
471
|
}
|
|
@@ -524,8 +564,8 @@ function HeatmapView({
|
|
|
524
564
|
{
|
|
525
565
|
data,
|
|
526
566
|
margin,
|
|
527
|
-
axisTop: { tickSize: 5, tickPadding: 5, tickRotation:
|
|
528
|
-
axisLeft: { tickSize: 5, tickPadding: 5, legend: rowColumn, legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
567
|
+
axisTop: { tickSize: 5, tickPadding: 5, tickRotation: s.xTickRotation, legend: s.showXLegend ? s.xAxisLabel || colColumn : "", legendPosition: s.xLegendPosition, legendOffset: -50 },
|
|
568
|
+
axisLeft: { tickSize: 5, tickPadding: 5, legend: s.showYLegend ? s.yAxisLabel || rowColumn : "", legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
529
569
|
axisRight: showTotals ? { tickSize: 5, tickPadding: 5, format: (id) => fmtNum(rowTotals[id]), legend: `${marginAgg} \u25B8`, legendOffset: 60 } : null,
|
|
530
570
|
axisBottom: showTotals ? { tickSize: 5, tickPadding: 5, tickRotation: -35, format: (id) => fmtNum(colTotals[id]) } : null,
|
|
531
571
|
colors: { type: "sequential", scheme: "blue_green", minValue: 0 },
|
|
@@ -602,6 +642,37 @@ function NumberControl({ value, onChange, min, max }) {
|
|
|
602
642
|
}
|
|
603
643
|
);
|
|
604
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"
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
|
|
670
|
+
const on = !!value;
|
|
671
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
672
|
+
/* @__PURE__ */ jsx(Toggle, { value: on, onChange: (v) => onChange(v ? fallback : "") }),
|
|
673
|
+
on && /* @__PURE__ */ jsx(ColorControl, { value, onChange })
|
|
674
|
+
] });
|
|
675
|
+
}
|
|
605
676
|
function Choice({ value, options, onChange }) {
|
|
606
677
|
const { theme } = useDashboard();
|
|
607
678
|
return /* @__PURE__ */ jsx("div", { className: "w-28", children: /* @__PURE__ */ jsx(
|
|
@@ -625,19 +696,32 @@ function ChartStyleControls({ style, onChange }) {
|
|
|
625
696
|
/* @__PURE__ */ jsxs(Group, { children: [
|
|
626
697
|
/* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
|
|
627
698
|
/* @__PURE__ */ jsx(Row, { label: "Text color", children: /* @__PURE__ */ jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
|
|
628
|
-
/* @__PURE__ */ jsx(Row, { label: "Title color", children: /* @__PURE__ */ jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
|
|
629
699
|
/* @__PURE__ */ jsx(Row, { label: "Grid color", children: /* @__PURE__ */ jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
|
|
630
700
|
/* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(NumberControl, { value: s.fontSize, min: 6, max: 24, onChange: (v) => set("fontSize", v) }) })
|
|
631
701
|
] }),
|
|
632
702
|
/* @__PURE__ */ jsxs(Group, { children: [
|
|
703
|
+
/* @__PURE__ */ jsx(Row, { label: "X axis title", children: /* @__PURE__ */ jsx(TextControl, { value: s.xAxisLabel, placeholder: "(column)", onChange: (v) => set("xAxisLabel", v) }) }),
|
|
704
|
+
/* @__PURE__ */ jsx(Row, { label: "Show X title", children: /* @__PURE__ */ jsx(Toggle, { value: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
|
|
705
|
+
/* @__PURE__ */ jsx(Row, { label: "Y axis title", children: /* @__PURE__ */ jsx(TextControl, { value: s.yAxisLabel, placeholder: "(column)", onChange: (v) => set("yAxisLabel", v) }) }),
|
|
706
|
+
/* @__PURE__ */ jsx(Row, { label: "Show Y title", children: /* @__PURE__ */ jsx(Toggle, { value: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
|
|
707
|
+
/* @__PURE__ */ jsx(Row, { label: "Title color", children: /* @__PURE__ */ jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
|
|
708
|
+
/* @__PURE__ */ jsx(Row, { label: "Title bold", children: /* @__PURE__ */ jsx(Toggle, { value: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
|
|
709
|
+
/* @__PURE__ */ jsx(Row, { label: "Title highlight", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) }),
|
|
633
710
|
/* @__PURE__ */ jsx(Row, { label: "X title pos", children: /* @__PURE__ */ jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
|
|
634
711
|
/* @__PURE__ */ jsx(Row, { label: "X title offset", children: /* @__PURE__ */ jsx(NumberControl, { value: s.xLegendOffset, onChange: (v) => set("xLegendOffset", v) }) }),
|
|
635
712
|
/* @__PURE__ */ jsx(Row, { label: "Y title pos", children: /* @__PURE__ */ jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
|
|
636
713
|
/* @__PURE__ */ jsx(Row, { label: "Y title offset", children: /* @__PURE__ */ jsx(NumberControl, { value: s.yLegendOffset, onChange: (v) => set("yLegendOffset", v) }) })
|
|
637
714
|
] }),
|
|
715
|
+
/* @__PURE__ */ jsxs(Group, { children: [
|
|
716
|
+
/* @__PURE__ */ jsx(Row, { label: "X tick angle", children: /* @__PURE__ */ jsx(NumberControl, { value: s.xTickRotation, min: -90, max: 90, onChange: (v) => set("xTickRotation", v) }) }),
|
|
717
|
+
/* @__PURE__ */ jsx(Row, { label: "Truncate ticks", children: /* @__PURE__ */ jsx(NumberControl, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
|
|
718
|
+
] }),
|
|
638
719
|
/* @__PURE__ */ jsxs(Group, { last: true, children: [
|
|
639
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
640
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
720
|
+
/* @__PURE__ */ jsx(Row, { label: "Series legend", children: /* @__PURE__ */ jsx(Choice, { value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }),
|
|
721
|
+
/* @__PURE__ */ jsx(Row, { label: "Panel title align", children: /* @__PURE__ */ jsx(Choice, { value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
|
|
722
|
+
/* @__PURE__ */ jsx(Row, { label: "Panel title bold", children: /* @__PURE__ */ jsx(Toggle, { value: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
|
|
723
|
+
/* @__PURE__ */ jsx(Row, { label: "Panel title color", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
|
|
724
|
+
/* @__PURE__ */ jsx(Row, { label: "Panel title bg", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
|
|
641
725
|
] })
|
|
642
726
|
] });
|
|
643
727
|
}
|
|
@@ -1362,6 +1446,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1362
1446
|
const [showTotals, setShowTotals] = useState(false);
|
|
1363
1447
|
const [style, setStyle] = useState(DEFAULT_CHART_STYLE);
|
|
1364
1448
|
const [showStyle, setShowStyle] = useState(false);
|
|
1449
|
+
const [showFields, setShowFields] = useState(false);
|
|
1365
1450
|
const [sqlRows, setSqlRows] = useState(null);
|
|
1366
1451
|
const [sqlLoading, setSqlLoading] = useState(false);
|
|
1367
1452
|
const [sqlError, setSqlError] = useState(null);
|
|
@@ -1647,8 +1732,23 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1647
1732
|
)
|
|
1648
1733
|
] }),
|
|
1649
1734
|
/* @__PURE__ */ jsxs("div", { className: "pt-4 border-t border-midnight-border", children: [
|
|
1650
|
-
/* @__PURE__ */
|
|
1651
|
-
|
|
1735
|
+
/* @__PURE__ */ jsxs(
|
|
1736
|
+
"button",
|
|
1737
|
+
{
|
|
1738
|
+
onClick: () => setShowFields((s) => !s),
|
|
1739
|
+
className: "flex items-center gap-1 text-xs uppercase text-midnight-text-muted font-mono hover:text-midnight-text-body transition-colors",
|
|
1740
|
+
children: [
|
|
1741
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: `w-3 h-3 transition-transform ${showFields ? "" : "-rotate-90"}` }),
|
|
1742
|
+
"Available Fields",
|
|
1743
|
+
/* @__PURE__ */ jsxs("span", { className: "text-midnight-text-muted/60 normal-case", children: [
|
|
1744
|
+
"(",
|
|
1745
|
+
columns.length,
|
|
1746
|
+
")"
|
|
1747
|
+
] })
|
|
1748
|
+
]
|
|
1749
|
+
}
|
|
1750
|
+
),
|
|
1751
|
+
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
1752
|
/* @__PURE__ */ jsx("span", { className: "text-midnight-text-body", children: col.name }),
|
|
1653
1753
|
/* @__PURE__ */ jsx("span", { className: "text-midnight-text-muted", children: col.type })
|
|
1654
1754
|
] }, col.name)) })
|
|
@@ -1677,6 +1777,57 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1677
1777
|
function ViewLoading() {
|
|
1678
1778
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
|
|
1679
1779
|
}
|
|
1780
|
+
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap"]);
|
|
1781
|
+
var SQL_DATA_TYPES = /* @__PURE__ */ new Set(["table", "metric"]);
|
|
1782
|
+
var TABLE_PREVIEW_ROWS = 200;
|
|
1783
|
+
function tableSql(config) {
|
|
1784
|
+
const cols = config.columns?.length ? config.columns.map(qIdent).join(", ") : "*";
|
|
1785
|
+
return `SELECT ${cols} FROM data LIMIT ${TABLE_PREVIEW_ROWS}`;
|
|
1786
|
+
}
|
|
1787
|
+
function metricSql(config) {
|
|
1788
|
+
return `SELECT ${aggExpr(config.agg, config.column)} AS v FROM data`;
|
|
1789
|
+
}
|
|
1790
|
+
function panelToChartConfig(chartType, config) {
|
|
1791
|
+
const cfg = { chartType, xFields: [], yFields: [], filters: config.filters || [] };
|
|
1792
|
+
switch (chartType) {
|
|
1793
|
+
case "bar":
|
|
1794
|
+
case "grouped-bar":
|
|
1795
|
+
if (config.group) cfg.xFields = [{ name: config.group }];
|
|
1796
|
+
cfg.yFields = config.yFields?.length ? config.yFields : config.value ? [{ name: config.value, agg: config.agg }] : [];
|
|
1797
|
+
break;
|
|
1798
|
+
case "pie":
|
|
1799
|
+
if (config.group) cfg.xFields = [{ name: config.group }];
|
|
1800
|
+
break;
|
|
1801
|
+
case "line":
|
|
1802
|
+
case "scatter":
|
|
1803
|
+
if (config.x) cfg.xFields = [{ name: config.x }];
|
|
1804
|
+
if (config.y) cfg.yFields = [{ name: config.y }];
|
|
1805
|
+
break;
|
|
1806
|
+
case "heatmap":
|
|
1807
|
+
if (config.row) cfg.xFields = [{ name: config.row }];
|
|
1808
|
+
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
1809
|
+
if (config.value) cfg.valueField = { name: config.value, agg: config.agg };
|
|
1810
|
+
break;
|
|
1811
|
+
}
|
|
1812
|
+
return cfg;
|
|
1813
|
+
}
|
|
1814
|
+
function panelSql(type, config) {
|
|
1815
|
+
if (config.sql) return config.sql;
|
|
1816
|
+
if (type === "table") return tableSql(config);
|
|
1817
|
+
if (type === "metric") return metricSql(config);
|
|
1818
|
+
if (SQL_CHART_TYPES.has(type)) return buildChartSQL(panelToChartConfig(type, config)) || "";
|
|
1819
|
+
return "";
|
|
1820
|
+
}
|
|
1821
|
+
function DataTable({ rows, columns }) {
|
|
1822
|
+
const colNames = columns?.length ? columns : rows[0] ? Object.keys(rows[0]) : [];
|
|
1823
|
+
return /* @__PURE__ */ jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
1824
|
+
/* @__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)) })
|
|
1829
|
+
] }) });
|
|
1830
|
+
}
|
|
1680
1831
|
function SqlPanel({ panel }) {
|
|
1681
1832
|
const { runQuery, persistPanelData } = useDashboard();
|
|
1682
1833
|
const { canRefresh } = useCapabilities();
|
|
@@ -1684,6 +1835,7 @@ function SqlPanel({ panel }) {
|
|
|
1684
1835
|
runQueryRef.current = runQuery;
|
|
1685
1836
|
const config = panel.config || {};
|
|
1686
1837
|
const chartType = config.chartType || panel.type;
|
|
1838
|
+
const sql = panelSql(chartType, config);
|
|
1687
1839
|
const precomputed = config.data;
|
|
1688
1840
|
const [rows, setRows] = useState(precomputed ?? null);
|
|
1689
1841
|
const [error, setError] = useState(null);
|
|
@@ -1702,7 +1854,7 @@ function SqlPanel({ panel }) {
|
|
|
1702
1854
|
let cancelled = false;
|
|
1703
1855
|
setLoading(true);
|
|
1704
1856
|
setError(null);
|
|
1705
|
-
runQueryRef.current(
|
|
1857
|
+
runQueryRef.current(sql).then(
|
|
1706
1858
|
(res) => {
|
|
1707
1859
|
if (!cancelled) {
|
|
1708
1860
|
setLoading(false);
|
|
@@ -1720,12 +1872,12 @@ function SqlPanel({ panel }) {
|
|
|
1720
1872
|
return () => {
|
|
1721
1873
|
cancelled = true;
|
|
1722
1874
|
};
|
|
1723
|
-
}, [
|
|
1875
|
+
}, [sql, canRefresh]);
|
|
1724
1876
|
const refresh = useCallback(async () => {
|
|
1725
1877
|
setRefreshing(true);
|
|
1726
1878
|
setError(null);
|
|
1727
1879
|
try {
|
|
1728
|
-
const res = await runQueryRef.current(
|
|
1880
|
+
const res = await runQueryRef.current(sql);
|
|
1729
1881
|
const r = res?.rows || [];
|
|
1730
1882
|
setRows(r);
|
|
1731
1883
|
persistPanelData?.(panel.id, r, (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -1734,7 +1886,7 @@ function SqlPanel({ panel }) {
|
|
|
1734
1886
|
} finally {
|
|
1735
1887
|
setRefreshing(false);
|
|
1736
1888
|
}
|
|
1737
|
-
}, [
|
|
1889
|
+
}, [sql, panel.id, persistPanelData]);
|
|
1738
1890
|
const refreshBtn = canRefresh ? /* @__PURE__ */ jsx(
|
|
1739
1891
|
"button",
|
|
1740
1892
|
{
|
|
@@ -1767,6 +1919,8 @@ function SqlPanel({ panel }) {
|
|
|
1767
1919
|
const first = rows[0];
|
|
1768
1920
|
const v = first ? Object.values(first)[0] : 0;
|
|
1769
1921
|
chart = /* @__PURE__ */ jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1922
|
+
} else if (chartType === "table") {
|
|
1923
|
+
chart = /* @__PURE__ */ jsx(DataTable, { rows, columns: config.columns });
|
|
1770
1924
|
} else {
|
|
1771
1925
|
const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
|
|
1772
1926
|
switch (chartType) {
|
|
@@ -1805,7 +1959,8 @@ function SqlPanel({ panel }) {
|
|
|
1805
1959
|
function PanelContent({ panel, records, columns }) {
|
|
1806
1960
|
const { type } = panel;
|
|
1807
1961
|
const config = panel.config || {};
|
|
1808
|
-
|
|
1962
|
+
const effType = config.chartType || type;
|
|
1963
|
+
if (config.sql || SQL_CHART_TYPES.has(effType) || SQL_DATA_TYPES.has(effType)) {
|
|
1809
1964
|
return /* @__PURE__ */ jsx(SqlPanel, { panel });
|
|
1810
1965
|
}
|
|
1811
1966
|
if (!records?.length && type !== "insight") {
|
|
@@ -1828,17 +1983,8 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1828
1983
|
return /* @__PURE__ */ jsx(MetricView, { records, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1829
1984
|
case "insight":
|
|
1830
1985
|
return /* @__PURE__ */ jsx(InsightView, { config: { text: config.text } });
|
|
1831
|
-
case "table":
|
|
1832
|
-
|
|
1833
|
-
const colNames = cols?.map((c) => c.name) || (records && records[0] ? Object.keys(records[0]) : []);
|
|
1834
|
-
return /* @__PURE__ */ jsx("div", { className: "overflow-auto h-full text-xs", children: /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
1835
|
-
/* @__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)) }) }),
|
|
1836
|
-
/* @__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) => {
|
|
1837
|
-
const cellValue = r[n];
|
|
1838
|
-
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);
|
|
1839
|
-
}) }, i)) })
|
|
1840
|
-
] }) });
|
|
1841
|
-
}
|
|
1986
|
+
case "table":
|
|
1987
|
+
return /* @__PURE__ */ jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name) });
|
|
1842
1988
|
default:
|
|
1843
1989
|
return /* @__PURE__ */ jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1844
1990
|
"Unknown panel type: ",
|
|
@@ -1869,7 +2015,19 @@ function DashboardRenderer({ dashboard, records, columns }) {
|
|
|
1869
2015
|
},
|
|
1870
2016
|
children: [
|
|
1871
2017
|
/* @__PURE__ */ jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated`, children: [
|
|
1872
|
-
/* @__PURE__ */ jsx(
|
|
2018
|
+
/* @__PURE__ */ jsx(
|
|
2019
|
+
"span",
|
|
2020
|
+
{
|
|
2021
|
+
className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
|
|
2022
|
+
style: {
|
|
2023
|
+
textAlign: panel.config?.style?.titleAlign || "left",
|
|
2024
|
+
fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
|
|
2025
|
+
background: panel.config?.style?.titleBackground || void 0,
|
|
2026
|
+
color: panel.config?.style?.titleColor || void 0
|
|
2027
|
+
},
|
|
2028
|
+
children: panel.title || panel.type
|
|
2029
|
+
}
|
|
2030
|
+
),
|
|
1873
2031
|
canEditPanels && removePanel && /* @__PURE__ */ jsx(
|
|
1874
2032
|
"button",
|
|
1875
2033
|
{
|