updated react-openapi

This commit is contained in:
2026-06-17 21:03:08 +05:30
parent cd89eb4c88
commit 0a668cf98d
64 changed files with 2412 additions and 2921 deletions

View File

@@ -0,0 +1,222 @@
import React, { useEffect, useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import {
Box,
Typography,
Button,
IconButton,
Tooltip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TablePagination,
Paper,
TextField,
InputAdornment,
TableSortLabel,
} from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import EditIcon from "@mui/icons-material/Edit";
import DeleteIcon from "@mui/icons-material/Delete";
import VisibilityIcon from "@mui/icons-material/Visibility";
import SearchIcon from "@mui/icons-material/Search";
import type { ResourceConfig, FieldConfig } from "../types";
import { useResource } from "../context/useResource";
import { useAppContext } from "../context/AppContext";
import { ListCellRenderer, applyDisplayFormat } from "./fields";
interface ResourceListProps {
resource: ResourceConfig;
basePath: string;
}
export function ResourceList({ resource, basePath }: ResourceListProps) {
const navigate = useNavigate();
const crud = useResource(resource);
const { resources: allResources } = useAppContext();
const [data, setData] = useState<any[]>([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(resource.pagination?.defaultLimit ?? 20);
const [search, setSearch] = useState("");
const [sortField, setSortField] = useState<string | null>(null);
const [sortDir, setSortDir] = useState<"asc" | "desc">("asc");
const visibleColumns = resource.listColumns
.map((colName) => resource.fields.find((f) => f.name === colName))
.filter((f): f is FieldConfig => !!f && !f.hidden?.list);
const fetchData = useCallback(async () => {
const params: Record<string, any> = {};
if (resource.pagination) {
params[resource.pagination.limitParam] = rowsPerPage;
params[resource.pagination.offsetParam] = page * rowsPerPage;
}
if (sortField) {
params.sort = sortDir === "desc" ? `-${sortField}` : sortField;
}
const result = await crud.list(params);
setData(result.items ?? []);
setTotal(result.total ?? result.items?.length ?? 0);
}, [crud.list, resource.pagination, rowsPerPage, page, sortField, sortDir]);
useEffect(() => {
fetchData();
}, [fetchData]);
const handleDelete = async (id: string | number) => {
if (!window.confirm("Are you sure you want to delete this item?")) return;
await crud.remove(id);
fetchData();
};
const handleSort = (field: string) => {
if (sortField === field) {
setSortDir((d) => (d === "asc" ? "desc" : "asc"));
} else {
setSortField(field);
setSortDir("asc");
}
};
return (
<Box>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 3 }}>
<Typography variant="h5" fontWeight={700}>
{resource.schemaName}
</Typography>
{resource.operations.create && (
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => navigate(`${basePath}/${resource.name}/new`)}
>
Create
</Button>
)}
</Box>
<Box sx={{ mb: 2, display: "flex", gap: 2, alignItems: "center" }}>
<TextField
size="small"
placeholder="Search..."
value={search}
onChange={(e) => setSearch(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon fontSize="small" />
</InputAdornment>
),
}}
sx={{ minWidth: 280 }}
/>
</Box>
<TableContainer component={Paper} variant="outlined">
<Table size="small">
<TableHead>
<TableRow>
{visibleColumns.map((col) => (
<TableCell key={col.name} sx={{ fontWeight: 700 }}>
{col.sortable ? (
<TableSortLabel
active={sortField === col.name}
direction={sortField === col.name ? sortDir : "asc"}
onClick={() => handleSort(col.name)}
>
{col.label}
</TableSortLabel>
) : (
col.label
)}
</TableCell>
))}
<TableCell align="right" sx={{ fontWeight: 700 }}>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.length === 0 ? (
<TableRow>
<TableCell colSpan={visibleColumns.length + 1} align="center">
<Typography variant="body2" color="text.secondary" sx={{ py: 4 }}>
No records found
</Typography>
</TableCell>
</TableRow>
) : (
data.map((row) => {
const rowId = row[resource.primaryKey];
return (
<TableRow
key={rowId}
hover
sx={{ cursor: "pointer" }}
onClick={() => navigate(`${basePath}/${resource.name}/${rowId}`)}
>
{visibleColumns.map((col) => {
let value = row[col.name];
let fmt = resource.displayFormat;
if (col.fk) {
const targetRes = allResources.find((r) => r.name === col.fk!.resource);
fmt = targetRes!.displayFormat;
} else if (col.refSchema && !col.fk && col.inlineDisplayFormat) {
fmt = col.inlineDisplayFormat;
}
return (
<TableCell key={col.name}>
<ListCellRenderer field={col} value={value} displayFormat={fmt} />
</TableCell>
);
})}
<TableCell align="right" onClick={(e) => e.stopPropagation()}>
{resource.operations.get && (
<Tooltip title="View">
<IconButton size="small" onClick={() => navigate(`${basePath}/${resource.name}/${rowId}`)}>
<VisibilityIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
{resource.operations.update && (
<Tooltip title="Edit">
<IconButton size="small" onClick={() => navigate(`${basePath}/${resource.name}/${rowId}/edit`)}>
<EditIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
{resource.operations.delete && (
<Tooltip title="Delete">
<IconButton size="small" onClick={() => handleDelete(rowId)} color="error">
<DeleteIcon fontSize="small" />
</IconButton>
</Tooltip>
)}
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
</TableContainer>
{resource.pagination && (
<TablePagination
component="div"
count={total}
page={page}
onPageChange={(_, p) => setPage(p)}
rowsPerPage={rowsPerPage}
onRowsPerPageChange={(e) => {
setRowsPerPage(parseInt(e.target.value, 10));
setPage(0);
}}
rowsPerPageOptions={[10, 20, 50, 100]}
/>
)}
</Box>
);
}