Checkpoint: Système d'attestations de formation implémenté
✅ Infrastructure backend : - Tables attestations et configAttestation en base de données - Service de génération PDF avec PDFKit (attestationService.ts) - Génération automatique de PDF avec template personnalisable - Upload automatique vers S3 avec URLs publiques - Procédures tRPC pour générer, récupérer et configurer ✅ Interface utilisateur : - Page de configuration des attestations (/admin/attestations) - Personnalisation du texte avec variables dynamiques (nomComplet, nomFormation, dateDebut, dateFin) - Configuration du signataire (nom et fonction) - Bouton "Générer" dans la page de validation des présences formateur - Génération automatique avec ouverture du PDF dans un nouvel onglet ✅ Fonctionnalités : - Vérification des doublons (une seule attestation par apprenant/séquence) - Génération uniquement pour les apprenants marqués comme présents - PDF professionnel avec détails de la formation et dates - Stockage permanent sur S3 📋 Améliorations futures possibles : - Upload de logo et signature personnalisés - Envoi automatique par email aux apprenants - Espace apprenant pour télécharger les attestations
This commit is contained in:
@@ -13,9 +13,48 @@ import {
|
||||
} from "@/components/ui/table";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
import { Calendar, FileText, MapPin, Upload, Users } from "lucide-react";
|
||||
import { Calendar, FileText, MapPin, Upload, Users, Award } from "lucide-react";
|
||||
import { useParams } from "wouter";
|
||||
import { toast } from "sonner";
|
||||
import { useState } from "react";
|
||||
|
||||
function AttestationButton({ inscriptionId, present }: { inscriptionId: number; present: boolean }) {
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const genererAttestation = trpc.attestations.generer.useMutation();
|
||||
|
||||
const handleGenerer = async () => {
|
||||
if (!present) {
|
||||
toast.error("L'apprenant doit être marqué comme présent pour générer une attestation");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGenerating(true);
|
||||
try {
|
||||
const result = await genererAttestation.mutateAsync({ inscriptionId });
|
||||
if (result.success && result.attestation) {
|
||||
toast.success(result.message || "Attestation générée avec succès");
|
||||
// Ouvrir le PDF dans un nouvel onglet
|
||||
window.open(result.attestation.pdfUrl, "_blank");
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || "Erreur lors de la génération de l'attestation");
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleGenerer}
|
||||
disabled={!present || isGenerating}
|
||||
>
|
||||
<Award className="h-4 w-4 mr-1" />
|
||||
{isGenerating ? "Génération..." : "Générer"}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FormateurSequence() {
|
||||
const { id } = useParams();
|
||||
@@ -189,6 +228,7 @@ export default function FormateurSequence() {
|
||||
<TableHead>Établissement</TableHead>
|
||||
<TableHead>Statut</TableHead>
|
||||
<TableHead className="text-center">Présent</TableHead>
|
||||
<TableHead className="text-center">Attestation</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -224,6 +264,9 @@ export default function FormateurSequence() {
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<AttestationButton inscriptionId={apprenant.inscriptionId} present={apprenant.statut === "confirmee"} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
||||
Reference in New Issue
Block a user