## Authentification formateur - Ajout du rôle "formateur" dans l'enum de la table users - Ajout du champ formateurId pour lier un compte à un formateur - Mise à jour de l'interface AdminUsers avec sélection du formateur - Redirection automatique selon le rôle (admin → /admin, formateur → /formateur) - Badges et filtres mis à jour pour inclure le rôle formateur ## Upload logo et signature - Création du composant ImageUpload.tsx avec prévisualisation - Création de l'endpoint /api/upload-image avec multer pour l'upload vers S3 - Intégration dans AdminAttestations.tsx - Modification du service PDF pour inclure le logo (en haut à droite) et la signature (en bas) - Mise à jour des procédures tRPC pour accepter logoUrl, logoS3Key, signatureUrl, signatureS3Key
195 lines
7.2 KiB
TypeScript
195 lines
7.2 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 } 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 [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 "{{nomFormation}}" organisée du {{dateDebut}} au {{dateFin}}."
|
|
rows={6}
|
|
className="font-serif"
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
<div className="flex justify-end 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>
|
|
</form>
|
|
</div>
|
|
</DashboardLayout>
|
|
);
|
|
}
|