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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
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 { useState, useMemo } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -13,6 +16,12 @@ export default function AdminSessionInscrits() {
|
||||
const [, setLocation] = useLocation();
|
||||
const [, params] = useRoute("/admin/sessions/:id/inscrits");
|
||||
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 { 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";
|
||||
};
|
||||
|
||||
// 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 styles = {
|
||||
confirmee: "bg-green-100 text-green-800",
|
||||
@@ -149,10 +201,88 @@ export default function AdminSessionInscrits() {
|
||||
<p className="text-muted-foreground mt-2">
|
||||
{getFormationName(session.formationId)} - {session.nom}
|
||||
</p>
|
||||
)}
|
||||
</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 ? (
|
||||
<div className="space-y-4">
|
||||
<Skeleton className="h-32 w-full" />
|
||||
@@ -239,7 +369,7 @@ export default function AdminSessionInscrits() {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{inscriptions && inscriptions.length > 0 ? (
|
||||
{filteredAndSortedInscriptions && filteredAndSortedInscriptions.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -253,7 +383,7 @@ export default function AdminSessionInscrits() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{inscriptions.map((item) => (
|
||||
{filteredAndSortedInscriptions.map((item) => (
|
||||
<TableRow key={item.inscription.id}>
|
||||
<TableCell className="font-medium">{item.apprenant?.nom}</TableCell>
|
||||
<TableCell>{item.apprenant?.prenom}</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user