Skip to content

Commit f02e9ee

Browse files
committed
feat(Report): add sales performance report and integrate report builder in ReportView
1 parent 987393d commit f02e9ee

File tree

4 files changed

+132
-73
lines changed

4 files changed

+132
-73
lines changed

apps/console/objectstack.shared.ts

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,24 @@ if (crmConfig.objects?.length) {
1515
// console.log('DEBUG: CRM Config content:', JSON.stringify(crmConfig, null, 2));
1616
}
1717

18+
// Patch CRM App Navigation to include Report (since it was removed from CRM config for strict validation)
19+
const crmApps = crmConfig.apps ? JSON.parse(JSON.stringify(crmConfig.apps)) : [];
20+
if (crmApps.length > 0) {
21+
const crmApp = crmApps[0];
22+
if (crmApp && crmApp.navigation) {
23+
// Insert report after dashboard
24+
const dashboardIdx = crmApp.navigation.findIndex((n: any) => n.id === 'nav_dashboard');
25+
const insertIdx = dashboardIdx !== -1 ? dashboardIdx + 1 : 0;
26+
crmApp.navigation.splice(insertIdx, 0, {
27+
id: 'nav_sales_report',
28+
type: 'report',
29+
reportName: 'sales_performance_q1',
30+
label: 'Sales Report',
31+
icon: 'file-bar-chart'
32+
});
33+
}
34+
}
35+
1836
export const sharedConfig = {
1937
// ============================================================================
2038
// Project Metadata
@@ -33,7 +51,7 @@ export const sharedConfig = {
3351
...(kitchenSinkConfig.objects || [])
3452
],
3553
apps: [
36-
...(crmConfig.apps || []),
54+
...crmApps,
3755
...(todoConfig.apps || []),
3856
...(kitchenSinkConfig.apps || [])
3957
],
@@ -43,7 +61,51 @@ export const sharedConfig = {
4361
...(kitchenSinkConfig.dashboards || [])
4462
],
4563
reports: [
46-
...(crmConfig.reports || [])
64+
...(crmConfig.reports || []),
65+
// Manually added report since CRM config validation prevents it
66+
{
67+
name: 'sales_performance_q1',
68+
label: 'Q1 Sales Performance',
69+
description: 'Quarterly analysis of sales revenue by region and product line',
70+
type: 'report',
71+
title: 'Q1 Sales Performance Report',
72+
sections: [
73+
{
74+
type: 'header',
75+
title: 'Executive Summary',
76+
subtitle: 'Generated on Feb 6, 2026'
77+
},
78+
{
79+
type: 'summary',
80+
title: 'Key Metrics',
81+
metrics: [
82+
{ label: 'Total Revenue', value: '$1,240,000', change: 12, trend: 'up' },
83+
{ label: 'Deals Closed', value: '45', change: 5, trend: 'up' },
84+
{ label: 'Avg Deal Size', value: '$27,500', change: -2, trend: 'down' }
85+
]
86+
},
87+
{
88+
type: 'chart',
89+
title: 'Revenue Trend',
90+
chart: {
91+
chartType: 'line',
92+
title: 'Monthly Revenue',
93+
xAxisField: 'month',
94+
yAxisFields: ['revenue'],
95+
data: [
96+
{ month: 'Jan', revenue: 320000 },
97+
{ month: 'Feb', revenue: 450000 },
98+
{ month: 'Mar', revenue: 470000 }
99+
]
100+
}
101+
},
102+
{
103+
type: 'section',
104+
title: 'Regional Breakdown',
105+
content: 'North America continues to lead with 45% of total revenue, followed by EMEA at 30%.'
106+
}
107+
]
108+
} as any
47109
],
48110
pages: [
49111
...(crmConfig.pages || []),

apps/console/src/components/ReportView.tsx

Lines changed: 67 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
11
import { useState } from 'react';
22
import { useParams } from 'react-router-dom';
3-
import { ReportViewer } from '@object-ui/plugin-report';
3+
import { ReportViewer, ReportBuilder } from '@object-ui/plugin-report';
44
import { Empty, EmptyTitle, EmptyDescription, Button } from '@object-ui/components';
5-
import { Code2 } from 'lucide-react';
5+
import { Code2, PenLine, ChevronLeft } from 'lucide-react';
66
import appConfig from '../../objectstack.shared';
77

8+
// Mock fields for the builder since we don't have a dynamic schema provider here yet
9+
const MOCK_FIELDS = [
10+
{ name: 'month', label: 'Month', type: 'string' },
11+
{ name: 'revenue', label: 'Revenue', type: 'number' },
12+
{ name: 'count', label: 'Count', type: 'number' },
13+
{ name: 'region', label: 'Region', type: 'string' },
14+
{ name: 'product', label: 'Product', type: 'string' },
15+
{ name: 'source', label: 'Lead Source', type: 'string' },
16+
{ name: 'stage', label: 'Stage', type: 'string' },
17+
{ name: 'amount', label: 'Amount', type: 'currency' },
18+
];
19+
820
export function ReportView() {
921
const { reportName } = useParams<{ reportName: string }>();
1022
const [showDebug, setShowDebug] = useState(false);
23+
const [isEditing, setIsEditing] = useState(false);
1124

1225
// Find report definition in config
1326
// Note: we need to cast appConfig because reports might not be in the strict type yet
14-
const report = (appConfig as any).reports?.find((r: any) => r.name === reportName);
27+
const initialReport = (appConfig as any).reports?.find((r: any) => r.name === reportName);
28+
const [reportData, setReportData] = useState(initialReport);
1529

16-
if (!report) {
30+
if (!initialReport) {
1731
return (
1832
<div className="h-full flex items-center justify-center p-8">
1933
<Empty>
@@ -24,12 +38,43 @@ export function ReportView() {
2438
);
2539
}
2640

41+
const handleSave = (newReport: any) => {
42+
console.log('Saving report:', newReport);
43+
setReportData(newReport);
44+
setIsEditing(false);
45+
};
46+
47+
if (isEditing) {
48+
return (
49+
<div className="flex flex-col h-full overflow-hidden bg-background">
50+
<div className="flex items-center p-4 border-b bg-muted/10 gap-2">
51+
<Button variant="ghost" size="sm" onClick={() => setIsEditing(false)}>
52+
<ChevronLeft className="h-4 w-4 mr-1" />
53+
Back to View
54+
</Button>
55+
<div className="font-medium">Edit Report: {reportData.title}</div>
56+
</div>
57+
<div className="flex-1 overflow-auto">
58+
<ReportBuilder
59+
schema={{
60+
title: 'Report Builder',
61+
report: reportData,
62+
availableFields: MOCK_FIELDS,
63+
onSave: handleSave,
64+
onCancel: () => setIsEditing(false)
65+
}}
66+
/>
67+
</div>
68+
</div>
69+
);
70+
}
71+
2772
// Wrap the report definition in the ReportViewer schema
2873
// The ReportViewer expects a schema property which is of type ReportViewerSchema
2974
// That schema has a 'report' property which is the actual report definition (ReportSchema)
3075
const viewerSchema = {
3176
type: 'report-viewer',
32-
report: report, // The report definition
77+
report: reportData, // The report definition
3378
showToolbar: true,
3479
allowExport: true
3580
};
@@ -39,17 +84,23 @@ export function ReportView() {
3984
<div className="flex justify-between items-center p-6 border-b shrink-0 bg-muted/10">
4085
<div>
4186
{/* Header is handled by ReportViewer usually, but we can have a page header too */}
42-
<h1 className="text-lg font-medium text-muted-foreground">Report Viewer</h1>
87+
<h1 className="text-lg font-medium text-muted-foreground">{reportData.title || 'Report Viewer'}</h1>
88+
</div>
89+
<div className="flex items-center gap-2">
90+
<Button variant="outline" size="sm" onClick={() => setIsEditing(true)}>
91+
<PenLine className="h-4 w-4 mr-2" />
92+
Edit Report
93+
</Button>
94+
<Button
95+
variant={showDebug ? "secondary" : "ghost"}
96+
size="sm"
97+
onClick={() => setShowDebug(!showDebug)}
98+
className="gap-2"
99+
>
100+
<Code2 className="h-4 w-4" />
101+
{showDebug ? 'Hide JSON' : 'Show JSON'}
102+
</Button>
43103
</div>
44-
<Button
45-
variant={showDebug ? "secondary" : "ghost"}
46-
size="sm"
47-
onClick={() => setShowDebug(!showDebug)}
48-
className="gap-2"
49-
>
50-
<Code2 className="h-4 w-4" />
51-
Metadata
52-
</Button>
53104
</div>
54105

55106
<div className="flex-1 overflow-hidden flex flex-row relative">
@@ -70,7 +121,7 @@ export function ReportView() {
70121
<h4 className="text-xs font-bold uppercase text-muted-foreground mb-2">Report Configuration</h4>
71122
<div className="relative rounded-md border bg-slate-950 text-slate-50 overflow-hidden">
72123
<pre className="text-xs p-3 overflow-auto max-h-[800px]">
73-
{JSON.stringify(report, null, 2)}
124+
{JSON.stringify(reportData, null, 2)}
74125
</pre>
75126
</div>
76127
</div>

examples/crm/objectstack.config.ts

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,51 +20,7 @@ export default defineStack({
2020
ProjectObject,
2121
EventObject
2222
],
23-
reports: [
24-
{
25-
name: 'sales_performance_q1',
26-
label: 'Q1 Sales Performance',
27-
description: 'Quarterly analysis of sales revenue by region and product line',
28-
type: 'report',
29-
title: 'Q1 Sales Performance Report',
30-
sections: [
31-
{
32-
type: 'header',
33-
title: 'Executive Summary',
34-
subtitle: 'Generated on Feb 6, 2026'
35-
},
36-
{
37-
type: 'summary',
38-
title: 'Key Metrics',
39-
metrics: [
40-
{ label: 'Total Revenue', value: '$1,240,000', change: 12, trend: 'up' },
41-
{ label: 'Deals Closed', value: '45', change: 5, trend: 'up' },
42-
{ label: 'Avg Deal Size', value: '$27,500', change: -2, trend: 'down' }
43-
]
44-
},
45-
{
46-
type: 'chart',
47-
title: 'Revenue Trend',
48-
chart: {
49-
chartType: 'line',
50-
title: 'Monthly Revenue',
51-
xAxisField: 'month',
52-
yAxisFields: ['revenue'],
53-
data: [
54-
{ month: 'Jan', revenue: 320000 },
55-
{ month: 'Feb', revenue: 450000 },
56-
{ month: 'Mar', revenue: 470000 }
57-
]
58-
}
59-
},
60-
{
61-
type: 'section',
62-
title: 'Regional Breakdown',
63-
content: 'North America continues to lead with 45% of total revenue, followed by EMEA at 30%.'
64-
}
65-
]
66-
}
67-
],
23+
reports: [],
6824
apps: [
6925
App.create({
7026
name: 'crm_app',
@@ -78,13 +34,6 @@ export default defineStack({
7834
label: 'Dashboard',
7935
icon: 'layout-dashboard'
8036
},
81-
{
82-
id: 'nav_sales_report',
83-
type: 'report',
84-
reportName: 'sales_performance_q1',
85-
label: 'Sales Report',
86-
icon: 'file-bar-chart'
87-
},
8837
{
8938
id: 'nav_contacts',
9039
type: 'object',

packages/plugin-report/src/index.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ ComponentRegistry.register(
1818
'report',
1919
ReportRenderer,
2020
{
21-
namespace: 'plugin-report',
2221
label: 'Report',
2322
category: 'Report',
2423
inputs: [
@@ -34,7 +33,6 @@ ComponentRegistry.register(
3433
'report-viewer',
3534
ReportViewer,
3635
{
37-
namespace: 'plugin-report',
3836
label: 'Report Viewer',
3937
category: 'Report',
4038
inputs: [
@@ -49,7 +47,6 @@ ComponentRegistry.register(
4947
'report-builder',
5048
ReportBuilder,
5149
{
52-
namespace: 'plugin-report',
5350
label: 'Report Builder',
5451
category: 'Report',
5552
inputs: [

0 commit comments

Comments
 (0)