@object-ui/plugin-dashboard 3.1.5 → 3.3.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.
- package/CHANGELOG.md +28 -0
- package/README.md +21 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1162 -939
- package/dist/index.umd.cjs +4 -4
- package/dist/packages/plugin-dashboard/src/DashboardConfigPanel.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/DashboardConfigPanel.stories.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/DashboardGridLayout.d.ts.map +1 -0
- package/dist/{src → packages/plugin-dashboard/src}/DashboardRenderer.d.ts +5 -0
- package/dist/packages/plugin-dashboard/src/DashboardRenderer.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/DashboardRenderer.stories.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/DashboardWithConfig.d.ts.map +1 -0
- package/dist/{src → packages/plugin-dashboard/src}/MetricCard.d.ts +4 -0
- package/dist/packages/plugin-dashboard/src/MetricCard.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts +31 -0
- package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/ObjectDataTable.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts +59 -0
- package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/ObjectPivotTable.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/PivotTable.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/WidgetConfigPanel.d.ts.map +1 -0
- package/dist/{src → packages/plugin-dashboard/src}/index.d.ts +4 -2
- package/dist/packages/plugin-dashboard/src/index.d.ts.map +1 -0
- package/dist/packages/plugin-dashboard/src/utils.d.ts.map +1 -0
- package/package.json +44 -11
- package/.turbo/turbo-build.log +0 -34
- package/dist/src/DashboardConfigPanel.d.ts.map +0 -1
- package/dist/src/DashboardConfigPanel.stories.d.ts.map +0 -1
- package/dist/src/DashboardGridLayout.d.ts.map +0 -1
- package/dist/src/DashboardRenderer.d.ts.map +0 -1
- package/dist/src/DashboardRenderer.stories.d.ts.map +0 -1
- package/dist/src/DashboardWithConfig.d.ts.map +0 -1
- package/dist/src/MetricCard.d.ts.map +0 -1
- package/dist/src/MetricWidget.d.ts +0 -24
- package/dist/src/MetricWidget.d.ts.map +0 -1
- package/dist/src/ObjectDataTable.d.ts.map +0 -1
- package/dist/src/ObjectPivotTable.d.ts.map +0 -1
- package/dist/src/PivotTable.d.ts.map +0 -1
- package/dist/src/WidgetConfigPanel.d.ts.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/utils.d.ts.map +0 -1
- package/src/DashboardConfigPanel.stories.tsx +0 -164
- package/src/DashboardConfigPanel.tsx +0 -158
- package/src/DashboardGridLayout.tsx +0 -367
- package/src/DashboardRenderer.stories.tsx +0 -173
- package/src/DashboardRenderer.tsx +0 -445
- package/src/DashboardWithConfig.tsx +0 -211
- package/src/MetricCard.tsx +0 -82
- package/src/MetricWidget.tsx +0 -76
- package/src/ObjectDataTable.tsx +0 -226
- package/src/ObjectPivotTable.tsx +0 -160
- package/src/PivotTable.tsx +0 -262
- package/src/WidgetConfigPanel.tsx +0 -540
- package/src/__tests__/DashboardConfigPanel.test.tsx +0 -206
- package/src/__tests__/DashboardGridLayout.test.tsx +0 -199
- package/src/__tests__/DashboardRenderer.autoRefresh.test.tsx +0 -124
- package/src/__tests__/DashboardRenderer.designMode.test.tsx +0 -386
- package/src/__tests__/DashboardRenderer.header.test.tsx +0 -114
- package/src/__tests__/DashboardRenderer.mobile.test.tsx +0 -214
- package/src/__tests__/DashboardRenderer.widgetData.test.tsx +0 -1283
- package/src/__tests__/DashboardWithConfig.test.tsx +0 -276
- package/src/__tests__/MetricCard.test.tsx +0 -82
- package/src/__tests__/ObjectDataTable.test.tsx +0 -211
- package/src/__tests__/ObjectPivotTable.test.tsx +0 -192
- package/src/__tests__/PivotTable.test.tsx +0 -162
- package/src/__tests__/WidgetConfigPanel.test.tsx +0 -492
- package/src/__tests__/ensureWidgetIds.test.tsx +0 -103
- package/src/index.tsx +0 -214
- package/src/utils.ts +0 -17
- package/tsconfig.json +0 -19
- package/vite.config.ts +0 -63
- package/vitest.config.ts +0 -9
- package/vitest.setup.tsx +0 -18
- /package/dist/{src → packages/plugin-dashboard/src}/DashboardConfigPanel.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/DashboardConfigPanel.stories.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/DashboardGridLayout.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/DashboardRenderer.stories.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/DashboardWithConfig.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/ObjectDataTable.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/ObjectPivotTable.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/PivotTable.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/WidgetConfigPanel.d.ts +0 -0
- /package/dist/{src → packages/plugin-dashboard/src}/utils.d.ts +0 -0
|
@@ -1,540 +0,0 @@
|
|
|
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
|
-
Combobox,
|
|
14
|
-
} from '@object-ui/components';
|
|
15
|
-
import { ConfigRow } from '@object-ui/components';
|
|
16
|
-
import type { ConfigPanelSchema, ConfigField } from '@object-ui/components';
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Widget type options derived from @object-ui/types DASHBOARD_WIDGET_TYPES
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
const WIDGET_TYPE_OPTIONS = [
|
|
23
|
-
{ value: 'metric', label: 'Metric' },
|
|
24
|
-
{ value: 'bar', label: 'Bar Chart' },
|
|
25
|
-
{ value: 'line', label: 'Line Chart' },
|
|
26
|
-
{ value: 'pie', label: 'Pie Chart' },
|
|
27
|
-
{ value: 'donut', label: 'Donut Chart' },
|
|
28
|
-
{ value: 'area', label: 'Area Chart' },
|
|
29
|
-
{ value: 'scatter', label: 'Scatter Plot' },
|
|
30
|
-
{ value: 'table', label: 'Table' },
|
|
31
|
-
{ value: 'pivot', label: 'Pivot Table' },
|
|
32
|
-
{ value: 'list', label: 'List' },
|
|
33
|
-
{ value: 'custom', label: 'Custom' },
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
const COLOR_VARIANT_OPTIONS = [
|
|
37
|
-
{ value: 'default', label: 'Default' },
|
|
38
|
-
{ value: 'blue', label: 'Blue' },
|
|
39
|
-
{ value: 'teal', label: 'Teal' },
|
|
40
|
-
{ value: 'orange', label: 'Orange' },
|
|
41
|
-
{ value: 'purple', label: 'Purple' },
|
|
42
|
-
{ value: 'success', label: 'Success' },
|
|
43
|
-
{ value: 'warning', label: 'Warning' },
|
|
44
|
-
{ value: 'danger', label: 'Danger' },
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
const AGGREGATE_OPTIONS = [
|
|
48
|
-
{ value: 'count', label: 'Count' },
|
|
49
|
-
{ value: 'sum', label: 'Sum' },
|
|
50
|
-
{ value: 'avg', label: 'Average' },
|
|
51
|
-
{ value: 'min', label: 'Min' },
|
|
52
|
-
{ value: 'max', label: 'Max' },
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
// Helpers
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
|
|
59
|
-
const CHART_TYPES = ['bar', 'line', 'area', 'pie', 'donut', 'scatter'];
|
|
60
|
-
|
|
61
|
-
function isChartType(t: string | undefined): boolean {
|
|
62
|
-
return !!t && CHART_TYPES.includes(t);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const SORT_BY_OPTIONS = [
|
|
66
|
-
{ value: 'group', label: 'Group' },
|
|
67
|
-
{ value: 'value', label: 'Value' },
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
const SORT_ORDER_OPTIONS = [
|
|
71
|
-
{ value: 'asc', label: '↑' },
|
|
72
|
-
{ value: 'desc', label: '↓' },
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
// Schema builder — creates a ConfigPanelSchema with dynamic options for
|
|
77
|
-
// object/field selectors when metadata is available. Sections are shown or
|
|
78
|
-
// hidden based on the current widget type via `visibleWhen`.
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
export type SelectOption = { value: string; label: string };
|
|
82
|
-
|
|
83
|
-
function buildFieldCombobox(
|
|
84
|
-
key: string,
|
|
85
|
-
label: string,
|
|
86
|
-
placeholder: string,
|
|
87
|
-
availableFields: SelectOption[] | undefined,
|
|
88
|
-
): ConfigField {
|
|
89
|
-
return {
|
|
90
|
-
key,
|
|
91
|
-
label,
|
|
92
|
-
type: 'custom',
|
|
93
|
-
render: (value: any, onChange: (v: any) => void, draft: Record<string, any>) => (
|
|
94
|
-
<ConfigRow label={label}>
|
|
95
|
-
<div data-testid={`config-field-${key}`}>
|
|
96
|
-
<Combobox
|
|
97
|
-
options={availableFields ?? []}
|
|
98
|
-
value={value ?? ''}
|
|
99
|
-
onValueChange={onChange}
|
|
100
|
-
placeholder={placeholder}
|
|
101
|
-
searchPlaceholder="Search fields…"
|
|
102
|
-
emptyText="No fields found."
|
|
103
|
-
className="h-7 w-32 text-xs"
|
|
104
|
-
disabled={!draft.object}
|
|
105
|
-
/>
|
|
106
|
-
</div>
|
|
107
|
-
</ConfigRow>
|
|
108
|
-
),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function buildWidgetSchema(
|
|
113
|
-
availableObjects?: SelectOption[],
|
|
114
|
-
availableFields?: SelectOption[],
|
|
115
|
-
widgetType?: string,
|
|
116
|
-
): ConfigPanelSchema {
|
|
117
|
-
const hasObjects = availableObjects && availableObjects.length > 0;
|
|
118
|
-
|
|
119
|
-
const objectField: ConfigField = hasObjects
|
|
120
|
-
? {
|
|
121
|
-
key: 'object',
|
|
122
|
-
label: 'Data source',
|
|
123
|
-
type: 'custom',
|
|
124
|
-
render: (value: any, onChange: (v: any) => void) => (
|
|
125
|
-
<ConfigRow label="Data source">
|
|
126
|
-
<div data-testid="config-field-object">
|
|
127
|
-
<Combobox
|
|
128
|
-
options={availableObjects}
|
|
129
|
-
value={value ?? ''}
|
|
130
|
-
onValueChange={onChange}
|
|
131
|
-
placeholder="Select object…"
|
|
132
|
-
searchPlaceholder="Search objects…"
|
|
133
|
-
emptyText="No objects found."
|
|
134
|
-
className="h-7 w-32 text-xs"
|
|
135
|
-
/>
|
|
136
|
-
</div>
|
|
137
|
-
</ConfigRow>
|
|
138
|
-
),
|
|
139
|
-
}
|
|
140
|
-
: {
|
|
141
|
-
key: 'object',
|
|
142
|
-
label: 'Data source',
|
|
143
|
-
type: 'input',
|
|
144
|
-
placeholder: 'Object name',
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
// --- Chart-specific field selectors (category / value / aggregate) --------
|
|
148
|
-
|
|
149
|
-
const categoryFieldDef: ConfigField = hasObjects
|
|
150
|
-
? buildFieldCombobox('categoryField', 'Category field', 'Select field…', availableFields)
|
|
151
|
-
: {
|
|
152
|
-
key: 'categoryField',
|
|
153
|
-
label: 'Category field',
|
|
154
|
-
type: 'input',
|
|
155
|
-
placeholder: 'e.g. status',
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const valueFieldDef: ConfigField = hasObjects
|
|
159
|
-
? buildFieldCombobox('valueField', 'Value field', 'Select field…', availableFields)
|
|
160
|
-
: {
|
|
161
|
-
key: 'valueField',
|
|
162
|
-
label: 'Value field',
|
|
163
|
-
type: 'input',
|
|
164
|
-
placeholder: 'e.g. amount',
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// --- Pivot-specific field selectors (row / column / value) ----------------
|
|
168
|
-
|
|
169
|
-
const pivotRowField: ConfigField = hasObjects
|
|
170
|
-
? buildFieldCombobox('rowField', 'Field', 'Select row field…', availableFields)
|
|
171
|
-
: { key: 'rowField', label: 'Field', type: 'input', placeholder: 'e.g. owner' };
|
|
172
|
-
|
|
173
|
-
const pivotColumnField: ConfigField = hasObjects
|
|
174
|
-
? buildFieldCombobox('columnField', 'Field', 'Select column field…', availableFields)
|
|
175
|
-
: { key: 'columnField', label: 'Field', type: 'input', placeholder: 'e.g. stage' };
|
|
176
|
-
|
|
177
|
-
const pivotValueField: ConfigField = hasObjects
|
|
178
|
-
? buildFieldCombobox('pivotValueField', 'Field', 'Select value field…', availableFields)
|
|
179
|
-
: { key: 'pivotValueField', label: 'Field', type: 'input', placeholder: 'e.g. amount' };
|
|
180
|
-
|
|
181
|
-
// ---- Breadcrumb varies by widget type ------------------------------------
|
|
182
|
-
|
|
183
|
-
const BREADCRUMB_LABELS: Record<string, string> = {
|
|
184
|
-
pivot: 'Pivot table',
|
|
185
|
-
table: 'Table',
|
|
186
|
-
};
|
|
187
|
-
const typeName = BREADCRUMB_LABELS[widgetType ?? '']
|
|
188
|
-
?? (isChartType(widgetType) ? 'Chart' : 'Widget');
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
breadcrumb: ['Dashboard', typeName],
|
|
192
|
-
sections: [
|
|
193
|
-
// ----- General (always visible) -------------------------------------
|
|
194
|
-
{
|
|
195
|
-
key: 'general',
|
|
196
|
-
title: 'General',
|
|
197
|
-
fields: [
|
|
198
|
-
{
|
|
199
|
-
key: 'title',
|
|
200
|
-
label: 'Title',
|
|
201
|
-
type: 'input',
|
|
202
|
-
placeholder: 'Widget title',
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
key: 'description',
|
|
206
|
-
label: 'Description',
|
|
207
|
-
type: 'input',
|
|
208
|
-
placeholder: 'Widget description',
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
key: 'type',
|
|
212
|
-
label: 'Widget type',
|
|
213
|
-
type: 'select',
|
|
214
|
-
options: WIDGET_TYPE_OPTIONS,
|
|
215
|
-
defaultValue: 'metric',
|
|
216
|
-
},
|
|
217
|
-
],
|
|
218
|
-
},
|
|
219
|
-
|
|
220
|
-
// ----- Data Binding (chart / metric / table / list / custom) ---------
|
|
221
|
-
{
|
|
222
|
-
key: 'data',
|
|
223
|
-
title: 'Data Binding',
|
|
224
|
-
collapsible: true,
|
|
225
|
-
visibleWhen: (d: Record<string, any>) => d.type !== 'pivot',
|
|
226
|
-
fields: [
|
|
227
|
-
objectField,
|
|
228
|
-
categoryFieldDef,
|
|
229
|
-
valueFieldDef,
|
|
230
|
-
{
|
|
231
|
-
key: 'aggregate',
|
|
232
|
-
label: 'Aggregation',
|
|
233
|
-
type: 'select',
|
|
234
|
-
options: AGGREGATE_OPTIONS,
|
|
235
|
-
defaultValue: 'count',
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
// ----- Pivot: Data source -------------------------------------------
|
|
241
|
-
{
|
|
242
|
-
key: 'pivot-data',
|
|
243
|
-
title: 'Data',
|
|
244
|
-
collapsible: true,
|
|
245
|
-
visibleWhen: (d: Record<string, any>) => d.type === 'pivot',
|
|
246
|
-
fields: [
|
|
247
|
-
objectField,
|
|
248
|
-
],
|
|
249
|
-
},
|
|
250
|
-
|
|
251
|
-
// ----- Pivot: Rows --------------------------------------------------
|
|
252
|
-
{
|
|
253
|
-
key: 'pivot-rows',
|
|
254
|
-
title: 'Rows',
|
|
255
|
-
collapsible: true,
|
|
256
|
-
visibleWhen: (d: Record<string, any>) => d.type === 'pivot',
|
|
257
|
-
fields: [
|
|
258
|
-
pivotRowField,
|
|
259
|
-
{
|
|
260
|
-
key: 'rowSortBy',
|
|
261
|
-
label: 'Sort by',
|
|
262
|
-
type: 'icon-group',
|
|
263
|
-
options: SORT_BY_OPTIONS,
|
|
264
|
-
defaultValue: 'group',
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
key: 'rowSortOrder',
|
|
268
|
-
label: 'Sort order',
|
|
269
|
-
type: 'icon-group',
|
|
270
|
-
options: SORT_ORDER_OPTIONS,
|
|
271
|
-
defaultValue: 'asc',
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
key: 'showRowLabels',
|
|
275
|
-
label: 'Show label',
|
|
276
|
-
type: 'switch',
|
|
277
|
-
defaultValue: true,
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
key: 'showRowTotals',
|
|
281
|
-
label: 'Show totals',
|
|
282
|
-
type: 'switch',
|
|
283
|
-
defaultValue: false,
|
|
284
|
-
},
|
|
285
|
-
],
|
|
286
|
-
},
|
|
287
|
-
|
|
288
|
-
// ----- Pivot: Columns -----------------------------------------------
|
|
289
|
-
{
|
|
290
|
-
key: 'pivot-columns',
|
|
291
|
-
title: 'Columns',
|
|
292
|
-
collapsible: true,
|
|
293
|
-
visibleWhen: (d: Record<string, any>) => d.type === 'pivot',
|
|
294
|
-
fields: [
|
|
295
|
-
pivotColumnField,
|
|
296
|
-
{
|
|
297
|
-
key: 'columnSortBy',
|
|
298
|
-
label: 'Sort by',
|
|
299
|
-
type: 'icon-group',
|
|
300
|
-
options: SORT_BY_OPTIONS,
|
|
301
|
-
defaultValue: 'group',
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
key: 'columnSortOrder',
|
|
305
|
-
label: 'Sort order',
|
|
306
|
-
type: 'icon-group',
|
|
307
|
-
options: SORT_ORDER_OPTIONS,
|
|
308
|
-
defaultValue: 'asc',
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
key: 'showColumnLabels',
|
|
312
|
-
label: 'Show label',
|
|
313
|
-
type: 'switch',
|
|
314
|
-
defaultValue: true,
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
key: 'showColumnTotals',
|
|
318
|
-
label: 'Show totals',
|
|
319
|
-
type: 'switch',
|
|
320
|
-
defaultValue: false,
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
},
|
|
324
|
-
|
|
325
|
-
// ----- Pivot: Values ------------------------------------------------
|
|
326
|
-
{
|
|
327
|
-
key: 'pivot-values',
|
|
328
|
-
title: 'Values',
|
|
329
|
-
collapsible: true,
|
|
330
|
-
visibleWhen: (d: Record<string, any>) => d.type === 'pivot',
|
|
331
|
-
fields: [
|
|
332
|
-
pivotValueField,
|
|
333
|
-
{
|
|
334
|
-
key: 'aggregation',
|
|
335
|
-
label: 'Aggregation',
|
|
336
|
-
type: 'select',
|
|
337
|
-
options: AGGREGATE_OPTIONS,
|
|
338
|
-
defaultValue: 'sum',
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
key: 'format',
|
|
342
|
-
label: 'Number format',
|
|
343
|
-
type: 'input',
|
|
344
|
-
placeholder: 'e.g. $,.2f',
|
|
345
|
-
},
|
|
346
|
-
],
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
// ----- Chart: Axis & Series (visible for chart types) ---------------
|
|
350
|
-
{
|
|
351
|
-
key: 'chart-axis',
|
|
352
|
-
title: 'Axis & Series',
|
|
353
|
-
collapsible: true,
|
|
354
|
-
visibleWhen: (d: Record<string, any>) => isChartType(d.type),
|
|
355
|
-
fields: [
|
|
356
|
-
{
|
|
357
|
-
key: 'xAxisLabel',
|
|
358
|
-
label: 'X-axis label',
|
|
359
|
-
type: 'input',
|
|
360
|
-
placeholder: 'e.g. Month',
|
|
361
|
-
},
|
|
362
|
-
{
|
|
363
|
-
key: 'yAxisLabel',
|
|
364
|
-
label: 'Y-axis label',
|
|
365
|
-
type: 'input',
|
|
366
|
-
placeholder: 'e.g. Revenue',
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
key: 'showLegend',
|
|
370
|
-
label: 'Show legend',
|
|
371
|
-
type: 'switch',
|
|
372
|
-
defaultValue: true,
|
|
373
|
-
},
|
|
374
|
-
],
|
|
375
|
-
},
|
|
376
|
-
|
|
377
|
-
// ----- Table: Column config (visible for table type) ----------------
|
|
378
|
-
{
|
|
379
|
-
key: 'table-columns',
|
|
380
|
-
title: 'Columns',
|
|
381
|
-
collapsible: true,
|
|
382
|
-
visibleWhen: (d: Record<string, any>) => d.type === 'table',
|
|
383
|
-
fields: [
|
|
384
|
-
{
|
|
385
|
-
key: 'searchable',
|
|
386
|
-
label: 'Searchable',
|
|
387
|
-
type: 'switch',
|
|
388
|
-
defaultValue: false,
|
|
389
|
-
},
|
|
390
|
-
{
|
|
391
|
-
key: 'pagination',
|
|
392
|
-
label: 'Pagination',
|
|
393
|
-
type: 'switch',
|
|
394
|
-
defaultValue: false,
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
},
|
|
398
|
-
|
|
399
|
-
// ----- Layout (always visible) --------------------------------------
|
|
400
|
-
{
|
|
401
|
-
key: 'layout',
|
|
402
|
-
title: 'Layout',
|
|
403
|
-
collapsible: true,
|
|
404
|
-
fields: [
|
|
405
|
-
{
|
|
406
|
-
key: 'layoutW',
|
|
407
|
-
label: 'Width (columns)',
|
|
408
|
-
type: 'slider',
|
|
409
|
-
min: 1,
|
|
410
|
-
max: 12,
|
|
411
|
-
step: 1,
|
|
412
|
-
defaultValue: 1,
|
|
413
|
-
},
|
|
414
|
-
{
|
|
415
|
-
key: 'layoutH',
|
|
416
|
-
label: 'Height (rows)',
|
|
417
|
-
type: 'slider',
|
|
418
|
-
min: 1,
|
|
419
|
-
max: 6,
|
|
420
|
-
step: 1,
|
|
421
|
-
defaultValue: 1,
|
|
422
|
-
},
|
|
423
|
-
],
|
|
424
|
-
},
|
|
425
|
-
|
|
426
|
-
// ----- Appearance (always visible, collapsed by default) ------------
|
|
427
|
-
{
|
|
428
|
-
key: 'appearance',
|
|
429
|
-
title: 'Appearance',
|
|
430
|
-
collapsible: true,
|
|
431
|
-
defaultCollapsed: true,
|
|
432
|
-
fields: [
|
|
433
|
-
{
|
|
434
|
-
key: 'colorVariant',
|
|
435
|
-
label: 'Color variant',
|
|
436
|
-
type: 'select',
|
|
437
|
-
options: COLOR_VARIANT_OPTIONS,
|
|
438
|
-
defaultValue: 'default',
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
key: 'actionUrl',
|
|
442
|
-
label: 'Action URL',
|
|
443
|
-
type: 'input',
|
|
444
|
-
placeholder: 'https://...',
|
|
445
|
-
},
|
|
446
|
-
],
|
|
447
|
-
},
|
|
448
|
-
],
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// ---------------------------------------------------------------------------
|
|
453
|
-
// Props
|
|
454
|
-
// ---------------------------------------------------------------------------
|
|
455
|
-
|
|
456
|
-
export interface WidgetConfigPanelProps {
|
|
457
|
-
/** Whether the panel is open */
|
|
458
|
-
open: boolean;
|
|
459
|
-
/** Close handler */
|
|
460
|
-
onClose: () => void;
|
|
461
|
-
/** Widget configuration (flattened: layout.w → layoutW, layout.h → layoutH) */
|
|
462
|
-
config: Record<string, any>;
|
|
463
|
-
/** Persist the updated widget config */
|
|
464
|
-
onSave: (config: Record<string, any>) => void;
|
|
465
|
-
/** Optional live-update callback */
|
|
466
|
-
onFieldChange?: (field: string, value: any) => void;
|
|
467
|
-
/** Extra content rendered in the header row (e.g. delete button) */
|
|
468
|
-
headerExtra?: React.ReactNode;
|
|
469
|
-
/** Available data-source objects for dropdown selection */
|
|
470
|
-
availableObjects?: SelectOption[];
|
|
471
|
-
/** Available fields of the currently selected object for dropdown selection */
|
|
472
|
-
availableFields?: SelectOption[];
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// ---------------------------------------------------------------------------
|
|
476
|
-
// Component
|
|
477
|
-
// ---------------------------------------------------------------------------
|
|
478
|
-
|
|
479
|
-
/** Resolve an I18nLabel (string or {key, defaultValue}) to a plain string. */
|
|
480
|
-
function resolveLabel(v: unknown): string {
|
|
481
|
-
if (v === undefined || v === null) return '';
|
|
482
|
-
if (typeof v === 'string') return v;
|
|
483
|
-
if (typeof v === 'object') {
|
|
484
|
-
const obj = v as Record<string, any>;
|
|
485
|
-
return obj.defaultValue || obj.key || '';
|
|
486
|
-
}
|
|
487
|
-
return String(v);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* WidgetConfigPanel — Schema-driven configuration panel for individual
|
|
492
|
-
* dashboard widgets.
|
|
493
|
-
*
|
|
494
|
-
* Supports editing: title, description, type, data binding (object,
|
|
495
|
-
* categoryField, valueField, aggregate), layout (width, height),
|
|
496
|
-
* appearance (colorVariant, actionUrl).
|
|
497
|
-
*
|
|
498
|
-
* Sections are context-aware: pivot, table, and chart types each show
|
|
499
|
-
* their own dedicated config sections via `visibleWhen` predicates.
|
|
500
|
-
*/
|
|
501
|
-
export function WidgetConfigPanel({
|
|
502
|
-
open,
|
|
503
|
-
onClose,
|
|
504
|
-
config,
|
|
505
|
-
onSave,
|
|
506
|
-
onFieldChange,
|
|
507
|
-
headerExtra,
|
|
508
|
-
availableObjects,
|
|
509
|
-
availableFields,
|
|
510
|
-
}: WidgetConfigPanelProps) {
|
|
511
|
-
// Pre-process config to resolve any I18nLabel values for title/description
|
|
512
|
-
const normalizedConfig: Record<string, any> = React.useMemo(() => ({
|
|
513
|
-
...config,
|
|
514
|
-
title: typeof config.title === 'object' ? resolveLabel(config.title) : config.title,
|
|
515
|
-
description: typeof config.description === 'object' ? resolveLabel(config.description) : config.description,
|
|
516
|
-
}), [config]);
|
|
517
|
-
|
|
518
|
-
const { draft, isDirty, updateField, discard } = useConfigDraft(normalizedConfig, {
|
|
519
|
-
onUpdate: onFieldChange,
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
const schema = React.useMemo(
|
|
523
|
-
() => buildWidgetSchema(availableObjects, availableFields, draft.type),
|
|
524
|
-
[availableObjects, availableFields, draft.type],
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
return (
|
|
528
|
-
<ConfigPanelRenderer
|
|
529
|
-
open={open}
|
|
530
|
-
onClose={onClose}
|
|
531
|
-
schema={schema}
|
|
532
|
-
draft={draft}
|
|
533
|
-
isDirty={isDirty}
|
|
534
|
-
onFieldChange={updateField}
|
|
535
|
-
onSave={() => onSave(draft)}
|
|
536
|
-
onDiscard={discard}
|
|
537
|
-
headerExtra={headerExtra}
|
|
538
|
-
/>
|
|
539
|
-
);
|
|
540
|
-
}
|