@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 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 [rows, setRows] = react.useState(null);
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(true);
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
- const sql = config.sql || "";
1694
- runQueryRef.current(sql).then(
1712
+ runQueryRef.current(config.sql || "").then(
1695
1713
  (res) => {
1696
- if (cancelled) return;
1697
- setLoading(false);
1698
- setRows(res?.rows || []);
1714
+ if (!cancelled) {
1715
+ setLoading(false);
1716
+ setRows(res?.rows || []);
1717
+ }
1699
1718
  },
1700
1719
  (err) => {
1701
- if (cancelled) return;
1702
- setLoading(false);
1703
- setRows(null);
1704
- setError(err instanceof Error ? err.message : String(err) || "Query failed");
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.jsx("div", { className: "flex items-center justify-center h-full text-xs text-red-400 px-2 text-center", children: error });
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
- return /* @__PURE__ */ jsxRuntime.jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
1722
- }
1723
- const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
1724
- switch (chartType) {
1725
- case "bar":
1726
- return /* @__PURE__ */ jsxRuntime.jsx(BarView, { data: shaped, groupColumn: config.group || "", valueColumn: config.value || "", style: config.style });
1727
- case "grouped-bar": {
1728
- const gb = shaped;
1729
- return /* @__PURE__ */ jsxRuntime.jsx(GroupedBarChart, { data: gb.data, keys: gb.keys, yFields: config.yFields || [], style: config.style });
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;