FIX - amélioration de la table admin

This commit is contained in:
Eddi3_As 2025-03-18 18:33:40 -04:00
parent abfebeb992
commit af2840e262
3 changed files with 190 additions and 73 deletions

View file

@ -0,0 +1,2 @@
export type LabelMap = { [key: string]: string };

View file

@ -0,0 +1,169 @@
import React, { useState } from "react";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TablePagination,
Paper,
Input,
IconButton,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Button,
InputAdornment,
Box,
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import SearchIcon from "@mui/icons-material/Search";
import { QuizTypeShort } from "../../Types/QuizType";
import { LabelMap } from "../../Types/LabelMap";
interface AdminTableProps {
data: QuizTypeShort[];
onDelete: (row: QuizTypeShort) => void;
filterKeys?: string[];
labelMap?: LabelMap;
}
const AdminTable: React.FC<AdminTableProps> = ({
data,
onDelete,
filterKeys = [],
labelMap = {},
}) => {
const [page, setPage] = useState<number>(0);
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
const [searchQuery, setSearchQuery] = useState<string>("");
const [openDialog, setOpenDialog] = useState<boolean>(false);
const [deleteRow, setDeleteRow] = useState<QuizTypeShort | null>(null);
const handleChangePage = (_event: unknown, newPage: number) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value);
setPage(0);
};
const handleOpenDialog = (row: QuizTypeShort) => {
setDeleteRow(row);
setOpenDialog(true);
};
const handleCloseDialog = () => {
setOpenDialog(false);
setDeleteRow(null);
};
const handleConfirmDelete = () => {
if (deleteRow) {
onDelete(deleteRow);
}
handleCloseDialog();
};
const filteredData = data.filter((row) => {
return Object.values(row).some((value) =>
value.toString().toLowerCase().includes(searchQuery.toLowerCase())
);
});
const headers = Object.keys(labelMap).filter((key) => !filterKeys.includes(key));
return (
<Paper sx={{ width: "100%", overflow: "hidden", padding: "16px" }}>
<Box display="flex" justifyContent="flex-start" marginBottom={2}>
<Input
placeholder="Search"
value={searchQuery}
onChange={handleSearchChange}
startAdornment={
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
}
sx={{ width: "30%" }}
/>
</Box>
<TableContainer>
<Table>
<TableHead>
<TableRow>
{headers.map((key) => (
<TableCell key={key} sx={{ fontWeight: "bold", fontSize: "1.1rem" }}>
{labelMap[key] || key} {/* Use custom label from map or fallback to key */}
</TableCell>
))}
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{filteredData
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row, index) => (
<TableRow key={row._id} sx={{ backgroundColor: index % 2 === 0 ? "#f9f9f9" : "inherit" }}>
{headers.map((key) => {
const value = row[key as keyof QuizTypeShort];
let displayValue;
if (value instanceof Date) {
displayValue = value.toLocaleDateString("en-GB");
} else if (value && typeof value === "string" && !isNaN(Date.parse(value))) {
displayValue = new Date(value).toLocaleDateString("en-GB");
} else {
displayValue = value;
}
return <TableCell key={key}>{displayValue}</TableCell>;
})}
<TableCell>
<IconButton color="error" onClick={() => handleOpenDialog(row)}>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={filteredData.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
<Dialog open={openDialog} onClose={handleCloseDialog}>
<DialogTitle>Confirm Deletion</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure you want to delete this record?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseDialog}>Cancel</Button>
<Button onClick={handleConfirmDelete} color="error">
Delete
</Button>
</DialogActions>
</Dialog>
</Paper>
);
};
export default AdminTable;

View file

@ -1,12 +1,10 @@
import React, { useState, useEffect } from "react";
import {
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper,
IconButton, Grid, Typography, CircularProgress, Box, TextField, Accordion, AccordionSummary, AccordionDetails
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import { Paper, Grid, Typography, CircularProgress, Box, TextField, Accordion, AccordionSummary, AccordionDetails} from "@mui/material";
import ApiService from '../../services/ApiService';
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { QuizTypeShort } from "../../Types/QuizType";
import AdminTable from "../../components/AdminTable/AdminTable";
const Users: React.FC = () => {
const [quizzes, setQuizzes] = useState<QuizTypeShort[]>([]);
@ -52,8 +50,8 @@ const Users: React.FC = () => {
setFilteredQuizzes(filtered);
}, [emailFilter, dateFilter, quizzes]);
const handleDelete = (id: string) => {
setQuizzes(quizzes.filter(quiz => quiz._id !== id));
const handleQuizDelete = (rowToDelete: QuizTypeShort) => {
setQuizzes((prevData) => prevData.filter((row) => row._id !== rowToDelete._id));
};
const totalQuizzes = quizzes.length;
@ -66,6 +64,14 @@ const Users: React.FC = () => {
);
}
const labelMap = {
_id: "ID",
email: "Enseignant",
title: "Titre",
created_at: "Création",
updated_at: "Mise à Jour",
};
return (
<Paper className="p-4" sx={{ boxShadow: 'none' }}>
<Grid container spacing={8} justifyContent="center">
@ -98,72 +104,12 @@ const Users: React.FC = () => {
</Grid>
</Grid>
<Accordion expanded={expanded} onChange={() => setExpanded(!expanded)}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="filter-content"
id="filter-header"
>
<Typography variant="h6">Filtres</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={3}>
<Grid item xs={12} sm={3}>
<TextField
fullWidth
variant="outlined"
size="small"
placeholder="Filtrer par email"
value={emailFilter}
onChange={(e) => setEmailFilter(e.target.value)}
<AdminTable
data={quizzes}
onDelete={handleQuizDelete}
filterKeys={["_id"]}
labelMap={labelMap}
/>
</Grid>
<Grid item xs={12} sm={3}>
<TextField
fullWidth
variant="outlined"
size="small"
placeholder="Filtrer par date"
value={dateFilter}
onChange={(e) => setDateFilter(e.target.value)}
/>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
{/* Table */}
<TableContainer component={Paper} className="mt-4">
<Table>
<TableHead>
<TableRow>
<TableCell>
Enseignant
</TableCell>
<TableCell>Titre</TableCell>
<TableCell>Crée</TableCell>
<TableCell>Modifié</TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredQuizzes.map((quiz) => (
<TableRow key={quiz._id}>
<TableCell>{quiz.email}</TableCell>
<TableCell>{quiz.title}</TableCell>
<TableCell>{new Date(quiz.created_at).toLocaleDateString()}</TableCell>
<TableCell>{new Date(quiz.updated_at).toLocaleDateString()}</TableCell>
<TableCell>
<IconButton onClick={() => handleDelete(quiz._id)} color="error">
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
);
};