Checkpoint: Ajout d'un système complet de filtres et de recherche : filtres par date, lieu, statut et capacité pour les sessions ; recherche par nom/description pour les formations ; recherche multi-critères pour les apprenants. Compteurs de résultats inclus.
This commit is contained in:
@@ -6,14 +6,15 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
import { Plus, Pencil, Trash2 } from "lucide-react";
|
import { Plus, Pencil, Trash2, Search, SlidersHorizontal } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
export default function AdminApprenants() {
|
export default function AdminApprenants() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
const [editingId, setEditingId] = useState<number | null>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
nom: "",
|
nom: "",
|
||||||
prenom: "",
|
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 (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -177,6 +193,36 @@ export default function AdminApprenants() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Filtres et recherche */}
|
||||||
|
<Card className="mb-6">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<SlidersHorizontal className="w-5 h-5" />
|
||||||
|
Recherche
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="search">Rechercher un apprenant</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, email ou code établissement..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Compteur de résultats */}
|
||||||
|
<div className="mt-4 text-sm text-muted-foreground">
|
||||||
|
{filteredApprenants.length} apprenant(s) trouvé(s)
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Liste des apprenants</CardTitle>
|
<CardTitle>Liste des apprenants</CardTitle>
|
||||||
@@ -191,7 +237,7 @@ export default function AdminApprenants() {
|
|||||||
<Skeleton className="h-10 w-full" />
|
<Skeleton className="h-10 w-full" />
|
||||||
<Skeleton className="h-10 w-full" />
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
) : apprenants && apprenants.length > 0 ? (
|
) : filteredApprenants && filteredApprenants.length > 0 ? (
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -203,7 +249,7 @@ export default function AdminApprenants() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{apprenants.map((apprenant) => (
|
{filteredApprenants.map((apprenant) => (
|
||||||
<TableRow key={apprenant.id}>
|
<TableRow key={apprenant.id}>
|
||||||
<TableCell className="font-medium">{apprenant.nom}</TableCell>
|
<TableCell className="font-medium">{apprenant.nom}</TableCell>
|
||||||
<TableCell>{apprenant.prenom}</TableCell>
|
<TableCell>{apprenant.prenom}</TableCell>
|
||||||
|
|||||||
@@ -6,16 +6,19 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import { Switch } from "@/components/ui/switch";
|
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 { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
import { Plus, Pencil, Trash2, ExternalLink } from "lucide-react";
|
import { Plus, Pencil, Trash2, ExternalLink, Search, SlidersHorizontal } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
export default function AdminFormations() {
|
export default function AdminFormations() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
const [editingId, setEditingId] = useState<number | null>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [filterActif, setFilterActif] = useState<string>("all");
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
nom: "",
|
nom: "",
|
||||||
description: "",
|
description: "",
|
||||||
@@ -94,6 +97,25 @@ export default function AdminFormations() {
|
|||||||
return `${window.location.origin}/inscription/${lien}`;
|
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 (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -180,6 +202,54 @@ export default function AdminFormations() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</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 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 ou description..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtre par statut */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="filterActif">Statut</Label>
|
||||||
|
<Select value={filterActif} onValueChange={setFilterActif}>
|
||||||
|
<SelectTrigger id="filterActif">
|
||||||
|
<SelectValue placeholder="Tous les statuts" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">Tous les statuts</SelectItem>
|
||||||
|
<SelectItem value="actif">Active</SelectItem>
|
||||||
|
<SelectItem value="inactif">Inactive</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Compteur de résultats */}
|
||||||
|
<div className="mt-4 text-sm text-muted-foreground">
|
||||||
|
{filteredFormations.length} formation(s) trouvée(s)
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Liste des formations</CardTitle>
|
<CardTitle>Liste des formations</CardTitle>
|
||||||
@@ -194,7 +264,7 @@ export default function AdminFormations() {
|
|||||||
<Skeleton className="h-10 w-full" />
|
<Skeleton className="h-10 w-full" />
|
||||||
<Skeleton className="h-10 w-full" />
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
) : formations && formations.length > 0 ? (
|
) : filteredFormations && filteredFormations.length > 0 ? (
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -206,7 +276,7 @@ export default function AdminFormations() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{formations.map((formation) => (
|
{filteredFormations.map((formation) => (
|
||||||
<TableRow key={formation.id}>
|
<TableRow key={formation.id}>
|
||||||
<TableCell className="font-medium">{formation.nom}</TableCell>
|
<TableCell className="font-medium">{formation.nom}</TableCell>
|
||||||
<TableCell className="max-w-xs truncate">{formation.description || "-"}</TableCell>
|
<TableCell className="max-w-xs truncate">{formation.description || "-"}</TableCell>
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { trpc } from "@/lib/trpc";
|
import { trpc } from "@/lib/trpc";
|
||||||
import { Plus, Pencil, Trash2, Users as UsersIcon } from "lucide-react";
|
import { Plus, Pencil, Trash2, Users as UsersIcon, Search, SlidersHorizontal } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
@@ -19,6 +19,10 @@ export default function AdminSessions() {
|
|||||||
const [, setLocation] = useLocation();
|
const [, setLocation] = useLocation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<number | null>(null);
|
const [editingId, setEditingId] = useState<number | null>(null);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const [filterLieu, setFilterLieu] = useState<string>("all");
|
||||||
|
const [filterStatut, setFilterStatut] = useState<string>("all");
|
||||||
|
const [sortBy, setSortBy] = useState<"date" | "lieu" | "places">("date");
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
formationId: "",
|
formationId: "",
|
||||||
nom: "",
|
nom: "",
|
||||||
@@ -127,6 +131,47 @@ export default function AdminSessions() {
|
|||||||
return formations?.find(f => f.id === formationId)?.nom || "Formation inconnue";
|
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) => {
|
const formatDate = (date: Date | string) => {
|
||||||
return format(new Date(date), "dd/MM/yyyy HH:mm", { locale: fr });
|
return format(new Date(date), "dd/MM/yyyy HH:mm", { locale: fr });
|
||||||
};
|
};
|
||||||
@@ -152,11 +197,12 @@ export default function AdminSessions() {
|
|||||||
return (
|
return (
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex justify-between items-center">
|
<div>
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-tight">Sessions</h1>
|
<h1 className="text-3xl font-bold tracking-tight">Sessions</h1>
|
||||||
<p className="text-muted-foreground mt-2">
|
<p className="text-muted-foreground mt-2">
|
||||||
Gérez les sessions de formation et leurs inscriptions
|
Gérez les sessions de formation
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Dialog open={open} onOpenChange={(isOpen) => {
|
<Dialog open={open} onOpenChange={(isOpen) => {
|
||||||
@@ -294,6 +340,87 @@ export default function AdminSessions() {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</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 de session ou formation..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="pl-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filtre par lieu */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="filterLieu">Lieu</Label>
|
||||||
|
<Select value={filterLieu} onValueChange={setFilterLieu}>
|
||||||
|
<SelectTrigger id="filterLieu">
|
||||||
|
<SelectValue placeholder="Tous les lieux" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">Tous les lieux</SelectItem>
|
||||||
|
{uniqueLieux.map(lieu => (
|
||||||
|
<SelectItem key={lieu} value={lieu}>{lieu}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</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="ouverte">Ouverte</SelectItem>
|
||||||
|
<SelectItem value="bloquee">Bloquée</SelectItem>
|
||||||
|
<SelectItem value="terminee">Terminée</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</SelectItem>
|
||||||
|
<SelectItem value="lieu">Lieu</SelectItem>
|
||||||
|
<SelectItem value="places">Capacité</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Compteur de résultats */}
|
||||||
|
<div className="mt-4 text-sm text-muted-foreground">
|
||||||
|
{filteredAndSortedSessions.length} session(s) trouvée(s)
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Liste des sessions</CardTitle>
|
<CardTitle>Liste des sessions</CardTitle>
|
||||||
@@ -308,7 +435,7 @@ export default function AdminSessions() {
|
|||||||
<Skeleton className="h-10 w-full" />
|
<Skeleton className="h-10 w-full" />
|
||||||
<Skeleton className="h-10 w-full" />
|
<Skeleton className="h-10 w-full" />
|
||||||
</div>
|
</div>
|
||||||
) : sessions && sessions.length > 0 ? (
|
) : filteredAndSortedSessions && filteredAndSortedSessions.length > 0 ? (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
@@ -324,7 +451,7 @@ export default function AdminSessions() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{sessions.map((session) => (
|
{filteredAndSortedSessions.map((session) => (
|
||||||
<TableRow key={session.id}>
|
<TableRow key={session.id}>
|
||||||
<TableCell className="font-medium">{getFormationName(session.formationId)}</TableCell>
|
<TableCell className="font-medium">{getFormationName(session.formationId)}</TableCell>
|
||||||
<TableCell>{session.nom}</TableCell>
|
<TableCell>{session.nom}</TableCell>
|
||||||
|
|||||||
6
todo.md
6
todo.md
@@ -61,3 +61,9 @@
|
|||||||
|
|
||||||
- [x] Corriger la mise à jour de la liste des apprenants après inscription
|
- [x] Corriger la mise à jour de la liste des apprenants après inscription
|
||||||
- [x] Implémenter l'envoi effectif des emails de confirmation
|
- [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
|
||||||
|
|||||||
Reference in New Issue
Block a user