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:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user