Files
formation-manager-itinova/client/src/pages/admin/AdminAttestations.tsx
Manus ce64c72f00 Checkpoint: Améliorations de la gestion des attestations de formation :
- Ajout du paramètre d'envoi automatique des attestations dans la page Paramètres
- Ajout de la prévisualisation du modèle d'attestation avec génération PDF
- Création de la page Historique des attestations dans la section Traçabilité
- Création de la table historiqueAttestations pour tracer les envois
- Ajout des procédures tRPC pour l'historique et la prévisualisation
2026-01-12 13:54:18 -05:00

237 lines
8.8 KiB
TypeScript

import { useState, useEffect } from "react";
import { trpc } from "@/lib/trpc";
import DashboardLayout from "@/components/DashboardLayout";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { toast } from "sonner";
import { FileText, Save, Eye } from "lucide-react";
import ImageUpload from "@/components/ImageUpload";
export default function AdminAttestations() {
const { data: config, isLoading, refetch } = trpc.attestations.getConfig.useQuery();
const updateConfig = trpc.attestations.updateConfig.useMutation();
const previsualiser = trpc.attestations.previsualiser.useMutation();
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [formData, setFormData] = useState({
nomSignataire: "",
fonctionSignataire: "",
texteAttestation: "",
logoUrl: "",
logoS3Key: "",
signatureUrl: "",
signatureS3Key: "",
});
// Mettre à jour le formulaire quand les données sont chargées
useEffect(() => {
if (config) {
setFormData({
nomSignataire: config.nomSignataire || "",
fonctionSignataire: config.fonctionSignataire || "",
texteAttestation: config.texteAttestation || "",
logoUrl: config.logoUrl || "",
logoS3Key: config.logoS3Key || "",
signatureUrl: config.signatureUrl || "",
signatureS3Key: config.signatureS3Key || "",
});
}
}, [config]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await updateConfig.mutateAsync(formData);
toast.success("Configuration enregistrée avec succès");
refetch();
} catch (error) {
toast.error("Erreur lors de l'enregistrement");
console.error(error);
}
};
if (isLoading) {
return (
<DashboardLayout>
<div className="flex items-center justify-center h-64">
<p className="text-muted-foreground">Chargement...</p>
</div>
</DashboardLayout>
);
}
return (
<DashboardLayout>
<div className="container max-w-4xl py-8">
<div className="flex items-center gap-3 mb-6">
<FileText className="h-8 w-8 text-primary" />
<div>
<h1 className="text-3xl font-bold">Configuration des attestations</h1>
<p className="text-muted-foreground">
Personnalisez le contenu et la signature des attestations de formation
</p>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Informations du signataire</CardTitle>
<CardDescription>
Ces informations apparaîtront en bas de l'attestation
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="nomSignataire">Nom du signataire</Label>
<Input
id="nomSignataire"
value={formData.nomSignataire}
onChange={(e) =>
setFormData({ ...formData, nomSignataire: e.target.value })
}
placeholder="Ex: Jean Dupont"
/>
</div>
<div className="space-y-2">
<Label htmlFor="fonctionSignataire">Fonction du signataire</Label>
<Input
id="fonctionSignataire"
value={formData.fonctionSignataire}
onChange={(e) =>
setFormData({ ...formData, fonctionSignataire: e.target.value })
}
placeholder="Ex: Directeur de la formation"
/>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Logo et signature</CardTitle>
<CardDescription>
Ajoutez un logo et une signature pour personnaliser vos attestations
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<ImageUpload
label="Logo de l'organisation"
currentImageUrl={formData.logoUrl}
onUploadComplete={(url, s3Key) =>
setFormData({ ...formData, logoUrl: url, logoS3Key: s3Key })
}
/>
<ImageUpload
label="Signature du signataire"
currentImageUrl={formData.signatureUrl}
onUploadComplete={(url, s3Key) =>
setFormData({ ...formData, signatureUrl: url, signatureS3Key: s3Key })
}
/>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Texte de l'attestation</CardTitle>
<CardDescription>
Personnalisez le texte principal de l'attestation. Utilisez les variables suivantes :
<ul className="list-disc list-inside mt-2 space-y-1">
<li><code className="bg-muted px-1 py-0.5 rounded">{'{{nomComplet}}'}</code> - Nom complet de l'apprenant</li>
<li><code className="bg-muted px-1 py-0.5 rounded">{'{{nomFormation}}'}</code> - Nom de la formation</li>
<li><code className="bg-muted px-1 py-0.5 rounded">{'{{dateDebut}}'}</code> - Date de début de la formation</li>
<li><code className="bg-muted px-1 py-0.5 rounded">{'{{dateFin}}'}</code> - Date de fin de la formation</li>
</ul>
</CardDescription>
</CardHeader>
<CardContent>
<Textarea
value={formData.texteAttestation}
onChange={(e) =>
setFormData({ ...formData, texteAttestation: e.target.value })
}
placeholder="Nous attestons que {{nomComplet}} a suivi avec assiduité la formation &quot;{{nomFormation}}&quot; organisée du {{dateDebut}} au {{dateFin}}."
rows={6}
className="font-serif"
/>
</CardContent>
</Card>
<div className="flex justify-between items-center">
<Button
type="button"
variant="secondary"
onClick={async () => {
try {
const result = await previsualiser.mutateAsync();
setPreviewUrl(result.pdfUrl);
toast.success("Prévisualisation générée");
} catch (error) {
toast.error("Erreur lors de la génération de la prévisualisation");
console.error(error);
}
}}
disabled={previsualiser.isPending}
>
<Eye className="mr-2 h-4 w-4" />
{previsualiser.isPending ? "Génération..." : "Prévisualiser le modèle"}
</Button>
<div className="flex gap-3">
<Button
type="button"
variant="outline"
onClick={() => {
if (config) {
setFormData({
nomSignataire: config.nomSignataire || "",
fonctionSignataire: config.fonctionSignataire || "",
texteAttestation: config.texteAttestation || "",
logoUrl: config.logoUrl || "",
logoS3Key: config.logoS3Key || "",
signatureUrl: config.signatureUrl || "",
signatureS3Key: config.signatureS3Key || "",
});
}
}}
>
Annuler
</Button>
<Button type="submit" disabled={updateConfig.isPending}>
<Save className="mr-2 h-4 w-4" />
{updateConfig.isPending ? "Enregistrement..." : "Enregistrer"}
</Button>
</div>
</div>
{/* Prévisualisation PDF */}
{previewUrl && (
<Card>
<CardHeader>
<CardTitle>Prévisualisation du modèle</CardTitle>
<CardDescription>
Aperçu du PDF d'attestation avec des données fictives
</CardDescription>
</CardHeader>
<CardContent>
<iframe
src={previewUrl}
className="w-full h-[800px] border rounded"
title="Prévisualisation de l'attestation"
/>
</CardContent>
</Card>
)}
</form>
</div>
</DashboardLayout>
);
}