Skip to content

Commit 9a66e0f

Browse files
committed
feat: enhance CRM example with new data structures, schemas, and routing for contacts and opportunities
1 parent ce45b8d commit 9a66e0f

6 files changed

Lines changed: 265 additions & 116 deletions

File tree

examples/crm-app/src/App.tsx

Lines changed: 42 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,134 +1,36 @@
1-
import { useEffect } from 'react';
2-
import { BrowserRouter, Routes, Route, Outlet, Link, useLocation } from 'react-router-dom';
1+
import { useEffect, useMemo } from 'react';
2+
import { BrowserRouter, Routes, Route, Outlet, Link, useLocation, useParams } from 'react-router-dom';
33
import { SidebarProvider, SidebarInset, SidebarTrigger } from '@object-ui/components';
44
import { SchemaRendererProvider, SchemaRenderer, useRenderer } from '@object-ui/react';
55
import { registerFields } from '@object-ui/fields';
66
import { registerLayout } from '@object-ui/layout';
7-
import '@object-ui/plugin-dashboard'; // Auto-register dashboard
7+
import '@object-ui/plugin-dashboard';
88
import { registerPlaceholders } from '@object-ui/components';
99
import { SidebarNav } from './components/SidebarNav';
1010

11-
// 1. Register components from packages (The "Controls Repository")
11+
// Data & Schemas
12+
import { mockData, getContact, getOpportunity } from './data';
13+
import { dashboardSchema } from './schemas/dashboard';
14+
import { contactListSchema, contactDetailSchema } from './schemas/contacts';
15+
import { opportunityListSchema } from './schemas/opportunities';
16+
17+
// 1. Register components
1218
registerFields();
1319
registerLayout();
14-
registerPlaceholders(); // Register missing components as placeholders
15-
16-
// 2. Define Mock Data (In a real app, this comes from an API)
17-
const mockData = {
18-
user: { name: "Demo User", role: "admin" },
19-
stats: { revenue: 125000, leads: 45, deals: 12 },
20-
contacts: [
21-
{ id: 1, name: "Alice Johnson", email: "alice@example.com", status: "Active" },
22-
{ id: 2, name: "Bob Smith", email: "bob@tech.com", status: "Lead" },
23-
{ id: 3, name: "Charlie Brown", email: "charlie@peanuts.com", status: "Customer" }
24-
]
25-
};
26-
27-
// 3. Define Page Schemas (The "JSON Rendering")
20+
registerPlaceholders();
2821

29-
// Dashboard Page
30-
const dashboardSchema = {
31-
type: "dashboard", // Changed from 'page' to 'dashboard'
32-
props: { title: "Executive Dashboard" },
33-
widgets: [
34-
{
35-
id: "w1",
36-
component: {
37-
type: "card",
38-
props: { title: "Total Revenue" },
39-
children: [{ type: "text", props: { value: "$125,000", className: "text-2xl font-bold" } }]
40-
}
41-
},
42-
{
43-
id: "w2",
44-
component: {
45-
type: "card",
46-
props: { title: "Active Leads" },
47-
children: [{ type: "text", props: { value: "45", className: "text-2xl font-bold" } }]
48-
}
49-
},
50-
{
51-
id: "w3",
52-
component: {
53-
type: "card",
54-
props: { title: "Open Deals" },
55-
children: [{ type: "text", props: { value: "12", className: "text-2xl font-bold" } }]
56-
}
57-
},
58-
{
59-
id: "w4",
60-
layout: { w: 3, h: 2 },
61-
title: "Recent Activity (Kanban Placeholder)",
62-
component: {
63-
type: "view:kanban",
64-
props: {
65-
columns: ["Todo", "In Progress", "Done"],
66-
data: [{id: 1, title: "Task 1", status: "Todo"}]
67-
}
68-
}
69-
}
70-
]
71-
};
72-
73-
// Contacts List Page
74-
const contactsSchema = {
75-
type: "page",
76-
props: { title: "Contacts" },
77-
children: [
78-
{
79-
type: "page-header",
80-
props: {
81-
title: "All Contacts",
82-
description: "Manage your customer relationships"
83-
},
84-
children: [
85-
{
86-
type: "action:button",
87-
props: { label: "Add Contact", variant: "default" },
88-
events: { onClick: [{ action: "navigate", params: { url: "/contacts/new" } }] }
89-
}
90-
]
91-
},
92-
{
93-
type: "card",
94-
className: "mt-6",
95-
children: [
96-
{
97-
type: "view:grid",
98-
bind: "contacts",
99-
props: {
100-
columns: [
101-
{ key: "name", label: "Name" },
102-
{ key: "email", label: "Email" },
103-
{ key: "status", label: "Status" }
104-
]
105-
}
106-
}
107-
]
108-
},
109-
{
110-
type: "view:map",
111-
props: {
112-
lat: 34.05,
113-
lng: -118.25,
114-
zoom: 10
115-
},
116-
className: "mt-4 h-64"
117-
}
118-
]
119-
};
120-
121-
// Layout Shell Component
22+
// Generic Layout Shell
12223
const Layout = () => {
12324
return (
12425
<SidebarProvider>
12526
<div className="flex min-h-screen w-full bg-gray-50 dark:bg-gray-900">
12627
<SidebarNav />
12728
<SidebarInset>
128-
<div className="p-4">
129-
<SidebarTrigger className="md:hidden" />
29+
<div className="p-4 border-b bg-background flex items-center md:hidden">
30+
<SidebarTrigger />
31+
<span className="ml-2 font-semibold">CRM Demo</span>
13032
</div>
131-
<main className="flex-1 overflow-y-auto p-4 md:p-8">
33+
<main className="flex-1 overflow-y-auto w-full p-4 md:p-8">
13234
<Outlet />
13335
</main>
13436
</SidebarInset>
@@ -137,6 +39,22 @@ const Layout = () => {
13739
);
13840
};
13941

42+
// Generic Detail Page Loader
43+
const ContactDetailPage = () => {
44+
const { id } = useParams();
45+
const contact = getContact(id || "");
46+
47+
if (!contact) return <div>Contact not found</div>;
48+
49+
// In a real app, SchemaRendererProvider would be at the App root,
50+
// but here we nest it to inject specific record data into the context
51+
return (
52+
<SchemaRendererProvider dataSource={contact} debug={true}>
53+
<SchemaRenderer schema={contactDetailSchema} />
54+
</SchemaRendererProvider>
55+
);
56+
}
57+
14058
function App() {
14159
return (
14260
<SchemaRendererProvider
@@ -147,8 +65,15 @@ function App() {
14765
<Routes>
14866
<Route path="/" element={<Layout />}>
14967
<Route index element={<SchemaRenderer schema={dashboardSchema} />} />
150-
<Route path="contacts" element={<SchemaRenderer schema={contactsSchema} />} />
151-
<Route path="opportunities" element={<div className="p-4">Opportunities Module (Coming Soon)</div>} />
68+
69+
<Route path="contacts">
70+
<Route index element={<SchemaRenderer schema={contactListSchema} />} />
71+
<Route path=":id" element={<ContactDetailPage />} />
72+
</Route>
73+
74+
<Route path="opportunities" element={<SchemaRenderer schema={opportunityListSchema} />} />
75+
76+
<Route path="products" element={<div className="p-4">Products Module (Coming Soon)</div>} />
15277
<Route path="settings" element={<div className="p-4">Settings Module (Coming Soon)</div>} />
15378
</Route>
15479
</Routes>
@@ -158,3 +83,4 @@ function App() {
15883
}
15984

16085
export default App;
86+

examples/crm-app/src/data.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Mock Data mimicking a relational database response
2+
export const mockData = {
3+
user: { name: "Demo User", role: "admin", avatar: "" },
4+
stats: { revenue: 125000, leads: 45, deals: 12 },
5+
contacts: [
6+
{ id: "1", name: "Alice Johnson", email: "alice@example.com", phone: "555-0101", title: "VP Sales", company: "TechCorp", status: "Active" },
7+
{ id: "2", name: "Bob Smith", email: "bob@tech.com", phone: "555-0102", title: "Developer", company: "Software Inc", status: "Lead" },
8+
{ id: "3", name: "Charlie Brown", email: "charlie@peanuts.com", phone: "555-0103", title: "Manager", company: "Good Grief LLC", status: "Customer" }
9+
],
10+
opportunities: [
11+
{ id: "101", name: "TechCorp Enterprise License", amount: 50000, stage: "Proposal", closeDate: "2024-06-30", accountId: "1" },
12+
{ id: "102", name: "Software Inc Pilot", amount: 5000, stage: "Closed Won", closeDate: "2024-01-15", accountId: "2" },
13+
{ id: "103", name: "Good Grief Consultant", amount: 12000, stage: "Negotiation", closeDate: "2024-05-20", accountId: "3" }
14+
]
15+
};
16+
17+
export const getContact = (id: string) => mockData.contacts.find(c => c.id === id);
18+
export const getOpportunity = (id: string) => mockData.opportunities.find(o => o.id === id);
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { PageSchema } from "@object-ui/types";
2+
3+
export const contactListSchema: PageSchema = {
4+
type: "page",
5+
props: { title: "Contacts" },
6+
children: [
7+
{
8+
type: "page:header",
9+
props: {
10+
title: "Contacts",
11+
description: "Manage your customers and leads"
12+
},
13+
children: [
14+
{
15+
type: "action:button",
16+
props: { label: "New Contact", variant: "default" },
17+
events: { onClick: [{ action: "navigate", params: { url: "/contacts/new" } }] }
18+
}
19+
]
20+
},
21+
{
22+
type: "page:card",
23+
className: "mt-4",
24+
children: [
25+
{
26+
type: "view:grid",
27+
bind: "contacts",
28+
props: {
29+
columns: [
30+
{ key: "name", label: "Name", type: "text" },
31+
{ key: "company", label: "Company", type: "text" },
32+
{ key: "email", label: "Email", type: "email" },
33+
{ key: "status", label: "Status", type: "badge" },
34+
{
35+
key: "actions", label: "Actions", type: "action:group",
36+
render: (row: any) => ({
37+
actions: [
38+
{ label: "View", action: "navigate", params: { url: `/contacts/${row.id}` } }
39+
]
40+
})
41+
}
42+
]
43+
}
44+
}
45+
]
46+
}
47+
]
48+
};
49+
50+
export const contactDetailSchema: PageSchema = {
51+
type: "page",
52+
props: { title: "Contact Details" },
53+
children: [
54+
{
55+
type: "page:header",
56+
props: {
57+
title: "${data.name}", // Expression binding
58+
description: "${data.title} at ${data.company}"
59+
},
60+
children: [
61+
{
62+
type: "action:button",
63+
props: { label: "Edit", variant: "outline" }
64+
},
65+
{
66+
type: "action:button",
67+
props: { label: "Delete", variant: "destructive" }
68+
}
69+
]
70+
},
71+
{
72+
type: "view:simple", // Form view
73+
props: {
74+
columns: 2
75+
},
76+
children: [
77+
{ type: "field:text", bind: "name", props: { label: "Full Name", readonly: true } },
78+
{ type: "field:email", bind: "email", props: { label: "Email Address", readonly: true } },
79+
{ type: "field:phone", bind: "phone", props: { label: "Phone", readonly: true } },
80+
{ type: "field:text", bind: "company", props: { label: "Company", readonly: true } },
81+
{ type: "field:text", bind: "title", props: { label: "Job Title", readonly: true } },
82+
{ type: "field:badge", bind: "status", props: { label: "Status" } }
83+
]
84+
},
85+
{
86+
type: "page:tabs",
87+
className: "mt-6",
88+
props: {
89+
defaultValue: "activity",
90+
items: [
91+
{ value: "activity", label: "Activity" },
92+
{ value: "history", label: "History" }
93+
]
94+
},
95+
children: [
96+
{
97+
type: "record:activity",
98+
props: { content: "Activity Timeline Placeholder" }
99+
}
100+
]
101+
}
102+
]
103+
};
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { DashboardSchema } from "@object-ui/types";
2+
3+
export const dashboardSchema: DashboardSchema = {
4+
type: "dashboard",
5+
props: { title: "Executive Dashboard" },
6+
widgets: [
7+
{
8+
id: "w1",
9+
layout: { w: 1, h: 1 },
10+
component: {
11+
type: "card",
12+
props: { title: "Total Revenue" },
13+
children: [
14+
{ type: "widget:metric", props: { value: "$125,000", trend: "+12%", label: "vs last month" } }
15+
]
16+
}
17+
},
18+
{
19+
id: "w2",
20+
layout: { w: 1, h: 1 },
21+
component: {
22+
type: "card",
23+
props: { title: "Active Leads" },
24+
children: [
25+
{ type: "widget:metric", props: { value: "45", trend: "+5%", label: "vs last week" } }
26+
]
27+
}
28+
},
29+
{
30+
id: "w3",
31+
layout: { w: 1, h: 1 },
32+
component: {
33+
type: "card",
34+
props: { title: "Open Deals" },
35+
children: [
36+
{ type: "widget:metric", props: { value: "12", trend: "-2%", label: "vs last month" } }
37+
]
38+
}
39+
},
40+
{
41+
id: "w4",
42+
layout: { w: 2, h: 2 },
43+
title: "Sales Pipeline",
44+
component: {
45+
type: "widget:bar",
46+
props: {
47+
title: "Revenue by Stage",
48+
data: [
49+
{ name: "Prospecting", value: 4000 },
50+
{ name: "Proposal", value: 3000 },
51+
{ name: "Negotiation", value: 2000 },
52+
{ name: "Closed", value: 2780 },
53+
]
54+
}
55+
}
56+
},
57+
{
58+
id: "w5",
59+
layout: { w: 1, h: 2 },
60+
title: "Recent Activity",
61+
component: {
62+
type: "widget:text",
63+
props: { content: "• Call with Alice\n• Email to Bob\n• Proposal sent to Charlie" }
64+
}
65+
}
66+
]
67+
};

0 commit comments

Comments
 (0)