Checkpoint: Implémentation complète de la fonctionnalité QR code formateur :

- Procédure generateQRCodeFormateur dans routers.ts
- Fonction validerPresenceFormateur dans presenceDb.ts
- Bouton "QR Code Formateur" dans FormateurEmargement.tsx
- Page EmargementFormateurScan.tsx pour le scan du QR code
- Route /emargement-formateur/{token}
- Gestion automatique de la date du jour
- Stockage de la signature sur S3
- Champ qrCodeFormateurToken dans le schéma sequences

Prêt pour déploiement et tests sur le VPS.
This commit is contained in:
Manus
2026-02-03 11:04:37 -05:00
parent 70f9055073
commit 98c3b21ec1
9 changed files with 2868 additions and 8 deletions

View File

@@ -26,13 +26,14 @@ export default function FormateurEmargement() {
const { user } = useAuth();
const [selectedSequenceId, setSelectedSequenceId] = useState<number | null>(null);
const [showQRDialog, setShowQRDialog] = useState(false);
const [showQRFormateurDialog, setShowQRFormateurDialog] = useState(false);
const [lastRefreshTime, setLastRefreshTime] = useState<Date>(new Date());
const [secondsSinceRefresh, setSecondsSinceRefresh] = useState(0);
// 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
// Générer le QR code apprenants
const { data: qrData, mutate: generateQR, isPending: generatingQR } = trpc.presences.generateQRCode.useMutation({
onSuccess: () => {
setShowQRDialog(true);
@@ -44,6 +45,18 @@ export default function FormateurEmargement() {
},
});
// Générer le QR code formateur
const { data: qrFormateurData, mutate: generateQRFormateur, isPending: generatingQRFormateur } = trpc.presences.generateQRCodeFormateur.useMutation({
onSuccess: () => {
setShowQRFormateurDialog(true);
},
onError: (error) => {
toast.error("Erreur lors de la génération du QR code formateur", {
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 },
@@ -121,6 +134,14 @@ export default function FormateurEmargement() {
generateQR({ sequenceId: selectedSequenceId });
};
const handleGenerateQRFormateur = () => {
if (!selectedSequenceId) {
toast.error("Veuillez sélectionner une séquence");
return;
}
generateQRFormateur({ sequenceId: selectedSequenceId });
};
const handleValiderPresence = (inscriptionId: number, dateFormationId: number, periode: "matin" | "apres_midi") => {
validerPresence({
inscriptionId,
@@ -209,7 +230,7 @@ export default function FormateurEmargement() {
{selectedSequenceId && (
<div className="space-y-3">
<div className="flex gap-2">
<div className="flex gap-2 flex-wrap">
<Button onClick={handleGenerateQR} disabled={generatingQR}>
{generatingQR ? (
<>
@@ -219,7 +240,20 @@ export default function FormateurEmargement() {
) : (
<>
<QrCode className="mr-2 h-4 w-4" />
Afficher le QR code
QR Code Apprenants
</>
)}
</Button>
<Button onClick={handleGenerateQRFormateur} disabled={generatingQRFormateur} variant="secondary">
{generatingQRFormateur ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Génération...
</>
) : (
<>
<QrCode className="mr-2 h-4 w-4" />
QR Code Formateur
</>
)}
</Button>
@@ -451,11 +485,11 @@ export default function FormateurEmargement() {
</Card>
)}
{/* Dialog QR Code */}
{/* Dialog QR Code Apprenants */}
<Dialog open={showQRDialog} onOpenChange={setShowQRDialog}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>QR Code d'émargement</DialogTitle>
<DialogTitle>QR Code d'émargement apprenants</DialogTitle>
<DialogDescription>
Les apprenants doivent scanner ce QR code pour valider leur présence
</DialogDescription>
@@ -475,6 +509,30 @@ export default function FormateurEmargement() {
</DialogContent>
</Dialog>
{/* Dialog QR Code Formateur */}
<Dialog open={showQRFormateurDialog} onOpenChange={setShowQRFormateurDialog}>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>QR Code d'émargement formateur</DialogTitle>
<DialogDescription>
Scannez ce QR code pour valider votre présence en tant que formateur
</DialogDescription>
</DialogHeader>
{qrFormateurData && (
<div className="flex flex-col items-center gap-4 py-4">
<img
src={qrFormateurData.qrCodeDataURL}
alt="QR Code Formateur"
className="w-full max-w-[300px] border rounded-lg"
/>
<p className="text-sm text-muted-foreground text-center">
Mode formateur : redirige vers la page d'émargement dédiée
</p>
</div>
)}
</DialogContent>
</Dialog>
{/* Dialog Signature */}
<Dialog open={showSignatureDialog} onOpenChange={setShowSignatureDialog}>
<DialogContent className="max-w-2xl">