@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
package/src/PivotTable.tsx
DELETED
|
@@ -1,262 +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 React, { useMemo } from 'react';
|
|
10
|
-
import type { PivotTableSchema, PivotAggregation } from '@object-ui/types';
|
|
11
|
-
import { cn } from '@object-ui/components';
|
|
12
|
-
|
|
13
|
-
export interface PivotTableProps {
|
|
14
|
-
schema: PivotTableSchema;
|
|
15
|
-
className?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Apply a simple format string to a number. Supports prefix/suffix like "$,.2f". */
|
|
19
|
-
function formatValue(value: number, format?: string): string {
|
|
20
|
-
if (!format) return String(value);
|
|
21
|
-
|
|
22
|
-
let prefix = '';
|
|
23
|
-
let suffix = '';
|
|
24
|
-
let useGrouping = false;
|
|
25
|
-
let decimals: number | undefined;
|
|
26
|
-
|
|
27
|
-
let fmt = format;
|
|
28
|
-
|
|
29
|
-
// Extract leading non-format characters as prefix (e.g. "$")
|
|
30
|
-
const prefixMatch = fmt.match(/^([^0-9.,#]*)/);
|
|
31
|
-
if (prefixMatch && prefixMatch[1]) {
|
|
32
|
-
// comma inside the prefix-ish area means grouping, not a literal prefix
|
|
33
|
-
const raw = prefixMatch[1];
|
|
34
|
-
prefix = raw.replace(',', '');
|
|
35
|
-
if (raw.includes(',')) useGrouping = true;
|
|
36
|
-
fmt = fmt.slice(prefixMatch[1].length);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Grouping indicator anywhere remaining
|
|
40
|
-
if (fmt.includes(',')) {
|
|
41
|
-
useGrouping = true;
|
|
42
|
-
fmt = fmt.replace(/,/g, '');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Decimal specifier e.g. ".2f"
|
|
46
|
-
const decMatch = fmt.match(/\.(\d+)f?/);
|
|
47
|
-
if (decMatch) {
|
|
48
|
-
decimals = Number(decMatch[1]);
|
|
49
|
-
fmt = fmt.slice(decMatch[0].length);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Remaining characters become suffix
|
|
53
|
-
suffix = fmt.replace(/[0-9#.f]/g, '');
|
|
54
|
-
|
|
55
|
-
const formatted = decimals !== undefined ? value.toFixed(decimals) : String(value);
|
|
56
|
-
|
|
57
|
-
if (useGrouping) {
|
|
58
|
-
const [intPart, decPart] = formatted.split('.');
|
|
59
|
-
const grouped = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
60
|
-
return prefix + (decPart !== undefined ? `${grouped}.${decPart}` : grouped) + suffix;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return prefix + formatted + suffix;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Aggregate an array of numbers with the given function. */
|
|
67
|
-
function aggregate(values: number[], fn: PivotAggregation): number {
|
|
68
|
-
if (values.length === 0) return 0;
|
|
69
|
-
switch (fn) {
|
|
70
|
-
case 'sum':
|
|
71
|
-
return values.reduce((a, b) => a + b, 0);
|
|
72
|
-
case 'count':
|
|
73
|
-
return values.length;
|
|
74
|
-
case 'avg':
|
|
75
|
-
return values.reduce((a, b) => a + b, 0) / values.length;
|
|
76
|
-
case 'min':
|
|
77
|
-
return Math.min(...values);
|
|
78
|
-
case 'max':
|
|
79
|
-
return Math.max(...values);
|
|
80
|
-
default:
|
|
81
|
-
return values.reduce((a, b) => a + b, 0);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* PivotTable – Cross-tabulation / Pivot Table component.
|
|
87
|
-
*
|
|
88
|
-
* Renders a matrix where rows correspond to `rowField`, columns to
|
|
89
|
-
* `columnField`, and cells show the aggregated `valueField`.
|
|
90
|
-
*/
|
|
91
|
-
export const PivotTable: React.FC<PivotTableProps> = ({ schema, className }) => {
|
|
92
|
-
const {
|
|
93
|
-
title,
|
|
94
|
-
rowField,
|
|
95
|
-
columnField,
|
|
96
|
-
valueField,
|
|
97
|
-
aggregation = 'sum',
|
|
98
|
-
data: rawData = [],
|
|
99
|
-
showRowTotals = false,
|
|
100
|
-
showColumnTotals = false,
|
|
101
|
-
format,
|
|
102
|
-
columnColors,
|
|
103
|
-
} = schema;
|
|
104
|
-
|
|
105
|
-
// Ensure data is always an array – provider config objects must not reach iteration
|
|
106
|
-
const data = Array.isArray(rawData) ? rawData : [];
|
|
107
|
-
|
|
108
|
-
const { rowKeys, colKeys, matrix, rowTotals, colTotals, grandTotal } = useMemo(() => {
|
|
109
|
-
// Collect unique row/column values preserving insertion order
|
|
110
|
-
const rowSet = new Map<string, true>();
|
|
111
|
-
const colSet = new Map<string, true>();
|
|
112
|
-
// Bucket raw values: bucket[row][col] = number[]
|
|
113
|
-
const bucket: Record<string, Record<string, number[]>> = {};
|
|
114
|
-
|
|
115
|
-
for (const item of data) {
|
|
116
|
-
const r = String(item[rowField] ?? '');
|
|
117
|
-
const c = String(item[columnField] ?? '');
|
|
118
|
-
const v = Number(item[valueField]) || 0;
|
|
119
|
-
|
|
120
|
-
rowSet.set(r, true);
|
|
121
|
-
colSet.set(c, true);
|
|
122
|
-
|
|
123
|
-
if (!bucket[r]) bucket[r] = {};
|
|
124
|
-
if (!bucket[r][c]) bucket[r][c] = [];
|
|
125
|
-
bucket[r][c].push(v);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const rKeys = Array.from(rowSet.keys());
|
|
129
|
-
const cKeys = Array.from(colSet.keys());
|
|
130
|
-
|
|
131
|
-
// Build aggregated matrix
|
|
132
|
-
const mat: Record<string, Record<string, number>> = {};
|
|
133
|
-
const rTotals: Record<string, number> = {};
|
|
134
|
-
const cTotals: Record<string, number> = {};
|
|
135
|
-
|
|
136
|
-
for (const r of rKeys) {
|
|
137
|
-
mat[r] = {};
|
|
138
|
-
const rowValues: number[] = [];
|
|
139
|
-
for (const c of cKeys) {
|
|
140
|
-
const cellValues = bucket[r]?.[c] ?? [];
|
|
141
|
-
const cellAgg = aggregate(cellValues, aggregation);
|
|
142
|
-
mat[r][c] = cellAgg;
|
|
143
|
-
rowValues.push(...cellValues);
|
|
144
|
-
|
|
145
|
-
// Accumulate column bucket values for column totals
|
|
146
|
-
if (!cTotals[c] && cTotals[c] !== 0) {
|
|
147
|
-
// Will compute after
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
rTotals[r] = aggregate(rowValues, aggregation);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Column totals
|
|
154
|
-
for (const c of cKeys) {
|
|
155
|
-
const colValues: number[] = [];
|
|
156
|
-
for (const r of rKeys) {
|
|
157
|
-
const cellValues = bucket[r]?.[c] ?? [];
|
|
158
|
-
colValues.push(...cellValues);
|
|
159
|
-
}
|
|
160
|
-
cTotals[c] = aggregate(colValues, aggregation);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Grand total
|
|
164
|
-
const allValues: number[] = [];
|
|
165
|
-
for (const item of data) {
|
|
166
|
-
allValues.push(Number(item[valueField]) || 0);
|
|
167
|
-
}
|
|
168
|
-
const gt = aggregate(allValues, aggregation);
|
|
169
|
-
|
|
170
|
-
return { rowKeys: rKeys, colKeys: cKeys, matrix: mat, rowTotals: rTotals, colTotals: cTotals, grandTotal: gt };
|
|
171
|
-
}, [data, rowField, columnField, valueField, aggregation]);
|
|
172
|
-
|
|
173
|
-
const fmt = (v: number) => formatValue(v, format);
|
|
174
|
-
|
|
175
|
-
if (data.length === 0) {
|
|
176
|
-
return (
|
|
177
|
-
<div className={cn('overflow-auto', className)}>
|
|
178
|
-
{title && (
|
|
179
|
-
<h3 className="text-sm font-semibold mb-2">{title}</h3>
|
|
180
|
-
)}
|
|
181
|
-
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground" data-testid="pivot-empty-state">
|
|
182
|
-
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 mb-2 opacity-40" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
183
|
-
<rect x="3" y="3" width="7" height="7" />
|
|
184
|
-
<rect x="14" y="3" width="7" height="7" />
|
|
185
|
-
<rect x="3" y="14" width="7" height="7" />
|
|
186
|
-
<rect x="14" y="14" width="7" height="7" />
|
|
187
|
-
</svg>
|
|
188
|
-
<p className="text-xs">No data available</p>
|
|
189
|
-
</div>
|
|
190
|
-
</div>
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return (
|
|
195
|
-
<div className={cn('overflow-auto', className)}>
|
|
196
|
-
{title && (
|
|
197
|
-
<h3 className="text-sm font-semibold mb-2">{title}</h3>
|
|
198
|
-
)}
|
|
199
|
-
<table className="w-full text-sm border-collapse" role="table">
|
|
200
|
-
<thead>
|
|
201
|
-
<tr className="border-b border-border">
|
|
202
|
-
<th className="text-left p-2 font-medium text-muted-foreground">{rowField}</th>
|
|
203
|
-
{colKeys.map((col) => (
|
|
204
|
-
<th
|
|
205
|
-
key={col}
|
|
206
|
-
className={cn(
|
|
207
|
-
'text-right p-2 font-medium',
|
|
208
|
-
columnColors?.[col] ?? 'text-muted-foreground',
|
|
209
|
-
)}
|
|
210
|
-
>
|
|
211
|
-
{col}
|
|
212
|
-
</th>
|
|
213
|
-
))}
|
|
214
|
-
{showRowTotals && (
|
|
215
|
-
<th className="text-right p-2 font-semibold text-muted-foreground bg-muted/20">Total</th>
|
|
216
|
-
)}
|
|
217
|
-
</tr>
|
|
218
|
-
</thead>
|
|
219
|
-
<tbody>
|
|
220
|
-
{rowKeys.map((row) => (
|
|
221
|
-
<tr key={row} className="border-b border-border/50 hover:bg-muted/30">
|
|
222
|
-
<td className="p-2 font-medium">{row}</td>
|
|
223
|
-
{colKeys.map((col) => (
|
|
224
|
-
<td
|
|
225
|
-
key={col}
|
|
226
|
-
className={cn(
|
|
227
|
-
'text-right p-2 tabular-nums',
|
|
228
|
-
columnColors?.[col],
|
|
229
|
-
)}
|
|
230
|
-
>
|
|
231
|
-
{fmt(matrix[row]?.[col] ?? 0)}
|
|
232
|
-
</td>
|
|
233
|
-
))}
|
|
234
|
-
{showRowTotals && (
|
|
235
|
-
<td className="text-right p-2 font-semibold tabular-nums bg-muted/20">
|
|
236
|
-
{fmt(rowTotals[row] ?? 0)}
|
|
237
|
-
</td>
|
|
238
|
-
)}
|
|
239
|
-
</tr>
|
|
240
|
-
))}
|
|
241
|
-
</tbody>
|
|
242
|
-
{showColumnTotals && (
|
|
243
|
-
<tfoot>
|
|
244
|
-
<tr className="border-t-2 border-border font-semibold bg-muted/40">
|
|
245
|
-
<td className="p-2">Total</td>
|
|
246
|
-
{colKeys.map((col) => (
|
|
247
|
-
<td key={col} className="text-right p-2 tabular-nums">
|
|
248
|
-
{fmt(colTotals[col] ?? 0)}
|
|
249
|
-
</td>
|
|
250
|
-
))}
|
|
251
|
-
{showRowTotals && (
|
|
252
|
-
<td className="text-right p-2 tabular-nums font-bold">
|
|
253
|
-
{fmt(grandTotal)}
|
|
254
|
-
</td>
|
|
255
|
-
)}
|
|
256
|
-
</tr>
|
|
257
|
-
</tfoot>
|
|
258
|
-
)}
|
|
259
|
-
</table>
|
|
260
|
-
</div>
|
|
261
|
-
);
|
|
262
|
-
};
|