Skip to content

Commit a7597f7

Browse files
committed
Add ObjectChart and ObjectGallery components; register with ComponentRegistry
1 parent e13807a commit a7597f7

6 files changed

Lines changed: 269 additions & 124 deletions

File tree

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
2+
import { describe, it, expect, beforeAll } from 'vitest';
3+
import { ComponentRegistry } from '@object-ui/core';
4+
5+
// Import all plugins to ensure they register their components
6+
import '@object-ui/plugin-grid';
7+
import '@object-ui/plugin-kanban';
8+
import '@object-ui/plugin-calendar';
9+
import '@object-ui/plugin-gantt';
10+
import '@object-ui/plugin-charts';
11+
import '@object-ui/plugin-list';
12+
import '@object-ui/plugin-detail';
13+
import '@object-ui/plugin-timeline';
14+
import '@object-ui/plugin-map';
15+
16+
describe('View Rendering Verification', () => {
17+
18+
it('should have object-grid registered', () => {
19+
expect(ComponentRegistry.get('object-grid')).toBeDefined();
20+
});
21+
22+
it('should have object-kanban registered', () => {
23+
expect(ComponentRegistry.get('object-kanban')).toBeDefined();
24+
});
25+
26+
it('should have object-calendar registered', () => {
27+
expect(ComponentRegistry.get('object-calendar')).toBeDefined();
28+
});
29+
30+
it('should have object-gantt registered', () => {
31+
expect(ComponentRegistry.get('object-gantt')).toBeDefined();
32+
});
33+
34+
it('should have object-timeline registered', () => {
35+
expect(ComponentRegistry.get('object-timeline')).toBeDefined();
36+
});
37+
38+
it('should have object-map registered', () => {
39+
expect(ComponentRegistry.get('object-map')).toBeDefined();
40+
});
41+
42+
// The problematic ones
43+
it('should have object-chart registered', () => {
44+
// plugin-charts registers 'chart' but ListView asks for 'object-chart'.
45+
// This test will likely fail if no alias exists.
46+
const chart = ComponentRegistry.get('object-chart');
47+
expect(chart).toBeDefined();
48+
});
49+
50+
it('should have object-gallery registered', () => {
51+
// plugin-list asks for 'object-gallery'.
52+
// This test will likely fail if it's missing.
53+
const gallery = ComponentRegistry.get('object-gallery');
54+
expect(gallery).toBeDefined();
55+
});
56+
57+
it('should NOT have spreadsheet registered (deprecated)', () => {
58+
const spreadsheet = ComponentRegistry.get('spreadsheet');
59+
expect(spreadsheet).toBeUndefined();
60+
61+
const objectSpreadsheet = ComponentRegistry.get('object-spreadsheet');
62+
expect(objectSpreadsheet).toBeUndefined();
63+
});
64+
});
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
2+
import React, { Suspense } from 'react';
3+
import { Skeleton } from '@object-ui/components';
4+
import type { ChartConfig } from './ChartContainerImpl';
5+
6+
// 🚀 Lazy load the implementation files
7+
const LazyChart = React.lazy(() => import('./ChartImpl'));
8+
const LazyAdvancedChart = React.lazy(() => import('./AdvancedChartImpl'));
9+
10+
export interface ChartBarRendererProps {
11+
schema: {
12+
type: string;
13+
id?: string;
14+
className?: string;
15+
data?: Array<Record<string, any>>;
16+
dataKey?: string;
17+
xAxisKey?: string;
18+
height?: number;
19+
color?: string;
20+
};
21+
}
22+
23+
/**
24+
* ChartBarRenderer - The public API for the bar chart component
25+
*/
26+
export const ChartBarRenderer: React.FC<ChartBarRendererProps> = ({ schema }) => {
27+
return (
28+
<Suspense fallback={<Skeleton className="w-full h-[400px]" />}>
29+
<LazyChart
30+
data={schema.data}
31+
dataKey={schema.dataKey}
32+
xAxisKey={schema.xAxisKey}
33+
height={schema.height}
34+
className={schema.className}
35+
color={schema.color}
36+
/>
37+
</Suspense>
38+
);
39+
};
40+
41+
export interface ChartRendererProps {
42+
schema: {
43+
type: string;
44+
id?: string;
45+
className?: string;
46+
chartType?: 'bar' | 'line' | 'area';
47+
data?: Array<Record<string, any>>;
48+
config?: Record<string, any>;
49+
xAxisKey?: string;
50+
series?: Array<{ dataKey: string }>;
51+
};
52+
}
53+
54+
/**
55+
* ChartRenderer - The public API for the advanced chart component
56+
*/
57+
export const ChartRenderer: React.FC<ChartRendererProps> = ({ schema }) => {
58+
// ⚡️ Adapter: Normalize JSON schema to Recharts Props
59+
const props = React.useMemo(() => {
60+
// 1. Defaults
61+
let series = schema.series;
62+
let xAxisKey = schema.xAxisKey;
63+
let config = schema.config;
64+
65+
// 2. Adapt Tremor/Simple format (categories -> series, index -> xAxisKey)
66+
if (!xAxisKey) {
67+
if ((schema as any).index) xAxisKey = (schema as any).index;
68+
else if ((schema as any).category) xAxisKey = (schema as any).category; // Support Pie/Donut category
69+
}
70+
71+
if (!series) {
72+
if ((schema as any).categories) {
73+
series = (schema as any).categories.map((cat: string) => ({ dataKey: cat }));
74+
} else if ((schema as any).value) {
75+
// Single value adapter (for Pie/Simple charts)
76+
series = [{ dataKey: (schema as any).value }];
77+
}
78+
}
79+
80+
// 3. Auto-generate config/colors if missing
81+
if (!config && series) {
82+
const colors = (schema as any).colors || ['hsl(var(--chart-1))', 'hsl(var(--chart-2))', 'hsl(var(--chart-3))'];
83+
const newConfig: ChartConfig = {};
84+
series.forEach((s: any, idx: number) => {
85+
newConfig[s.dataKey] = { label: s.dataKey, color: colors[idx % colors.length] };
86+
});
87+
config = newConfig;
88+
}
89+
90+
return {
91+
chartType: schema.chartType,
92+
data: schema.data,
93+
config,
94+
xAxisKey,
95+
series,
96+
className: schema.className
97+
};
98+
}, [schema]);
99+
100+
return (
101+
<Suspense fallback={<Skeleton className="w-full h-[400px]" />}>
102+
<LazyAdvancedChart
103+
chartType={props.chartType}
104+
data={props.data}
105+
config={props.config}
106+
xAxisKey={props.xAxisKey}
107+
series={props.series}
108+
className={props.className}
109+
/>
110+
</Suspense>
111+
);
112+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
import React from 'react';
3+
import { useDataScope } from '@object-ui/react';
4+
import { ChartRenderer } from './ChartRenderer';
5+
import { ComponentRegistry } from '@object-ui/core';
6+
7+
export const ObjectChart = (props: any) => {
8+
const { schema } = props;
9+
const { data, isLoading } = useDataScope();
10+
11+
// Merge data if not provided in schema
12+
const finalSchema = {
13+
...schema,
14+
data: schema.data || data?.value || data || [] // handle { value: [] } from OData
15+
};
16+
17+
// If we are bound to an object but no data, we might want to wait or show empty state
18+
// ChartRenderer handles empty data gracefully usually.
19+
20+
return <ChartRenderer {...props} schema={finalSchema} />;
21+
};
22+
23+
// Register it
24+
ComponentRegistry.register('object-chart', ObjectChart, {
25+
namespace: 'plugin-charts',
26+
label: 'Object Chart',
27+
category: 'view'
28+
});

packages/plugin-charts/src/index.tsx

Lines changed: 4 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -6,50 +6,13 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
import React, { Suspense } from 'react';
109
import { ComponentRegistry } from '@object-ui/core';
11-
import { Skeleton } from '@object-ui/components';
12-
import type { ChartConfig } from './ChartContainerImpl';
10+
import { ChartBarRenderer, ChartRenderer } from './ChartRenderer';
11+
import './ObjectChart'; // Import for side-effects (registration of object-chart)
1312

1413
// Export types for external use
1514
export type { BarChartSchema } from './types';
16-
17-
// 🚀 Lazy load the implementation files
18-
// This ensures Recharts is only loaded when the component is actually rendered
19-
const LazyChart = React.lazy(() => import('./ChartImpl'));
20-
const LazyAdvancedChart = React.lazy(() => import('./AdvancedChartImpl'));
21-
22-
export interface ChartBarRendererProps {
23-
schema: {
24-
type: string;
25-
id?: string;
26-
className?: string;
27-
data?: Array<Record<string, any>>;
28-
dataKey?: string;
29-
xAxisKey?: string;
30-
height?: number;
31-
color?: string;
32-
};
33-
}
34-
35-
/**
36-
* ChartBarRenderer - The public API for the bar chart component
37-
* This wrapper handles lazy loading internally using React.Suspense
38-
*/
39-
export const ChartBarRenderer: React.FC<ChartBarRendererProps> = ({ schema }) => {
40-
return (
41-
<Suspense fallback={<Skeleton className="w-full h-[400px]" />}>
42-
<LazyChart
43-
data={schema.data}
44-
dataKey={schema.dataKey}
45-
xAxisKey={schema.xAxisKey}
46-
height={schema.height}
47-
className={schema.className}
48-
color={schema.color}
49-
/>
50-
</Suspense>
51-
);
52-
};
15+
export { ChartBarRenderer, ChartRenderer };
5316

5417
// Register the component with the ComponentRegistry
5518
ComponentRegistry.register(
@@ -82,82 +45,6 @@ ComponentRegistry.register(
8245
}
8346
);
8447

85-
// Advanced Chart Renderer with multiple chart types
86-
export interface ChartRendererProps {
87-
schema: {
88-
type: string;
89-
id?: string;
90-
className?: string;
91-
chartType?: 'bar' | 'line' | 'area';
92-
data?: Array<Record<string, any>>;
93-
config?: Record<string, any>;
94-
xAxisKey?: string;
95-
series?: Array<{ dataKey: string }>;
96-
};
97-
}
98-
99-
/**
100-
* ChartRenderer - The public API for the advanced chart component
101-
* Supports multiple chart types (bar, line, area) with full configuration
102-
*/
103-
export const ChartRenderer: React.FC<ChartRendererProps> = ({ schema }) => {
104-
// ⚡️ Adapter: Normalize JSON schema to Recharts Props
105-
const props = React.useMemo(() => {
106-
// 1. Defaults
107-
let series = schema.series;
108-
let xAxisKey = schema.xAxisKey;
109-
let config = schema.config;
110-
111-
// 2. Adapt Tremor/Simple format (categories -> series, index -> xAxisKey)
112-
if (!xAxisKey) {
113-
if ((schema as any).index) xAxisKey = (schema as any).index;
114-
else if ((schema as any).category) xAxisKey = (schema as any).category; // Support Pie/Donut category
115-
}
116-
117-
if (!series) {
118-
if ((schema as any).categories) {
119-
series = (schema as any).categories.map((cat: string) => ({ dataKey: cat }));
120-
} else if ((schema as any).value) {
121-
// Single value adapter (for Pie/Simple charts)
122-
series = [{ dataKey: (schema as any).value }];
123-
}
124-
}
125-
126-
// 3. Auto-generate config/colors if missing
127-
if (!config && series) {
128-
const colors = (schema as any).colors || ['hsl(var(--chart-1))', 'hsl(var(--chart-2))', 'hsl(var(--chart-3))'];
129-
const newConfig: ChartConfig = {};
130-
series.forEach((s: any, idx: number) => {
131-
newConfig[s.dataKey] = { label: s.dataKey, color: colors[idx % colors.length] };
132-
});
133-
config = newConfig;
134-
}
135-
136-
return {
137-
chartType: schema.chartType,
138-
data: schema.data,
139-
config,
140-
xAxisKey,
141-
series,
142-
className: schema.className
143-
};
144-
}, [schema]);
145-
146-
return (
147-
<Suspense fallback={<Skeleton className="w-full h-[400px]" />}>
148-
<LazyAdvancedChart
149-
// Pass adapted props
150-
chartType={props.chartType}
151-
data={props.data}
152-
config={props.config}
153-
xAxisKey={props.xAxisKey}
154-
series={props.series}
155-
className={props.className}
156-
/>
157-
</Suspense>
158-
);
159-
};
160-
16148
// Register the advanced chart component
16249
ComponentRegistry.register(
16350
'chart',
@@ -210,16 +97,10 @@ ComponentRegistry.register(
21097
}
21198
);
21299

213-
// Standard Export Protocol - for manual integration
214-
export const chartComponents = {
215-
'bar-chart': ChartBarRenderer,
216-
'chart': ChartRenderer,
217-
};
218-
219100
// Alias for CRM App compatibility
220101
ComponentRegistry.register(
221102
'chart:bar',
222-
ChartRenderer, // Use the smart renderer to handle Tremor-like props
103+
ChartRenderer,
223104
{
224105
namespace: 'plugin-charts',
225106
label: 'Bar Chart (Alias)',
@@ -228,7 +109,6 @@ ComponentRegistry.register(
228109
}
229110
);
230111

231-
// Register specific chart type aliases
232112
ComponentRegistry.register(
233113
'pie-chart',
234114
ChartRenderer,

0 commit comments

Comments
 (0)