diff --git a/client/src/pages/AdminApprenants.tsx b/client/src/pages/AdminApprenants.tsx index 66f2b6d..8a79c37 100644 --- a/client/src/pages/AdminApprenants.tsx +++ b/client/src/pages/AdminApprenants.tsx @@ -6,14 +6,15 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { trpc } from "@/lib/trpc"; -import { Plus, Pencil, Trash2 } from "lucide-react"; -import { useState } from "react"; +import { Plus, Pencil, Trash2, Search, SlidersHorizontal } from "lucide-react"; +import { useState, useMemo } from "react"; import { toast } from "sonner"; import { Skeleton } from "@/components/ui/skeleton"; export default function AdminApprenants() { const [open, setOpen] = useState(false); const [editingId, setEditingId] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); const [formData, setFormData] = useState({ nom: "", prenom: "", @@ -89,6 +90,21 @@ export default function AdminApprenants() { } }; + // Filtrer les apprenants + const filteredApprenants = useMemo(() => { + if (!apprenants) return []; + + return apprenants.filter(apprenant => { + const matchesSearch = searchTerm === "" || + apprenant.nom.toLowerCase().includes(searchTerm.toLowerCase()) || + apprenant.prenom.toLowerCase().includes(searchTerm.toLowerCase()) || + apprenant.email.toLowerCase().includes(searchTerm.toLowerCase()) || + apprenant.codeEtablissement.toLowerCase().includes(searchTerm.toLowerCase()); + + return matchesSearch; + }); + }, [apprenants, searchTerm]); + return (
@@ -177,6 +193,36 @@ export default function AdminApprenants() {
+ {/* Filtres et recherche */} + + + + + Recherche + + + +
+ +
+ + setSearchTerm(e.target.value)} + className="pl-8" + /> +
+
+ + {/* Compteur de résultats */} +
+ {filteredApprenants.length} apprenant(s) trouvé(s) +
+
+
+ Liste des apprenants @@ -191,7 +237,7 @@ export default function AdminApprenants() { - ) : apprenants && apprenants.length > 0 ? ( + ) : filteredApprenants && filteredApprenants.length > 0 ? ( @@ -203,7 +249,7 @@ export default function AdminApprenants() { - {apprenants.map((apprenant) => ( + {filteredApprenants.map((apprenant) => ( {apprenant.nom} {apprenant.prenom} diff --git a/client/src/pages/AdminFormations.tsx b/client/src/pages/AdminFormations.tsx index 19b3aee..14a5c27 100644 --- a/client/src/pages/AdminFormations.tsx +++ b/client/src/pages/AdminFormations.tsx @@ -6,16 +6,19 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { trpc } from "@/lib/trpc"; -import { Plus, Pencil, Trash2, ExternalLink } from "lucide-react"; -import { useState } from "react"; +import { Plus, Pencil, Trash2, ExternalLink, Search, SlidersHorizontal } from "lucide-react"; +import { useState, useMemo } from "react"; import { toast } from "sonner"; import { Skeleton } from "@/components/ui/skeleton"; export default function AdminFormations() { const [open, setOpen] = useState(false); const [editingId, setEditingId] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); + const [filterActif, setFilterActif] = useState("all"); const [formData, setFormData] = useState({ nom: "", description: "", @@ -94,6 +97,25 @@ export default function AdminFormations() { return `${window.location.origin}/inscription/${lien}`; }; + // Filtrer les formations + const filteredFormations = useMemo(() => { + if (!formations) return []; + + return formations.filter(formation => { + // Filtre par recherche + const matchesSearch = searchTerm === "" || + formation.nom.toLowerCase().includes(searchTerm.toLowerCase()) || + (formation.description && formation.description.toLowerCase().includes(searchTerm.toLowerCase())); + + // Filtre par statut actif + const matchesActif = filterActif === "all" || + (filterActif === "actif" && formation.actif) || + (filterActif === "inactif" && !formation.actif); + + return matchesSearch && matchesActif; + }); + }, [formations, searchTerm, filterActif]); + return (
@@ -180,6 +202,54 @@ export default function AdminFormations() {
+ {/* Filtres et recherche */} + + + + + Filtres et recherche + + + +
+ {/* Recherche */} +
+ +
+ + setSearchTerm(e.target.value)} + className="pl-8" + /> +
+
+ + {/* Filtre par statut */} +
+ + +
+
+ + {/* Compteur de résultats */} +
+ {filteredFormations.length} formation(s) trouvée(s) +
+
+
+ Liste des formations @@ -194,7 +264,7 @@ export default function AdminFormations() { - ) : formations && formations.length > 0 ? ( + ) : filteredFormations && filteredFormations.length > 0 ? (
@@ -206,7 +276,7 @@ export default function AdminFormations() { - {formations.map((formation) => ( + {filteredFormations.map((formation) => ( {formation.nom} {formation.description || "-"} diff --git a/client/src/pages/AdminSessions.tsx b/client/src/pages/AdminSessions.tsx index e681976..4cab606 100644 --- a/client/src/pages/AdminSessions.tsx +++ b/client/src/pages/AdminSessions.tsx @@ -7,8 +7,8 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { trpc } from "@/lib/trpc"; -import { Plus, Pencil, Trash2, Users as UsersIcon } from "lucide-react"; -import { useState } from "react"; +import { Plus, Pencil, Trash2, Users as UsersIcon, Search, SlidersHorizontal } from "lucide-react"; +import { useState, useMemo } from "react"; import { toast } from "sonner"; import { Skeleton } from "@/components/ui/skeleton"; import { format } from "date-fns"; @@ -19,6 +19,10 @@ export default function AdminSessions() { const [, setLocation] = useLocation(); const [open, setOpen] = useState(false); const [editingId, setEditingId] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); + const [filterLieu, setFilterLieu] = useState("all"); + const [filterStatut, setFilterStatut] = useState("all"); + const [sortBy, setSortBy] = useState<"date" | "lieu" | "places">("date"); const [formData, setFormData] = useState({ formationId: "", nom: "", @@ -127,6 +131,47 @@ export default function AdminSessions() { return formations?.find(f => f.id === formationId)?.nom || "Formation inconnue"; }; + // Extraire les lieux uniques pour le filtre + const uniqueLieux = useMemo(() => { + if (!sessions) return []; + const lieux = sessions.map(s => s.lieu); + return Array.from(new Set(lieux)).sort(); + }, [sessions]); + + // Filtrer et trier les sessions + const filteredAndSortedSessions = useMemo(() => { + if (!sessions) return []; + + let filtered = sessions.filter(session => { + // Filtre par recherche (nom de session ou formation) + const matchesSearch = searchTerm === "" || + session.nom.toLowerCase().includes(searchTerm.toLowerCase()) || + getFormationName(session.formationId).toLowerCase().includes(searchTerm.toLowerCase()); + + // Filtre par lieu + const matchesLieu = filterLieu === "all" || session.lieu === filterLieu; + + // Filtre par statut + const matchesStatut = filterStatut === "all" || session.statut === filterStatut; + + return matchesSearch && matchesLieu && matchesStatut; + }); + + // Tri + filtered.sort((a, b) => { + if (sortBy === "date") { + return new Date(a.dateDebut).getTime() - new Date(b.dateDebut).getTime(); + } else if (sortBy === "lieu") { + return a.lieu.localeCompare(b.lieu); + } else if (sortBy === "places") { + return a.capaciteMax - b.capaciteMax; + } + return 0; + }); + + return filtered; + }, [sessions, searchTerm, filterLieu, filterStatut, sortBy, formations]); + const formatDate = (date: Date | string) => { return format(new Date(date), "dd/MM/yyyy HH:mm", { locale: fr }); }; @@ -152,13 +197,14 @@ export default function AdminSessions() { return (
-
-
-

Sessions

-

- Gérez les sessions de formation et leurs inscriptions -

-
+
+
+
+

Sessions

+

+ Gérez les sessions de formation +

+
{ setOpen(isOpen); if (!isOpen) resetForm(); @@ -292,6 +338,87 @@ export default function AdminSessions() { +
+ + {/* Filtres et recherche */} + + + + + Filtres et recherche + + + +
+ {/* Recherche */} +
+ +
+ + setSearchTerm(e.target.value)} + className="pl-8" + /> +
+
+ + {/* Filtre par lieu */} +
+ + +
+ + {/* Filtre par statut */} +
+ + +
+ + {/* Tri */} +
+ + +
+
+ + {/* Compteur de résultats */} +
+ {filteredAndSortedSessions.length} session(s) trouvée(s) +
+
+
@@ -308,7 +435,7 @@ export default function AdminSessions() {
- ) : sessions && sessions.length > 0 ? ( + ) : filteredAndSortedSessions && filteredAndSortedSessions.length > 0 ? (
@@ -323,8 +450,8 @@ export default function AdminSessions() { Actions - - {sessions.map((session) => ( + + {filteredAndSortedSessions.map((session) => ( {getFormationName(session.formationId)} {session.nom} diff --git a/todo.md b/todo.md index e276415..f76bb73 100644 --- a/todo.md +++ b/todo.md @@ -61,3 +61,9 @@ - [x] Corriger la mise à jour de la liste des apprenants après inscription - [x] Implémenter l'envoi effectif des emails de confirmation + +## Nouvelles fonctionnalités + +- [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 apprenants