@object-ui/plugin-dashboard 3.0.3 → 3.1.1

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.
Files changed (55) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/CHANGELOG.md +10 -0
  3. package/dist/index.js +3848 -2635
  4. package/dist/index.umd.cjs +5 -5
  5. package/dist/src/DashboardConfigPanel.d.ts +28 -0
  6. package/dist/src/DashboardConfigPanel.d.ts.map +1 -0
  7. package/dist/src/DashboardConfigPanel.stories.d.ts +14 -0
  8. package/dist/src/DashboardConfigPanel.stories.d.ts.map +1 -0
  9. package/dist/src/DashboardGridLayout.d.ts.map +1 -1
  10. package/dist/src/DashboardRenderer.d.ts +14 -0
  11. package/dist/src/DashboardRenderer.d.ts.map +1 -1
  12. package/dist/src/DashboardWithConfig.d.ts +32 -0
  13. package/dist/src/DashboardWithConfig.d.ts.map +1 -0
  14. package/dist/src/MetricCard.d.ts +8 -2
  15. package/dist/src/MetricCard.d.ts.map +1 -1
  16. package/dist/src/MetricWidget.d.ts +12 -3
  17. package/dist/src/MetricWidget.d.ts.map +1 -1
  18. package/dist/src/ObjectDataTable.d.ts +39 -0
  19. package/dist/src/ObjectDataTable.d.ts.map +1 -0
  20. package/dist/src/ObjectPivotTable.d.ts +29 -0
  21. package/dist/src/ObjectPivotTable.d.ts.map +1 -0
  22. package/dist/src/PivotTable.d.ts +14 -0
  23. package/dist/src/PivotTable.d.ts.map +1 -0
  24. package/dist/src/WidgetConfigPanel.d.ts +43 -0
  25. package/dist/src/WidgetConfigPanel.d.ts.map +1 -0
  26. package/dist/src/index.d.ts +13 -1
  27. package/dist/src/index.d.ts.map +1 -1
  28. package/dist/src/utils.d.ts +15 -0
  29. package/dist/src/utils.d.ts.map +1 -0
  30. package/package.json +7 -7
  31. package/src/DashboardConfigPanel.stories.tsx +164 -0
  32. package/src/DashboardConfigPanel.tsx +158 -0
  33. package/src/DashboardGridLayout.tsx +101 -3
  34. package/src/DashboardRenderer.tsx +269 -28
  35. package/src/DashboardWithConfig.tsx +211 -0
  36. package/src/MetricCard.tsx +11 -4
  37. package/src/MetricWidget.tsx +18 -11
  38. package/src/ObjectDataTable.tsx +191 -0
  39. package/src/ObjectPivotTable.tsx +160 -0
  40. package/src/PivotTable.tsx +262 -0
  41. package/src/WidgetConfigPanel.tsx +540 -0
  42. package/src/__tests__/DashboardConfigPanel.test.tsx +206 -0
  43. package/src/__tests__/DashboardRenderer.designMode.test.tsx +386 -0
  44. package/src/__tests__/DashboardRenderer.header.test.tsx +114 -0
  45. package/src/__tests__/DashboardRenderer.mobile.test.tsx +214 -0
  46. package/src/__tests__/DashboardRenderer.widgetData.test.tsx +1022 -0
  47. package/src/__tests__/DashboardWithConfig.test.tsx +276 -0
  48. package/src/__tests__/MetricCard.test.tsx +23 -0
  49. package/src/__tests__/ObjectDataTable.test.tsx +122 -0
  50. package/src/__tests__/ObjectPivotTable.test.tsx +192 -0
  51. package/src/__tests__/PivotTable.test.tsx +162 -0
  52. package/src/__tests__/WidgetConfigPanel.test.tsx +492 -0
  53. package/src/__tests__/ensureWidgetIds.test.tsx +103 -0
  54. package/src/index.tsx +107 -1
  55. package/src/utils.ts +17 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import * as React from 'react';
10
+ import {
11
+ ConfigPanelRenderer,
12
+ useConfigDraft,
13
+ } from '@object-ui/components';
14
+ import type { ConfigPanelSchema } from '@object-ui/components';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Schema — describes the full DashboardConfigPanel structure
18
+ // ---------------------------------------------------------------------------
19
+
20
+ const dashboardSchema: ConfigPanelSchema = {
21
+ breadcrumb: ['Dashboard', 'Configuration'],
22
+ sections: [
23
+ {
24
+ key: 'layout',
25
+ title: 'Layout',
26
+ fields: [
27
+ {
28
+ key: 'columns',
29
+ label: 'Columns',
30
+ type: 'slider',
31
+ defaultValue: 3,
32
+ min: 1,
33
+ max: 12,
34
+ step: 1,
35
+ },
36
+ {
37
+ key: 'gap',
38
+ label: 'Gap',
39
+ type: 'slider',
40
+ defaultValue: 4,
41
+ min: 0,
42
+ max: 16,
43
+ step: 1,
44
+ },
45
+ {
46
+ key: 'rowHeight',
47
+ label: 'Row height',
48
+ type: 'input',
49
+ defaultValue: '120',
50
+ placeholder: 'e.g. 120',
51
+ },
52
+ ],
53
+ },
54
+ {
55
+ key: 'data',
56
+ title: 'Data',
57
+ collapsible: true,
58
+ fields: [
59
+ {
60
+ key: 'refreshInterval',
61
+ label: 'Refresh interval',
62
+ type: 'select',
63
+ defaultValue: '0',
64
+ options: [
65
+ { value: '0', label: 'Manual' },
66
+ { value: '30', label: '30s' },
67
+ { value: '60', label: '1 min' },
68
+ { value: '300', label: '5 min' },
69
+ ],
70
+ },
71
+ ],
72
+ },
73
+ {
74
+ key: 'appearance',
75
+ title: 'Appearance',
76
+ collapsible: true,
77
+ defaultCollapsed: true,
78
+ fields: [
79
+ {
80
+ key: 'title',
81
+ label: 'Title',
82
+ type: 'input',
83
+ placeholder: 'Dashboard title',
84
+ },
85
+ {
86
+ key: 'showDescription',
87
+ label: 'Show description',
88
+ type: 'switch',
89
+ defaultValue: true,
90
+ },
91
+ {
92
+ key: 'theme',
93
+ label: 'Theme',
94
+ type: 'select',
95
+ defaultValue: 'auto',
96
+ options: [
97
+ { value: 'light', label: 'Light' },
98
+ { value: 'dark', label: 'Dark' },
99
+ { value: 'auto', label: 'Auto' },
100
+ ],
101
+ },
102
+ ],
103
+ },
104
+ ],
105
+ };
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Props
109
+ // ---------------------------------------------------------------------------
110
+
111
+ export interface DashboardConfigPanelProps {
112
+ /** Whether the panel is open */
113
+ open: boolean;
114
+ /** Close handler */
115
+ onClose: () => void;
116
+ /** Initial / committed dashboard configuration */
117
+ config: Record<string, any>;
118
+ /** Persist the updated config */
119
+ onSave: (config: Record<string, any>) => void;
120
+ /** Optional live-update callback */
121
+ onFieldChange?: (field: string, value: any) => void;
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Component
126
+ // ---------------------------------------------------------------------------
127
+
128
+ /**
129
+ * DashboardConfigPanel — Schema-driven configuration panel for dashboards.
130
+ *
131
+ * Built entirely on the generic ConfigPanelRenderer + useConfigDraft,
132
+ * demonstrating that a full config panel can be expressed in ~60 lines
133
+ * of declarative schema rather than 1500+ lines of imperative code.
134
+ */
135
+ export function DashboardConfigPanel({
136
+ open,
137
+ onClose,
138
+ config,
139
+ onSave,
140
+ onFieldChange,
141
+ }: DashboardConfigPanelProps) {
142
+ const { draft, isDirty, updateField, discard } = useConfigDraft(config, {
143
+ onUpdate: onFieldChange,
144
+ });
145
+
146
+ return (
147
+ <ConfigPanelRenderer
148
+ open={open}
149
+ onClose={onClose}
150
+ schema={dashboardSchema}
151
+ draft={draft}
152
+ isDirty={isDirty}
153
+ onFieldChange={updateField}
154
+ onSave={() => onSave(draft)}
155
+ onDiscard={discard}
156
+ />
157
+ );
158
+ }
@@ -5,6 +5,7 @@ import { cn, Card, CardHeader, CardTitle, CardContent, Button } from '@object-ui
5
5
  import { Edit, GripVertical, Save, X, RefreshCw } from 'lucide-react';
6
6
  import { SchemaRenderer, useHasDndProvider, useDnd } from '@object-ui/react';
7
7
  import type { DashboardSchema, DashboardWidgetSchema } from '@object-ui/types';
8
+ import { isObjectProvider } from './utils';
8
9
 
9
10
  /** Bridges editMode transitions to the ObjectUI DnD system when a DndProvider is present. */
10
11
  function DndEditModeBridge({ editMode }: { editMode: boolean }) {
@@ -128,11 +129,57 @@ export const DashboardGridLayout: React.FC<DashboardGridLayoutProps> = ({
128
129
 
129
130
  const widgetType = widget.type;
130
131
  const options = (widget.options || {}) as Record<string, any>;
131
- if (widgetType === 'bar' || widgetType === 'line' || widgetType === 'area' || widgetType === 'pie' || widgetType === 'donut') {
132
+ if (widgetType === 'bar' || widgetType === 'line' || widgetType === 'area' || widgetType === 'pie' || widgetType === 'donut' || widgetType === 'scatter') {
132
133
  const widgetData = (widget as any).data || options.data;
134
+ // Widget-level fields (from config panel) override options-level fields
135
+ const xAxisKey = widget.categoryField || options.xField || 'name';
136
+ const yField = widget.valueField || options.yField || 'value';
137
+
138
+ // provider: 'object' — delegate to ObjectChart for async data loading
139
+ if (isObjectProvider(widgetData)) {
140
+ // Merge widget-level fields with data provider config.
141
+ // Widget-level fields take precedence so that config panel
142
+ // edits are immediately reflected in the live preview.
143
+ const providerAgg = widgetData.aggregate;
144
+ const effectiveAggregate = providerAgg ? {
145
+ field: widget.valueField || providerAgg.field,
146
+ function: widget.aggregate || providerAgg.function,
147
+ groupBy: widget.categoryField || providerAgg.groupBy,
148
+ } : undefined;
149
+ const effectiveYField = effectiveAggregate?.field || yField;
150
+ return {
151
+ type: 'object-chart',
152
+ chartType: widgetType,
153
+ objectName: widget.object || widgetData.object,
154
+ aggregate: effectiveAggregate,
155
+ xAxisKey: xAxisKey,
156
+ series: [{ dataKey: effectiveYField }],
157
+ colors: CHART_COLORS,
158
+ className: "h-full"
159
+ };
160
+ }
161
+
162
+ // No explicit data provider but widget has object binding
163
+ // (e.g. newly created widget via config panel) — build object-chart
164
+ if (!widgetData && widget.object) {
165
+ const aggregate = widget.aggregate ? {
166
+ field: widget.valueField || 'value',
167
+ function: widget.aggregate,
168
+ groupBy: widget.categoryField || 'name',
169
+ } : undefined;
170
+ return {
171
+ type: 'object-chart',
172
+ chartType: widgetType,
173
+ objectName: widget.object,
174
+ aggregate,
175
+ xAxisKey: xAxisKey,
176
+ series: [{ dataKey: widget.valueField || 'value' }],
177
+ colors: CHART_COLORS,
178
+ className: "h-full"
179
+ };
180
+ }
181
+
133
182
  const dataItems = Array.isArray(widgetData) ? widgetData : widgetData?.items || [];
134
- const xAxisKey = options.xField || 'name';
135
- const yField = options.yField || 'value';
136
183
 
137
184
  return {
138
185
  type: 'chart',
@@ -147,6 +194,35 @@ export const DashboardGridLayout: React.FC<DashboardGridLayoutProps> = ({
147
194
 
148
195
  if (widgetType === 'table') {
149
196
  const widgetData = (widget as any).data || options.data;
197
+
198
+ // provider: 'object' — pass through object config for async data loading
199
+ if (isObjectProvider(widgetData)) {
200
+ const { data: _data, ...restOptions } = options;
201
+ return {
202
+ type: 'data-table',
203
+ ...restOptions,
204
+ objectName: widget.object || widgetData.object,
205
+ dataProvider: widgetData,
206
+ data: [],
207
+ searchable: false,
208
+ pagination: false,
209
+ className: "border-0"
210
+ };
211
+ }
212
+
213
+ // No explicit data provider but widget has object binding
214
+ if (!widgetData && widget.object) {
215
+ return {
216
+ type: 'data-table',
217
+ ...options,
218
+ objectName: widget.object,
219
+ data: [],
220
+ searchable: false,
221
+ pagination: false,
222
+ className: "border-0"
223
+ };
224
+ }
225
+
150
226
  return {
151
227
  type: 'data-table',
152
228
  ...options,
@@ -157,6 +233,28 @@ export const DashboardGridLayout: React.FC<DashboardGridLayoutProps> = ({
157
233
  };
158
234
  }
159
235
 
236
+ if (widgetType === 'pivot') {
237
+ const widgetData = (widget as any).data || options.data;
238
+
239
+ // provider: 'object' — pass through object config for async data loading
240
+ if (isObjectProvider(widgetData)) {
241
+ const { data: _data, ...restOptions } = options;
242
+ return {
243
+ type: 'pivot',
244
+ ...restOptions,
245
+ objectName: widget.object || widgetData.object,
246
+ dataProvider: widgetData,
247
+ data: [],
248
+ };
249
+ }
250
+
251
+ return {
252
+ type: 'pivot',
253
+ ...options,
254
+ data: Array.isArray(widgetData) ? widgetData : widgetData?.items || [],
255
+ };
256
+ }
257
+
160
258
  return {
161
259
  ...widget,
162
260
  ...options