1- import { useState } from 'react' ;
1+ import { useState , useMemo } from 'react' ;
22import { useParams , useSearchParams } from 'react-router-dom' ;
33import { ObjectGrid } from '@object-ui/plugin-grid' ;
4- import { Button } from '@object-ui/components' ;
5- import { Plus } from 'lucide-react' ;
4+ import { ObjectKanban } from '@object-ui/plugin-kanban' ;
5+ import { ObjectCalendar } from '@object-ui/plugin-calendar' ;
6+ import { Button , Tabs , TabsList , TabsTrigger } from '@object-ui/components' ;
7+ import { Plus , LayoutTemplate , Calendar as CalendarIcon , Kanban as KanbanIcon , Table as TableIcon } from 'lucide-react' ;
68
79export function ObjectView ( { dataSource, objects, onEdit } : any ) {
810 const { objectName } = useParams ( ) ;
9- const [ searchParams ] = useSearchParams ( ) ;
10- const viewName = searchParams . get ( 'view' ) ;
11+ const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
1112 const [ refreshKey , setRefreshKey ] = useState ( 0 ) ;
13+
14+ // Get Object Definition
1215 const objectDef = objects . find ( ( o : any ) => o . name === objectName ) ;
1316
1417 if ( ! objectDef ) {
@@ -20,26 +23,152 @@ export function ObjectView({ dataSource, objects, onEdit }: any) {
2023 ) ;
2124 }
2225
23- // Generate columns from fields if not specified
24- const normalizedFields = Array . isArray ( objectDef . fields )
25- ? objectDef . fields
26- : Object . entries ( objectDef . fields || { } ) . map ( ( [ key , value ] : [ string , any ] ) => ( { name : key , ...value } ) ) ;
26+ // Resolve Views
27+ const views = useMemo ( ( ) => {
28+ const definedViews = objectDef . list_views || { } ;
29+ const viewList = Object . entries ( definedViews ) . map ( ( [ key , value ] : [ string , any ] ) => ( {
30+ id : key ,
31+ ...value ,
32+ type : value . type || 'grid'
33+ } ) ) ;
34+
35+ // Ensure at least one default view exists
36+ if ( viewList . length === 0 ) {
37+ viewList . push ( {
38+ id : 'all' ,
39+ label : 'All Records' ,
40+ type : 'grid' ,
41+ columns : objectDef . fields ? Object . keys ( objectDef . fields ) . slice ( 0 , 5 ) : [ ]
42+ } ) ;
43+ }
44+ return viewList ;
45+ } , [ objectDef ] ) ;
2746
28- const columns = normalizedFields . map ( ( f : any ) => ( {
29- field : f . name ,
30- label : f . label || f . name ,
31- width : 150
32- } ) ) . slice ( 0 , 8 ) ;
47+ // Active View State
48+ const activeViewId = searchParams . get ( 'view' ) || views [ 0 ] ?. id ;
49+ const activeView = views . find ( ( v : any ) => v . id === activeViewId ) || views [ 0 ] ;
50+
51+ // Helper: Normalize Columns for Grid
52+ const getGridColumns = ( view : any ) => {
53+ if ( ! view . columns ) return [ ] ;
54+ return view . columns . map ( ( colName : string ) => {
55+ // Find field definition
56+ const fieldDef = Array . isArray ( objectDef . fields )
57+ ? objectDef . fields . find ( ( f : any ) => f . name === colName )
58+ : objectDef . fields ?. [ colName ] ;
59+
60+ return {
61+ field : colName ,
62+ label : fieldDef ?. label || colName ,
63+ width : 150
64+ } ;
65+ } ) ;
66+ } ;
67+
68+ const handleViewChange = ( viewId : string ) => {
69+ setSearchParams ( { view : viewId } ) ;
70+ } ;
71+
72+ const renderCurrentView = ( ) => {
73+ const commonProps = {
74+ key : `${ objectName } -${ activeView . id } -${ refreshKey } ` ,
75+ dataSource,
76+ className : "h-full border-none"
77+ } ;
78+
79+ // Define onRowClick/Edit handlers
80+ const interactionProps = {
81+ onEdit,
82+ onRowClick : ( record : any ) => onEdit ( record ) , // Default to edit on click
83+ } ;
84+
85+ switch ( activeView . type ) {
86+ case 'kanban' :
87+ return (
88+ < ObjectKanban
89+ { ...commonProps }
90+ schema = { {
91+ type : 'kanban' ,
92+ objectName : objectDef . name ,
93+ groupBy : activeView . groupBy || 'status' ,
94+ columns : activeView . columns ,
95+ cardTitle : objectDef . titleField || 'name' , // Default title field
96+ cardFields : activeView . columns
97+ } }
98+ { ...interactionProps }
99+ />
100+ ) ;
101+ case 'calendar' :
102+ return (
103+ < ObjectCalendar
104+ { ...commonProps }
105+ schema = { {
106+ type : 'calendar' ,
107+ objectName : objectDef . name ,
108+ dateField : activeView . dateField || 'due_date' ,
109+ endField : activeView . endField ,
110+ titleField : activeView . titleField || 'name' ,
111+ } }
112+ { ...interactionProps }
113+ />
114+ ) ;
115+ case 'grid' :
116+ default :
117+ return (
118+ < ObjectGrid
119+ { ...commonProps }
120+ schema = { {
121+ type : 'object-grid' ,
122+ objectName : objectDef . name ,
123+ filterable : true ,
124+ columns : getGridColumns ( activeView ) ,
125+ filter : activeView . filter ,
126+ sort : activeView . sort
127+ } }
128+ { ...interactionProps }
129+ onDelete = { async ( record : any ) => {
130+ if ( confirm ( `Delete record?` ) ) {
131+ await dataSource . delete ( objectName , record . id || record . _id ) ;
132+ setRefreshKey ( k => k + 1 ) ;
133+ }
134+ } }
135+ />
136+ ) ;
137+ }
138+ } ;
33139
34140 return (
35141 < div className = "h-full flex flex-col gap-4" >
36142 { /* Header Section */ }
37143 < div className = "flex justify-between items-start" >
38144 < div className = "space-y-1" >
39145 < h1 className = "text-2xl font-bold tracking-tight text-slate-900" > { objectDef . label } </ h1 >
40- < p className = "text-slate-500 text-sm" >
41- { viewName ? `View: ${ viewName } ` : ( objectDef . description || 'Manage your records' ) }
42- </ p >
146+ < div className = "flex items-center gap-2" >
147+ { /* View Switcher Tabs */ }
148+ { views . length > 1 && (
149+ < Tabs value = { activeView . id } onValueChange = { handleViewChange } className = "h-8" >
150+ < TabsList className = "h-8 p-0 bg-transparent border-0 gap-2" >
151+ { views . map ( ( v : any ) => (
152+ < TabsTrigger
153+ key = { v . id }
154+ value = { v . id }
155+ className = "h-8 px-3 data-[state=active]:bg-muted data-[state=active]:shadow-none border border-transparent data-[state=active]:border-border rounded-md transition-all"
156+ >
157+ { v . type === 'kanban' && < KanbanIcon className = "mr-2 h-3.5 w-3.5" /> }
158+ { v . type === 'calendar' && < CalendarIcon className = "mr-2 h-3.5 w-3.5" /> }
159+ { v . type === 'grid' && < TableIcon className = "mr-2 h-3.5 w-3.5" /> }
160+ { v . label }
161+ </ TabsTrigger >
162+ ) ) }
163+ </ TabsList >
164+ </ Tabs >
165+ ) }
166+ { views . length <= 1 && (
167+ < p className = "text-slate-500 text-sm" >
168+ { objectDef . description || 'Manage your records' }
169+ </ p >
170+ ) }
171+ </ div >
43172 </ div >
44173 < div className = "flex gap-2" >
45174 < Button onClick = { ( ) => onEdit ( null ) } className = "shadow-none" >
@@ -48,27 +177,10 @@ export function ObjectView({ dataSource, objects, onEdit }: any) {
48177 </ div >
49178 </ div >
50179
51- { /* Grid Section */ }
180+ { /* Content Area */ }
52181 < div className = "flex-1 rounded-xl border bg-card text-card-foreground shadow-sm overflow-hidden p-0 relative" >
53182 < div className = "absolute inset-0" >
54- < ObjectGrid
55- key = { `${ objectName } -${ refreshKey } ` }
56- schema = { {
57- type : 'object-grid' ,
58- objectName : objectDef . name ,
59- filterable : true ,
60- columns : columns ,
61- } }
62- dataSource = { dataSource }
63- onEdit = { onEdit }
64- onDelete = { async ( record : any ) => {
65- if ( confirm ( `Delete record?` ) ) {
66- await dataSource . delete ( objectName , record . id ) ;
67- setRefreshKey ( k => k + 1 ) ;
68- }
69- } }
70- className = "h-full border-none"
71- />
183+ { renderCurrentView ( ) }
72184 </ div >
73185 </ div >
74186 </ div >
0 commit comments