Checkpoint: Ajout de l'association Service→Signature dans l'onglet Signatures des Paramètres et apposition automatique de la signature sur les PDFs exportés lors de l'export BAP. Inclut : table serviceSignatures, routes tRPC, interface de configuration, et modification de exportToPdf pour apposer la signature en bas à droite du PDF avec le nom du signataire.
This commit is contained in:
@@ -402,10 +402,155 @@ function SignaturesSection() {
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Service → Signature associations */}
|
||||
<ServiceSignaturesSection signatures={signatures || []} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceSignaturesSection({ signatures }: { signatures: Array<{ id: number; firstName: string; lastName: string; imageUrl: string }> }) {
|
||||
const utils = trpc.useUtils();
|
||||
const { data: departments, isLoading: depsLoading } = trpc.departments.getByUser.useQuery();
|
||||
const { data: associations, isLoading: assocLoading } = trpc.serviceSignatures.list.useQuery();
|
||||
|
||||
const upsertMutation = trpc.serviceSignatures.upsert.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success("Association enregistrée");
|
||||
utils.serviceSignatures.list.invalidate();
|
||||
},
|
||||
onError: (e) => toast.error(e.message || "Erreur lors de l'enregistrement"),
|
||||
});
|
||||
|
||||
const deleteMutation = trpc.serviceSignatures.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success("Association supprimée");
|
||||
utils.serviceSignatures.list.invalidate();
|
||||
},
|
||||
onError: (e) => toast.error(e.message || "Erreur lors de la suppression"),
|
||||
});
|
||||
|
||||
const getAssociation = (serviceName: string) =>
|
||||
associations?.find(a => a.serviceName.toLowerCase() === serviceName.toLowerCase());
|
||||
|
||||
if (depsLoading || assocLoading) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader><CardTitle className="flex items-center gap-2"><User className="w-5 h-5 text-emerald-500" />Association Service → Signature</CardTitle></CardHeader>
|
||||
<CardContent><div className="flex justify-center py-6"><Loader2 className="w-6 h-6 animate-spin text-primary" /></div></CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!departments || departments.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2"><User className="w-5 h-5 text-emerald-500" />Association Service → Signature</CardTitle>
|
||||
<CardDescription>Associez une signature à chaque service pour l'apposer automatiquement lors de l'export BAP</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground text-center py-4">Aucun service configuré. Ajoutez des services dans l'onglet <strong>Listes</strong> d'abord.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
if (!signatures || signatures.length === 0) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2"><User className="w-5 h-5 text-emerald-500" />Association Service → Signature</CardTitle>
|
||||
<CardDescription>Associez une signature à chaque service pour l'apposer automatiquement lors de l'export BAP</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground text-center py-4">Aucune signature enregistrée. Ajoutez des signatures ci-dessus d'abord.</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2"><User className="w-5 h-5 text-emerald-500" />Association Service → Signature</CardTitle>
|
||||
<CardDescription>Associez une signature à chaque service pour l'apposer automatiquement lors de l'export PDF BAP</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Service</TableHead>
|
||||
<TableHead>Signature associée</TableHead>
|
||||
<TableHead>Aperçu</TableHead>
|
||||
<TableHead className="w-20">Action</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{departments.map((dept: { id: number; name: string; userId: number; createdAt: Date }) => {
|
||||
const assoc = getAssociation(dept.name);
|
||||
const currentSigId = assoc?.signatureId ?? 0;
|
||||
return (
|
||||
<TableRow key={dept.id}>
|
||||
<TableCell className="font-medium">{dept.name}</TableCell>
|
||||
<TableCell>
|
||||
<select
|
||||
className="w-full border rounded-md px-3 py-1.5 text-sm bg-background"
|
||||
value={currentSigId}
|
||||
onChange={(e) => {
|
||||
const sigId = parseInt(e.target.value);
|
||||
if (sigId === 0) {
|
||||
deleteMutation.mutate({ serviceName: dept.name });
|
||||
} else {
|
||||
upsertMutation.mutate({ serviceName: dept.name, signatureId: sigId });
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value={0}>— Aucune signature —</option>
|
||||
{signatures.map(sig => (
|
||||
<option key={sig.id} value={sig.id}>
|
||||
{sig.firstName} {sig.lastName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{assoc && signatures.find(s => s.id === assoc.signatureId) ? (
|
||||
<div className="h-10 w-24 border rounded overflow-hidden bg-white flex items-center justify-center">
|
||||
<img
|
||||
src={signatures.find(s => s.id === assoc.signatureId)!.imageUrl}
|
||||
alt="aperçu"
|
||||
className="max-h-full max-w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground italic">Aucune</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{assoc && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-red-500 hover:text-red-700 hover:bg-red-50 h-7 w-7 p-0"
|
||||
onClick={() => deleteMutation.mutate({ serviceName: dept.name })}
|
||||
disabled={deleteMutation.isPending}
|
||||
title="Supprimer l'association"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function LlmFieldsConfigSection() {
|
||||
const { data: fields, isLoading } = trpc.llmFieldsConfig.getAll.useQuery();
|
||||
const utils = trpc.useUtils();
|
||||
|
||||
Reference in New Issue
Block a user