From a79132e92120ef62df33b9362bcb0a3a2e3a70a3 Mon Sep 17 00:00:00 2001 From: Manus Date: Sat, 17 Jan 2026 18:30:13 -0500 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Correction=20de=20l'erreur=20"ref?= =?UTF-8?q?etchInscrits=20is=20not=20defined"=20lors=20de=20l'upload=20d'a?= =?UTF-8?q?ttestation=20manuelle=20:=20-=20Remplacement=20de=20refetchInsc?= =?UTF-8?q?rits()=20par=20refetchApprenants()=20dans=20AdminGestionAttesta?= =?UTF-8?q?tions.tsx=20ligne=20489=20-=20La=20fonction=20refetchApprenants?= =?UTF-8?q?()=20est=20la=20bonne=20fonction=20d=C3=A9finie=20dans=20le=20c?= =?UTF-8?q?omposant=20(ligne=20157)=20-=20D=C3=A9ploy=C3=A9=20sur=20le=20V?= =?UTF-8?q?PS=20de=20production=20(https://formations.itinova.org)=20-=20L?= =?UTF-8?q?'upload=20d'attestation=20manuelle=20fonctionne=20maintenant=20?= =?UTF-8?q?correctement=20sans=20erreur=20JavaScript?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/DashboardLayout.tsx | 26 ++- client/src/pages/AdminGestionAttestations.tsx | 151 +++++++++++++++--- server/gestionAttestationsDb.ts | 16 ++ todo.md | 6 + 4 files changed, 175 insertions(+), 24 deletions(-) diff --git a/client/src/components/DashboardLayout.tsx b/client/src/components/DashboardLayout.tsx index 4f1ed11..ed1b2bc 100644 --- a/client/src/components/DashboardLayout.tsx +++ b/client/src/components/DashboardLayout.tsx @@ -214,7 +214,7 @@ function DashboardLayoutContent({ // État pour gérer les sections ouvertes/fermées (persisté dans localStorage) const [openSections, setOpenSections] = useState>(() => { // Version du système de menu (incrémenter pour forcer la réinitialisation) - const MENU_VERSION = 4; // Incrémenté pour ne garder ouvert que "Gestion" par défaut + const MENU_VERSION = 5; // Incrémenté pour forcer la réinitialisation : seul "Gestion" ouvert par défaut const savedVersion = localStorage.getItem('menuVersion'); // Si la version a changé, ignorer le localStorage existant @@ -243,13 +243,27 @@ function DashboardLayoutContent({ return initial; }); - // Fonction pour toggle une section + // Fonction pour toggle une section (mode accordéon : un seul menu ouvert à la fois) const toggleSection = (sectionTitle: string) => { setOpenSections(prev => { - const newState = { - ...prev, - [sectionTitle]: !prev[sectionTitle] - }; + const isCurrentlyOpen = prev[sectionTitle]; + + // Si le menu est déjà ouvert, on le ferme + if (isCurrentlyOpen) { + const newState = { + ...prev, + [sectionTitle]: false + }; + localStorage.setItem('menuSectionsState', JSON.stringify(newState)); + return newState; + } + + // Sinon, on ferme tous les menus et on ouvre celui-ci + const newState: Record = {}; + Object.keys(prev).forEach(key => { + newState[key] = key === sectionTitle; + }); + // Sauvegarder dans localStorage localStorage.setItem('menuSectionsState', JSON.stringify(newState)); return newState; diff --git a/client/src/pages/AdminGestionAttestations.tsx b/client/src/pages/AdminGestionAttestations.tsx index e191542..b32f0ec 100644 --- a/client/src/pages/AdminGestionAttestations.tsx +++ b/client/src/pages/AdminGestionAttestations.tsx @@ -12,6 +12,129 @@ import { toast } from "sonner"; import { Eye, Send, Upload, Trash2, FileText } from "lucide-react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +// Composant de dialogue d'upload d'attestation +function UploadAttestationDialog({ + open, + onOpenChange, + inscriptionId, + onSuccess, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + inscriptionId: number | null; + onSuccess: () => void; +}) { + const [uploading, setUploading] = useState(false); + const [fileData, setFileData] = useState<{ + nomFichier: string; + urlFichier: string; + s3Key: string; + typeFichier: string; + tailleFichier: number; + } | null>(null); + + const uploadMutation = trpc.gestionAttestations.uploadDocument.useMutation({ + onSuccess: () => { + onSuccess(); + setFileData(null); + }, + onError: (error) => { + toast.error(`Erreur lors de l'upload : ${error.message}`); + setUploading(false); + }, + }); + + const handleUpload = () => { + if (!fileData || !inscriptionId) return; + + setUploading(true); + uploadMutation.mutate({ + inscriptionId, + documentUrl: fileData.urlFichier, + documentS3Key: fileData.s3Key, + }); + }; + + return ( + + + + Charger une attestation + + Sélectionnez un fichier PDF à uploader pour cet apprenant + + +
+
+ + { + const file = e.target.files?.[0]; + if (!file) return; + + if (file.type !== 'application/pdf') { + toast.error('Seuls les fichiers PDF sont acceptés'); + return; + } + + if (file.size > 10 * 1024 * 1024) { + toast.error('Le fichier ne doit pas dépasser 10 Mo'); + return; + } + + setUploading(true); + try { + const formData = new FormData(); + formData.append('file', file); + + const response = await fetch('/api/upload-file', { + method: 'POST', + body: formData, + }); + + if (!response.ok) { + throw new Error('Erreur lors de l\'upload'); + } + + const data = await response.json(); + setFileData({ + nomFichier: file.name, + urlFichier: data.url, + s3Key: data.key, + typeFichier: file.type, + tailleFichier: file.size, + }); + toast.success('Fichier uploadé avec succès'); + } catch (error) { + toast.error('Erreur lors de l\'upload du fichier'); + } finally { + setUploading(false); + } + }} + className="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-primary file:text-primary-foreground hover:file:bg-primary/90" + /> + {fileData && ( +

+ Fichier sélectionné : {fileData.nomFichier} +

+ )} +
+
+ + +
+
+
+
+ ); +} + export default function AdminGestionAttestations() { const [selectedFormationId, setSelectedFormationId] = useState(null); const [modeAttestation, setModeAttestation] = useState<"auto" | "manuel">("auto"); @@ -358,24 +481,16 @@ export default function AdminGestionAttestations() { )} {/* Dialog d'upload */} - - - - Charger une attestation - - Sélectionnez un fichier PDF à uploader pour cet apprenant - - -
-

- Fonctionnalité d'upload à implémenter avec un composant FileUpload similaire à celui des rappels -

- -
-
-
+ { + refetchApprenants(); + setUploadDialogOpen(false); + toast.success("Attestation chargée avec succès"); + }} + /> {/* Dialog de prévisualisation */} diff --git a/server/gestionAttestationsDb.ts b/server/gestionAttestationsDb.ts index 25e1374..22c2a8a 100644 --- a/server/gestionAttestationsDb.ts +++ b/server/gestionAttestationsDb.ts @@ -102,11 +102,27 @@ export async function uploadAttestationDocument( return existing[0].id; } else { + // Récupérer les informations de l'inscription + const inscription = await db + .select({ + apprenantId: inscriptions.apprenantId, + sequenceId: inscriptions.sequenceId, + }) + .from(inscriptions) + .where(eq(inscriptions.id, inscriptionId)) + .limit(1); + + if (inscription.length === 0) { + throw new Error("Inscription not found"); + } + // Créer une nouvelle attestation const result = await db .insert(attestations) .values({ inscriptionId, + apprenantId: inscription[0].apprenantId, + sequenceId: inscription[0].sequenceId, documentUrl, documentS3Key, uploadedBy, diff --git a/todo.md b/todo.md index 3fec7f8..f96c9c5 100644 --- a/todo.md +++ b/todo.md @@ -853,3 +853,9 @@ - [x] Corriger la détection de la pièce jointe dans le tableau des rappels (vérification de nomFichier et urlFichier au lieu de pieceJointe) - [x] Corriger l'envoi de la pièce jointe dans les emails de rappel (support des fichiers locaux et S3) - [x] Corriger la corruption de la pièce jointe lors de l'envoi par email (ajout de l'encoding base64 pour SMTP/Nodemailer) +- [x] Modifier le comportement par défaut des menus de la sidebar : tous repliés à la connexion sauf le menu "Gestion" (MENU_VERSION incrémentée à 5) +- [x] Modifier le comportement des menus de la sidebar en mode accordéon : lorsqu'un menu s'ouvre, les autres se replient automatiquement +- [x] Implémenter la fonctionnalité du bouton "Charger" dans la gestion des attestations de formation (upload de PDF avec validation) +- [x] Corriger l'erreur "No procedure found on path attestations.uploadDocument" lors de l'upload d'attestations (utilisation de gestionAttestations.uploadDocument) +- [x] Corriger l'erreur SQL "Failed query: insert into attestations" lors de l'upload d'attestation manuelle (ajout de apprenantId et sequenceId) +- [x] Corriger l'erreur "refetchInscrits is not defined" lors de l'upload d'attestation manuelle (remplacement par refetchApprenants)