@quantumwake/terminal-ux-dashboard-components 0.1.13 → 0.1.15
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 +178 -105
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -9
- package/dist/index.d.ts +19 -9
- package/dist/index.js +178 -106
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.d.cts
CHANGED
|
@@ -31,6 +31,14 @@ interface DashboardCapabilities {
|
|
|
31
31
|
addPanel?: (panel: PanelInput) => void;
|
|
32
32
|
removePanel?: (panelId: string) => void;
|
|
33
33
|
persistPanelData?: (panelId: string, rows: QueryResult['rows'], refreshedAt: string) => void;
|
|
34
|
+
persistLayout?: (items: PanelLayout[]) => void;
|
|
35
|
+
}
|
|
36
|
+
interface PanelLayout {
|
|
37
|
+
id: string;
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
w: number;
|
|
41
|
+
h: number;
|
|
34
42
|
}
|
|
35
43
|
interface DashboardContextValue extends DashboardCapabilities {
|
|
36
44
|
theme: DashboardTheme;
|
|
@@ -54,6 +62,7 @@ declare function useCapabilities(): {
|
|
|
54
62
|
canAddPanel: boolean;
|
|
55
63
|
canEditPanels: boolean;
|
|
56
64
|
canRefresh: boolean;
|
|
65
|
+
canEditLayout: boolean;
|
|
57
66
|
};
|
|
58
67
|
|
|
59
68
|
type ChartType = 'bar' | 'grouped-bar' | 'pie' | 'line' | 'scatter' | 'heatmap';
|
|
@@ -105,6 +114,7 @@ interface ChartStyle {
|
|
|
105
114
|
legendBold: boolean;
|
|
106
115
|
legendHighlight: string;
|
|
107
116
|
xTickRotation: number;
|
|
117
|
+
yTickRotation: number;
|
|
108
118
|
tickTruncate: number;
|
|
109
119
|
legendAnchor: LegendAnchor;
|
|
110
120
|
titleAlign: TitleAlign;
|
|
@@ -325,12 +335,6 @@ interface ChartStyleControlsProps {
|
|
|
325
335
|
style?: Partial<ChartStyle> | null;
|
|
326
336
|
onChange: (next: ChartStyle) => void;
|
|
327
337
|
}
|
|
328
|
-
/**
|
|
329
|
-
* ChartStyleControls — the standardized appearance editor used across all
|
|
330
|
-
* charts. Edits a plain style object (see chartStyle.ts); theme-aware via the
|
|
331
|
-
* host-injected DashboardContext theme. Parent persists the style and passes it
|
|
332
|
-
* to the views.
|
|
333
|
-
*/
|
|
334
338
|
declare function ChartStyleControls({ style, onChange }: ChartStyleControlsProps): react.JSX.Element;
|
|
335
339
|
|
|
336
340
|
interface SqlConsoleColumn {
|
|
@@ -382,6 +386,8 @@ interface DashboardPanel$1 {
|
|
|
382
386
|
title?: string;
|
|
383
387
|
width?: number;
|
|
384
388
|
height?: number;
|
|
389
|
+
x?: number;
|
|
390
|
+
y?: number;
|
|
385
391
|
config?: PanelConfig;
|
|
386
392
|
}
|
|
387
393
|
interface Dashboard$1 {
|
|
@@ -398,8 +404,12 @@ interface DashboardRendererProps {
|
|
|
398
404
|
}
|
|
399
405
|
/**
|
|
400
406
|
* DashboardRenderer — renders a grid of AI-generated or manually configured
|
|
401
|
-
* panels
|
|
402
|
-
*
|
|
407
|
+
* panels via react-grid-layout. Editing is capability-gated:
|
|
408
|
+
* - studio (persistLayout wired) ⇒ panels drag (by their header) + resize, and
|
|
409
|
+
* the new geometry is persisted back to the host.
|
|
410
|
+
* - published viewer (no persistLayout) ⇒ a static grid, rendered exactly where
|
|
411
|
+
* the studio left the panels.
|
|
412
|
+
* The per-panel remove button is similarly gated behind canEditPanels.
|
|
403
413
|
*/
|
|
404
414
|
declare function DashboardRenderer({ dashboard, records, columns }: DashboardRendererProps): react.JSX.Element | null;
|
|
405
415
|
|
|
@@ -504,4 +514,4 @@ interface DataExplorerProps {
|
|
|
504
514
|
}
|
|
505
515
|
declare function DataExplorer({ records, columns, states, activeStateId, profile, dashboard, dashboardId, savedDashboards, loading, analyzing, defaultMode, tabs, onSelectState, onRefreshStates, }: DataExplorerProps): react.JSX.Element;
|
|
506
516
|
|
|
507
|
-
export { type AggFn, type BarDatum, BarView, type BarViewProps, ChartBuilder, type ChartBuilderColumn, type ChartBuilderProps, type ChartConfig, type ChartField, type ChartFilter, type ChartPanel, type ChartStyle, ChartStyleControls, type ChartStyleControlsProps, type ChartType, DEFAULT_CHART_STYLE, type Dashboard$1 as Dashboard, type DashboardCapabilities, type DashboardContextValue, type DashboardPanel$1 as DashboardPanel, DashboardProvider, type DashboardProviderProps, DashboardRenderer, type DashboardRendererProps, type DashboardTheme, DataExplorer, type DataExplorerProps, type HeatmapSerie, HeatmapView, type HeatmapViewProps, type InsightConfig, InsightView, type InsightViewProps, LEGEND_ANCHORS, type LegendAnchor, type LegendPosition, type LineSerie, LineView, type LineViewProps, type MetricConfig, MetricView, type MetricViewProps, type PanelInput, type PieDatum, PieView, type PieViewProps, PivotView, type PivotViewProps, type QueryResult, type Row, type SavedDashboard, type ScatterSerie, ScatterView, type ScatterViewProps, SqlConsole, type SqlConsoleColumn, type SqlConsoleProps, type TitleAlign, aggExpr, aggregate, axisLegend, buildChartSQL, buildNivoTheme, compileWhere, groupBy, legendConfig, qIdent, qLit, shapeChartData, useCapabilities, useDashboard, withStyleDefaults };
|
|
517
|
+
export { type AggFn, type BarDatum, BarView, type BarViewProps, ChartBuilder, type ChartBuilderColumn, type ChartBuilderProps, type ChartConfig, type ChartField, type ChartFilter, type ChartPanel, type ChartStyle, ChartStyleControls, type ChartStyleControlsProps, type ChartType, DEFAULT_CHART_STYLE, type Dashboard$1 as Dashboard, type DashboardCapabilities, type DashboardContextValue, type DashboardPanel$1 as DashboardPanel, DashboardProvider, type DashboardProviderProps, DashboardRenderer, type DashboardRendererProps, type DashboardTheme, DataExplorer, type DataExplorerProps, type HeatmapSerie, HeatmapView, type HeatmapViewProps, type InsightConfig, InsightView, type InsightViewProps, LEGEND_ANCHORS, type LegendAnchor, type LegendPosition, type LineSerie, LineView, type LineViewProps, type MetricConfig, MetricView, type MetricViewProps, type PanelInput, type PanelLayout, type PieDatum, PieView, type PieViewProps, PivotView, type PivotViewProps, type QueryResult, type Row, type SavedDashboard, type ScatterSerie, ScatterView, type ScatterViewProps, SqlConsole, type SqlConsoleColumn, type SqlConsoleProps, type TitleAlign, aggExpr, aggregate, axisLegend, buildChartSQL, buildNivoTheme, compileWhere, groupBy, legendConfig, qIdent, qLit, shapeChartData, useCapabilities, useDashboard, withStyleDefaults };
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,14 @@ interface DashboardCapabilities {
|
|
|
31
31
|
addPanel?: (panel: PanelInput) => void;
|
|
32
32
|
removePanel?: (panelId: string) => void;
|
|
33
33
|
persistPanelData?: (panelId: string, rows: QueryResult['rows'], refreshedAt: string) => void;
|
|
34
|
+
persistLayout?: (items: PanelLayout[]) => void;
|
|
35
|
+
}
|
|
36
|
+
interface PanelLayout {
|
|
37
|
+
id: string;
|
|
38
|
+
x: number;
|
|
39
|
+
y: number;
|
|
40
|
+
w: number;
|
|
41
|
+
h: number;
|
|
34
42
|
}
|
|
35
43
|
interface DashboardContextValue extends DashboardCapabilities {
|
|
36
44
|
theme: DashboardTheme;
|
|
@@ -54,6 +62,7 @@ declare function useCapabilities(): {
|
|
|
54
62
|
canAddPanel: boolean;
|
|
55
63
|
canEditPanels: boolean;
|
|
56
64
|
canRefresh: boolean;
|
|
65
|
+
canEditLayout: boolean;
|
|
57
66
|
};
|
|
58
67
|
|
|
59
68
|
type ChartType = 'bar' | 'grouped-bar' | 'pie' | 'line' | 'scatter' | 'heatmap';
|
|
@@ -105,6 +114,7 @@ interface ChartStyle {
|
|
|
105
114
|
legendBold: boolean;
|
|
106
115
|
legendHighlight: string;
|
|
107
116
|
xTickRotation: number;
|
|
117
|
+
yTickRotation: number;
|
|
108
118
|
tickTruncate: number;
|
|
109
119
|
legendAnchor: LegendAnchor;
|
|
110
120
|
titleAlign: TitleAlign;
|
|
@@ -325,12 +335,6 @@ interface ChartStyleControlsProps {
|
|
|
325
335
|
style?: Partial<ChartStyle> | null;
|
|
326
336
|
onChange: (next: ChartStyle) => void;
|
|
327
337
|
}
|
|
328
|
-
/**
|
|
329
|
-
* ChartStyleControls — the standardized appearance editor used across all
|
|
330
|
-
* charts. Edits a plain style object (see chartStyle.ts); theme-aware via the
|
|
331
|
-
* host-injected DashboardContext theme. Parent persists the style and passes it
|
|
332
|
-
* to the views.
|
|
333
|
-
*/
|
|
334
338
|
declare function ChartStyleControls({ style, onChange }: ChartStyleControlsProps): react.JSX.Element;
|
|
335
339
|
|
|
336
340
|
interface SqlConsoleColumn {
|
|
@@ -382,6 +386,8 @@ interface DashboardPanel$1 {
|
|
|
382
386
|
title?: string;
|
|
383
387
|
width?: number;
|
|
384
388
|
height?: number;
|
|
389
|
+
x?: number;
|
|
390
|
+
y?: number;
|
|
385
391
|
config?: PanelConfig;
|
|
386
392
|
}
|
|
387
393
|
interface Dashboard$1 {
|
|
@@ -398,8 +404,12 @@ interface DashboardRendererProps {
|
|
|
398
404
|
}
|
|
399
405
|
/**
|
|
400
406
|
* DashboardRenderer — renders a grid of AI-generated or manually configured
|
|
401
|
-
* panels
|
|
402
|
-
*
|
|
407
|
+
* panels via react-grid-layout. Editing is capability-gated:
|
|
408
|
+
* - studio (persistLayout wired) ⇒ panels drag (by their header) + resize, and
|
|
409
|
+
* the new geometry is persisted back to the host.
|
|
410
|
+
* - published viewer (no persistLayout) ⇒ a static grid, rendered exactly where
|
|
411
|
+
* the studio left the panels.
|
|
412
|
+
* The per-panel remove button is similarly gated behind canEditPanels.
|
|
403
413
|
*/
|
|
404
414
|
declare function DashboardRenderer({ dashboard, records, columns }: DashboardRendererProps): react.JSX.Element | null;
|
|
405
415
|
|
|
@@ -504,4 +514,4 @@ interface DataExplorerProps {
|
|
|
504
514
|
}
|
|
505
515
|
declare function DataExplorer({ records, columns, states, activeStateId, profile, dashboard, dashboardId, savedDashboards, loading, analyzing, defaultMode, tabs, onSelectState, onRefreshStates, }: DataExplorerProps): react.JSX.Element;
|
|
506
516
|
|
|
507
|
-
export { type AggFn, type BarDatum, BarView, type BarViewProps, ChartBuilder, type ChartBuilderColumn, type ChartBuilderProps, type ChartConfig, type ChartField, type ChartFilter, type ChartPanel, type ChartStyle, ChartStyleControls, type ChartStyleControlsProps, type ChartType, DEFAULT_CHART_STYLE, type Dashboard$1 as Dashboard, type DashboardCapabilities, type DashboardContextValue, type DashboardPanel$1 as DashboardPanel, DashboardProvider, type DashboardProviderProps, DashboardRenderer, type DashboardRendererProps, type DashboardTheme, DataExplorer, type DataExplorerProps, type HeatmapSerie, HeatmapView, type HeatmapViewProps, type InsightConfig, InsightView, type InsightViewProps, LEGEND_ANCHORS, type LegendAnchor, type LegendPosition, type LineSerie, LineView, type LineViewProps, type MetricConfig, MetricView, type MetricViewProps, type PanelInput, type PieDatum, PieView, type PieViewProps, PivotView, type PivotViewProps, type QueryResult, type Row, type SavedDashboard, type ScatterSerie, ScatterView, type ScatterViewProps, SqlConsole, type SqlConsoleColumn, type SqlConsoleProps, type TitleAlign, aggExpr, aggregate, axisLegend, buildChartSQL, buildNivoTheme, compileWhere, groupBy, legendConfig, qIdent, qLit, shapeChartData, useCapabilities, useDashboard, withStyleDefaults };
|
|
517
|
+
export { type AggFn, type BarDatum, BarView, type BarViewProps, ChartBuilder, type ChartBuilderColumn, type ChartBuilderProps, type ChartConfig, type ChartField, type ChartFilter, type ChartPanel, type ChartStyle, ChartStyleControls, type ChartStyleControlsProps, type ChartType, DEFAULT_CHART_STYLE, type Dashboard$1 as Dashboard, type DashboardCapabilities, type DashboardContextValue, type DashboardPanel$1 as DashboardPanel, DashboardProvider, type DashboardProviderProps, DashboardRenderer, type DashboardRendererProps, type DashboardTheme, DataExplorer, type DataExplorerProps, type HeatmapSerie, HeatmapView, type HeatmapViewProps, type InsightConfig, InsightView, type InsightViewProps, LEGEND_ANCHORS, type LegendAnchor, type LegendPosition, type LineSerie, LineView, type LineViewProps, type MetricConfig, MetricView, type MetricViewProps, type PanelInput, type PanelLayout, type PieDatum, PieView, type PieViewProps, PivotView, type PivotViewProps, type QueryResult, type Row, type SavedDashboard, type ScatterSerie, ScatterView, type ScatterViewProps, SqlConsole, type SqlConsoleColumn, type SqlConsoleProps, type TitleAlign, aggExpr, aggregate, axisLegend, buildChartSQL, buildNivoTheme, compileWhere, groupBy, legendConfig, qIdent, qLit, shapeChartData, useCapabilities, useDashboard, withStyleDefaults };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, useState, useEffect, useRef,
|
|
1
|
+
import { createContext, useContext, useMemo, useState, useEffect, useRef, useCallback, Suspense } 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';
|
|
@@ -11,6 +11,7 @@ import { Loader2, Play, ChevronDown, AlertCircle, Rows3, BarChart3, PieChart, Tr
|
|
|
11
11
|
import CodeMirror, { EditorView, Prec, keymap } from '@uiw/react-codemirror';
|
|
12
12
|
import { sql, PostgreSQL, schemaCompletionSource, keywordCompletionSource } from '@codemirror/lang-sql';
|
|
13
13
|
import { snippetCompletion, completeFromList, autocompletion, acceptCompletion } from '@codemirror/autocomplete';
|
|
14
|
+
import GridLayout, { WidthProvider } from 'react-grid-layout';
|
|
14
15
|
|
|
15
16
|
// src/context/DashboardContext.tsx
|
|
16
17
|
var DashboardContext = createContext(null);
|
|
@@ -39,7 +40,9 @@ function useCapabilities() {
|
|
|
39
40
|
canAddPanel: !!c.addPanel,
|
|
40
41
|
canEditPanels: !!c.removePanel,
|
|
41
42
|
// Studio can recompute + persist a panel's precomputed result.
|
|
42
|
-
canRefresh: !!c.persistPanelData
|
|
43
|
+
canRefresh: !!c.persistPanelData,
|
|
44
|
+
// Studio can drag/resize panels and persist the grid layout.
|
|
45
|
+
canEditLayout: !!c.persistLayout
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -225,6 +228,7 @@ var DEFAULT_CHART_STYLE = {
|
|
|
225
228
|
legendHighlight: "",
|
|
226
229
|
// Tick overflow handling.
|
|
227
230
|
xTickRotation: -35,
|
|
231
|
+
yTickRotation: 0,
|
|
228
232
|
tickTruncate: 0,
|
|
229
233
|
// Series legend placement (charts that have one: pie, grouped bar).
|
|
230
234
|
legendAnchor: "right",
|
|
@@ -300,7 +304,7 @@ var axisLegend = (style, axis, columnName, opts2 = {}) => {
|
|
|
300
304
|
out.legendPosition = isX ? s.xLegendPosition : s.yLegendPosition;
|
|
301
305
|
out.legendOffset = isX ? s.xLegendOffset : s.yLegendOffset;
|
|
302
306
|
}
|
|
303
|
-
|
|
307
|
+
out.tickRotation = isX ? s.xTickRotation : s.yTickRotation;
|
|
304
308
|
if (opts2.numeric) out.format = (v) => Number(v).toLocaleString();
|
|
305
309
|
else if (s.tickTruncate > 0) out.format = (v) => truncate(v, s.tickTruncate);
|
|
306
310
|
return out;
|
|
@@ -609,11 +613,17 @@ function PivotView({ records }) {
|
|
|
609
613
|
)
|
|
610
614
|
] });
|
|
611
615
|
}
|
|
612
|
-
function
|
|
616
|
+
function Section({ title, children }) {
|
|
613
617
|
const { theme } = useDashboard();
|
|
614
|
-
return /* @__PURE__ */ jsxs("div", { className:
|
|
615
|
-
/* @__PURE__ */ jsx("
|
|
616
|
-
/* @__PURE__ */ jsx("div", { className: "
|
|
618
|
+
return /* @__PURE__ */ jsxs("div", { className: `py-3 border-t ${theme.border} first:border-t-0 first:pt-0`, children: [
|
|
619
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wider text-midnight-text-muted/70 font-mono mb-2", children: title }),
|
|
620
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1.5", children })
|
|
621
|
+
] });
|
|
622
|
+
}
|
|
623
|
+
function Row({ label, children }) {
|
|
624
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[88px_1fr] items-center gap-2 min-h-[30px]", children: [
|
|
625
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted truncate", title: label, children: label }),
|
|
626
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-end", children })
|
|
617
627
|
] });
|
|
618
628
|
}
|
|
619
629
|
function ColorControl({ value, onChange }) {
|
|
@@ -624,23 +634,29 @@ function ColorControl({ value, onChange }) {
|
|
|
624
634
|
type: "color",
|
|
625
635
|
value: /^#/.test(value) ? value : "#94a3b8",
|
|
626
636
|
onChange: (e) => onChange(e.target.value),
|
|
627
|
-
className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer`
|
|
637
|
+
className: `w-9 h-6 bg-transparent border ${theme.border} cursor-pointer p-0`
|
|
628
638
|
}
|
|
629
639
|
);
|
|
630
640
|
}
|
|
631
|
-
function
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
641
|
+
function SliderControl({ value, onChange, min, max, step = 1, unit = "" }) {
|
|
642
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 w-full", children: [
|
|
643
|
+
/* @__PURE__ */ jsx(
|
|
644
|
+
"input",
|
|
645
|
+
{
|
|
646
|
+
type: "range",
|
|
647
|
+
min,
|
|
648
|
+
max,
|
|
649
|
+
step,
|
|
650
|
+
value,
|
|
651
|
+
onChange: (e) => onChange(Number(e.target.value)),
|
|
652
|
+
className: "flex-1 h-1 accent-midnight-accent cursor-pointer"
|
|
653
|
+
}
|
|
654
|
+
),
|
|
655
|
+
/* @__PURE__ */ jsxs("span", { className: "w-10 shrink-0 text-right text-[11px] font-mono tabular-nums text-midnight-text-body", children: [
|
|
637
656
|
value,
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
className: `w-20 px-2 py-1 text-xs ${theme.font} bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent`
|
|
642
|
-
}
|
|
643
|
-
);
|
|
657
|
+
unit
|
|
658
|
+
] })
|
|
659
|
+
] });
|
|
644
660
|
}
|
|
645
661
|
function TextControl({ value, onChange, placeholder }) {
|
|
646
662
|
const { theme } = useDashboard();
|
|
@@ -651,78 +667,79 @@ function TextControl({ value, onChange, placeholder }) {
|
|
|
651
667
|
value,
|
|
652
668
|
placeholder,
|
|
653
669
|
onChange: (e) => onChange(e.target.value),
|
|
654
|
-
className: `w-
|
|
670
|
+
className: `w-full px-2 py-1 text-xs font-mono bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent transition-colors`
|
|
655
671
|
}
|
|
656
672
|
);
|
|
657
673
|
}
|
|
658
|
-
function
|
|
674
|
+
function Switch({ value, onChange }) {
|
|
659
675
|
return /* @__PURE__ */ jsx(
|
|
660
|
-
"
|
|
676
|
+
"button",
|
|
661
677
|
{
|
|
662
|
-
type: "
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
678
|
+
type: "button",
|
|
679
|
+
role: "switch",
|
|
680
|
+
"aria-checked": value,
|
|
681
|
+
onClick: () => onChange(!value),
|
|
682
|
+
className: `relative inline-flex h-4 w-7 shrink-0 items-center rounded-full transition-colors ${value ? "bg-midnight-accent" : "bg-midnight-border"}`,
|
|
683
|
+
children: /* @__PURE__ */ jsx("span", { className: `inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${value ? "translate-x-3.5" : "translate-x-0.5"}` })
|
|
666
684
|
}
|
|
667
685
|
);
|
|
668
686
|
}
|
|
669
687
|
function OptionalColor({ value, onChange, fallback = "#a78bfa" }) {
|
|
670
688
|
const on = !!value;
|
|
671
689
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
672
|
-
/* @__PURE__ */ jsx(
|
|
673
|
-
|
|
690
|
+
on && /* @__PURE__ */ jsx(ColorControl, { value, onChange }),
|
|
691
|
+
/* @__PURE__ */ jsx(Switch, { value: on, onChange: (v) => onChange(v ? fallback : "") })
|
|
674
692
|
] });
|
|
675
693
|
}
|
|
676
694
|
function Choice({ value, options, onChange }) {
|
|
677
695
|
const { theme } = useDashboard();
|
|
678
|
-
return /* @__PURE__ */ jsx(
|
|
696
|
+
return /* @__PURE__ */ jsx(
|
|
679
697
|
"select",
|
|
680
698
|
{
|
|
681
699
|
value,
|
|
682
700
|
onChange: (e) => onChange(e.target.value),
|
|
683
|
-
className: `w-full px-2 py-1 text-xs
|
|
701
|
+
className: `w-full px-2 py-1 text-xs font-mono bg-midnight-surface border ${theme.border} text-midnight-text-body outline-none focus:border-midnight-accent transition-colors`,
|
|
684
702
|
children: options.map((o) => /* @__PURE__ */ jsx("option", { value: o, children: o }, o))
|
|
685
703
|
}
|
|
686
|
-
)
|
|
687
|
-
}
|
|
688
|
-
function Group({ children, last }) {
|
|
689
|
-
const { theme } = useDashboard();
|
|
690
|
-
return /* @__PURE__ */ jsx("div", { className: `space-y-2 pb-3 ${last ? "" : `mb-3 border-b ${theme.border}`}`, children });
|
|
704
|
+
);
|
|
691
705
|
}
|
|
692
706
|
function ChartStyleControls({ style, onChange }) {
|
|
693
707
|
const s = withStyleDefaults(style);
|
|
694
708
|
const set = (key, value) => onChange({ ...s, [key]: value });
|
|
695
709
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
696
|
-
/* @__PURE__ */ jsxs(
|
|
710
|
+
/* @__PURE__ */ jsxs(Section, { title: "Canvas", children: [
|
|
697
711
|
/* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(ColorControl, { value: s.background, onChange: (v) => set("background", v) }) }),
|
|
698
|
-
/* @__PURE__ */ jsx(Row, { label: "Text
|
|
699
|
-
/* @__PURE__ */ jsx(Row, { label: "Grid
|
|
700
|
-
/* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(
|
|
712
|
+
/* @__PURE__ */ jsx(Row, { label: "Text", children: /* @__PURE__ */ jsx(ColorControl, { value: s.textColor, onChange: (v) => set("textColor", v) }) }),
|
|
713
|
+
/* @__PURE__ */ jsx(Row, { label: "Grid", children: /* @__PURE__ */ jsx(ColorControl, { value: s.gridColor, onChange: (v) => set("gridColor", v) }) }),
|
|
714
|
+
/* @__PURE__ */ jsx(Row, { label: "Font size", children: /* @__PURE__ */ jsx(SliderControl, { value: s.fontSize, min: 6, max: 24, unit: "px", onChange: (v) => set("fontSize", v) }) })
|
|
701
715
|
] }),
|
|
702
|
-
/* @__PURE__ */ jsxs(
|
|
703
|
-
/* @__PURE__ */ jsx(Row, { label: "X
|
|
704
|
-
/* @__PURE__ */ jsx(Row, { label: "Show X
|
|
705
|
-
/* @__PURE__ */ jsx(Row, { label: "Y
|
|
706
|
-
/* @__PURE__ */ jsx(Row, { label: "Show Y
|
|
707
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
708
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
709
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
710
|
-
/* @__PURE__ */ jsx(Row, { label: "X title pos", children: /* @__PURE__ */ jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
|
|
711
|
-
/* @__PURE__ */ jsx(Row, { label: "X title offset", children: /* @__PURE__ */ jsx(NumberControl, { value: s.xLegendOffset, onChange: (v) => set("xLegendOffset", v) }) }),
|
|
712
|
-
/* @__PURE__ */ jsx(Row, { label: "Y title pos", children: /* @__PURE__ */ jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
|
|
713
|
-
/* @__PURE__ */ jsx(Row, { label: "Y title offset", children: /* @__PURE__ */ jsx(NumberControl, { value: s.yLegendOffset, onChange: (v) => set("yLegendOffset", v) }) })
|
|
716
|
+
/* @__PURE__ */ jsxs(Section, { title: "Axis titles", children: [
|
|
717
|
+
/* @__PURE__ */ jsx(Row, { label: "X title", children: /* @__PURE__ */ jsx(TextControl, { value: s.xAxisLabel, placeholder: "(column)", onChange: (v) => set("xAxisLabel", v) }) }),
|
|
718
|
+
/* @__PURE__ */ jsx(Row, { label: "Show X", children: /* @__PURE__ */ jsx(Switch, { value: s.showXLegend, onChange: (v) => set("showXLegend", v) }) }),
|
|
719
|
+
/* @__PURE__ */ jsx(Row, { label: "Y title", children: /* @__PURE__ */ jsx(TextControl, { value: s.yAxisLabel, placeholder: "(column)", onChange: (v) => set("yAxisLabel", v) }) }),
|
|
720
|
+
/* @__PURE__ */ jsx(Row, { label: "Show Y", children: /* @__PURE__ */ jsx(Switch, { value: s.showYLegend, onChange: (v) => set("showYLegend", v) }) }),
|
|
721
|
+
/* @__PURE__ */ jsx(Row, { label: "Color", children: /* @__PURE__ */ jsx(ColorControl, { value: s.legendColor, onChange: (v) => set("legendColor", v) }) }),
|
|
722
|
+
/* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(Switch, { value: s.legendBold, onChange: (v) => set("legendBold", v) }) }),
|
|
723
|
+
/* @__PURE__ */ jsx(Row, { label: "Highlight", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.legendHighlight, onChange: (v) => set("legendHighlight", v) }) })
|
|
714
724
|
] }),
|
|
715
|
-
/* @__PURE__ */ jsxs(
|
|
716
|
-
/* @__PURE__ */ jsx(Row, { label: "X
|
|
717
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
725
|
+
/* @__PURE__ */ jsxs(Section, { title: "Axis placement", children: [
|
|
726
|
+
/* @__PURE__ */ jsx(Row, { label: "X pos", children: /* @__PURE__ */ jsx(Choice, { value: s.xLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("xLegendPosition", v) }) }),
|
|
727
|
+
/* @__PURE__ */ jsx(Row, { label: "X offset", children: /* @__PURE__ */ jsx(SliderControl, { value: s.xLegendOffset, min: -80, max: 80, onChange: (v) => set("xLegendOffset", v) }) }),
|
|
728
|
+
/* @__PURE__ */ jsx(Row, { label: "Y pos", children: /* @__PURE__ */ jsx(Choice, { value: s.yLegendPosition, options: ["start", "middle", "end"], onChange: (v) => set("yLegendPosition", v) }) }),
|
|
729
|
+
/* @__PURE__ */ jsx(Row, { label: "Y offset", children: /* @__PURE__ */ jsx(SliderControl, { value: s.yLegendOffset, min: -80, max: 80, onChange: (v) => set("yLegendOffset", v) }) })
|
|
718
730
|
] }),
|
|
719
|
-
/* @__PURE__ */ jsxs(
|
|
720
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
721
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
722
|
-
/* @__PURE__ */ jsx(Row, { label: "
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
731
|
+
/* @__PURE__ */ jsxs(Section, { title: "Ticks", children: [
|
|
732
|
+
/* @__PURE__ */ jsx(Row, { label: "X angle", children: /* @__PURE__ */ jsx(SliderControl, { value: s.xTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("xTickRotation", v) }) }),
|
|
733
|
+
/* @__PURE__ */ jsx(Row, { label: "Y angle", children: /* @__PURE__ */ jsx(SliderControl, { value: s.yTickRotation, min: -90, max: 90, unit: "\xB0", onChange: (v) => set("yTickRotation", v) }) }),
|
|
734
|
+
/* @__PURE__ */ jsx(Row, { label: "Truncate", children: /* @__PURE__ */ jsx(SliderControl, { value: s.tickTruncate, min: 0, max: 40, onChange: (v) => set("tickTruncate", v) }) })
|
|
735
|
+
] }),
|
|
736
|
+
/* @__PURE__ */ jsxs(Section, { title: "Panel title", children: [
|
|
737
|
+
/* @__PURE__ */ jsx(Row, { label: "Align", children: /* @__PURE__ */ jsx(Choice, { value: s.titleAlign, options: ["left", "center", "right"], onChange: (v) => set("titleAlign", v) }) }),
|
|
738
|
+
/* @__PURE__ */ jsx(Row, { label: "Bold", children: /* @__PURE__ */ jsx(Switch, { value: s.titleBold, onChange: (v) => set("titleBold", v) }) }),
|
|
739
|
+
/* @__PURE__ */ jsx(Row, { label: "Color", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleColor, onChange: (v) => set("titleColor", v), fallback: "#e2e8f0" }) }),
|
|
740
|
+
/* @__PURE__ */ jsx(Row, { label: "Background", children: /* @__PURE__ */ jsx(OptionalColor, { value: s.titleBackground, onChange: (v) => set("titleBackground", v), fallback: "#1e293b" }) })
|
|
741
|
+
] }),
|
|
742
|
+
/* @__PURE__ */ jsx(Section, { title: "Series legend", children: /* @__PURE__ */ jsx(Row, { label: "Position", children: /* @__PURE__ */ jsx(Choice, { value: s.legendAnchor, options: ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"], onChange: (v) => set("legendAnchor", v) }) }) })
|
|
726
743
|
] });
|
|
727
744
|
}
|
|
728
745
|
var AGGREGATES = [
|
|
@@ -1992,57 +2009,112 @@ function PanelContent({ panel, records, columns }) {
|
|
|
1992
2009
|
] });
|
|
1993
2010
|
}
|
|
1994
2011
|
}
|
|
1995
|
-
var
|
|
2012
|
+
var GRID_COLS = 12;
|
|
2013
|
+
var ROW_HEIGHT = 80;
|
|
2014
|
+
var GRID_MARGIN = 10;
|
|
2015
|
+
var DRAG_HANDLE = "panel-drag-handle";
|
|
2016
|
+
var RGL_CSS = `
|
|
2017
|
+
.react-grid-layout { position: relative; transition: height 200ms ease; }
|
|
2018
|
+
.react-grid-item { transition: all 200ms ease; transition-property: left, top, width, height; box-sizing: border-box; }
|
|
2019
|
+
.react-grid-item.cssTransforms { transition-property: transform, width, height; }
|
|
2020
|
+
.react-grid-item.resizing { transition: none; z-index: 3; will-change: width, height; }
|
|
2021
|
+
.react-grid-item.react-draggable-dragging { transition: none; z-index: 3; will-change: transform; }
|
|
2022
|
+
.react-grid-item.react-grid-placeholder { background: rgba(99,102,241,0.18); border: 1px dashed #6366f1; border-radius: 2px; transition-duration: 100ms; z-index: 2; user-select: none; }
|
|
2023
|
+
.react-grid-item > .react-resizable-handle { position: absolute; width: 18px; height: 18px; bottom: 0; right: 0; cursor: se-resize; }
|
|
2024
|
+
.react-grid-item > .react-resizable-handle::after { content: ''; position: absolute; right: 4px; bottom: 4px; width: 6px; height: 6px; border-right: 2px solid rgba(148,163,184,0.7); border-bottom: 2px solid rgba(148,163,184,0.7); }
|
|
2025
|
+
.${DRAG_HANDLE} { cursor: grab; }
|
|
2026
|
+
.react-grid-item.react-draggable-dragging .${DRAG_HANDLE} { cursor: grabbing; }
|
|
2027
|
+
`;
|
|
2028
|
+
var rglCssInjected = false;
|
|
2029
|
+
function useInjectRglCss() {
|
|
2030
|
+
useEffect(() => {
|
|
2031
|
+
if (rglCssInjected || typeof document === "undefined") return;
|
|
2032
|
+
const el = document.createElement("style");
|
|
2033
|
+
el.setAttribute("data-rgl", "dashboard-renderer");
|
|
2034
|
+
el.textContent = RGL_CSS;
|
|
2035
|
+
document.head.appendChild(el);
|
|
2036
|
+
rglCssInjected = true;
|
|
2037
|
+
}, []);
|
|
2038
|
+
}
|
|
2039
|
+
var GridLayoutWithWidth = WidthProvider(GridLayout);
|
|
2040
|
+
function buildLayout(panels) {
|
|
2041
|
+
let cx = 0, cy = 0, rowH = 0;
|
|
2042
|
+
return panels.map((p) => {
|
|
2043
|
+
const w = Math.min(Math.max(p.width || 6, 1), GRID_COLS);
|
|
2044
|
+
const h = Math.max(p.height || 4, 1);
|
|
2045
|
+
if (typeof p.x === "number" && typeof p.y === "number") {
|
|
2046
|
+
return { i: p.id, x: p.x, y: p.y, w, h, minW: 2, minH: 2 };
|
|
2047
|
+
}
|
|
2048
|
+
if (cx + w > GRID_COLS) {
|
|
2049
|
+
cx = 0;
|
|
2050
|
+
cy += rowH;
|
|
2051
|
+
rowH = 0;
|
|
2052
|
+
}
|
|
2053
|
+
const item = { i: p.id, x: cx, y: cy, w, h, minW: 2, minH: 2 };
|
|
2054
|
+
cx += w;
|
|
2055
|
+
rowH = Math.max(rowH, h);
|
|
2056
|
+
return item;
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
1996
2059
|
function DashboardRenderer({ dashboard, records, columns }) {
|
|
1997
|
-
const { theme, removePanel } = useDashboard();
|
|
1998
|
-
const { canEditPanels } = useCapabilities();
|
|
2060
|
+
const { theme, removePanel, persistLayout } = useDashboard();
|
|
2061
|
+
const { canEditPanels, canEditLayout } = useCapabilities();
|
|
2062
|
+
useInjectRglCss();
|
|
2063
|
+
const panels = dashboard?.panels ?? [];
|
|
2064
|
+
const layout = useMemo(() => buildLayout(panels), [panels]);
|
|
2065
|
+
const onLayoutChange = useCallback((next) => {
|
|
2066
|
+
if (!persistLayout) return;
|
|
2067
|
+
persistLayout(next.map((l) => ({ id: l.i, x: l.x, y: l.y, w: l.w, h: l.h })));
|
|
2068
|
+
}, [persistLayout]);
|
|
1999
2069
|
if (!dashboard) return null;
|
|
2000
2070
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 p-2", children: [
|
|
2001
2071
|
dashboard.insights && /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-elevated px-4 py-3 flex items-start gap-3`, children: [
|
|
2002
2072
|
/* @__PURE__ */ jsx(Sparkles, { className: "w-5 h-5 text-midnight-accent shrink-0 mt-0.5" }),
|
|
2003
2073
|
/* @__PURE__ */ jsx("p", { className: `text-sm ${theme.text} leading-relaxed`, children: dashboard.insights })
|
|
2004
2074
|
] }),
|
|
2005
|
-
/* @__PURE__ */ jsx(
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2075
|
+
/* @__PURE__ */ jsx(
|
|
2076
|
+
GridLayoutWithWidth,
|
|
2077
|
+
{
|
|
2078
|
+
className: "layout",
|
|
2079
|
+
layout,
|
|
2080
|
+
cols: GRID_COLS,
|
|
2081
|
+
rowHeight: ROW_HEIGHT,
|
|
2082
|
+
margin: [GRID_MARGIN, GRID_MARGIN],
|
|
2083
|
+
isDraggable: canEditLayout,
|
|
2084
|
+
isResizable: canEditLayout,
|
|
2085
|
+
draggableHandle: `.${DRAG_HANDLE}`,
|
|
2086
|
+
onDragStop: onLayoutChange,
|
|
2087
|
+
onResizeStop: onLayoutChange,
|
|
2088
|
+
compactType: canEditLayout ? "vertical" : null,
|
|
2089
|
+
children: panels.map((panel) => /* @__PURE__ */ jsxs("div", { className: `border ${theme.border} bg-midnight-surface flex flex-col overflow-hidden`, children: [
|
|
2090
|
+
/* @__PURE__ */ jsxs("div", { className: `flex items-center justify-between px-3 py-1.5 border-b ${theme.border} bg-midnight-elevated ${canEditLayout ? DRAG_HANDLE : ""}`, children: [
|
|
2091
|
+
/* @__PURE__ */ jsx(
|
|
2092
|
+
"span",
|
|
2093
|
+
{
|
|
2094
|
+
className: "flex-1 text-xs font-mono text-midnight-text-body truncate px-1",
|
|
2095
|
+
style: {
|
|
2096
|
+
textAlign: panel.config?.style?.titleAlign || "left",
|
|
2097
|
+
fontWeight: panel.config?.style?.titleBold ? 700 : void 0,
|
|
2098
|
+
background: panel.config?.style?.titleBackground || void 0,
|
|
2099
|
+
color: panel.config?.style?.titleColor || void 0
|
|
2100
|
+
},
|
|
2101
|
+
children: panel.title || panel.type
|
|
2102
|
+
}
|
|
2103
|
+
),
|
|
2104
|
+
canEditPanels && removePanel && /* @__PURE__ */ jsx(
|
|
2105
|
+
"button",
|
|
2106
|
+
{
|
|
2107
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
2108
|
+
onClick: () => removePanel(panel.id),
|
|
2109
|
+
className: "p-0.5 hover:bg-midnight-raised text-midnight-text-muted hover:text-midnight-text-body transition-colors",
|
|
2110
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
|
|
2111
|
+
}
|
|
2112
|
+
)
|
|
2113
|
+
] }),
|
|
2114
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(ViewLoading, {}), children: /* @__PURE__ */ jsx(PanelContent, { panel, records, columns }) }) })
|
|
2115
|
+
] }, panel.id))
|
|
2116
|
+
}
|
|
2117
|
+
)
|
|
2046
2118
|
] });
|
|
2047
2119
|
}
|
|
2048
2120
|
function ProfileSummary({ profile, theme }) {
|