diff --git a/client/src/App.tsx b/client/src/App.tsx index 1dc887e..d0cedc7 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -10,6 +10,7 @@ import Admin from "./pages/Admin"; import AdminFormations from "./pages/AdminFormations"; import AdminSequences from "./pages/AdminSequences"; import AdminApprenants from "./pages/AdminApprenants"; +import AdminApprenantDetail from "./pages/AdminApprenantDetail"; import AdminSequenceInscrits from "./pages/AdminSequenceInscrits"; import AdminUsers from "./pages/AdminUsers"; import AdminEmailTemplates from "./pages/AdminEmailTemplates"; @@ -28,6 +29,7 @@ function Router() { + diff --git a/client/src/pages/AdminApprenantDetail.tsx b/client/src/pages/AdminApprenantDetail.tsx new file mode 100644 index 0000000..1a42d35 --- /dev/null +++ b/client/src/pages/AdminApprenantDetail.tsx @@ -0,0 +1,191 @@ +import { useState } from "react"; +import { useLocation, useParams } from "wouter"; +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 { Badge } from "@/components/ui/badge"; +import { trpc } from "@/lib/trpc"; +import { ArrowLeft, Calendar, MapPin, User } from "lucide-react"; +import { format } from "date-fns"; +import { fr } from "date-fns/locale"; + +export default function AdminApprenantDetail() { + const params = useParams(); + const apprenantId = params.id ? parseInt(params.id) : 0; + const [, setLocation] = useLocation(); + + const { data: apprenant, isLoading: loadingApprenant } = trpc.apprenants.getById.useQuery({ id: apprenantId }); + const { data: inscriptions, isLoading: loadingInscriptions } = trpc.apprenants.getInscriptions.useQuery({ id: apprenantId }); + + if (loadingApprenant || loadingInscriptions) { + return ( + + + Chargement... + + + ); + } + + if (!apprenant) { + return ( + + + Apprenant introuvable + + + ); + } + + const getStatutBadge = (statut: string) => { + switch (statut) { + case 'confirmee': + return Confirmée; + case 'en_attente': + return En attente; + case 'annulee': + return Annulée; + default: + return {statut}; + } + }; + + const getFonctionLabel = (fonction: string) => { + switch (fonction) { + case 'directeur': + return 'Directeur'; + case 'chef_service': + return 'Chef de service'; + default: + return 'Autre'; + } + }; + + const getPublicCibleLabel = (publicCible: string) => { + switch (publicCible) { + case 'directeur': + return 'Directeurs'; + case 'chef_service': + return 'Chefs de service'; + case 'tous': + return 'Tous'; + default: + return 'Autre'; + } + }; + + return ( + + + {/* Header */} + + + setLocation('/admin/apprenants')} + className="mb-2" + > + + Retour aux apprenants + + Détail de l'apprenant + + Consultez toutes les inscriptions de cet apprenant + + + + + {/* Informations de l'apprenant */} + + + + + Informations personnelles + + + + + + Nom complet + {apprenant.nom} {apprenant.prenom} + + + Email + {apprenant.email} + + + Code établissement + {apprenant.codeEtablissement} + + + Fonction + {getFonctionLabel(apprenant.fonction)} + + + + + + {/* Liste des inscriptions */} + + + Inscriptions aux formations + + {inscriptions?.length || 0} inscription(s) au total + + + + {!inscriptions || inscriptions.length === 0 ? ( + + Aucune inscription trouvée pour cet apprenant + + ) : ( + + + + Formation + Séquence + Dates + Lieu + Public cible + Date d'inscription + Statut + + + + {inscriptions.map((insc: any) => ( + + {insc.formation?.nom || 'N/A'} + {insc.sequence?.nom || 'N/A'} + + + {insc.sequence?.dates?.map((date: any, idx: number) => ( + + + {format(new Date(date.dateDebut), 'dd/MM/yyyy HH:mm', { locale: fr })} + + ))} + + + + + + {insc.sequence?.lieu || 'N/A'} + + + {getPublicCibleLabel(insc.sequence?.publicCible || '')} + + {format(new Date(insc.dateInscription), 'dd/MM/yyyy HH:mm', { locale: fr })} + + {getStatutBadge(insc.statut)} + + ))} + + + )} + + + + + ); +} diff --git a/client/src/pages/AdminApprenants.tsx b/client/src/pages/AdminApprenants.tsx index 7ac7ca8..e5f5162 100644 --- a/client/src/pages/AdminApprenants.tsx +++ b/client/src/pages/AdminApprenants.tsx @@ -7,12 +7,14 @@ import { Label } from "@/components/ui/label"; 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 { Plus, Pencil, Trash2, Search, SlidersHorizontal } from "lucide-react"; +import { Plus, Pencil, Trash2, Search, SlidersHorizontal, Eye } from "lucide-react"; import { useState, useMemo } from "react"; +import { useLocation } from "wouter"; import { toast } from "sonner"; import { Skeleton } from "@/components/ui/skeleton"; export default function AdminApprenants() { + const [, setLocation] = useLocation(); const [open, setOpen] = useState(false); const [editingId, setEditingId] = useState(null); const [searchTerm, setSearchTerm] = useState(""); @@ -298,6 +300,14 @@ export default function AdminApprenants() { + setLocation(`/admin/apprenants/${apprenant.id}`)} + title="Voir les détails et inscriptions" + > + + { + const results = await db.getInscriptionsByApprenant(input.id); + // Récupérer les informations de formation et séquence pour chaque inscription + const inscriptionsAvecDetails = await Promise.all( + results.map(async (result: any) => { + const insc = result.inscription; + if (!insc) return null; + const sequence = await db.getSequenceById(insc.sequenceId); + if (!sequence) return null; + const formation = await db.getFormationById(sequence.formationId); + const dates = await db.getDatesBySequence(insc.sequenceId); + return { + ...insc, + sequence: { ...sequence, dates }, + formation, + }; + }) + ); + return inscriptionsAvecDetails.filter(i => i !== null); + }), }), // ===== FORMATEURS ===== diff --git a/todo.md b/todo.md index cdffe5f..5b7e850 100644 --- a/todo.md +++ b/todo.md @@ -367,3 +367,11 @@ ## Signature du formateur sur les feuilles de présence - [x] Ajouter un champ de signature pour le formateur en bas de chaque page de feuille de présence + +## Détail par apprenant + +- [x] Créer une procédure tRPC pour récupérer toutes les inscriptions d'un apprenant avec les informations de formation et séquence +- [x] Créer la page AdminApprenantDetail affichant la liste des inscriptions par apprenant +- [x] Ajouter un bouton "Détail par apprenant" dans la page AdminApprenants +- [x] Ajouter la route /admin/apprenants/:id dans App.tsx +- [x] Tester l'affichage des détails d'un apprenant
+ Consultez toutes les inscriptions de cet apprenant +