@quantumwake/terminal-ux-dashboard-components 0.1.8 → 0.1.10
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 +103 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -7
- package/dist/index.d.ts +4 -7
- package/dist/index.js +104 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -44,7 +44,9 @@ function useCapabilities() {
|
|
|
44
44
|
canRefine: !!c.refineDashboard,
|
|
45
45
|
canNew: !!c.newDashboard,
|
|
46
46
|
canAddPanel: !!c.addPanel,
|
|
47
|
-
canEditPanels: !!c.removePanel
|
|
47
|
+
canEditPanels: !!c.removePanel,
|
|
48
|
+
// Studio can recompute + persist a panel's precomputed result.
|
|
49
|
+
canRefresh: !!c.persistPanelData
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -1346,6 +1348,7 @@ var ChartPreview = ({
|
|
|
1346
1348
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "Select a chart type" });
|
|
1347
1349
|
}
|
|
1348
1350
|
};
|
|
1351
|
+
var MAX_PRECOMPUTE_ROWS = 5e3;
|
|
1349
1352
|
function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
1350
1353
|
const { theme, runQuery } = useDashboard();
|
|
1351
1354
|
const runQueryRef = react.useRef(runQuery);
|
|
@@ -1474,6 +1477,10 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1474
1477
|
config.chartType = chartType;
|
|
1475
1478
|
if (filters.length) config.filters = filters;
|
|
1476
1479
|
if (yFields.length) config.yFields = yFields.map((f) => ({ name: f.name, agg: f.agg }));
|
|
1480
|
+
if (sqlRows && sqlRows.length <= MAX_PRECOMPUTE_ROWS) {
|
|
1481
|
+
config.data = sqlRows;
|
|
1482
|
+
config.refreshedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1483
|
+
}
|
|
1477
1484
|
}
|
|
1478
1485
|
config.style = style;
|
|
1479
1486
|
onSave({
|
|
@@ -1678,70 +1685,129 @@ function ViewLoading() {
|
|
|
1678
1685
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
|
|
1679
1686
|
}
|
|
1680
1687
|
function SqlPanel({ panel }) {
|
|
1681
|
-
const { runQuery } = useDashboard();
|
|
1688
|
+
const { runQuery, persistPanelData } = useDashboard();
|
|
1689
|
+
const { canRefresh } = useCapabilities();
|
|
1682
1690
|
const runQueryRef = react.useRef(runQuery);
|
|
1683
1691
|
runQueryRef.current = runQuery;
|
|
1684
1692
|
const config = panel.config || {};
|
|
1685
1693
|
const chartType = config.chartType || panel.type;
|
|
1686
|
-
const
|
|
1694
|
+
const precomputed = config.data;
|
|
1695
|
+
const [rows, setRows] = react.useState(precomputed ?? null);
|
|
1687
1696
|
const [error, setError] = react.useState(null);
|
|
1688
|
-
const [loading, setLoading] = react.useState(
|
|
1697
|
+
const [loading, setLoading] = react.useState(!precomputed && canRefresh);
|
|
1698
|
+
const [refreshing, setRefreshing] = react.useState(false);
|
|
1689
1699
|
react.useEffect(() => {
|
|
1700
|
+
if (precomputed) {
|
|
1701
|
+
setRows(precomputed);
|
|
1702
|
+
setLoading(false);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
if (!canRefresh) {
|
|
1706
|
+
setLoading(false);
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1690
1709
|
let cancelled = false;
|
|
1691
1710
|
setLoading(true);
|
|
1692
1711
|
setError(null);
|
|
1693
|
-
|
|
1694
|
-
runQueryRef.current(sql).then(
|
|
1712
|
+
runQueryRef.current(config.sql || "").then(
|
|
1695
1713
|
(res) => {
|
|
1696
|
-
if (cancelled)
|
|
1697
|
-
|
|
1698
|
-
|
|
1714
|
+
if (!cancelled) {
|
|
1715
|
+
setLoading(false);
|
|
1716
|
+
setRows(res?.rows || []);
|
|
1717
|
+
}
|
|
1699
1718
|
},
|
|
1700
1719
|
(err) => {
|
|
1701
|
-
if (cancelled)
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1720
|
+
if (!cancelled) {
|
|
1721
|
+
setLoading(false);
|
|
1722
|
+
setRows(null);
|
|
1723
|
+
setError(err instanceof Error ? err.message : String(err) || "Query failed");
|
|
1724
|
+
}
|
|
1705
1725
|
}
|
|
1706
1726
|
);
|
|
1707
1727
|
return () => {
|
|
1708
1728
|
cancelled = true;
|
|
1709
1729
|
};
|
|
1710
|
-
}, [config.sql]);
|
|
1730
|
+
}, [config.sql, canRefresh]);
|
|
1731
|
+
const refresh = react.useCallback(async () => {
|
|
1732
|
+
setRefreshing(true);
|
|
1733
|
+
setError(null);
|
|
1734
|
+
try {
|
|
1735
|
+
const res = await runQueryRef.current(config.sql || "");
|
|
1736
|
+
const r = res?.rows || [];
|
|
1737
|
+
setRows(r);
|
|
1738
|
+
persistPanelData?.(panel.id, r, (/* @__PURE__ */ new Date()).toISOString());
|
|
1739
|
+
} catch (err) {
|
|
1740
|
+
setError(err instanceof Error ? err.message : String(err) || "Query failed");
|
|
1741
|
+
} finally {
|
|
1742
|
+
setRefreshing(false);
|
|
1743
|
+
}
|
|
1744
|
+
}, [config.sql, panel.id, persistPanelData]);
|
|
1745
|
+
const refreshBtn = canRefresh ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1746
|
+
"button",
|
|
1747
|
+
{
|
|
1748
|
+
onClick: refresh,
|
|
1749
|
+
disabled: refreshing,
|
|
1750
|
+
title: config.refreshedAt ? `Last computed ${new Date(config.refreshedAt).toLocaleString()} \u2014 click to refresh` : "Compute & save this chart\u2019s data",
|
|
1751
|
+
className: "absolute top-1 right-1 z-10 p-1 bg-midnight-elevated/80 border border-midnight-border text-midnight-text-muted hover:text-midnight-accent transition-colors",
|
|
1752
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: `w-3 h-3 ${refreshing ? "animate-spin" : ""}` })
|
|
1753
|
+
}
|
|
1754
|
+
) : null;
|
|
1711
1755
|
if (loading && !rows) {
|
|
1712
1756
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Running\u2026" });
|
|
1713
1757
|
}
|
|
1758
|
+
if (!rows && !error && !canRefresh) {
|
|
1759
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-1 px-2 text-center text-xs text-midnight-text-muted", children: [
|
|
1760
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-4 h-4" }),
|
|
1761
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Chart data not available" }),
|
|
1762
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-midnight-text-subdued", children: "Download the dataset to view this chart." })
|
|
1763
|
+
] });
|
|
1764
|
+
}
|
|
1714
1765
|
if (error) {
|
|
1715
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1766
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center h-full px-2 text-center text-xs text-red-400", children: [
|
|
1767
|
+
refreshBtn,
|
|
1768
|
+
error
|
|
1769
|
+
] });
|
|
1716
1770
|
}
|
|
1717
1771
|
if (!rows) return null;
|
|
1772
|
+
let chart;
|
|
1718
1773
|
if (chartType === "metric") {
|
|
1719
1774
|
const first = rows[0];
|
|
1720
1775
|
const v = first ? Object.values(first)[0] : 0;
|
|
1721
|
-
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1776
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
|
|
1777
|
+
} else {
|
|
1778
|
+
const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
|
|
1779
|
+
switch (chartType) {
|
|
1780
|
+
case "bar":
|
|
1781
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(BarView, { data: shaped, groupColumn: config.group || "", valueColumn: config.value || "", style: config.style });
|
|
1782
|
+
break;
|
|
1783
|
+
case "grouped-bar": {
|
|
1784
|
+
const gb = shaped;
|
|
1785
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(GroupedBarChart, { data: gb.data, keys: gb.keys, yFields: config.yFields || [], style: config.style });
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
case "pie":
|
|
1789
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(PieView, { data: shaped, groupColumn: config.group || "", style: config.style });
|
|
1790
|
+
break;
|
|
1791
|
+
case "line":
|
|
1792
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(LineView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
|
|
1793
|
+
break;
|
|
1794
|
+
case "scatter":
|
|
1795
|
+
chart = /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
|
|
1796
|
+
break;
|
|
1797
|
+
case "heatmap":
|
|
1798
|
+
chart = shaped.length ? /* @__PURE__ */ jsxRuntime.jsx(HeatmapView, { data: shaped, rowColumn: config.row || "", colColumn: config.col || "", rowOrder: config.rowOrder, colOrder: config.colOrder, marginAgg: config.marginAgg, showTotals: config.showTotals, style: config.style }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
|
|
1799
|
+
break;
|
|
1800
|
+
default:
|
|
1801
|
+
chart = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1802
|
+
"Unsupported SQL chart: ",
|
|
1803
|
+
chartType
|
|
1804
|
+
] });
|
|
1730
1805
|
}
|
|
1731
|
-
case "pie":
|
|
1732
|
-
return /* @__PURE__ */ jsxRuntime.jsx(PieView, { data: shaped, groupColumn: config.group || "", style: config.style });
|
|
1733
|
-
case "line":
|
|
1734
|
-
return /* @__PURE__ */ jsxRuntime.jsx(LineView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
|
|
1735
|
-
case "scatter":
|
|
1736
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
|
|
1737
|
-
case "heatmap":
|
|
1738
|
-
return shaped.length ? /* @__PURE__ */ jsxRuntime.jsx(HeatmapView, { data: shaped, rowColumn: config.row || "", colColumn: config.col || "", rowOrder: config.rowOrder, colOrder: config.colOrder, marginAgg: config.marginAgg, showTotals: config.showTotals, style: config.style }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
|
|
1739
|
-
default:
|
|
1740
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
1741
|
-
"Unsupported SQL chart: ",
|
|
1742
|
-
chartType
|
|
1743
|
-
] });
|
|
1744
1806
|
}
|
|
1807
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative h-full w-full", children: [
|
|
1808
|
+
refreshBtn,
|
|
1809
|
+
chart
|
|
1810
|
+
] });
|
|
1745
1811
|
}
|
|
1746
1812
|
function PanelContent({ panel, records, columns }) {
|
|
1747
1813
|
const { type } = panel;
|