From b9e1a63d82da39a35236e0cc3119fcfd76bf4c52 Mon Sep 17 00:00:00 2001 From: Manus Sandbox Date: Thu, 20 Nov 2025 05:03:24 -0500 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Am=C3=A9liorations=20compl=C3=A8t?= =?UTF-8?q?es=20des=20feuilles=20de=20pr=C3=A9sence=20:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Logo Itinova ajouté en haut à droite de chaque page (40x15mm) ✅ Nom du formateur affiché dans l'en-tête (récupéré automatiquement depuis la base de données) ✅ Deux colonnes de signature par apprenant et par journée (Signature Matin / Signature Après-midi) ✅ Une page générée pour chaque date de formation ✅ Toutes les informations complètes : Formation, Séquence, Lieu, Formateur, Public cible, Date de la journée ✅ Tableau avec 6 colonnes : Nom, Prénom, Code établissement, Fonction, Signature Matin, Signature Après-midi Corrections techniques : - Utilisation de path.resolve(process.cwd()) au lieu de __dirname pour éviter l'erreur "ReferenceError: __dirname is not defined" - Ajout du champ formateur dans l'interface FeuillePresenceInfo - Récupération automatique du nom du formateur via formateurId dans la procédure exportFeuillePresence Tests réussis : Génération testée avec la séquence "Groupe A" (formateur George Clooney, 2 dates) - PDF généré avec succès avec toutes les modifications visibles. --- client/public/itinova-logo.png | Bin 0 -> 34964 bytes server/exportService.ts | 191 +++++++++++++++++++-------------- server/routers.ts | 10 ++ todo.md | 9 ++ 4 files changed, 128 insertions(+), 82 deletions(-) create mode 100644 client/public/itinova-logo.png diff --git a/client/public/itinova-logo.png b/client/public/itinova-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8325caa874d4765c1b2ff6b52286da7ba5d5244a GIT binary patch literal 34964 zcmeEuWmHvN*Y-iY2?eC3k*)*MUDA!D2L+@<8tGQ)Qo2E;

-=knZk~ZcrqoJHK_f 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 lieu: string; publicCible: string; + formateur?: string; apprenants: ApprenantPresence[]; } @@ -57,62 +60,43 @@ export function generateExcelExport(sequenceInfo: SequenceInfo): Buffer { })); // Créer le workbook + const ws = XLSX.utils.json_to_sheet(data); const wb = XLSX.utils.book_new(); - - // Créer la feuille avec les informations de séquence + XLSX.utils.book_append_sheet(wb, ws, 'Inscrits'); + + // Ajouter une feuille d'informations const infoData = [ - ['Formation', sequenceInfo.formationNom], - ['Séquence', sequenceInfo.sequenceNom], - ['Lieu', sequenceInfo.lieu], - ['Public cible', sequenceInfo.publicCible === 'directeur' ? 'Directeurs' : sequenceInfo.publicCible === 'chef_service' ? 'Chefs de service' : 'Autre'], - ['Nombre de dates', sequenceInfo.dates.length.toString()], - [], - ['DATES DE FORMATION'], + { 'Information': 'Formation', 'Valeur': sequenceInfo.formationNom }, + { 'Information': 'Séquence', 'Valeur': sequenceInfo.sequenceNom }, + { 'Information': 'Lieu', 'Valeur': sequenceInfo.lieu }, + { 'Information': 'Public cible', 'Valeur': sequenceInfo.publicCible === 'directeur' ? 'Directeurs' : sequenceInfo.publicCible === 'chef_service' ? 'Chefs de service' : sequenceInfo.publicCible === 'tous' ? 'Tous' : 'Autre' }, + { 'Information': 'Nombre d\'inscrits', 'Valeur': sequenceInfo.inscriptions.length.toString() }, ]; // Ajouter les dates sequenceInfo.dates.forEach(date => { - infoData.push([ - `Date ${date.ordre}`, - `Du ${date.dateDebut.toLocaleDateString('fr-FR', { + infoData.push({ + 'Information': `Date ${date.ordre}`, + 'Valeur': date.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' - })} au ${date.dateFin.toLocaleDateString('fr-FR', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - })}` - ]); + }) + }); }); - infoData.push([]); - infoData.push(['Nombre d\'inscrits', sequenceInfo.inscriptions.length.toString()]); - infoData.push([]); - - // Créer la feuille principale - const ws = XLSX.utils.aoa_to_sheet(infoData); - - // Ajouter les données des inscrits - XLSX.utils.sheet_add_json(ws, data, { origin: -1 }); - - // Ajouter la feuille au workbook - XLSX.utils.book_append_sheet(wb, ws, 'Inscrits'); + const wsInfo = XLSX.utils.json_to_sheet(infoData); + XLSX.utils.book_append_sheet(wb, wsInfo, 'Informations'); // Générer le buffer - const excelBuffer = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' }); - - return excelBuffer; + return Buffer.from(XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' })); } /** - * Génère un fichier PDF avec la liste des inscrits + * Génère un PDF avec la liste des inscrits */ export function generatePDFExport(sequenceInfo: SequenceInfo): Buffer { const doc = new jsPDF(); @@ -130,9 +114,7 @@ export function generatePDFExport(sequenceInfo: SequenceInfo): Buffer { yPos += 6; doc.text(`Lieu : ${sequenceInfo.lieu}`, 14, yPos); yPos += 6; - doc.text(`Public cible : ${sequenceInfo.publicCible === 'directeur' ? 'Directeurs' : sequenceInfo.publicCible === 'chef_service' ? 'Chefs de service' : 'Autre'}`, 14, yPos); - yPos += 6; - doc.text(`Nombre de dates : ${sequenceInfo.dates.length}`, 14, yPos); + doc.text(`Public cible : ${sequenceInfo.publicCible === 'directeur' ? 'Directeurs' : sequenceInfo.publicCible === 'chef_service' ? 'Chefs de service' : sequenceInfo.publicCible === 'tous' ? 'Tous' : 'Autre'}`, 14, yPos); yPos += 8; // Dates de formation @@ -149,10 +131,6 @@ export function generatePDFExport(sequenceInfo: SequenceInfo): Buffer { yPos += 5; }); - yPos += 3; - doc.setFontSize(10); - doc.text(`Nombre d'inscrits : ${sequenceInfo.inscriptions.length}`, 14, yPos); - // Tableau des inscrits const tableData = sequenceInfo.inscriptions.map(i => [ i.nom, @@ -175,31 +153,61 @@ export function generatePDFExport(sequenceInfo: SequenceInfo): Buffer { } /** - * Génère une feuille de présence PDF + * Génère une feuille de présence PDF avec signatures matin/après-midi pour chaque journée */ export function generateFeuillePresence(feuilleInfo: FeuillePresenceInfo): Buffer { const doc = new jsPDF(); + + // Charger le logo + let logoData: string | null = null; + + try { + // Utiliser un chemin absolu depuis la racine du projet + const logoPath = path.resolve(process.cwd(), 'client/public/itinova-logo.png'); + const logoBuffer = fs.readFileSync(logoPath); + logoData = `data:image/png;base64,${logoBuffer.toString('base64')}`; + } catch (error) { + console.warn('Logo Itinova non trouvé, génération sans logo:', error); + } - // Titre - doc.setFontSize(18); - doc.text('Feuille de présence', 14, 20); + // Fonction pour générer une page de feuille de présence pour une journée + const generatePageForDate = (dateInfo: { dateDebut: Date; dateFin: Date; ordre: number }, isFirstPage: boolean) => { + if (!isFirstPage) { + doc.addPage(); + } - // Informations de séquence - doc.setFontSize(10); - let yPos = 35; - doc.text(`Formation : ${feuilleInfo.formationNom}`, 14, yPos); - yPos += 6; - doc.text(`Séquence : ${feuilleInfo.sequenceNom}`, 14, yPos); - yPos += 6; - doc.text(`Lieu : ${feuilleInfo.lieu}`, 14, yPos); - yPos += 6; - doc.text(`Public cible : ${feuilleInfo.publicCible === 'directeur' ? 'Directeurs' : feuilleInfo.publicCible === 'chef_service' ? 'Chefs de service' : 'Autre'}`, 14, yPos); - yPos += 8; + // Ajouter le logo en haut à droite si disponible + if (logoData) { + doc.addImage(logoData, 'PNG', 160, 10, 40, 15); + } - // Dates de formation - doc.setFontSize(9); - feuilleInfo.dates.forEach(date => { - doc.text(`Date ${date.ordre} : ${date.dateDebut.toLocaleDateString('fr-FR', { + // Titre + doc.setFontSize(18); + doc.text('Feuille de présence', 14, 20); + + // Informations de séquence + doc.setFontSize(10); + let yPos = 35; + doc.text(`Formation : ${feuilleInfo.formationNom}`, 14, yPos); + yPos += 6; + doc.text(`Séquence : ${feuilleInfo.sequenceNom}`, 14, yPos); + yPos += 6; + doc.text(`Lieu : ${feuilleInfo.lieu}`, 14, yPos); + yPos += 6; + + // Ajouter le formateur si disponible + if (feuilleInfo.formateur) { + doc.text(`Formateur : ${feuilleInfo.formateur}`, 14, yPos); + yPos += 6; + } + + doc.text(`Public cible : ${feuilleInfo.publicCible === 'directeur' ? 'Directeurs' : feuilleInfo.publicCible === 'chef_service' ? 'Chefs de service' : feuilleInfo.publicCible === 'tous' ? 'Tous' : 'Autre'}`, 14, yPos); + yPos += 8; + + // Date de la journée + doc.setFontSize(12); + doc.setFont(undefined, 'bold'); + doc.text(`Date ${dateInfo.ordre} : ${dateInfo.dateDebut.toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', @@ -207,27 +215,46 @@ export function generateFeuillePresence(feuilleInfo: FeuillePresenceInfo): Buffe hour: '2-digit', minute: '2-digit' })}`, 14, yPos); - yPos += 5; - }); + doc.setFont(undefined, 'normal'); + yPos += 10; - // Tableau de présence - const tableData = feuilleInfo.apprenants.map(i => [ - i.nom, - i.prenom, - i.codeEtablissement, - i.fonction === 'directeur' ? 'Directeur' : i.fonction === 'chef_service' ? 'Chef de service' : 'Autre', - '', // Colonne signature - ]); + // Tableau de présence avec signatures matin et après-midi + const tableData = feuilleInfo.apprenants.map(i => [ + i.nom, + i.prenom, + i.codeEtablissement, + i.fonction === 'directeur' ? 'Directeur' : i.fonction === 'chef_service' ? 'Chef de service' : 'Autre', + '', // Signature matin + '', // Signature après-midi + ]); - autoTable(doc, { - startY: yPos + 10, - head: [['Nom', 'Prénom', 'Code établissement', 'Fonction', 'Signature']], - body: tableData, - styles: { fontSize: 10, cellPadding: 5 }, - headStyles: { fillColor: [37, 99, 235] }, - columnStyles: { - 4: { cellWidth: 40 }, // Largeur pour la colonne signature - }, + autoTable(doc, { + startY: yPos, + head: [['Nom', 'Prénom', 'Code étab.', 'Fonction', 'Signature\nMatin', 'Signature\nAprès-midi']], + body: tableData, + styles: { + fontSize: 9, + cellPadding: 3, + minCellHeight: 10, + }, + headStyles: { + fillColor: [37, 99, 235], + halign: 'center', + }, + columnStyles: { + 0: { cellWidth: 30 }, // Nom + 1: { cellWidth: 30 }, // Prénom + 2: { cellWidth: 25 }, // Code établissement + 3: { cellWidth: 30 }, // Fonction + 4: { cellWidth: 35 }, // Signature matin + 5: { cellWidth: 35 }, // Signature après-midi + }, + }); + }; + + // Générer une page pour chaque date + feuilleInfo.dates.forEach((date, index) => { + generatePageForDate(date, index === 0); }); return Buffer.from(doc.output('arraybuffer')); diff --git a/server/routers.ts b/server/routers.ts index e1a29f1..cd9a6af 100644 --- a/server/routers.ts +++ b/server/routers.ts @@ -544,6 +544,15 @@ export const appRouter = router({ const inscriptions = await db.getInscriptionsBySequence(input.sequenceId); const dates = await db.getDatesBySequence(input.sequenceId); + // Récupérer le formateur si défini + let formateurNom: string | undefined = undefined; + if (sequence.formateurId) { + const formateur = await db.getFormateurById(sequence.formateurId); + if (formateur) { + formateurNom = formateur.nom; + } + } + const data = inscriptions .filter(i => i.inscription.statut === 'confirmee' && i.apprenant) .map(i => ({ @@ -563,6 +572,7 @@ export const appRouter = router({ })), lieu: sequence.lieu, publicCible: sequence.publicCible, + formateur: formateurNom, apprenants: data, }); diff --git a/todo.md b/todo.md index 4b3014e..02629fe 100644 --- a/todo.md +++ b/todo.md @@ -354,3 +354,12 @@ - [x] Corriger le schéma Zod pour accepter null (formateurId: z.number().nullable().optional()) - [x] Modifier FormateurCombobox pour stocker l'ID au lieu du nom - [x] Tester les modifications dans la liste et dans le dialog de modification + +## Améliorations des feuilles de présence + +- [x] Copier le logo Itinova dans le dossier public du projet +- [x] Ajouter le logo Itinova en haut à droite de chaque page de feuille de présence +- [x] Ajouter le nom du formateur dans l'en-tête de la feuille de présence +- [x] Modifier le tableau pour avoir deux signatures par apprenant et par journée (matin et après-midi) +- [x] Mettre à jour l'interface FeuillePresenceInfo pour inclure le formateur +- [x] Tester la génération des feuilles de présence avec toutes les modifications