From 622432833156acfb280d0f9d51cc3a85350eb87f Mon Sep 17 00:00:00 2001 From: Manus Date: Fri, 5 Jun 2026 09:12:22 -0400 Subject: [PATCH] fix: export PDF A4 paysage, 1 seule page, taille police dynamique --- client/src/pages/VentilationFreePro.tsx | 64 ++++++++++++++++++------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/client/src/pages/VentilationFreePro.tsx b/client/src/pages/VentilationFreePro.tsx index 3df2e1b..c5e2455 100644 --- a/client/src/pages/VentilationFreePro.tsx +++ b/client/src/pages/VentilationFreePro.tsx @@ -88,32 +88,36 @@ function exportToPdf( importRecord: ImportRecord, lines: VentilationLine[] ) { - const doc = new jsPDF({ orientation: "portrait", unit: "mm", format: "a4" }); + // A4 paysage : 297mm × 210mm + const doc = new jsPDF({ orientation: "landscape", unit: "mm", format: "a4" }); + const pageW = 297; + const pageH = 210; + const margin = 14; // En-tête doc.setFontSize(14); doc.setFont("helvetica", "bold"); - doc.text("Ventiltion facture FREE PRO", 105, 15, { align: "center" }); + doc.text("Ventilation facture FREE PRO", pageW / 2, 14, { align: "center" }); // Date du mois const [moisStr, anneeStr] = importRecord.moisLabel.split("/"); const dateLabel = `01/${moisStr}/${anneeStr}`; - doc.setFontSize(12); - doc.text(dateLabel, 105, 22, { align: "center" }); + doc.setFontSize(11); + doc.text(dateLabel, pageW / 2, 21, { align: "center" }); // Date d'édition (haut droite) const now = new Date(); const editDate = `Edité le ${now.toLocaleDateString("fr-FR")} - ${now.toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" })}`; - doc.setFontSize(9); + doc.setFontSize(8); doc.setFont("helvetica", "normal"); - doc.text(editDate, 200, 10, { align: "right" }); + doc.text(editDate, pageW - margin, 9, { align: "right" }); // Référence pièce - doc.setFontSize(10); + doc.setFontSize(9); doc.setFont("helvetica", "bold"); - doc.text("ref_piece", 14, 32); + doc.text("ref_piece :", margin, 29); doc.setFont("helvetica", "normal"); - doc.text(importRecord.refPiece ?? "", 55, 32); + doc.text(importRecord.refPiece ?? "", margin + 25, 29); // Tableau principal const tableData = lines.map((l) => [ @@ -125,21 +129,45 @@ function exportToPdf( // Total général const totalCentimes = lines.reduce((s, l) => s + l.montantCentimes, 0); + // Calcul dynamique des largeurs pour tenir sur 1 page + const usableW = pageW - margin * 2; // ~269mm + const colStructure = usableW * 0.50; // ~134mm + const colType = usableW * 0.30; // ~81mm + const colMontant = usableW * 0.20; // ~54mm + + // Calcul de la taille de police pour tenir sur 1 page + // A4 paysage : ~170mm de hauteur utile (210 - 35 header - 10 footer) + const nbRows = lines.length + 2; // +1 header +1 footer + const availH = pageH - 38 - 12; // zone tableau + const rowH = Math.min(8, Math.floor(availH / nbRows)); + const fontSize = rowH >= 7 ? 9 : rowH >= 6 ? 8 : 7; + autoTable(doc, { - startY: 38, - head: [["structure", "Type", "Montant"]], + startY: 33, + head: [["Structure", "Type", "Montant TTC"]], body: tableData, foot: [["Total général", "", formatMontant(totalCentimes)]], - styles: { fontSize: 9, cellPadding: 2 }, - headStyles: { fillColor: [255, 255, 255], textColor: [0, 0, 0], fontStyle: "bold", lineWidth: 0.3, lineColor: [0, 0, 0] }, - footStyles: { fillColor: [255, 255, 255], textColor: [0, 0, 0], fontStyle: "bold", lineWidth: 0.3, lineColor: [0, 0, 0] }, + styles: { fontSize, cellPadding: rowH >= 7 ? 2 : 1.5, overflow: "linebreak" }, + headStyles: { + fillColor: [255, 255, 255], textColor: [0, 0, 0], + fontStyle: "bold", lineWidth: 0.3, lineColor: [0, 0, 0], + }, + footStyles: { + fillColor: [255, 255, 255], textColor: [0, 0, 0], + fontStyle: "bold", lineWidth: 0.3, lineColor: [0, 0, 0], + }, bodyStyles: { lineWidth: 0.1, lineColor: [180, 180, 180] }, columnStyles: { - 0: { cellWidth: 80 }, - 1: { cellWidth: 50 }, - 2: { cellWidth: 50, halign: "right" }, + 0: { cellWidth: colStructure }, + 1: { cellWidth: colType }, + 2: { cellWidth: colMontant, halign: "right" }, }, - alternateRowStyles: { fillColor: [255, 255, 255] }, + alternateRowStyles: { fillColor: [248, 248, 248] }, + // Forcer tout sur 1 page + pageBreak: "avoid", + rowPageBreak: "avoid", + margin: { left: margin, right: margin }, + tableWidth: usableW, }); const moisPad = moisStr.padStart(2, "0");