import { useAuth } from "@/_core/hooks/useAuth"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { trpc } from "@/lib/trpc"; import { CheckCircle2, Circle, Loader2, QrCode, RefreshCw } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import DashboardLayout from "@/components/DashboardLayout"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; export default function FormateurEmargement() { const { user } = useAuth(); const [selectedSequenceId, setSelectedSequenceId] = useState(null); const [showQRDialog, setShowQRDialog] = useState(false); // Récupérer toutes les séquences (filtrées côté serveur pour les formateurs) const { data: sequences, isLoading: loadingSequences } = trpc.sequences.list.useQuery(); // Générer le QR code const { data: qrData, mutate: generateQR, isPending: generatingQR } = trpc.presences.generateQRCode.useMutation({ onSuccess: () => { setShowQRDialog(true); }, onError: (error) => { toast.error("Erreur lors de la génération du QR code", { description: error.message, }); }, }); // Récupérer les inscrits avec les dates de formation const { data: inscrits, isLoading: loadingInscrits, refetch: refetchInscrits } = trpc.inscriptions.listWithDates.useQuery( { sequenceId: selectedSequenceId || 0 }, { enabled: !!selectedSequenceId } ); // Récupérer les présences de la séquence const { data: presences, refetch: refetchPresences } = trpc.presences.listBySequence.useQuery( { sequenceId: selectedSequenceId || 0 }, { enabled: !!selectedSequenceId } ); // Valider une présence manuellement const { mutate: validerPresence, isPending: validating } = trpc.presences.valider.useMutation({ onSuccess: () => { toast.success("Présence validée avec succès"); refetchInscrits(); refetchPresences(); }, onError: (error) => { toast.error("Erreur lors de la validation", { description: error.message, }); }, }); const handleGenerateQR = () => { if (!selectedSequenceId) { toast.error("Veuillez sélectionner une séquence"); return; } generateQR({ sequenceId: selectedSequenceId }); }; const handleValiderPresence = (inscriptionId: number, dateFormationId: number, periode: "matin" | "apres_midi") => { validerPresence({ inscriptionId, dateFormationId, periode, modeValidation: "manuel", validateurId: user?.id, }); }; // Vérifier si un apprenant a validé sa présence pour une date et une période const isPresent = (inscriptionId: number, dateFormationId: number, periode: "matin" | "apres_midi") => { return presences?.some( (p) => p.inscriptionId === inscriptionId && p.dateFormationId === dateFormationId && p.periode === periode ); }; // Récupérer les informations de présence pour une date et une période const getPresenceInfo = (inscriptionId: number, dateFormationId: number, periode: "matin" | "apres_midi") => { return presences?.find( (p) => p.inscriptionId === inscriptionId && p.dateFormationId === dateFormationId && p.periode === periode ); }; // État pour la modale de signature const [selectedSignature, setSelectedSignature] = useState(null); const [showSignatureDialog, setShowSignatureDialog] = useState(false); if (loadingSequences) { return (
); } if (!sequences || sequences.length === 0) { return (
Aucune séquence Vous n'avez aucune séquence de formation assignée pour le moment.
); } return (

Émargement numérique

Gérez les présences de vos apprenants avec le QR code

{/* Sélection de la séquence */} Sélectionner une séquence Choisissez la séquence pour laquelle vous souhaitez gérer les présences {selectedSequenceId && (
)}
{/* Liste des inscrits */} {selectedSequenceId && ( Liste des inscrits Validez manuellement les présences si nécessaire {loadingInscrits ? (
) : !inscrits || inscrits.length === 0 ? (

Aucun inscrit pour cette séquence

) : (
{inscrits.map((inscrit: any) => (

{inscrit.apprenant?.prenom} {inscrit.apprenant?.nom}

{inscrit.apprenant?.email}

{inscrit.inscription.statut}
{/* Dates de formation */}
{inscrit.dates && Array.isArray(inscrit.dates) && inscrit.dates.length > 0 ? inscrit.dates.map((date: any) => { if (!date || !date.id) return null; const presentMatin = isPresent(inscrit.inscription.id, date.id, "matin"); const presentApresMidi = isPresent(inscrit.inscription.id, date.id, "apres_midi"); return (

Date {date.ordre}

{new Date(date.dateDebut).toLocaleDateString("fr-FR", { weekday: "long", year: "numeric", month: "long", day: "numeric", })}

{/* Matin */}
{presentMatin ? ( ) : ( )} 🌅 Matin {(() => { const presenceInfo = getPresenceInfo(inscrit.inscription.id, date.id, "matin"); if (presenceInfo) { return (
{inscrit.apprenant?.prenom} {inscrit.apprenant?.nom} a validé(e) le {new Date(presenceInfo.heurePresence).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" })} {presenceInfo.signatureUrl && ( )}
); } return null; })()}
{!presentMatin && ( )}
{/* Après-midi */}
{presentApresMidi ? ( ) : ( )} 🌆 Après-midi {(() => { const presenceInfo = getPresenceInfo(inscrit.inscription.id, date.id, "apres_midi"); if (presenceInfo) { return (
{inscrit.apprenant?.prenom} {inscrit.apprenant?.nom} a validé(e) le {new Date(presenceInfo.heurePresence).toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit" })} {presenceInfo.signatureUrl && ( )}
); } return null; })()}
{!presentApresMidi && ( )}
); }) : (

Aucune date de formation disponible

)}
))}
)}
)} {/* Dialog QR Code */} QR Code d'émargement Les apprenants doivent scanner ce QR code pour valider leur présence {qrData && (
QR Code

Affichez ce QR code en plein écran pour faciliter le scan

)}
{/* Dialog Signature */} Signature de l'apprenant Signature manuscrite capturée lors de la validation de présence {selectedSignature && (
Signature
)}
); }