mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
FIX - amélioration de la table admin
This commit is contained in:
parent
abfebeb992
commit
af2840e262
3 changed files with 190 additions and 73 deletions
2
client/src/Types/LabelMap.tsx
Normal file
2
client/src/Types/LabelMap.tsx
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
|
||||
export type LabelMap = { [key: string]: string };
|
||||
169
client/src/components/AdminTable/AdminTable.tsx
Normal file
169
client/src/components/AdminTable/AdminTable.tsx
Normal 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;
|
||||
|
|
@ -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)}
|
||||
/>
|
||||
</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>
|
||||
<AdminTable
|
||||
data={quizzes}
|
||||
onDelete={handleQuizDelete}
|
||||
filterKeys={["_id"]}
|
||||
labelMap={labelMap}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue