@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.
Files changed (84) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +21 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +1162 -939
  5. package/dist/index.umd.cjs +4 -4
  6. package/dist/packages/plugin-dashboard/src/DashboardConfigPanel.d.ts.map +1 -0
  7. package/dist/packages/plugin-dashboard/src/DashboardConfigPanel.stories.d.ts.map +1 -0
  8. package/dist/packages/plugin-dashboard/src/DashboardGridLayout.d.ts.map +1 -0
  9. package/dist/{src → packages/plugin-dashboard/src}/DashboardRenderer.d.ts +5 -0
  10. package/dist/packages/plugin-dashboard/src/DashboardRenderer.d.ts.map +1 -0
  11. package/dist/packages/plugin-dashboard/src/DashboardRenderer.stories.d.ts.map +1 -0
  12. package/dist/packages/plugin-dashboard/src/DashboardWithConfig.d.ts.map +1 -0
  13. package/dist/{src → packages/plugin-dashboard/src}/MetricCard.d.ts +4 -0
  14. package/dist/packages/plugin-dashboard/src/MetricCard.d.ts.map +1 -0
  15. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts +31 -0
  16. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts.map +1 -0
  17. package/dist/packages/plugin-dashboard/src/ObjectDataTable.d.ts.map +1 -0
  18. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts +59 -0
  19. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts.map +1 -0
  20. package/dist/packages/plugin-dashboard/src/ObjectPivotTable.d.ts.map +1 -0
  21. package/dist/packages/plugin-dashboard/src/PivotTable.d.ts.map +1 -0
  22. package/dist/packages/plugin-dashboard/src/WidgetConfigPanel.d.ts.map +1 -0
  23. package/dist/{src → packages/plugin-dashboard/src}/index.d.ts +4 -2
  24. package/dist/packages/plugin-dashboard/src/index.d.ts.map +1 -0
  25. package/dist/packages/plugin-dashboard/src/utils.d.ts.map +1 -0
  26. package/package.json +44 -11
  27. package/.turbo/turbo-build.log +0 -34
  28. package/dist/src/DashboardConfigPanel.d.ts.map +0 -1
  29. package/dist/src/DashboardConfigPanel.stories.d.ts.map +0 -1
  30. package/dist/src/DashboardGridLayout.d.ts.map +0 -1
  31. package/dist/src/DashboardRenderer.d.ts.map +0 -1
  32. package/dist/src/DashboardRenderer.stories.d.ts.map +0 -1
  33. package/dist/src/DashboardWithConfig.d.ts.map +0 -1
  34. package/dist/src/MetricCard.d.ts.map +0 -1
  35. package/dist/src/MetricWidget.d.ts +0 -24
  36. package/dist/src/MetricWidget.d.ts.map +0 -1
  37. package/dist/src/ObjectDataTable.d.ts.map +0 -1
  38. package/dist/src/ObjectPivotTable.d.ts.map +0 -1
  39. package/dist/src/PivotTable.d.ts.map +0 -1
  40. package/dist/src/WidgetConfigPanel.d.ts.map +0 -1
  41. package/dist/src/index.d.ts.map +0 -1
  42. package/dist/src/utils.d.ts.map +0 -1
  43. package/src/DashboardConfigPanel.stories.tsx +0 -164
  44. package/src/DashboardConfigPanel.tsx +0 -158
  45. package/src/DashboardGridLayout.tsx +0 -367
  46. package/src/DashboardRenderer.stories.tsx +0 -173
  47. package/src/DashboardRenderer.tsx +0 -445
  48. package/src/DashboardWithConfig.tsx +0 -211
  49. package/src/MetricCard.tsx +0 -82
  50. package/src/MetricWidget.tsx +0 -76
  51. package/src/ObjectDataTable.tsx +0 -226
  52. package/src/ObjectPivotTable.tsx +0 -160
  53. package/src/PivotTable.tsx +0 -262
  54. package/src/WidgetConfigPanel.tsx +0 -540
  55. package/src/__tests__/DashboardConfigPanel.test.tsx +0 -206
  56. package/src/__tests__/DashboardGridLayout.test.tsx +0 -199
  57. package/src/__tests__/DashboardRenderer.autoRefresh.test.tsx +0 -124
  58. package/src/__tests__/DashboardRenderer.designMode.test.tsx +0 -386
  59. package/src/__tests__/DashboardRenderer.header.test.tsx +0 -114
  60. package/src/__tests__/DashboardRenderer.mobile.test.tsx +0 -214
  61. package/src/__tests__/DashboardRenderer.widgetData.test.tsx +0 -1283
  62. package/src/__tests__/DashboardWithConfig.test.tsx +0 -276
  63. package/src/__tests__/MetricCard.test.tsx +0 -82
  64. package/src/__tests__/ObjectDataTable.test.tsx +0 -211
  65. package/src/__tests__/ObjectPivotTable.test.tsx +0 -192
  66. package/src/__tests__/PivotTable.test.tsx +0 -162
  67. package/src/__tests__/WidgetConfigPanel.test.tsx +0 -492
  68. package/src/__tests__/ensureWidgetIds.test.tsx +0 -103
  69. package/src/index.tsx +0 -214
  70. package/src/utils.ts +0 -17
  71. package/tsconfig.json +0 -19
  72. package/vite.config.ts +0 -63
  73. package/vitest.config.ts +0 -9
  74. package/vitest.setup.tsx +0 -18
  75. /package/dist/{src → packages/plugin-dashboard/src}/DashboardConfigPanel.d.ts +0 -0
  76. /package/dist/{src → packages/plugin-dashboard/src}/DashboardConfigPanel.stories.d.ts +0 -0
  77. /package/dist/{src → packages/plugin-dashboard/src}/DashboardGridLayout.d.ts +0 -0
  78. /package/dist/{src → packages/plugin-dashboard/src}/DashboardRenderer.stories.d.ts +0 -0
  79. /package/dist/{src → packages/plugin-dashboard/src}/DashboardWithConfig.d.ts +0 -0
  80. /package/dist/{src → packages/plugin-dashboard/src}/ObjectDataTable.d.ts +0 -0
  81. /package/dist/{src → packages/plugin-dashboard/src}/ObjectPivotTable.d.ts +0 -0
  82. /package/dist/{src → packages/plugin-dashboard/src}/PivotTable.d.ts +0 -0
  83. /package/dist/{src → packages/plugin-dashboard/src}/WidgetConfigPanel.d.ts +0 -0
  84. /package/dist/{src → packages/plugin-dashboard/src}/utils.d.ts +0 -0
@@ -1,82 +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 from 'react';
10
- import { Card, CardContent, CardHeader, CardTitle } from '@object-ui/components';
11
- import { cn } from '@object-ui/components';
12
- import { ArrowDownIcon, ArrowUpIcon, MinusIcon } from 'lucide-react';
13
- import * as LucideIcons from 'lucide-react';
14
-
15
- /** Resolve an I18nLabel (string or {key, defaultValue}) to a plain string. */
16
- function resolveLabel(label: string | { key?: string; defaultValue?: string } | undefined): string | undefined {
17
- if (label === undefined || label === null) return undefined;
18
- if (typeof label === 'string') return label;
19
- return label.defaultValue || label.key;
20
- }
21
-
22
- export interface MetricCardProps {
23
- title?: string | { key?: string; defaultValue?: string };
24
- value: string | number;
25
- icon?: string;
26
- trend?: 'up' | 'down' | 'neutral';
27
- trendValue?: string;
28
- description?: string | { key?: string; defaultValue?: string };
29
- className?: string;
30
- }
31
-
32
- /**
33
- * MetricCard - Standalone metric card component for dashboard KPIs
34
- * Displays a metric value with optional icon, trend indicator, and description
35
- */
36
- export const MetricCard: React.FC<MetricCardProps> = ({
37
- title,
38
- value,
39
- icon,
40
- trend,
41
- trendValue,
42
- description,
43
- className,
44
- ...props
45
- }) => {
46
- // Resolve icon from lucide-react
47
- const IconComponent = icon && (LucideIcons as any)[icon];
48
-
49
- return (
50
- <Card className={cn("h-full", className)} {...props}>
51
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
52
- <CardTitle className="text-sm font-medium">
53
- {resolveLabel(title)}
54
- </CardTitle>
55
- {IconComponent && (
56
- <IconComponent className="h-4 w-4 text-muted-foreground" />
57
- )}
58
- </CardHeader>
59
- <CardContent>
60
- <div className="text-2xl font-bold">{value}</div>
61
- {(trend || trendValue || description) && (
62
- <p className="text-xs text-muted-foreground flex items-center mt-1">
63
- {trend && trendValue && (
64
- <span className={cn(
65
- "flex items-center mr-2",
66
- trend === 'up' && "text-green-500",
67
- trend === 'down' && "text-red-500",
68
- trend === 'neutral' && "text-yellow-500"
69
- )}>
70
- {trend === 'up' && <ArrowUpIcon className="h-3 w-3 mr-1" />}
71
- {trend === 'down' && <ArrowDownIcon className="h-3 w-3 mr-1" />}
72
- {trend === 'neutral' && <MinusIcon className="h-3 w-3 mr-1" />}
73
- {trendValue}
74
- </span>
75
- )}
76
- {resolveLabel(description)}
77
- </p>
78
- )}
79
- </CardContent>
80
- </Card>
81
- );
82
- };
@@ -1,76 +0,0 @@
1
- import React, { useMemo } from 'react';
2
- import { Card, CardContent, CardHeader, CardTitle } from '@object-ui/components';
3
- import { cn } from '@object-ui/components';
4
- import { ArrowDownIcon, ArrowUpIcon, MinusIcon } from 'lucide-react';
5
- import * as LucideIcons from 'lucide-react';
6
-
7
- /** Resolve an I18nLabel (string or {key, defaultValue}) to a plain string. */
8
- function resolveLabel(label: string | { key?: string; defaultValue?: string } | undefined): string | undefined {
9
- if (label === undefined || label === null) return undefined;
10
- if (typeof label === 'string') return label;
11
- return label.defaultValue || label.key;
12
- }
13
-
14
- export interface MetricWidgetProps {
15
- label: string | { key?: string; defaultValue?: string };
16
- value: string | number;
17
- trend?: {
18
- value: number;
19
- label?: string | { key?: string; defaultValue?: string };
20
- direction?: 'up' | 'down' | 'neutral';
21
- };
22
- icon?: React.ReactNode | string;
23
- className?: string;
24
- description?: string | { key?: string; defaultValue?: string };
25
- }
26
-
27
- export const MetricWidget = ({
28
- label,
29
- value,
30
- trend,
31
- icon,
32
- className,
33
- description,
34
- ...props
35
- }: MetricWidgetProps) => {
36
- // Resolve icon if it's a string
37
- const resolvedIcon = useMemo(() => {
38
- if (typeof icon === 'string') {
39
- const IconComponent = (LucideIcons as any)[icon];
40
- return IconComponent ? <IconComponent className="h-4 w-4 text-muted-foreground" /> : null;
41
- }
42
- return icon;
43
- }, [icon]);
44
-
45
- return (
46
- <Card className={cn("h-full overflow-hidden", className)} {...props}>
47
- <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
48
- <CardTitle className="text-sm font-medium truncate">
49
- {resolveLabel(label)}
50
- </CardTitle>
51
- {resolvedIcon && <div className="h-4 w-4 text-muted-foreground shrink-0">{resolvedIcon}</div>}
52
- </CardHeader>
53
- <CardContent>
54
- <div className="text-2xl font-bold truncate">{value}</div>
55
- {(trend || description) && (
56
- <p className="text-xs text-muted-foreground flex items-center mt-1 truncate">
57
- {trend && (
58
- <span className={cn(
59
- "flex items-center mr-2 shrink-0",
60
- trend.direction === 'up' && "text-green-500",
61
- trend.direction === 'down' && "text-red-500",
62
- trend.direction === 'neutral' && "text-yellow-500"
63
- )}>
64
- {trend.direction === 'up' && <ArrowUpIcon className="h-3 w-3 mr-1" />}
65
- {trend.direction === 'down' && <ArrowDownIcon className="h-3 w-3 mr-1" />}
66
- {trend.direction === 'neutral' && <MinusIcon className="h-3 w-3 mr-1" />}
67
- {trend.value}%
68
- </span>
69
- )}
70
- <span className="truncate">{resolveLabel(description) || resolveLabel(trend?.label)}</span>
71
- </p>
72
- )}
73
- </CardContent>
74
- </Card>
75
- );
76
- };
@@ -1,226 +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, { useState, useEffect, useContext, useMemo } from 'react';
10
- import { useDataScope, SchemaRendererContext, SchemaRenderer } from '@object-ui/react';
11
- import { extractRecords } from '@object-ui/core';
12
- import { Skeleton, cn } from '@object-ui/components';
13
-
14
- export interface ObjectDataTableProps {
15
- schema: {
16
- type: string;
17
- objectName?: string;
18
- dataProvider?: { provider: string; object?: string };
19
- bind?: string;
20
- filter?: any;
21
- data?: any[];
22
- columns?: any[];
23
- searchable?: boolean;
24
- pagination?: boolean;
25
- className?: string;
26
- [key: string]: any;
27
- };
28
- dataSource?: any;
29
- className?: string;
30
- }
31
-
32
- /** A column definition after normalization, with header and accessor key. */
33
- interface NormalizedColumn {
34
- header: string;
35
- accessorKey: string;
36
- [key: string]: any;
37
- }
38
-
39
- /**
40
- * Normalize columns to support both string[] shorthand and object[] formats.
41
- *
42
- * - `string[]` entries are converted to `{ header, accessorKey }` objects,
43
- * handling both snake_case and camelCase for header generation.
44
- * - Object entries are returned as-is.
45
- */
46
- export function normalizeColumns(columns: (string | Record<string, any>)[]): NormalizedColumn[] {
47
- return columns.map((col) => {
48
- if (typeof col === 'string') {
49
- return {
50
- header: col
51
- // snake_case → spaces
52
- .replace(/_/g, ' ')
53
- // camelCase → spaces before uppercase letters
54
- .replace(/([A-Z])/g, ' $1')
55
- .trim()
56
- // Title Case each word
57
- .replace(/\b\w/g, (c: string) => c.toUpperCase()),
58
- accessorKey: col,
59
- };
60
- }
61
- return col;
62
- });
63
- }
64
-
65
- /**
66
- * ObjectDataTable — Async-aware wrapper for data-table.
67
- *
68
- * When `objectName` is provided and a `dataSource` is available via context
69
- * or props, fetches records automatically and passes them to the registered
70
- * `data-table` component via SchemaRenderer.
71
- *
72
- * Also auto-derives columns from fetched data keys when no explicit columns
73
- * are configured.
74
- *
75
- * Lifecycle states:
76
- * - **Loading** → skeleton placeholder
77
- * - **Error** → error message
78
- * - **Empty** → friendly "No data available" message
79
- * - **Data** → data-table with fetched rows
80
- */
81
- export const ObjectDataTable: React.FC<ObjectDataTableProps> = ({ schema, dataSource: propDataSource, className }) => {
82
- const context = useContext(SchemaRendererContext);
83
- const dataSource = propDataSource || context?.dataSource;
84
- const boundData = useDataScope(schema.bind);
85
-
86
- const [fetchedData, setFetchedData] = useState<any[]>([]);
87
- const [loading, setLoading] = useState(false);
88
- const [error, setError] = useState<string | null>(null);
89
-
90
- useEffect(() => {
91
- let isMounted = true;
92
-
93
- const fetchData = async () => {
94
- if (!dataSource || !schema.objectName) return;
95
- if (isMounted) {
96
- setLoading(true);
97
- setError(null);
98
- }
99
- try {
100
- let data: any[];
101
-
102
- if (typeof dataSource.find === 'function') {
103
- const results = await dataSource.find(schema.objectName, {
104
- $filter: schema.filter,
105
- });
106
- data = extractRecords(results);
107
- } else {
108
- return;
109
- }
110
-
111
- if (isMounted) {
112
- setFetchedData(data);
113
- }
114
- } catch (e) {
115
- console.error('[ObjectDataTable] Fetch error:', e);
116
- if (isMounted) {
117
- setError(e instanceof Error ? e.message : 'Failed to load data');
118
- }
119
- } finally {
120
- if (isMounted) setLoading(false);
121
- }
122
- };
123
-
124
- if (schema.objectName && !boundData && (!schema.data || schema.data.length === 0)) {
125
- fetchData();
126
- }
127
-
128
- return () => { isMounted = false; };
129
- }, [schema.objectName, dataSource, boundData, schema.data, schema.filter]);
130
-
131
- // Resolve data: bound data > static schema data > fetched data
132
- const rawData = boundData || schema.data || fetchedData;
133
- const finalData = Array.isArray(rawData) ? rawData : [];
134
-
135
- // Auto-derive columns from data keys when none are provided
136
- const derivedColumns = useMemo(() => {
137
- if (schema.columns && schema.columns.length > 0) {
138
- return normalizeColumns(schema.columns);
139
- }
140
- if (finalData.length === 0) return [];
141
- // Exclude internal/private fields (prefixed with '_') from auto-derived columns
142
- const keys = Object.keys(finalData[0]).filter(k => !k.startsWith('_'));
143
- // Convert camelCase keys to human-readable headers (e.g. firstName → First Name)
144
- return keys.map(k => ({
145
- header: k.charAt(0).toUpperCase() + k.slice(1).replace(/([A-Z])/g, ' $1'),
146
- accessorKey: k,
147
- }));
148
- }, [schema.columns, finalData]);
149
-
150
- // Loading skeleton
151
- if (loading && finalData.length === 0) {
152
- return (
153
- <div className={cn('overflow-auto', className)} data-testid="table-loading">
154
- <div className="space-y-2 p-2">
155
- <div className="flex gap-2">
156
- <Skeleton className="h-6 w-1/4" />
157
- <Skeleton className="h-6 w-1/4" />
158
- <Skeleton className="h-6 w-1/4" />
159
- <Skeleton className="h-6 w-1/4" />
160
- </div>
161
- {[1, 2, 3, 4].map((i) => (
162
- <div key={i} className="flex gap-2">
163
- <Skeleton className="h-5 w-1/4" />
164
- <Skeleton className="h-5 w-1/4" />
165
- <Skeleton className="h-5 w-1/4" />
166
- <Skeleton className="h-5 w-1/4" />
167
- </div>
168
- ))}
169
- </div>
170
- </div>
171
- );
172
- }
173
-
174
- // Error state
175
- if (error) {
176
- return (
177
- <div className={cn('overflow-auto', className)} data-testid="table-error">
178
- <div className="flex flex-col items-center justify-center py-8 text-destructive" data-testid="table-error-message">
179
- <svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 mb-2 opacity-60" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
180
- <circle cx="12" cy="12" r="10" />
181
- <line x1="12" y1="8" x2="12" y2="12" />
182
- <line x1="12" y1="16" x2="12.01" y2="16" />
183
- </svg>
184
- <p className="text-xs">{error}</p>
185
- </div>
186
- </div>
187
- );
188
- }
189
-
190
- // No data source available but objectName configured
191
- if (!dataSource && schema.objectName && finalData.length === 0) {
192
- return (
193
- <div className={cn('overflow-auto', className)}>
194
- <div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
195
- <p className="text-xs">No data source available for &ldquo;{schema.objectName}&rdquo;</p>
196
- </div>
197
- </div>
198
- );
199
- }
200
-
201
- // Empty state
202
- if (finalData.length === 0) {
203
- return (
204
- <div className={cn('overflow-auto', className)} data-testid="table-empty-state">
205
- <div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
206
- <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">
207
- <rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
208
- <line x1="3" y1="9" x2="21" y2="9" />
209
- <line x1="9" y1="21" x2="9" y2="9" />
210
- </svg>
211
- <p className="text-xs">No data available</p>
212
- </div>
213
- </div>
214
- );
215
- }
216
-
217
- // Delegate to data-table via SchemaRenderer
218
- const tableSchema = {
219
- ...schema,
220
- type: 'data-table',
221
- data: finalData,
222
- columns: derivedColumns,
223
- };
224
-
225
- return <SchemaRenderer schema={tableSchema} className={className} />;
226
- };
@@ -1,160 +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, { useState, useEffect, useContext } from 'react';
10
- import { useDataScope, SchemaRendererContext } from '@object-ui/react';
11
- import { extractRecords } from '@object-ui/core';
12
- import { Skeleton, cn } from '@object-ui/components';
13
- import { PivotTable } from './PivotTable';
14
- import type { PivotTableSchema } from '@object-ui/types';
15
-
16
- export interface ObjectPivotTableProps {
17
- schema: PivotTableSchema & {
18
- objectName?: string;
19
- dataProvider?: { provider: string; object?: string };
20
- bind?: string;
21
- filter?: any;
22
- };
23
- dataSource?: any;
24
- className?: string;
25
- }
26
-
27
- /**
28
- * ObjectPivotTable — Async-aware wrapper around PivotTable.
29
- *
30
- * When `objectName` is provided and a `dataSource` is available via context
31
- * or props, fetches records automatically and passes them to PivotTable.
32
- *
33
- * Lifecycle states:
34
- * - **Loading** → skeleton placeholder
35
- * - **Error** → error message
36
- * - **Empty** → friendly "No data available" (delegated to PivotTable)
37
- * - **Data** → PivotTable with fetched rows
38
- */
39
- export const ObjectPivotTable: React.FC<ObjectPivotTableProps> = ({ schema, dataSource: propDataSource, className }) => {
40
- const context = useContext(SchemaRendererContext);
41
- const dataSource = propDataSource || context?.dataSource;
42
- const boundData = useDataScope(schema.bind);
43
-
44
- const [fetchedData, setFetchedData] = useState<any[]>([]);
45
- const [loading, setLoading] = useState(false);
46
- const [error, setError] = useState<string | null>(null);
47
-
48
- useEffect(() => {
49
- let isMounted = true;
50
-
51
- const fetchData = async () => {
52
- if (!dataSource || !schema.objectName) return;
53
- if (isMounted) {
54
- setLoading(true);
55
- setError(null);
56
- }
57
- try {
58
- let data: any[];
59
-
60
- if (typeof dataSource.find === 'function') {
61
- const results = await dataSource.find(schema.objectName, {
62
- $filter: schema.filter,
63
- });
64
- data = extractRecords(results);
65
- } else {
66
- return;
67
- }
68
-
69
- if (isMounted) {
70
- setFetchedData(data);
71
- }
72
- } catch (e) {
73
- console.error('[ObjectPivotTable] Fetch error:', e);
74
- if (isMounted) {
75
- setError(e instanceof Error ? e.message : 'Failed to load data');
76
- }
77
- } finally {
78
- if (isMounted) setLoading(false);
79
- }
80
- };
81
-
82
- if (schema.objectName && !boundData && (!schema.data || schema.data.length === 0)) {
83
- fetchData();
84
- }
85
-
86
- return () => { isMounted = false; };
87
- }, [schema.objectName, dataSource, boundData, schema.data, schema.filter]);
88
-
89
- // Resolve data: bound data > static schema data > fetched data
90
- const rawData = boundData || schema.data || fetchedData;
91
- const finalData = Array.isArray(rawData) ? rawData : [];
92
-
93
- // Loading skeleton
94
- if (loading && finalData.length === 0) {
95
- return (
96
- <div className={cn('overflow-auto', className)} data-testid="pivot-loading">
97
- {schema.title && (
98
- <h3 className="text-sm font-semibold mb-2">{schema.title}</h3>
99
- )}
100
- <div className="space-y-2 p-2">
101
- <div className="flex gap-2">
102
- <Skeleton className="h-6 w-24" />
103
- <Skeleton className="h-6 w-20" />
104
- <Skeleton className="h-6 w-20" />
105
- <Skeleton className="h-6 w-20" />
106
- </div>
107
- {[1, 2, 3].map((i) => (
108
- <div key={i} className="flex gap-2">
109
- <Skeleton className="h-5 w-24" />
110
- <Skeleton className="h-5 w-20" />
111
- <Skeleton className="h-5 w-20" />
112
- <Skeleton className="h-5 w-20" />
113
- </div>
114
- ))}
115
- </div>
116
- </div>
117
- );
118
- }
119
-
120
- // Error state
121
- if (error) {
122
- return (
123
- <div className={cn('overflow-auto', className)} data-testid="pivot-error">
124
- {schema.title && (
125
- <h3 className="text-sm font-semibold mb-2">{schema.title}</h3>
126
- )}
127
- <div className="flex flex-col items-center justify-center py-8 text-destructive" data-testid="pivot-error-message">
128
- <svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8 mb-2 opacity-60" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
129
- <circle cx="12" cy="12" r="10" />
130
- <line x1="12" y1="8" x2="12" y2="12" />
131
- <line x1="12" y1="16" x2="12.01" y2="16" />
132
- </svg>
133
- <p className="text-xs">{error}</p>
134
- </div>
135
- </div>
136
- );
137
- }
138
-
139
- // No data source available but objectName configured
140
- if (!dataSource && schema.objectName && finalData.length === 0) {
141
- return (
142
- <div className={cn('overflow-auto', className)}>
143
- {schema.title && (
144
- <h3 className="text-sm font-semibold mb-2">{schema.title}</h3>
145
- )}
146
- <div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
147
- <p className="text-xs">No data source available for &ldquo;{schema.objectName}&rdquo;</p>
148
- </div>
149
- </div>
150
- );
151
- }
152
-
153
- // Delegate to PivotTable with resolved data
154
- const finalSchema: PivotTableSchema = {
155
- ...schema,
156
- data: finalData,
157
- };
158
-
159
- return <PivotTable schema={finalSchema} className={className} />;
160
- };