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
This commit is contained in:
Manus
2026-01-12 13:54:18 -05:00
parent 61f28781f0
commit ce64c72f00
13 changed files with 563 additions and 19 deletions

View File

@@ -7,12 +7,14 @@ 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 } from "lucide-react";
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: "",
@@ -162,31 +164,71 @@ export default function AdminAttestations() {
</CardContent>
</Card>
<div className="flex justify-end gap-3">
<div className="flex justify-between items-center">
<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 || "",
});
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}
>
Annuler
</Button>
<Button type="submit" disabled={updateConfig.isPending}>
<Save className="mr-2 h-4 w-4" />
{updateConfig.isPending ? "Enregistrement..." : "Enregistrer"}
<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>