@@ -13,54 +13,130 @@ interface ObjectViewProps {
1313}
1414
1515export function ObjectView ( { objectName } : ObjectViewProps ) {
16- const [ rowData , setRowData ] = useState < any [ ] > ( [ ] ) ;
16+ // const [rowData, setRowData] = useState<any[]>([]); // Using Server Side
1717 const [ columnDefs , setColumnDefs ] = useState < ColDef [ ] > ( [ ] ) ;
1818 const [ loading , setLoading ] = useState ( false ) ;
1919 const [ activeTab , setActiveTab ] = useState < 'data' | 'schema' > ( 'data' ) ;
2020 const [ schemaFile , setSchemaFile ] = useState < string | null > ( null ) ;
2121
22- const fetchData = async ( ) => {
22+ const [ gridApi , setGridApi ] = useState < any > ( null ) ;
23+
24+ // Initial load for columns
25+ useEffect ( ( ) => {
26+ const loadColumns = async ( ) => {
27+ const metaRes = await fetch ( '/api/metadata' ) ;
28+ const meta = await metaRes . json ( ) ;
29+ const objects = Array . isArray ( meta ) ? meta : meta . objects ;
30+ const currentObj = objects . find ( ( o : any ) => o . name === objectName ) ;
31+
32+ if ( currentObj ) {
33+ const cols : ColDef [ ] = [
34+ { field : 'id' , headerName : 'ID' , width : 100 , pinned : 'left' , filter : 'agTextColumnFilter' }
35+ ] ;
36+
37+ Object . entries ( currentObj . fields ) . forEach ( ( [ key , field ] : [ string , any ] ) => {
38+ cols . push ( {
39+ field : key ,
40+ headerName : field . label || key ,
41+ flex : 1 ,
42+ filter : 'agTextColumnFilter' , // Enforce text filter for simplicity in Infinite Model
43+ filterParams : {
44+ filterOptions : [ 'contains' , 'equals' ] ,
45+ suppressAndOrCondition : true
46+ }
47+ } ) ;
48+ } ) ;
49+
50+ setColumnDefs ( cols ) ;
51+ }
52+ } ;
53+ loadColumns ( ) ;
54+ } , [ objectName ] ) ;
55+
56+ const onGridReady = ( params : any ) => {
57+ setGridApi ( params . api ) ;
2358 setLoading ( true ) ;
24- try {
25- // 1. Fetch Schema to build columns
26- // Ideally we should cache metadata, but fetching it here is safer for now
27- const metaRes = await fetch ( '/api/metadata' ) ;
28- const meta = await metaRes . json ( ) ;
29- const objects = Array . isArray ( meta ) ? meta : meta . objects ;
30- const currentObj = objects . find ( ( o : any ) => o . name === objectName ) ;
31-
32- if ( currentObj ) {
33- const cols : ColDef [ ] = [
34- { field : 'id' , headerName : 'ID' , width : 100 , pinned : 'left' }
35- ] ;
59+
60+ const datasource = {
61+ getRows : async ( params : any ) => {
62+ const { startRow, endRow, filterModel, sortModel } = params ;
63+ setLoading ( true ) ;
3664
37- Object . entries ( currentObj . fields ) . forEach ( ( [ key , field ] : [ string , any ] ) => {
38- cols . push ( {
39- field : key ,
40- headerName : field . label || key ,
41- flex : 1 ,
42- filter : true
65+ try {
66+ // 1. Convert Filters
67+ const filters : any [ ] = [ ] ;
68+ if ( filterModel ) {
69+ for ( const key of Object . keys ( filterModel ) ) {
70+ const model = filterModel [ key ] ;
71+ // agTextColumnFilter model: { filterType: 'text', type: 'contains', filter: 'value' }
72+ if ( model . filterType === 'text' ) {
73+ if ( model . type === 'contains' ) {
74+ filters . push ( [ key , 'contains' , model . filter ] ) ;
75+ } else if ( model . type === 'equals' ) {
76+ filters . push ( [ key , '=' , model . filter ] ) ;
77+ }
78+ }
79+ }
80+ }
81+
82+ // 2. Convert Sort
83+ const sort = sortModel . map ( ( s : any ) => [ s . colId , s . sort ] ) ;
84+
85+ // 3. Fetch Data
86+ const response = await fetch ( '/api/objectql' , {
87+ method : 'POST' ,
88+ headers : { 'Content-Type' : 'application/json' } ,
89+ body : JSON . stringify ( {
90+ op : 'find' ,
91+ object : objectName ,
92+ args : {
93+ skip : startRow ,
94+ limit : endRow - startRow ,
95+ filters : filters . length > 0 ? filters : undefined ,
96+ sort : sort . length > 0 ? sort : undefined
97+ }
98+ } )
4399 } ) ;
44- } ) ;
45-
46- // Add system fields if not present
47- if ( ! cols . find ( c => c . field === 'createdAt' ) ) cols . push ( { field : 'createdAt' , hide : true } ) ;
48- if ( ! cols . find ( c => c . field === 'updatedAt' ) ) cols . push ( { field : 'updatedAt' , hide : true } ) ;
100+ const resJson = await response . json ( ) ;
101+ const rows = resJson . data || resJson ; // normalize
102+
103+ // 4. Fetch Count (for total pagination)
104+ // Optimization: Only fetch count if we don't know it or filter changed?
105+ // For Infinite Scroll simplicity, we fetch it.
106+ let lastRow = - 1 ;
107+ if ( rows . length < ( endRow - startRow ) ) {
108+ lastRow = startRow + rows . length ;
109+ } else {
110+ const countRes = await fetch ( '/api/objectql' , {
111+ method : 'POST' ,
112+ headers : { 'Content-Type' : 'application/json' } ,
113+ body : JSON . stringify ( {
114+ op : 'count' ,
115+ object : objectName ,
116+ args : filters . length > 0 ? filters : { }
117+ } )
118+ } ) ;
119+ const countJson = await countRes . json ( ) ;
120+ lastRow = typeof countJson === 'number' ? countJson : countJson . data ;
121+ }
49122
50- setColumnDefs ( cols ) ;
123+ params . successCallback ( rows , lastRow ) ;
124+
125+ } catch ( e ) {
126+ console . error ( 'Datasource getRows error:' , e ) ;
127+ params . failCallback ( ) ;
128+ } finally {
129+ setLoading ( false ) ;
130+ }
51131 }
132+ } ;
52133
53- // 2. Fetch Data
54- const res = await fetch ( `/api/data/${ objectName } ` ) ;
55- const result = await res . json ( ) ;
56- // Handle both array and { data: [] } formats
57- const rows = Array . isArray ( result ) ? result : ( result . data || [ ] ) ;
58- setRowData ( rows ) ;
134+ params . api . setDatasource ( datasource ) ;
135+ } ;
59136
60- } catch ( e ) {
61- console . error ( e ) ;
62- } finally {
63- setLoading ( false ) ;
137+ const refreshData = ( ) => {
138+ if ( gridApi ) {
139+ gridApi . refreshInfiniteCache ( ) ;
64140 }
65141 } ;
66142
@@ -86,13 +162,12 @@ export function ObjectView({ objectName }: ObjectViewProps) {
86162 } , [ objectName ] ) ;
87163
88164 useEffect ( ( ) => {
89- if ( activeTab === 'data' ) {
90- fetchData ( ) ;
91- } else {
165+ if ( activeTab === 'schema' ) {
92166 fetchSchemaFile ( ) ;
93167 }
94168 } , [ objectName , activeTab ] ) ;
95169
170+
96171 const defaultColDef = useMemo ( ( ) => {
97172 return {
98173 sortable : true ,
@@ -138,7 +213,7 @@ export function ObjectView({ objectName }: ObjectViewProps) {
138213
139214 { activeTab === 'data' && (
140215 < div className = "flex items-center space-x-2" >
141- < Button variant = "outline" size = "sm" onClick = { fetchData } disabled = { loading } >
216+ < Button variant = "outline" size = "sm" onClick = { refreshData } disabled = { loading } >
142217 < RefreshCw className = { cn ( "mr-2 h-4 w-4" , loading && "animate-spin" ) } />
143218 Refresh
144219 </ Button >
@@ -156,12 +231,15 @@ export function ObjectView({ objectName }: ObjectViewProps) {
156231 < div className = "rounded-md overflow-hidden bg-card h-full" style = { { opacity : loading ? 0.6 : 1 } } >
157232 < div className = "ag-theme-quartz h-full w-full" >
158233 < AgGridReact
159- rowData = { rowData }
234+ rowModelType = "infinite"
235+ onGridReady = { onGridReady }
160236 columnDefs = { columnDefs }
161237 defaultColDef = { defaultColDef }
162238 pagination = { true }
163239 paginationPageSize = { 20 }
240+ cacheBlockSize = { 20 }
164241 rowSelection = "multiple"
242+ maxBlocksInCache = { 10 }
165243 />
166244 </ div >
167245 </ div >
0 commit comments