Skip to content

Commit b9e490b

Browse files
committed
Add AgGridView component and integrate with ObjectListView
Introduces a new AgGridView component using ag-grid-react and ag-grid-community, updates dependencies, and replaces GridView with AgGridView in ObjectListView for enhanced table features. Also updates package scripts and exports AgGridView from the UI package.
1 parent df3c521 commit b9e490b

File tree

6 files changed

+213
-48
lines changed

6 files changed

+213
-48
lines changed

package-lock.json

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
"examples/*"
77
],
88
"scripts": {
9-
"dev": "concurrently \"npm run dev:server\" \"npm run dev:client\" --kill-others --names \"SERVER,CLIENT\" -c \"magenta,blue\"",
10-
"start": "npm run start --workspace=@objectql/server",
11-
"dev:server": "npm run dev --workspace=@objectql/server",
12-
"dev:client": "npm run dev --workspace=@objectql/client",
9+
"dev": "concurrently \"npm run server\" \"npm run client\" --kill-others --names \"SERVER,CLIENT\" -c \"magenta,blue\"",
10+
"server": "npm run dev --workspace=@objectql/server",
11+
"client": "npm run dev --workspace=@objectql/client",
1312
"build": "tsc -b && npm run build --workspaces",
13+
"start": "npm run start --workspace=@objectql/server",
1414
"test": "npm run test --workspaces",
1515
"changeset": "changeset",
1616
"version": "changeset version",

packages/client/src/components/dashboard/ObjectListView.tsx

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect } from 'react';
2-
import { Button, Badge, Modal, Spinner, GridView, Input } from '@objectql/ui';
2+
import { Button, Badge, Modal, Spinner, AgGridView, Input } from '@objectql/ui';
33
import { ObjectForm } from './ObjectForm';
44
import { cn } from '../../lib/utils';
55
// import { useRouter } from ... passed as prop
@@ -22,7 +22,7 @@ export function ObjectListView({ objectName, user, isCreating, navigate, objectS
2222
const [loading, setLoading] = useState(false);
2323
const [error, setError] = useState<string | null>(null);
2424
const [viewMode, setViewMode] = useState<'table' | 'grid'>('table');
25-
const [sortConfig, setSortConfig] = useState<SortConfig[]>([]);
25+
const [sortConfig] = useState<SortConfig[]>([]);
2626
const [searchTerm, setSearchTerm] = useState('');
2727
const [showFilter, setShowFilter] = useState(false); // New state for filter visibility
2828

@@ -173,41 +173,35 @@ export function ObjectListView({ objectName, user, isCreating, navigate, objectS
173173
.catch(err => alert(err.message));
174174
}
175175

176-
const generateColumns = () => {
177-
// Prefer schema for columns, fallback to data inspection
178-
let fields: string[] = [];
179-
180-
if (objectSchema && objectSchema.fields) {
181-
fields = Object.keys(objectSchema.fields);
182-
if (!fields.includes('createdAt')) fields.push('createdAt');
183-
if (!fields.includes('updatedAt')) fields.push('updatedAt');
184-
} else if (data && data.length > 0) {
185-
fields = Object.keys(data[0]).filter(key => !['_id', '__v'].includes(key));
186-
}
176+
const getAgGridColumns = () => {
177+
// AgGrid Column Definitions
178+
// Prefer schema for columns, fallback to data inspection
179+
let fields: string[] = [];
180+
if (objectSchema && objectSchema.fields) {
181+
fields = Object.keys(objectSchema.fields);
182+
if (!fields.includes('createdAt')) fields.push('createdAt');
183+
if (!fields.includes('updatedAt')) fields.push('updatedAt');
184+
} else if (data && data.length > 0) {
185+
fields = Object.keys(data[0]).filter(key => !['_id', '__v'].includes(key));
186+
}
187187

188-
return fields.map(key => {
189-
const field = objectSchema?.fields?.[key];
190-
const type = getFieldType(key) as 'text' | 'number' | 'boolean' | 'date' | 'select' | 'badge';
191-
192-
return {
193-
id: key,
194-
label: getFieldLabel(key),
195-
type: type,
196-
width: type === 'boolean' ? 80 : type === 'date' ? 150 : type === 'number' ? 120 : 200,
197-
editable: !['id', 'createdBy', 'updatedBy', 'createdAt', 'updatedAt'].includes(key),
198-
sortable: true,
199-
...(type === 'badge' && field?.options ? {
200-
options: field.options.map((opt: string) => ({
201-
value: opt,
202-
label: opt,
203-
variant: 'default'
204-
}))
205-
} : {})
206-
};
207-
});
188+
return fields.map(key => {
189+
const type = getFieldType(key);
190+
191+
return {
192+
field: key, // AgGrid uses 'field' instead of 'id'
193+
headerName: getFieldLabel(key),
194+
editable: !['id', 'createdBy', 'updatedBy', 'createdAt', 'updatedAt'].includes(key),
195+
sortable: true,
196+
filter: true,
197+
resizable: true,
198+
width: type === 'boolean' ? 100 : type === 'date' ? 180 : type === 'number' ? 120 : 200,
199+
// Optional: Custom cell renderers can be added here if needed
200+
};
201+
});
208202
};
209203

210-
const columns = generateColumns();
204+
const columns = getAgGridColumns();
211205

212206
return (
213207
<div className="flex flex-col h-full bg-stone-50">
@@ -303,16 +297,17 @@ export function ObjectListView({ objectName, user, isCreating, navigate, objectS
303297
</Button>
304298
</div>
305299
) : (
306-
<GridView
307-
columns={columns}
308-
data={data}
309-
onRowClick={(row: any) => navigate(`/object/${objectName}/${row.id || row._id}`)}
310-
onCellEdit={handleCellEdit}
311-
onDelete={handleDelete}
312-
emptyMessage={`No ${label.toLowerCase()} found`}
313-
enableSorting={true}
314-
onSortChange={setSortConfig}
315-
/>
300+
<div className="h-full w-full">
301+
<AgGridView
302+
columns={columns}
303+
data={data}
304+
onRowClick={(row: any) => navigate(`/object/${objectName}/${row.id || row._id}`)}
305+
onCellEdit={handleCellEdit}
306+
onDelete={handleDelete}
307+
enableSorting={true}
308+
className="h-full w-full"
309+
/>
310+
</div>
316311
)}
317312
</div>
318313

packages/ui/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
"@radix-ui/react-label": "^2.1.8",
3636
"@radix-ui/react-slot": "^1.2.4",
3737
"@tanstack/react-table": "^8.21.3",
38+
"ag-grid-community": "^35.0.0",
39+
"ag-grid-react": "^35.0.0",
3840
"class-variance-authority": "^0.7.1",
3941
"clsx": "^2.1.1",
4042
"lucide-react": "^0.562.0",
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { AgGridReact } from 'ag-grid-react';
2+
import { AllCommunityModule, ModuleRegistry, ColDef, themeQuartz } from 'ag-grid-community';
3+
import { useMemo, useEffect, useState, useRef } from 'react';
4+
import { cn } from '../../lib/utils'; // Assuming you have a utils file
5+
6+
// Register all Community features
7+
ModuleRegistry.registerModules([AllCommunityModule]);
8+
9+
interface AgGridViewProps {
10+
data: any[];
11+
columns: ColDef[]; // Use AgGrid's ColDef
12+
onRowClick?: (row: any) => void;
13+
onCellEdit?: (rowIndex: number, columnId: string, value: any) => void;
14+
onDelete?: (row: any) => void; // Add onDelete prop
15+
enableSorting?: boolean;
16+
className?: string; // Allow custom class names
17+
style?: React.CSSProperties; // Allow custom styles
18+
theme?: string; // Allow theme override
19+
}
20+
21+
export const AgGridView = ({
22+
data,
23+
columns,
24+
onRowClick,
25+
onCellEdit,
26+
onDelete,
27+
enableSorting = true,
28+
className,
29+
style,
30+
theme = "ag-theme-quartz" // Default theme
31+
}: AgGridViewProps) => {
32+
33+
const gridRef = useRef<AgGridReact>(null);
34+
35+
// Default column definitions
36+
const defaultColDef = useMemo<ColDef>(() => {
37+
return {
38+
flex: 1,
39+
minWidth: 100,
40+
sortable: enableSorting,
41+
filter: true, // Enable filtering by default for AgGrid
42+
resizable: true,
43+
editable: !!onCellEdit, // Enable editing if handler provided (can be overridden per column)
44+
};
45+
}, [enableSorting, onCellEdit]);
46+
47+
// augment columns with actions if onDelete provided
48+
const gridColumns = useMemo(() => {
49+
if (!onDelete) return columns;
50+
return [
51+
...columns,
52+
{
53+
headerName: '',
54+
field: 'actions',
55+
width: 80,
56+
minWidth: 80,
57+
maxWidth: 80,
58+
sortable: false,
59+
filter: false,
60+
editable: false,
61+
cellRenderer: (params: any) => (
62+
<button
63+
onClick={(e) => {
64+
e.stopPropagation(); // Prevent row click
65+
onDelete(params.data);
66+
}}
67+
className="text-red-600 hover:text-red-800 p-1"
68+
>
69+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>
70+
</button>
71+
)
72+
}
73+
];
74+
}, [columns, onDelete]);
75+
76+
77+
// Handle cell editing
78+
const onCellValueChanged = (event: any) => {
79+
if (onCellEdit && event.data) {
80+
// AgGrid uses rowNode.rowIndex to identify rows.
81+
// Be mindful if you use sorting/filtering in AgGrid, rowIndex refers to the view index.
82+
// If you need the original data index, you might need to manage IDs.
83+
// Here we assume data order matches or we pass ID if needed.
84+
// But simpler: just pass the row data ID if available, or index.
85+
const rowIndex = event.rowIndex;
86+
const colId = event.column.getColId();
87+
const newValue = event.newValue;
88+
onCellEdit(rowIndex, colId, newValue);
89+
}
90+
};
91+
92+
// Handle row click
93+
const onRowClicked = (event: any) => {
94+
if (onRowClick) {
95+
onRowClick(event.data);
96+
}
97+
};
98+
99+
100+
return (
101+
<div
102+
className={cn( theme, className)} // Apply theme class
103+
style={{ height: '100%', width: '100%', ...style }} // Ensure container has height
104+
>
105+
<AgGridReact
106+
ref={gridRef}
107+
rowData={data}
108+
columnDefs={gridColumns}
109+
defaultColDef={defaultColDef}
110+
onCellValueChanged={onCellValueChanged}
111+
onRowClicked={onRowClicked}
112+
rowSelection="multiple" // Optional: Enable row selection
113+
pagination={true} // Optional: Enable pagination
114+
paginationPageSize={20}
115+
paginationPageSizeSelector={[20, 50, 100]}
116+
/>
117+
</div>
118+
);
119+
};

packages/ui/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export * from './components/Table';
2323
export * from './components/grid/DataTable';
2424
export * from './components/grid/DataTableFilter';
2525
export * from './components/grid/GridView';
26+
export * from './components/grid/AgGridView'; // Export updated wrapper
2627
export * from './components/AutoForm';
2728

2829
export * from './components/Modal';

0 commit comments

Comments
 (0)