import DashboardLayout from "@/components/DashboardLayout"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 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, 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"; 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("all"); const [filterEtablissement, setFilterEtablissement] = useState("all"); const [sortBy, setSortBy] = useState<"date" | "nom">("date"); const utils = trpc.useUtils(); const { data: session, isLoading: loadingSession } = trpc.sessions.getById.useQuery({ id: sessionId }); const { data: inscriptions, isLoading: loadingInscriptions } = trpc.inscriptions.listBySession.useQuery({ sessionId }); const { data: formations } = trpc.formations.list.useQuery(); const updateStatutMutation = trpc.inscriptions.updateStatut.useMutation({ onSuccess: () => { utils.inscriptions.listBySession.invalidate({ sessionId }); toast.success("Statut mis à jour avec succès"); }, onError: (error) => { toast.error("Erreur lors de la mise à jour : " + error.message); }, }); const sendEmailsMutation = trpc.inscriptions.sendGroupEmails.useMutation({ onSuccess: (result) => { toast.success(`Emails envoyés : ${result.sent} réussis, ${result.failed} échecs`); }, onError: (error) => { toast.error("Erreur lors de l'envoi : " + error.message); }, }); const handleStatutChange = (inscriptionId: number, newStatut: string) => { updateStatutMutation.mutate({ id: inscriptionId, statut: newStatut as "confirmee" | "liste_attente" | "annulee", }); }; const exportExcelMutation = trpc.inscriptions.exportExcel.useMutation({ onSuccess: (result) => { const blob = new Blob([Uint8Array.from(atob(result.data), c => c.charCodeAt(0))], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `inscrits-session-${sessionId}.xlsx`; a.click(); window.URL.revokeObjectURL(url); toast.success("Export Excel téléchargé"); }, onError: (error) => { toast.error("Erreur lors de l'export : " + error.message); }, }); const exportPDFMutation = trpc.inscriptions.exportPDF.useMutation({ onSuccess: (result) => { const blob = new Blob([Uint8Array.from(atob(result.data), c => c.charCodeAt(0))], { type: 'application/pdf' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `inscrits-session-${sessionId}.pdf`; a.click(); window.URL.revokeObjectURL(url); toast.success("Export PDF téléchargé"); }, onError: (error) => { toast.error("Erreur lors de l'export : " + error.message); }, }); const exportFeuilleMutation = trpc.inscriptions.exportFeuillePresence.useMutation({ onSuccess: (result) => { const blob = new Blob([Uint8Array.from(atob(result.data), c => c.charCodeAt(0))], { type: 'application/pdf' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `feuille-presence-session-${sessionId}.pdf`; a.click(); window.URL.revokeObjectURL(url); toast.success("Feuille de présence téléchargée"); }, onError: (error) => { toast.error("Erreur lors de la génération : " + error.message); }, }); const handleExportExcel = () => { exportExcelMutation.mutate({ sessionId }); }; const handleExportPDF = () => { exportPDFMutation.mutate({ sessionId }); }; const handleGenerateFeuillePresence = () => { exportFeuilleMutation.mutate({ sessionId }); }; const getFormationName = (formationId: number) => { 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", liste_attente: "bg-orange-100 text-orange-800", annulee: "bg-gray-100 text-gray-800", }; const labels = { confirmee: "Confirmée", liste_attente: "Liste d'attente", annulee: "Annulée", }; return ( {labels[statut as keyof typeof labels]} ); }; const inscritsConfirmes = inscriptions?.filter(i => i.inscription.statut === "confirmee") || []; const listeAttente = inscriptions?.filter(i => i.inscription.statut === "liste_attente") || []; return (

Inscrits à la session

{session && (

{getFormationName(session.formationId)} - {session.nom}

)}
{/* Filtres et recherche */} Filtres et recherche
{/* Recherche */}
setSearchTerm(e.target.value)} className="pl-8" />
{/* Filtre par statut */}
{/* Filtre par établissement */}
{/* Tri */}
{/* Compteur de résultats */}
{filteredAndSortedInscriptions.length} inscrit(s) trouvé(s) sur {inscriptions?.length || 0} total
{loadingSession || loadingInscriptions ? (
) : session ? ( <>
Places confirmées
{inscritsConfirmes.length} / {session.capaciteMax}

{session.capaciteMax - inscritsConfirmes.length} places restantes

Liste d'attente
{listeAttente.length}

En attente de places

Total inscriptions
{inscriptions?.length || 0}

Toutes statuts confondus

Liste des inscrits Gérez les inscriptions et leurs statuts {filteredAndSortedInscriptions && filteredAndSortedInscriptions.length > 0 ? ( Nom Prénom Email Code établissement Date d'inscription Statut Actions {filteredAndSortedInscriptions.map((item) => ( {item.apprenant?.nom} {item.apprenant?.prenom} {item.apprenant?.email} {item.apprenant?.codeEtablissement} {new Date(item.inscription.dateInscription).toLocaleDateString('fr-FR')} {getStatutBadge(item.inscription.statut)} ))}
) : (
Aucune inscription pour cette session.
)}
) : (
Session introuvable.
)}
); }