From fa8ade83d6db68679e21d752f2bafeaecd649c4c Mon Sep 17 00:00:00 2001 From: Krao Hasanee Date: Mon, 8 Jun 2026 15:58:42 -0400 Subject: [PATCH] fix: subcontractor PDF title + multi-page support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 'PURCHASE ORDER' → 'SUBCONTRACTOR INVOICE' - Items loop now calls addPage() when hitting safeBottom (792-80pt) - Continuation pages get header with invoice number + '(continued)' - Summary/Notes sections also page-break if needed - Footer text updated to match invoice context - Footer line position dynamic (tracks y), not hardcoded at 704 Co-Authored-By: Claude Sonnet 4.6 --- .claude/settings.local.json | 3 +- src/lib/invoice.js | 83 ++++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 1216c02..976b369 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -16,7 +16,8 @@ "Bash(sed -n '1,15p' src/pages/external/ExternalMyInvoices.jsx)", "Bash(sed -n '1,15p' src/pages/client/ClientMyInvoices.jsx)", "Bash(git commit -q -m 'fix: restore popupOverlayStyle import in ExternalMyInvoices *)", - "Bash(git commit -q -m 'style: increase card-bg alpha to match Safari rendering *)" + "Bash(git commit -q -m 'style: increase card-bg alpha to match Safari rendering *)", + "Bash(git commit -q -m 'fix: subcontractor PDF title + multi-page support *)" ] } } diff --git a/src/lib/invoice.js b/src/lib/invoice.js index 636f3f8..1e5f80b 100644 --- a/src/lib/invoice.js +++ b/src/lib/invoice.js @@ -624,7 +624,7 @@ export async function generateSubcontractorPOPDF(po, options = {}) { doc.setTextColor(30, 30, 30); doc.setFont('helvetica', 'bold'); doc.setFontSize(22); - doc.text('PURCHASE ORDER', margin, y); + doc.text('SUBCONTRACTOR INVOICE', margin, y); doc.setFontSize(10); doc.setTextColor(90, 90, 90); @@ -678,6 +678,29 @@ export async function generateSubcontractorPOPDF(po, options = {}) { }); y += rows.length * 28 + 22; + const pageHeight = 792; + const safeBottom = pageHeight - 80; // leave room for footer + + function addContinuationHeader() { + doc.addPage(); + doc.setFillColor(20, 20, 20); + doc.rect(0, 0, pageWidth, headerH, 'F'); + if (logo) { + const logoW = 96; + const logoH = logoW / (logo.naturalWidth / logo.naturalHeight); + doc.addImage(logo, 'PNG', margin, 16, logoW, logoH); + } + doc.setFont('helvetica', 'normal'); + doc.setFontSize(9); + doc.setTextColor(170, 170, 170); + doc.text('1855.368.7434 | hello@fourgebranding.com | www.fourgebranding.com', rightEdge, 35, { align: 'right' }); + doc.setFont('helvetica', 'normal'); + doc.setFontSize(9); + doc.setTextColor(120, 120, 120); + doc.text(`${poNumber} (continued)`, margin, headerH + 14); + return headerH + 28; + } + if (po.items?.length > 0) { doc.setFont('helvetica', 'bold'); doc.setFontSize(11); @@ -689,38 +712,49 @@ export async function generateSubcontractorPOPDF(po, options = {}) { .slice() .sort((a, b) => Number(a.sort_order || 0) - Number(b.sort_order || 0)); - sortedItems.forEach((item, index) => { - const rowTop = y + index * 28; - if (index % 2 === 0) { + let rowIndex = 0; + for (const item of sortedItems) { + if (y + 28 > safeBottom) { + y = addContinuationHeader(); + rowIndex = 0; + } + if (rowIndex % 2 === 0) { doc.setFillColor(248, 248, 248); - doc.rect(tableX, rowTop - 14, tableW, 28, 'F'); + doc.rect(tableX, y - 14, tableW, 28, 'F'); } doc.setFont('helvetica', 'normal'); doc.setFontSize(10); doc.setTextColor(45, 45, 45); - doc.text(item.description || item.task?.title || 'Work Item', tableX + 12, rowTop); + doc.text(item.description || item.task?.title || 'Work Item', tableX + 12, y); doc.setFont('helvetica', 'bold'); - doc.text(formatCurrency(item.amount), tableX + tableW - 12, rowTop, { align: 'right' }); - }); - - y += sortedItems.length * 28 + 22; + doc.text(formatCurrency(item.amount), tableX + tableW - 12, y, { align: 'right' }); + y += 28; + rowIndex++; + } + y += 22; } - doc.setFont('helvetica', 'bold'); - doc.setFontSize(11); - doc.setTextColor(30, 30, 30); - doc.text(po.items?.length > 0 ? 'Summary' : 'Scope of Work', margin, y); - y += 16; + if (y + 40 > safeBottom) y = addContinuationHeader(); - doc.setFont('helvetica', 'normal'); - doc.setFontSize(10); - doc.setTextColor(70, 70, 70); - const scopeLines = doc.splitTextToSize(po.description || '', pageWidth - margin * 2); - doc.text(scopeLines, margin, y); - y += scopeLines.length * 13 + 18; + if (po.description) { + doc.setFont('helvetica', 'bold'); + doc.setFontSize(11); + doc.setTextColor(30, 30, 30); + doc.text(po.items?.length > 0 ? 'Summary' : 'Scope of Work', margin, y); + y += 16; + + doc.setFont('helvetica', 'normal'); + doc.setFontSize(10); + doc.setTextColor(70, 70, 70); + const scopeLines = doc.splitTextToSize(po.description, pageWidth - margin * 2); + doc.text(scopeLines, margin, y); + y += scopeLines.length * 13 + 18; + } if (po.notes) { + if (y + 40 > safeBottom) y = addContinuationHeader(); doc.setFont('helvetica', 'bold'); + doc.setFontSize(11); doc.setTextColor(30, 30, 30); doc.text('Notes', margin, y); y += 16; @@ -730,15 +764,16 @@ export async function generateSubcontractorPOPDF(po, options = {}) { doc.text(noteLines, margin, y); } + const footerLineY = Math.max(y + 20, pageHeight - 60); doc.setDrawColor(220, 220, 220); - doc.line(margin, 704, rightEdge, 704); + doc.line(margin, footerLineY, rightEdge, footerLineY); doc.setFont('helvetica', 'normal'); doc.setFontSize(9); doc.setTextColor(120, 120, 120); doc.text( - 'This purchase order authorizes the subcontractor work described above. Payment is subject to Fourge Branding approval and completion of assigned work.', + 'This invoice authorizes payment for the subcontractor work described above. Payment is subject to Fourge Branding approval and completion of assigned work.', margin, - 724, + footerLineY + 16, { maxWidth: pageWidth - margin * 2 }, );