fix: subcontractor PDF title + multi-page support

- '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 <noreply@anthropic.com>
This commit is contained in:
Krao Hasanee
2026-06-08 15:58:42 -04:00
parent e450f1b0d1
commit fa8ade83d6
2 changed files with 61 additions and 25 deletions
+2 -1
View File
@@ -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 *)"
]
}
}
+59 -24
View File
@@ -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 },
);