@quantumwake/terminal-ux-dashboard-components 0.1.7 → 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.d.cts CHANGED
@@ -30,6 +30,7 @@ interface DashboardCapabilities {
30
30
  newDashboard?: () => void;
31
31
  addPanel?: (panel: PanelInput) => void;
32
32
  removePanel?: (panelId: string) => void;
33
+ persistPanelData?: (panelId: string, rows: QueryResult['rows'], refreshedAt: string) => void;
33
34
  }
34
35
  interface DashboardContextValue extends DashboardCapabilities {
35
36
  theme: DashboardTheme;
@@ -52,6 +53,7 @@ declare function useCapabilities(): {
52
53
  canNew: boolean;
53
54
  canAddPanel: boolean;
54
55
  canEditPanels: boolean;
56
+ canRefresh: boolean;
55
57
  };
56
58
 
57
59
  type ChartType = 'bar' | 'grouped-bar' | 'pie' | 'line' | 'scatter' | 'heatmap';
@@ -358,6 +360,8 @@ interface PanelConfig {
358
360
  text?: string;
359
361
  label?: string;
360
362
  column?: string;
363
+ data?: Row[];
364
+ refreshedAt?: string;
361
365
  [key: string]: unknown;
362
366
  }
363
367
  interface DashboardPanel$1 {
@@ -404,13 +408,6 @@ interface ChartBuilderProps {
404
408
  stateId?: string;
405
409
  onSave?: (panel: ChartPanel) => void;
406
410
  }
407
- /**
408
- * ChartBuilder — visual chart authoring over a state's dataset. Generates DuckDB
409
- * SQL from the visual config (SQL engine mode, exact over the full dataset) or
410
- * aggregates the loaded sample client-side (Direct mode). The host injects
411
- * runQuery + theme via <DashboardProvider>; an optional onSave persists the
412
- * assembled panel (absent ⇒ the save affordance is hidden / read-only).
413
- */
414
411
  declare function ChartBuilder({ records, columns, stateId, onSave }: ChartBuilderProps): react.JSX.Element;
415
412
 
416
413
  /** A dataset column descriptor: name + DuckDB-ish type tag. */
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ interface DashboardCapabilities {
30
30
  newDashboard?: () => void;
31
31
  addPanel?: (panel: PanelInput) => void;
32
32
  removePanel?: (panelId: string) => void;
33
+ persistPanelData?: (panelId: string, rows: QueryResult['rows'], refreshedAt: string) => void;
33
34
  }
34
35
  interface DashboardContextValue extends DashboardCapabilities {
35
36
  theme: DashboardTheme;
@@ -52,6 +53,7 @@ declare function useCapabilities(): {
52
53
  canNew: boolean;
53
54
  canAddPanel: boolean;
54
55
  canEditPanels: boolean;
56
+ canRefresh: boolean;
55
57
  };
56
58
 
57
59
  type ChartType = 'bar' | 'grouped-bar' | 'pie' | 'line' | 'scatter' | 'heatmap';
@@ -358,6 +360,8 @@ interface PanelConfig {
358
360
  text?: string;
359
361
  label?: string;
360
362
  column?: string;
363
+ data?: Row[];
364
+ refreshedAt?: string;
361
365
  [key: string]: unknown;
362
366
  }
363
367
  interface DashboardPanel$1 {
@@ -404,13 +408,6 @@ interface ChartBuilderProps {
404
408
  stateId?: string;
405
409
  onSave?: (panel: ChartPanel) => void;
406
410
  }
407
- /**
408
- * ChartBuilder — visual chart authoring over a state's dataset. Generates DuckDB
409
- * SQL from the visual config (SQL engine mode, exact over the full dataset) or
410
- * aggregates the loaded sample client-side (Direct mode). The host injects
411
- * runQuery + theme via <DashboardProvider>; an optional onSave persists the
412
- * assembled panel (absent ⇒ the save affordance is hidden / read-only).
413
- */
414
411
  declare function ChartBuilder({ records, columns, stateId, onSave }: ChartBuilderProps): react.JSX.Element;
415
412
 
416
413
  /** A dataset column descriptor: name + DuckDB-ish type tag. */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useMemo, useState, useEffect, useRef, Suspense } from 'react';
1
+ import { createContext, useContext, useMemo, useState, useEffect, useRef, Suspense, useCallback } from 'react';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { ResponsiveBar } from '@nivo/bar';
4
4
  import { ResponsivePie } from '@nivo/pie';
@@ -9,7 +9,8 @@ import PivotTableUI from 'react-pivottable/PivotTableUI';
9
9
  import 'react-pivottable/pivottable.css';
10
10
  import { Loader2, Play, ChevronDown, AlertCircle, Rows3, BarChart3, PieChart, TrendingUp, ScatterChart, LayoutGrid, Code, Sparkles, X, Terminal, RefreshCw, Minimize2, Maximize2, Plus, Save, FolderOpen, Trash2, Filter, Database, Send, GripVertical } from 'lucide-react';
11
11
  import CodeMirror, { EditorView, Prec, keymap } from '@uiw/react-codemirror';
12
- import { sql } from '@codemirror/lang-sql';
12
+ import { sql, PostgreSQL, schemaCompletionSource, keywordCompletionSource } from '@codemirror/lang-sql';
13
+ import { snippetCompletion, completeFromList, autocompletion, acceptCompletion } from '@codemirror/autocomplete';
13
14
 
14
15
  // src/context/DashboardContext.tsx
15
16
  var DashboardContext = createContext(null);
@@ -36,7 +37,9 @@ function useCapabilities() {
36
37
  canRefine: !!c.refineDashboard,
37
38
  canNew: !!c.newDashboard,
38
39
  canAddPanel: !!c.addPanel,
39
- canEditPanels: !!c.removePanel
40
+ canEditPanels: !!c.removePanel,
41
+ // Studio can recompute + persist a panel's precomputed result.
42
+ canRefresh: !!c.persistPanelData
40
43
  };
41
44
  }
42
45
 
@@ -638,6 +641,280 @@ function ChartStyleControls({ style, onChange }) {
638
641
  ] })
639
642
  ] });
640
643
  }
644
+ var AGGREGATES = [
645
+ "count",
646
+ "count_if",
647
+ "sum",
648
+ "avg",
649
+ "min",
650
+ "max",
651
+ "median",
652
+ "mode",
653
+ "stddev",
654
+ "stddev_pop",
655
+ "stddev_samp",
656
+ "var_pop",
657
+ "var_samp",
658
+ "variance",
659
+ "quantile",
660
+ "quantile_cont",
661
+ "quantile_disc",
662
+ "approx_count_distinct",
663
+ "approx_quantile",
664
+ "arg_max",
665
+ "arg_min",
666
+ "first",
667
+ "last",
668
+ "product",
669
+ "bool_and",
670
+ "bool_or",
671
+ "bit_and",
672
+ "bit_or",
673
+ "bit_xor",
674
+ "string_agg",
675
+ "list",
676
+ "array_agg",
677
+ "histogram",
678
+ "corr",
679
+ "covar_pop",
680
+ "covar_samp",
681
+ "kurtosis",
682
+ "skewness",
683
+ "entropy",
684
+ "row_number",
685
+ "rank",
686
+ "dense_rank",
687
+ "percent_rank",
688
+ "cume_dist",
689
+ "ntile",
690
+ "lag",
691
+ "lead",
692
+ "first_value",
693
+ "last_value",
694
+ "nth_value"
695
+ ];
696
+ var SCALARS = [
697
+ // string
698
+ "length",
699
+ "len",
700
+ "lower",
701
+ "upper",
702
+ "trim",
703
+ "ltrim",
704
+ "rtrim",
705
+ "substring",
706
+ "substr",
707
+ "concat",
708
+ "concat_ws",
709
+ "replace",
710
+ "reverse",
711
+ "repeat",
712
+ "lpad",
713
+ "rpad",
714
+ "left",
715
+ "right",
716
+ "contains",
717
+ "starts_with",
718
+ "ends_with",
719
+ "position",
720
+ "split_part",
721
+ "string_split",
722
+ "regexp_matches",
723
+ "regexp_replace",
724
+ "regexp_extract",
725
+ "regexp_full_match",
726
+ "regexp_split_to_array",
727
+ "format",
728
+ "printf",
729
+ "md5",
730
+ "sha256",
731
+ "levenshtein",
732
+ "jaccard",
733
+ "ascii",
734
+ "chr",
735
+ // date / time
736
+ "now",
737
+ "current_date",
738
+ "current_timestamp",
739
+ "today",
740
+ "date_trunc",
741
+ "date_part",
742
+ "date_diff",
743
+ "date_add",
744
+ "date_sub",
745
+ "age",
746
+ "strftime",
747
+ "strptime",
748
+ "epoch",
749
+ "epoch_ms",
750
+ "extract",
751
+ "make_date",
752
+ "make_time",
753
+ "make_timestamp",
754
+ "last_day",
755
+ "dayname",
756
+ "monthname",
757
+ "year",
758
+ "month",
759
+ "day",
760
+ "hour",
761
+ "minute",
762
+ "second",
763
+ "dayofweek",
764
+ "dayofyear",
765
+ "week",
766
+ "quarter",
767
+ "time_bucket",
768
+ // math
769
+ "abs",
770
+ "ceil",
771
+ "ceiling",
772
+ "floor",
773
+ "round",
774
+ "trunc",
775
+ "sign",
776
+ "mod",
777
+ "pow",
778
+ "power",
779
+ "sqrt",
780
+ "cbrt",
781
+ "exp",
782
+ "ln",
783
+ "log",
784
+ "log2",
785
+ "log10",
786
+ "greatest",
787
+ "least",
788
+ "gcd",
789
+ "lcm",
790
+ "factorial",
791
+ "random",
792
+ "pi",
793
+ "degrees",
794
+ "radians",
795
+ "sin",
796
+ "cos",
797
+ "tan",
798
+ "asin",
799
+ "acos",
800
+ "atan",
801
+ "atan2",
802
+ // conditional / null / cast
803
+ "coalesce",
804
+ "ifnull",
805
+ "nullif",
806
+ "nvl",
807
+ "if",
808
+ "try_cast",
809
+ "cast",
810
+ "typeof",
811
+ // list / struct / map / json
812
+ "list_value",
813
+ "list_aggregate",
814
+ "list_distinct",
815
+ "list_sort",
816
+ "list_reverse",
817
+ "list_slice",
818
+ "list_concat",
819
+ "list_contains",
820
+ "list_position",
821
+ "list_extract",
822
+ "list_transform",
823
+ "list_filter",
824
+ "unnest",
825
+ "array_length",
826
+ "array_to_string",
827
+ "struct_pack",
828
+ "struct_extract",
829
+ "map",
830
+ "map_keys",
831
+ "map_values",
832
+ "json_extract",
833
+ "json_extract_string",
834
+ "to_json",
835
+ "from_json",
836
+ "json_array_length",
837
+ "json_keys",
838
+ "generate_series",
839
+ "range"
840
+ ];
841
+ var TYPES = [
842
+ "BOOLEAN",
843
+ "TINYINT",
844
+ "SMALLINT",
845
+ "INTEGER",
846
+ "BIGINT",
847
+ "HUGEINT",
848
+ "UTINYINT",
849
+ "USMALLINT",
850
+ "UINTEGER",
851
+ "UBIGINT",
852
+ "FLOAT",
853
+ "REAL",
854
+ "DOUBLE",
855
+ "DECIMAL",
856
+ "NUMERIC",
857
+ "VARCHAR",
858
+ "CHAR",
859
+ "TEXT",
860
+ "BLOB",
861
+ "BIT",
862
+ "DATE",
863
+ "TIME",
864
+ "TIMESTAMP",
865
+ "TIMESTAMPTZ",
866
+ "INTERVAL",
867
+ "UUID",
868
+ "JSON",
869
+ "LIST",
870
+ "ARRAY",
871
+ "STRUCT",
872
+ "MAP",
873
+ "UNION",
874
+ "ENUM"
875
+ ];
876
+ var EXTRA_KEYWORDS = [
877
+ "QUALIFY",
878
+ "EXCLUDE",
879
+ "PIVOT",
880
+ "UNPIVOT",
881
+ "ASOF",
882
+ "POSITIONAL",
883
+ "SEMI",
884
+ "ANTI",
885
+ "SUMMARIZE",
886
+ "DISTINCT ON",
887
+ "GROUP BY ALL",
888
+ "ORDER BY ALL",
889
+ "USING SAMPLE"
890
+ ];
891
+ function opts(labels, type, boost = 0) {
892
+ return labels.map((label) => ({ label, type, boost }));
893
+ }
894
+ var SNIPPETS = [
895
+ snippetCompletion("SELECT ${col}, count(*) AS n\nFROM data\nGROUP BY ${col}\nORDER BY n DESC", {
896
+ label: "group by count",
897
+ type: "snippet",
898
+ detail: "aggregate by column"
899
+ }),
900
+ snippetCompletion("SELECT *\nFROM data\nORDER BY ${col} DESC\nLIMIT 10", {
901
+ label: "top N",
902
+ type: "snippet",
903
+ detail: "order + limit"
904
+ }),
905
+ snippetCompletion("SELECT DISTINCT ${col}\nFROM data", {
906
+ label: "select distinct",
907
+ type: "snippet"
908
+ })
909
+ ];
910
+ var duckdbCompletionSource = completeFromList([
911
+ ...opts(AGGREGATES, "function", 1),
912
+ // boost aggregates slightly — common in analysis
913
+ ...opts(SCALARS, "function"),
914
+ ...opts(TYPES, "type"),
915
+ ...opts(EXTRA_KEYWORDS, "keyword"),
916
+ ...SNIPPETS
917
+ ]);
641
918
  var DEFAULT_SQL = "SELECT *\nFROM data\nLIMIT 100";
642
919
  var renderCell = (v) => {
643
920
  if (v == null) return "";
@@ -677,18 +954,32 @@ function SqlConsole({ columns = [], stateId, defaultColumnsOpen = false, default
677
954
  void run();
678
955
  };
679
956
  const schemaKey = columns.map((c) => c.name).join(",");
680
- const extensions = useMemo(
681
- () => [
682
- sql({ schema: { data: columns.map((c) => c.name) }, upperCaseKeywords: true }),
957
+ const extensions = useMemo(() => {
958
+ const sqlConfig = {
959
+ dialect: PostgreSQL,
960
+ schema: { data: columns.map((c) => c.name) },
961
+ defaultTable: "data",
962
+ upperCaseKeywords: true
963
+ };
964
+ return [
965
+ sql(sqlConfig),
966
+ autocompletion({
967
+ override: [
968
+ schemaCompletionSource(sqlConfig),
969
+ keywordCompletionSource(PostgreSQL, true),
970
+ duckdbCompletionSource
971
+ ]
972
+ }),
683
973
  EditorView.lineWrapping,
684
- Prec.highest(keymap.of([{ key: "Mod-Enter", run: () => {
685
- runRef.current();
686
- return true;
687
- } }]))
688
- ],
689
- [schemaKey]
690
- // eslint-disable-line react-hooks/exhaustive-deps
691
- );
974
+ Prec.highest(keymap.of([
975
+ { key: "Mod-Enter", run: () => {
976
+ runRef.current();
977
+ return true;
978
+ } },
979
+ { key: "Tab", run: acceptCompletion }
980
+ ]))
981
+ ];
982
+ }, [schemaKey]);
692
983
  const resultColumns = result?.columns || [];
693
984
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
694
985
  /* @__PURE__ */ jsxs("div", { className: `border-b ${theme.border} bg-midnight-elevated p-3 shrink-0`, children: [
@@ -718,7 +1009,7 @@ function SqlConsole({ columns = [], stateId, defaultColumnsOpen = false, default
718
1009
  onChange: setSql,
719
1010
  extensions,
720
1011
  theme: "dark",
721
- basicSetup: { lineNumbers: false, foldGutter: false, highlightActiveLine: false },
1012
+ basicSetup: { lineNumbers: false, foldGutter: false, highlightActiveLine: false, autocompletion: false },
722
1013
  height: "120px",
723
1014
  placeholder: "SELECT ... FROM data",
724
1015
  className: "border border-midnight-border text-sm overflow-hidden focus-within:border-midnight-accent"
@@ -1050,6 +1341,7 @@ var ChartPreview = ({
1050
1341
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "Select a chart type" });
1051
1342
  }
1052
1343
  };
1344
+ var MAX_PRECOMPUTE_ROWS = 5e3;
1053
1345
  function ChartBuilder({ records, columns, stateId, onSave }) {
1054
1346
  const { theme, runQuery } = useDashboard();
1055
1347
  const runQueryRef = useRef(runQuery);
@@ -1178,6 +1470,10 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
1178
1470
  config.chartType = chartType;
1179
1471
  if (filters.length) config.filters = filters;
1180
1472
  if (yFields.length) config.yFields = yFields.map((f) => ({ name: f.name, agg: f.agg }));
1473
+ if (sqlRows && sqlRows.length <= MAX_PRECOMPUTE_ROWS) {
1474
+ config.data = sqlRows;
1475
+ config.refreshedAt = (/* @__PURE__ */ new Date()).toISOString();
1476
+ }
1181
1477
  }
1182
1478
  config.style = style;
1183
1479
  onSave({
@@ -1382,70 +1678,129 @@ function ViewLoading() {
1382
1678
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
1383
1679
  }
1384
1680
  function SqlPanel({ panel }) {
1385
- const { runQuery } = useDashboard();
1681
+ const { runQuery, persistPanelData } = useDashboard();
1682
+ const { canRefresh } = useCapabilities();
1386
1683
  const runQueryRef = useRef(runQuery);
1387
1684
  runQueryRef.current = runQuery;
1388
1685
  const config = panel.config || {};
1389
1686
  const chartType = config.chartType || panel.type;
1390
- const [rows, setRows] = useState(null);
1687
+ const precomputed = config.data;
1688
+ const [rows, setRows] = useState(precomputed ?? null);
1391
1689
  const [error, setError] = useState(null);
1392
- const [loading, setLoading] = useState(true);
1690
+ const [loading, setLoading] = useState(!precomputed && canRefresh);
1691
+ const [refreshing, setRefreshing] = useState(false);
1393
1692
  useEffect(() => {
1693
+ if (precomputed) {
1694
+ setRows(precomputed);
1695
+ setLoading(false);
1696
+ return;
1697
+ }
1698
+ if (!canRefresh) {
1699
+ setLoading(false);
1700
+ return;
1701
+ }
1394
1702
  let cancelled = false;
1395
1703
  setLoading(true);
1396
1704
  setError(null);
1397
- const sql = config.sql || "";
1398
- runQueryRef.current(sql).then(
1705
+ runQueryRef.current(config.sql || "").then(
1399
1706
  (res) => {
1400
- if (cancelled) return;
1401
- setLoading(false);
1402
- setRows(res?.rows || []);
1707
+ if (!cancelled) {
1708
+ setLoading(false);
1709
+ setRows(res?.rows || []);
1710
+ }
1403
1711
  },
1404
1712
  (err) => {
1405
- if (cancelled) return;
1406
- setLoading(false);
1407
- setRows(null);
1408
- setError(err instanceof Error ? err.message : String(err) || "Query failed");
1713
+ if (!cancelled) {
1714
+ setLoading(false);
1715
+ setRows(null);
1716
+ setError(err instanceof Error ? err.message : String(err) || "Query failed");
1717
+ }
1409
1718
  }
1410
1719
  );
1411
1720
  return () => {
1412
1721
  cancelled = true;
1413
1722
  };
1414
- }, [config.sql]);
1723
+ }, [config.sql, canRefresh]);
1724
+ const refresh = useCallback(async () => {
1725
+ setRefreshing(true);
1726
+ setError(null);
1727
+ try {
1728
+ const res = await runQueryRef.current(config.sql || "");
1729
+ const r = res?.rows || [];
1730
+ setRows(r);
1731
+ persistPanelData?.(panel.id, r, (/* @__PURE__ */ new Date()).toISOString());
1732
+ } catch (err) {
1733
+ setError(err instanceof Error ? err.message : String(err) || "Query failed");
1734
+ } finally {
1735
+ setRefreshing(false);
1736
+ }
1737
+ }, [config.sql, panel.id, persistPanelData]);
1738
+ const refreshBtn = canRefresh ? /* @__PURE__ */ jsx(
1739
+ "button",
1740
+ {
1741
+ onClick: refresh,
1742
+ disabled: refreshing,
1743
+ title: config.refreshedAt ? `Last computed ${new Date(config.refreshedAt).toLocaleString()} \u2014 click to refresh` : "Compute & save this chart\u2019s data",
1744
+ 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",
1745
+ children: /* @__PURE__ */ jsx(RefreshCw, { className: `w-3 h-3 ${refreshing ? "animate-spin" : ""}` })
1746
+ }
1747
+ ) : null;
1415
1748
  if (loading && !rows) {
1416
1749
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Running\u2026" });
1417
1750
  }
1751
+ if (!rows && !error && !canRefresh) {
1752
+ return /* @__PURE__ */ 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: [
1753
+ /* @__PURE__ */ jsx(AlertCircle, { className: "w-4 h-4" }),
1754
+ /* @__PURE__ */ jsx("span", { children: "Chart data not available" }),
1755
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-midnight-text-subdued", children: "Download the dataset to view this chart." })
1756
+ ] });
1757
+ }
1418
1758
  if (error) {
1419
- return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-red-400 px-2 text-center", children: error });
1759
+ return /* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center h-full px-2 text-center text-xs text-red-400", children: [
1760
+ refreshBtn,
1761
+ error
1762
+ ] });
1420
1763
  }
1421
1764
  if (!rows) return null;
1765
+ let chart;
1422
1766
  if (chartType === "metric") {
1423
1767
  const first = rows[0];
1424
1768
  const v = first ? Object.values(first)[0] : 0;
1425
- return /* @__PURE__ */ jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
1426
- }
1427
- const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
1428
- switch (chartType) {
1429
- case "bar":
1430
- return /* @__PURE__ */ jsx(BarView, { data: shaped, groupColumn: config.group || "", valueColumn: config.value || "", style: config.style });
1431
- case "grouped-bar": {
1432
- const gb = shaped;
1433
- return /* @__PURE__ */ jsx(GroupedBarChart, { data: gb.data, keys: gb.keys, yFields: config.yFields || [], style: config.style });
1769
+ chart = /* @__PURE__ */ jsx(MetricView, { value: Number(v) || 0, config: { column: config.column || "", agg: config.agg, label: config.label } });
1770
+ } else {
1771
+ const shaped = shapeChartData(chartType, rows, { yFields: config.yFields || [] });
1772
+ switch (chartType) {
1773
+ case "bar":
1774
+ chart = /* @__PURE__ */ jsx(BarView, { data: shaped, groupColumn: config.group || "", valueColumn: config.value || "", style: config.style });
1775
+ break;
1776
+ case "grouped-bar": {
1777
+ const gb = shaped;
1778
+ chart = /* @__PURE__ */ jsx(GroupedBarChart, { data: gb.data, keys: gb.keys, yFields: config.yFields || [], style: config.style });
1779
+ break;
1780
+ }
1781
+ case "pie":
1782
+ chart = /* @__PURE__ */ jsx(PieView, { data: shaped, groupColumn: config.group || "", style: config.style });
1783
+ break;
1784
+ case "line":
1785
+ chart = /* @__PURE__ */ jsx(LineView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
1786
+ break;
1787
+ case "scatter":
1788
+ chart = /* @__PURE__ */ jsx(ScatterView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
1789
+ break;
1790
+ case "heatmap":
1791
+ chart = shaped.length ? /* @__PURE__ */ 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__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
1792
+ break;
1793
+ default:
1794
+ chart = /* @__PURE__ */ jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
1795
+ "Unsupported SQL chart: ",
1796
+ chartType
1797
+ ] });
1434
1798
  }
1435
- case "pie":
1436
- return /* @__PURE__ */ jsx(PieView, { data: shaped, groupColumn: config.group || "", style: config.style });
1437
- case "line":
1438
- return /* @__PURE__ */ jsx(LineView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
1439
- case "scatter":
1440
- return /* @__PURE__ */ jsx(ScatterView, { data: shaped, xColumn: config.x || "", yColumn: config.y || "", style: config.style });
1441
- case "heatmap":
1442
- return shaped.length ? /* @__PURE__ */ 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__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
1443
- default:
1444
- return /* @__PURE__ */ jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
1445
- "Unsupported SQL chart: ",
1446
- chartType
1447
- ] });
1448
1799
  }
1800
+ return /* @__PURE__ */ jsxs("div", { className: "relative h-full w-full", children: [
1801
+ refreshBtn,
1802
+ chart
1803
+ ] });
1449
1804
  }
1450
1805
  function PanelContent({ panel, records, columns }) {
1451
1806
  const { type } = panel;