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 React, { useState, useEffect } from "react";
|
||||||
import {
|
import { Paper, Grid, Typography, CircularProgress, Box, TextField, Accordion, AccordionSummary, AccordionDetails} from "@mui/material";
|
||||||
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 ApiService from '../../services/ApiService';
|
import ApiService from '../../services/ApiService';
|
||||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
import { QuizTypeShort } from "../../Types/QuizType";
|
import { QuizTypeShort } from "../../Types/QuizType";
|
||||||
|
import AdminTable from "../../components/AdminTable/AdminTable";
|
||||||
|
|
||||||
|
|
||||||
const Users: React.FC = () => {
|
const Users: React.FC = () => {
|
||||||
const [quizzes, setQuizzes] = useState<QuizTypeShort[]>([]);
|
const [quizzes, setQuizzes] = useState<QuizTypeShort[]>([]);
|
||||||
|
|
@ -52,8 +50,8 @@ const Users: React.FC = () => {
|
||||||
setFilteredQuizzes(filtered);
|
setFilteredQuizzes(filtered);
|
||||||
}, [emailFilter, dateFilter, quizzes]);
|
}, [emailFilter, dateFilter, quizzes]);
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleQuizDelete = (rowToDelete: QuizTypeShort) => {
|
||||||
setQuizzes(quizzes.filter(quiz => quiz._id !== id));
|
setQuizzes((prevData) => prevData.filter((row) => row._id !== rowToDelete._id));
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalQuizzes = quizzes.length;
|
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 (
|
return (
|
||||||
<Paper className="p-4" sx={{ boxShadow: 'none' }}>
|
<Paper className="p-4" sx={{ boxShadow: 'none' }}>
|
||||||
<Grid container spacing={8} justifyContent="center">
|
<Grid container spacing={8} justifyContent="center">
|
||||||
|
|
@ -98,72 +104,12 @@ const Users: React.FC = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<AdminTable
|
||||||
<Accordion expanded={expanded} onChange={() => setExpanded(!expanded)}>
|
data={quizzes}
|
||||||
<AccordionSummary
|
onDelete={handleQuizDelete}
|
||||||
expandIcon={<ExpandMoreIcon />}
|
filterKeys={["_id"]}
|
||||||
aria-controls="filter-content"
|
labelMap={labelMap}
|
||||||
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>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue