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,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<number | null>(null);
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [filterActif, setFilterActif] = useState<string>("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 (
|
||||
<DashboardLayout>
|
||||
<div className="space-y-6">
|
||||
@@ -180,6 +202,54 @@ export default function AdminFormations() {
|
||||
</Dialog>
|
||||
</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>
|
||||
<CardHeader>
|
||||
<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" />
|
||||
</div>
|
||||
) : formations && formations.length > 0 ? (
|
||||
) : filteredFormations && filteredFormations.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
@@ -206,7 +276,7 @@ export default function AdminFormations() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{formations.map((formation) => (
|
||||
{filteredFormations.map((formation) => (
|
||||
<TableRow key={formation.id}>
|
||||
<TableCell className="font-medium">{formation.nom}</TableCell>
|
||||
<TableCell className="max-w-xs truncate">{formation.description || "-"}</TableCell>
|
||||
|
||||
Reference in New Issue
Block a user