@quantumwake/terminal-ux-dashboard-components 0.1.12 → 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 +167 -41
- 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 +167 -41
- 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)) })
|
|
@@ -1678,6 +1778,15 @@ 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
|
}
|
|
1680
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
|
+
}
|
|
1681
1790
|
function panelToChartConfig(chartType, config) {
|
|
1682
1791
|
const cfg = { chartType, xFields: [], yFields: [], filters: config.filters || [] };
|
|
1683
1792
|
switch (chartType) {
|
|
@@ -1702,11 +1811,23 @@ function panelToChartConfig(chartType, config) {
|
|
|
1702
1811
|
}
|
|
1703
1812
|
return cfg;
|
|
1704
1813
|
}
|
|
1705
|
-
function panelSql(
|
|
1814
|
+
function panelSql(type, config) {
|
|
1706
1815
|
if (config.sql) return config.sql;
|
|
1707
|
-
if (
|
|
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)) || "";
|
|
1708
1819
|
return "";
|
|
1709
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
|
+
}
|
|
1710
1831
|
function SqlPanel({ panel }) {
|
|
1711
1832
|
const { runQuery, persistPanelData } = useDashboard();
|
|
1712
1833
|
const { canRefresh } = useCapabilities();
|
|
@@ -1798,6 +1919,8 @@ function SqlPanel({ panel }) {
|
|
|
1798
1919
|
const first = rows[0];
|
|
1799
1920
|
const v = first ? Object.values(first)[0] : 0;
|
|
1800
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 });
|
|
1801
1924
|
} else {
|
|
1802
1925
|
const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
|
|
1803
1926
|
switch (chartType) {
|
|
@@ -1837,7 +1960,7 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1837
1960
|
const { type } = panel;
|
|
1838
1961
|
const config = panel.config || {};
|
|
1839
1962
|
const effType = config.chartType || type;
|
|
1840
|
-
if (config.sql || SQL_CHART_TYPES.has(effType)) {
|
|
1963
|
+
if (config.sql || SQL_CHART_TYPES.has(effType) || SQL_DATA_TYPES.has(effType)) {
|
|
1841
1964
|
return /* @__PURE__ */ jsx(SqlPanel, { panel });
|
|
1842
1965
|
}
|
|
1843
1966
|
if (!records?.length && type !== "insight") {
|
|
@@ -1860,17 +1983,8 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1860
1983
|
return /* @__PURE__ */ jsx(MetricView, { records, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1861
1984
|
case "insight":
|
|
1862
1985
|
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
|
-
}
|
|
1986
|
+
case "table":
|
|
1987
|
+
return /* @__PURE__ */ jsx(DataTable, { rows: (records || []).slice(0, 50), columns: config.columns || columns?.map((c) => c.name) });
|
|
1874
1988
|
default:
|
|
1875
1989
|
return /* @__PURE__ */ jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1876
1990
|
"Unknown panel type: ",
|
|
@@ -1901,7 +2015,19 @@ function DashboardRenderer({ dashboard, records, columns }) {
|
|
|
1901
2015
|
},
|
|
1902
2016
|
children: [
|
|
1903
2017
|
/* @__PURE__ */ jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated`, children: [
|
|
1904
|
-
/* @__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
|
+
),
|
|
1905
2031
|
canEditPanels && removePanel && /* @__PURE__ */ jsx(
|
|
1906
2032
|
"button",
|
|
1907
2033
|
{
|