Skip to content

Commit f4186c3

Browse files
authored
Merge pull request #1200 from objectstack-ai/claude/optimize-record-detail-page
2 parents 0df01d7 + 05d7e5c commit f4186c3

File tree

5 files changed

+322
-140
lines changed

5 files changed

+322
-140
lines changed

apps/console/src/__tests__/MetadataDetailPage.test.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ beforeEach(() => {
7777
</div>
7878
);
7979

80+
ComponentRegistry.register('object-detail-tabs', mockWidget('object-detail-tabs'));
8081
ComponentRegistry.register('object-properties', mockWidget('object-properties'));
8182
ComponentRegistry.register('object-relationships', mockWidget('object-relationships'));
8283
ComponentRegistry.register('object-keys', mockWidget('object-keys'));
@@ -209,7 +210,7 @@ describe('MetadataDetailPage', () => {
209210
expect(screen.getByTestId('schema-detail-content')).toBeInTheDocument();
210211
});
211212

212-
it('should render all object detail widget sections', () => {
213+
it('should render object detail tabs widget', () => {
213214
mockGetItems.mockResolvedValue([
214215
{ name: 'account', label: 'Accounts', description: 'Customer accounts' },
215216
]);
@@ -223,13 +224,8 @@ describe('MetadataDetailPage', () => {
223224
</Routes>
224225
</MemoryRouter>,
225226
);
226-
// All widget sections should be rendered via SchemaRenderer
227-
expect(screen.getByTestId('mock-object-properties')).toBeInTheDocument();
228-
expect(screen.getByTestId('mock-object-relationships')).toBeInTheDocument();
229-
expect(screen.getByTestId('mock-object-keys')).toBeInTheDocument();
230-
expect(screen.getByTestId('mock-object-data-experience')).toBeInTheDocument();
231-
expect(screen.getByTestId('mock-object-data-preview')).toBeInTheDocument();
232-
expect(screen.getByTestId('mock-object-field-designer')).toBeInTheDocument();
227+
// The tabbed widget should be rendered
228+
expect(screen.getByTestId('mock-object-detail-tabs')).toBeInTheDocument();
233229
});
234230

235231
it('should navigate back to object list route when back button is clicked', () => {
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/**
2+
* Object Detail Tabs Widget
3+
*
4+
* Self-contained, schema-driven tabbed widget for the object detail page.
5+
* Organizes object configuration into logical tabs similar to Power Apps:
6+
* - Details: Object properties and basic information
7+
* - Fields: Field designer for managing object fields
8+
* - Relationships: Relationships and unique keys
9+
* - Data: Data preview and experience placeholders
10+
*
11+
* Schema: { type: 'object-detail-tabs', objectName: 'account' }
12+
*
13+
* @module components/schema/ObjectDetailTabsWidget
14+
*/
15+
16+
import { useState } from 'react';
17+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@object-ui/components';
18+
import { Settings2, Columns, Link2, Table } from 'lucide-react';
19+
import type { SchemaNode } from '@object-ui/core';
20+
import { SchemaRenderer } from '@object-ui/react';
21+
22+
/** Schema props for object detail tabs widget. */
23+
interface ObjectDetailTabsSchema extends SchemaNode {
24+
objectName: string;
25+
}
26+
27+
export function ObjectDetailTabsWidget({ schema }: { schema: ObjectDetailTabsSchema }) {
28+
const objectName = schema.objectName;
29+
const [activeTab, setActiveTab] = useState('details');
30+
31+
// Create schema objects for each widget
32+
const detailsSchema: SchemaNode & { objectName: string } = {
33+
type: 'object-properties',
34+
id: `${objectName}-properties`,
35+
objectName,
36+
};
37+
38+
const fieldsSchema: SchemaNode & { objectName: string } = {
39+
type: 'object-field-designer',
40+
id: `${objectName}-field-designer`,
41+
objectName,
42+
};
43+
44+
const relationshipsSchema: SchemaNode & { objectName: string } = {
45+
type: 'object-relationships',
46+
id: `${objectName}-relationships`,
47+
objectName,
48+
};
49+
50+
const keysSchema: SchemaNode & { objectName: string } = {
51+
type: 'object-keys',
52+
id: `${objectName}-keys`,
53+
objectName,
54+
};
55+
56+
const dataExperienceSchema: SchemaNode & { objectName: string } = {
57+
type: 'object-data-experience',
58+
id: `${objectName}-data-experience`,
59+
objectName,
60+
};
61+
62+
const dataPreviewSchema: SchemaNode & { objectName: string } = {
63+
type: 'object-data-preview',
64+
id: `${objectName}-data-preview`,
65+
objectName,
66+
};
67+
68+
return (
69+
<div className="w-full" data-testid="object-detail-tabs">
70+
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
71+
<TabsList className="w-full justify-start border-b rounded-none bg-transparent p-0 h-auto">
72+
<TabsTrigger
73+
value="details"
74+
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
75+
>
76+
<Settings2 className="h-4 w-4 mr-2" />
77+
Details
78+
</TabsTrigger>
79+
<TabsTrigger
80+
value="fields"
81+
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
82+
>
83+
<Columns className="h-4 w-4 mr-2" />
84+
Fields
85+
</TabsTrigger>
86+
<TabsTrigger
87+
value="relationships"
88+
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
89+
>
90+
<Link2 className="h-4 w-4 mr-2" />
91+
Relationships
92+
</TabsTrigger>
93+
<TabsTrigger
94+
value="data"
95+
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent data-[state=active]:shadow-none"
96+
>
97+
<Table className="h-4 w-4 mr-2" />
98+
Data
99+
</TabsTrigger>
100+
</TabsList>
101+
102+
<TabsContent value="details" className="mt-6">
103+
<div className="space-y-6">
104+
<SchemaRenderer schema={detailsSchema} />
105+
</div>
106+
</TabsContent>
107+
108+
<TabsContent value="fields" className="mt-6">
109+
<div className="space-y-6">
110+
<SchemaRenderer schema={fieldsSchema} />
111+
</div>
112+
</TabsContent>
113+
114+
<TabsContent value="relationships" className="mt-6">
115+
<div className="space-y-6">
116+
<SchemaRenderer schema={relationshipsSchema} />
117+
<SchemaRenderer schema={keysSchema} />
118+
</div>
119+
</TabsContent>
120+
121+
<TabsContent value="data" className="mt-6">
122+
<div className="space-y-6">
123+
<SchemaRenderer schema={dataPreviewSchema} />
124+
<SchemaRenderer schema={dataExperienceSchema} />
125+
</div>
126+
</TabsContent>
127+
</Tabs>
128+
</div>
129+
);
130+
}

0 commit comments

Comments
 (0)