Checkpoint: Ajout de filtres complets sur la page de gestion des inscrits : recherche par nom/prénom/email, filtre par statut d'inscription (confirmée/liste d'attente/annulée), filtre par code établissement, tri par date d'inscription ou nom alphabétique, avec compteur de résultats filtrés.
This commit is contained in:
@@ -4,8 +4,11 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
|
|||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
import { ArrowLeft, Download, Mail } from "lucide-react";
|
import { ArrowLeft, Download, Mail, Search, SlidersHorizontal } from "lucide-react";
|
||||||
import { useLocation, useRoute } from "wouter";
|
import { useLocation, useRoute } from "wouter";
|
||||||
|
import { useState, useMemo } from "react";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -13,6 +16,12 @@ export default function AdminSessionInscrits() {
|
|||||||
const [, setLocation] = useLocation();
|
const [, setLocation] = useLocation();
|
||||||
const [, params] = useRoute("/admin/sessions/:id/inscrits");
|
const [, params] = useRoute("/admin/sessions/:id/inscrits");
|
||||||
const sessionId = params?.id ? parseInt(params.id) : 0;
|
const sessionId = params?.id ? parseInt(params.id) : 0;
|
||||||
|
|
||||||
|
// États pour les filtres
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [filterStatut, setFilterStatut] = useState<string>("all");
|
||||||
|
const [filterEtablissement, setFilterEtablissement] = useState<string>("all");
|
||||||
|
const [sortBy, setSortBy] = useState<"date" | "nom">("date");
|
||||||
|
|
||||||
const utils = trpc.useUtils();
|
const utils = trpc.useUtils();
|
||||||
const { data: session, isLoading: loadingSession } = trpc.sessions.getById.useQuery({ id: sessionId });
|
const { data: session, isLoading: loadingSession } = trpc.sessions.getById.useQuery({ id: sessionId });
|
||||||
@@ -115,6 +124,49 @@ export default function AdminSessionInscrits() {
|
|||||||
return formations?.find(f => f.id === formationId)?.nom || "Formation inconnue";
|
return formations?.find(f => f.id === formationId)?.nom || "Formation inconnue";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Extraire les codes établissement uniques
|
||||||
|
const uniqueEtablissements = useMemo(() => {
|
||||||
|
if (!inscriptions) return [];
|
||||||
|
const codes = inscriptions
|
||||||
|
.filter(i => i.apprenant)
|
||||||
|
.map(i => i.apprenant!.codeEtablissement);
|
||||||
|
return Array.from(new Set(codes)).sort();
|
||||||
|
}, [inscriptions]);
|
||||||
|
|
||||||
|
// Filtrer et trier les inscriptions
|
||||||
|
const filteredAndSortedInscriptions = useMemo(() => {
|
||||||
|
if (!inscriptions) return [];
|
||||||
|
|
||||||
|
let filtered = inscriptions.filter(inscription => {
|
||||||
|
// Filtre par recherche (nom, prénom, email)
|
||||||
|
const matchesSearch = searchTerm === "" ||
|
||||||
|
inscription.apprenant?.nom.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
inscription.apprenant?.prenom.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
inscription.apprenant?.email.toLowerCase().includes(searchTerm.toLowerCase());
|
||||||
|
|
||||||
|
// Filtre par statut
|
||||||
|
const matchesStatut = filterStatut === "all" || inscription.inscription.statut === filterStatut;
|
||||||
|
|
||||||
|
// Filtre par établissement
|
||||||
|
const matchesEtablissement = filterEtablissement === "all" ||
|
||||||
|
inscription.apprenant?.codeEtablissement === filterEtablissement;
|
||||||
|
|
||||||
|
return matchesSearch && matchesStatut && matchesEtablissement;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tri
|
||||||
|
filtered.sort((a, b) => {
|
||||||
|
if (sortBy === "date") {
|
||||||
|
return new Date(b.inscription.dateInscription).getTime() - new Date(a.inscription.dateInscription).getTime();
|
||||||
|
} else if (sortBy === "nom") {
|
||||||
|
return (a.apprenant?.nom || "").localeCompare(b.apprenant?.nom || "");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return filtered;
|
||||||
|
}, [inscriptions, searchTerm, filterStatut, filterEtablissement, sortBy]);
|
||||||
|
|
||||||
const getStatutBadge = (statut: string) => {
|
const getStatutBadge = (statut: string) => {
|
||||||
const styles = {
|
const styles = {
|
||||||
confirmee: "bg-green-100 text-green-800",
|
confirmee: "bg-green-100 text-green-800",
|
||||||
@@ -149,10 +201,88 @@ export default function AdminSessionInscrits() {
|
|||||||
<p className="text-muted-foreground mt-2">
|
<p className="text-muted-foreground mt-2">
|
||||||
{getFormationName(session.formationId)} - {session.nom}
|
{getFormationName(session.formationId)} - {session.nom}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)} </div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Filtres et recherche */}
|
||||||
|
<Card className="mb-6">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<SlidersHorizontal className="w-5 h-5" />
|
||||||
|
Filtres et recherche
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
{/* Recherche */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="search">Rechercher</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
id="search"
|
||||||
|
placeholder="Nom, prénom ou email..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtre par statut */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="filterStatut">Statut</Label>
|
||||||
|
<Select value={filterStatut} onValueChange={setFilterStatut}>
|
||||||
|
<SelectTrigger id="filterStatut">
|
||||||
|
<SelectValue placeholder="Tous les statuts" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">Tous les statuts</SelectItem>
|
||||||
|
<SelectItem value="confirmee">Confirmée</SelectItem>
|
||||||
|
<SelectItem value="liste_attente">Liste d'attente</SelectItem>
|
||||||
|
<SelectItem value="annulee">Annulée</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtre par établissement */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="filterEtablissement">Établissement</Label>
|
||||||
|
<Select value={filterEtablissement} onValueChange={setFilterEtablissement}>
|
||||||
|
<SelectTrigger id="filterEtablissement">
|
||||||
|
<SelectValue placeholder="Tous les établissements" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">Tous les établissements</SelectItem>
|
||||||
|
{uniqueEtablissements.map(code => (
|
||||||
|
<SelectItem key={code} value={code}>{code}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tri */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="sortBy">Trier par</Label>
|
||||||
|
<Select value={sortBy} onValueChange={(value: any) => setSortBy(value)}>
|
||||||
|
<SelectTrigger id="sortBy">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="date">Date d'inscription</SelectItem>
|
||||||
|
<SelectItem value="nom">Nom</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Compteur de résultats */}
|
||||||
|
<div className="mt-4 text-sm text-muted-foreground">
|
||||||
|
{filteredAndSortedInscriptions.length} inscrit(s) trouvé(s) sur {inscriptions?.length || 0} total
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{loadingSession || loadingInscriptions ? (
|
{loadingSession || loadingInscriptions ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Skeleton className="h-32 w-full" />
|
<Skeleton className="h-32 w-full" />
|
||||||
@@ -239,7 +369,7 @@ export default function AdminSessionInscrits() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{inscriptions && inscriptions.length > 0 ? (
|
{filteredAndSortedInscriptions && filteredAndSortedInscriptions.length > 0 ? (
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -253,7 +383,7 @@ export default function AdminSessionInscrits() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{inscriptions.map((item) => (
|
{filteredAndSortedInscriptions.map((item) => (
|
||||||
<TableRow key={item.inscription.id}>
|
<TableRow key={item.inscription.id}>
|
||||||
<TableCell className="font-medium">{item.apprenant?.nom}</TableCell>
|
<TableCell className="font-medium">{item.apprenant?.nom}</TableCell>
|
||||||
<TableCell>{item.apprenant?.prenom}</TableCell>
|
<TableCell>{item.apprenant?.prenom}</TableCell>
|
||||||
|
|||||||
2
todo.md
2
todo.md
@@ -67,3 +67,5 @@
|
|||||||
- [x] Ajouter des filtres de tri sur la page des sessions (date, lieu, places restantes)
|
- [x] Ajouter des filtres de tri sur la page des sessions (date, lieu, places restantes)
|
||||||
- [x] Ajouter des filtres de recherche sur la page des formations
|
- [x] Ajouter des filtres de recherche sur la page des formations
|
||||||
- [x] Ajouter des filtres de recherche sur la page des apprenants
|
- [x] Ajouter des filtres de recherche sur la page des apprenants
|
||||||
|
|
||||||
|
- [x] Ajouter des filtres complets sur la page de gestion des inscrits (recherche, statut, établissement, tri par date)
|
||||||
|
|||||||
Reference in New Issue
Block a user