configuration for how fields look and EnhancedTable component for enhanced table display

This commit is contained in:
2026-04-01 18:47:23 +05:30
parent 344106f1a4
commit 44567496a1
8 changed files with 298 additions and 512 deletions

View File

@@ -0,0 +1,158 @@
import * as React from 'react';
import {
Box,
Typography,
Button,
IconButton,
Link,
Tooltip,
} from '@mui/material';
import {
DataGrid,
GridColDef,
GridActionsCellItem,
GridRenderCellParams,
} from '@mui/x-data-grid';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import { ResourceConfig, ResourceField } from '../types/config';
interface EnhancedTableProps {
config: ResourceConfig;
data: any[];
onEdit: (item: any) => void;
onDelete: (id: string) => void;
onCreate: () => void;
onNavigateToResource?: (resourceName: string, id: string) => void;
}
export default function EnhancedTable({
config,
data,
onEdit,
onDelete,
onCreate,
onNavigateToResource,
}: EnhancedTableProps) {
const columns: GridColDef[] = React.useMemo(() => {
const cols: GridColDef[] = Object.entries(config.fields).map(([key, field]) => {
const col: GridColDef = {
field: key,
headerName: field.label,
flex: 1,
minWidth: 150,
renderCell: (params: GridRenderCellParams) => {
const value = params.value;
// 1. Custom Formatter
if (field.formatter) {
return field.formatter(value);
}
// 2. Relational Link
if (field.relation && value) {
const relationId = typeof value === 'object' ? value.id : value;
if (relationId) {
return (
<Link
component="button"
variant="body2"
onClick={(e) => {
e.stopPropagation();
onNavigateToResource?.(field.relation!, relationId);
}}
>
{relationId}
</Link>
);
}
}
// 3. Nested Object / Array Display
if (field.type === 'array' && Array.isArray(value)) {
if (field.displayField) {
return value
.map((item) => (typeof item === 'object' ? item[field.displayField!] : item))
.filter(Boolean)
.join(', ');
}
return `${value.length} items`;
}
if (field.type === 'object' && value) {
if (field.displayField && value[field.displayField]) {
return value[field.displayField];
}
return JSON.stringify(value);
}
// 4. Default renderings
if (field.type === 'boolean') return value ? 'Yes' : 'No';
if (field.type === 'datetime' || field.type === 'date') {
return new Date(value).toLocaleString();
}
return value;
}
};
return col;
});
cols.push({
field: 'actions',
type: 'actions',
headerName: 'Actions',
width: 100,
getActions: (params) => [
<GridActionsCellItem
icon={<EditIcon />}
label="Edit"
onClick={() => onEdit(params.row)}
/>,
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={() => onDelete(params.id as string)}
/>,
],
});
return cols;
}, [config, onEdit, onDelete, onNavigateToResource]);
return (
<Box sx={{ height: 600, width: '100%' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3, alignItems: 'center' }}>
<Typography variant="h5">{config.pluralLabel}</Typography>
<Button variant="contained" color="primary" onClick={onCreate}>
Add {config.label}
</Button>
</Box>
<DataGrid
rows={data || []}
columns={columns}
getRowId={(row) => {
const pk = config.primaryKey;
if (row[pk] !== undefined && row[pk] !== null) return row[pk];
// Fallback: search for common ID fields
const fallbackKeys = ['id', 'uuid', 'pk'];
for (const key of fallbackKeys) {
if (row[key] !== undefined && row[key] !== null) return row[key];
}
debugger;
// Absolute fallback: index (not ideal but avoids crash)
return `temp-id-${data.indexOf(row)}`;
}}
disableRowSelectionOnClick
initialState={{
pagination: {
paginationModel: { page: 0, pageSize: 10 },
},
}}
pageSizeOptions={[5, 10, 25]}
/>
</Box>
);
}

View File

@@ -1,95 +0,0 @@
import * as React from 'react';
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
IconButton,
Typography,
Box,
Button
} from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import { ResourceConfig } from '../types/config';
interface GenericTableProps {
config: ResourceConfig;
data: any[];
onEdit: (item: any) => void;
onDelete: (id: string) => void;
onCreate: () => void;
}
export default function GenericTable({
config,
data,
onEdit,
onDelete,
onCreate,
}: GenericTableProps) {
const fields = Object.entries(config.fields);
return (
<Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 3, alignItems: 'center' }}>
<Typography variant="h5">{config.pluralLabel}</Typography>
<Button variant="contained" color="primary" onClick={onCreate}>
Add {config.label}
</Button>
</Box>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }}>
<TableHead>
<TableRow>
{fields.map(([key, field]) => (
<TableCell key={key}>{field.label}</TableCell>
))}
<TableCell align="right">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map((item) => (
<TableRow key={item[config.primaryKey]}>
{fields.map(([key, field]) => (
<TableCell key={key}>
{renderCellValue(item[key], field)}
</TableCell>
))}
<TableCell align="right">
<IconButton onClick={() => onEdit(item)}>
<EditIcon />
</IconButton>
<IconButton onClick={() => onDelete(item[config.primaryKey])}>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Box>
);
}
function renderCellValue(value: any, field: any) {
if (value === null || value === undefined) return '-';
switch (field.type) {
case 'boolean':
return value ? 'Yes' : 'No';
case 'date':
return new Date(value).toLocaleDateString();
case 'object':
return JSON.stringify(value);
case 'array':
return `${value.length} items`;
default:
return String(value);
}
}

View File

@@ -1,15 +1,16 @@
import * as React from 'react';
import { Box, Typography, Button, Paper, CircularProgress } from '@mui/material';
import { Box, Typography, Paper, CircularProgress } from '@mui/material';
import { ResourceConfig } from '../types/config';
import { useResource } from '../hooks/useResource';
import GenericTable from './GenericTable';
import GenericForm from './GenericForm';
import EnhancedTable from './EnhancedTable';
interface ResourceViewProps {
config: ResourceConfig;
onNavigateToResource?: (resourceName: string, id: string) => void;
}
export default function ResourceView({ config }: ResourceViewProps) {
export default function ResourceView({ config, onNavigateToResource }: ResourceViewProps) {
const [view, setView] = React.useState<'list' | 'create' | 'edit'>('list');
const [selectedItem, setSelectedItem] = React.useState<any>(null);
@@ -56,12 +57,13 @@ export default function ResourceView({ config }: ResourceViewProps) {
return (
<Box>
{view === 'list' ? (
<GenericTable
<EnhancedTable
config={config}
data={data || []}
onEdit={handleEdit}
onDelete={handleDelete}
onCreate={handleCreate}
onNavigateToResource={onNavigateToResource}
/>
) : (
<Paper sx={{ p: 4 }}>