Skip to content

Commit 1990cb4

Browse files
committed
添加报告视图组件及相关配置,支持报告展示和调试元数据检查
1 parent 92c1950 commit 1990cb4

7 files changed

Lines changed: 162 additions & 4 deletions

File tree

apps/console/objectstack.shared.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export const sharedConfig = {
4242
...(todoConfig.dashboards || []),
4343
...(kitchenSinkConfig.dashboards || [])
4444
],
45+
reports: [
46+
...(crmConfig.reports || [])
47+
],
4548
pages: [
4649
...(crmConfig.pages || []),
4750
...(todoConfig.pages || []),

apps/console/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { LoadingScreen } from './components/LoadingScreen';
1414
import { ObjectView } from './components/ObjectView';
1515
import { DashboardView } from './components/DashboardView';
1616
import { PageView } from './components/PageView';
17+
import { ReportView } from './components/ReportView';
1718

1819
import { DetailView } from '@object-ui/plugin-detail';
1920
import { useParams } from 'react-router-dom';
@@ -249,6 +250,9 @@ export function AppContent() {
249250
<Route path="dashboard/:dashboardName" element={
250251
<DashboardView />
251252
} />
253+
<Route path="report/:reportName" element={
254+
<ReportView />
255+
} />
252256
<Route path="page/:pageName" element={
253257
<PageView />
254258
} />

apps/console/src/components/AppSidebar.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ function NavigationItemRenderer({ item, activeAppName }: { item: any, activeAppN
309309
}
310310
} else if (item.type === 'dashboard') {
311311
href = item.dashboardName ? `${baseUrl}/dashboard/${item.dashboardName}` : '#';
312+
} else if (item.type === 'report') {
313+
href = item.reportName ? `${baseUrl}/report/${item.reportName}` : '#';
312314
} else if (item.type === 'url') {
313315
href = item.url || '#';
314316
isExternal = item.target === '_blank';
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useState } from 'react';
2+
import { useParams } from 'react-router-dom';
3+
import { ReportViewer } from '@object-ui/plugin-dashboard';
4+
import { Empty, EmptyTitle, EmptyDescription, Button } from '@object-ui/components';
5+
import { Code2 } from 'lucide-react';
6+
import appConfig from '../../objectstack.shared';
7+
8+
export function ReportView() {
9+
const { reportName } = useParams<{ reportName: string }>();
10+
const [showDebug, setShowDebug] = useState(false);
11+
12+
// Find report definition in config
13+
// 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);
15+
16+
if (!report) {
17+
return (
18+
<div className="h-full flex items-center justify-center p-8">
19+
<Empty>
20+
<EmptyTitle>Report Not Found</EmptyTitle>
21+
<EmptyDescription>The report "{reportName}" could not be found.</EmptyDescription>
22+
</Empty>
23+
</div>
24+
);
25+
}
26+
27+
// Wrap the report definition in the ReportViewer schema
28+
// The ReportViewer expects a schema property which is of type ReportViewerSchema
29+
// That schema has a 'report' property which is the actual report definition (ReportSchema)
30+
const viewerSchema = {
31+
type: 'report-viewer',
32+
report: report, // The report definition
33+
showToolbar: true,
34+
allowExport: true
35+
};
36+
37+
return (
38+
<div className="flex flex-col h-full overflow-hidden bg-background">
39+
<div className="flex justify-between items-center p-6 border-b shrink-0 bg-muted/10">
40+
<div>
41+
{/* 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>
43+
</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>
53+
</div>
54+
55+
<div className="flex-1 overflow-hidden flex flex-row relative">
56+
<div className="flex-1 overflow-auto p-8 bg-muted/5">
57+
<div className="max-w-5xl mx-auto shadow-sm border rounded-xl bg-background overflow-hidden min-h-[600px]">
58+
<ReportViewer schema={viewerSchema} />
59+
</div>
60+
</div>
61+
62+
{showDebug && (
63+
<div className="w-[400px] border-l bg-muted/30 p-0 overflow-hidden flex flex-col shrink-0 shadow-xl z-20 transition-all">
64+
<div className="p-3 border-b bg-muted/50 font-semibold text-sm flex items-center justify-between">
65+
<span>Metadata Inspector</span>
66+
<span className="text-xs text-muted-foreground">JSON Protocol</span>
67+
</div>
68+
<div className="flex-1 overflow-auto p-4 space-y-6">
69+
<div>
70+
<h4 className="text-xs font-bold uppercase text-muted-foreground mb-2">Report Configuration</h4>
71+
<div className="relative rounded-md border bg-slate-950 text-slate-50 overflow-hidden">
72+
<pre className="text-xs p-3 overflow-auto max-h-[800px]">
73+
{JSON.stringify(report, null, 2)}
74+
</pre>
75+
</div>
76+
</div>
77+
</div>
78+
</div>
79+
)}
80+
</div>
81+
</div>
82+
);
83+
}

apps/console/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface ObjectStackConfig {
2727
objects?: any[];
2828
apps?: any[];
2929
dashboards?: any[];
30+
reports?: any[];
3031
pages?: any[];
3132
manifest?: {
3233
data?: any[];

examples/crm/objectstack.config.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,51 @@ 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+
],
2368
apps: [
2469
App.create({
2570
name: 'crm_app',
@@ -33,6 +78,13 @@ export default defineStack({
3378
label: 'Dashboard',
3479
icon: 'layout-dashboard'
3580
},
81+
{
82+
id: 'nav_sales_report',
83+
type: 'report',
84+
reportName: 'sales_performance_q1',
85+
label: 'Sales Report',
86+
icon: 'file-bar-chart'
87+
},
3688
{
3789
id: 'nav_contacts',
3890
type: 'object',

packages/plugin-dashboard/src/ReportRenderer.tsx

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,25 @@ export const ReportRenderer: React.FC<ReportRendererProps> = ({ schema }) => {
3535
<CardContent className="flex-1 overflow-auto space-y-4">
3636
{/* Render Chart Section if present */}
3737
{hasChart && (() => {
38-
const ChartComponent = ComponentRegistry.get(chartType);
39-
return ChartComponent ? (
38+
// Resolve chart component logic
39+
// 1. Try resolving using chart definition's type (e.g. 'chart', 'bar-chart')
40+
// 2. Default to 'chart' (registered by plugin-charts)
41+
const targetType = schema.chart?.type || 'chart';
42+
const ChartComponent = ComponentRegistry.get(targetType);
43+
44+
if (!ChartComponent) {
45+
return (
46+
<div className="min-h-[100px] border rounded-md p-4 bg-muted/20 text-muted-foreground flex items-center justify-center">
47+
Unknown component type: {targetType}
48+
</div>
49+
)
50+
}
51+
52+
return (
4053
<div className="min-h-[300px] border rounded-md p-4 bg-white/50">
41-
<ChartComponent schema={{ ...schema.chart, data }} />
54+
<ChartComponent schema={{ ...schema.chart, data: schema.chart.data || data }} />
4255
</div>
43-
) : null;
56+
);
4457
})()}
4558

4659
{/* Render Data Grid Section */}

0 commit comments

Comments
 (0)