mirror of
https://github.com/kbenestad/invoice.git
synced 2026-06-18 08:04:32 +00:00
Update PDF to kbenestad colour scheme and smart filename
- Replace dark navy (30,45,69) with accent blue #2f6fed (47,111,237) throughout: issuer name, invoice word, charge-to name, table header bar, divider line, to-pay bar - Named colour constants (ACCENT, BODY, MUTED, BORDER, WHITE, STRIPE) for clarity - Filename: [ISSUER]_[YYYYMMDD]_[INVOICENUMBER].pdf with non-alphanumeric chars sanitised https://claude.ai/code/session_01MkM7p8Us3L8YAfLKGA13NS
This commit is contained in:
parent
46d2c95d94
commit
a03b0e0bef
1 changed files with 46 additions and 33 deletions
|
|
@ -1613,19 +1613,27 @@ function buildPDF() {
|
|||
let y = MT;
|
||||
let ly = y, ry = y;
|
||||
|
||||
if (sName) { fb(13); tc(30,45,69); tL(sName, ML, ly); ly += 6; }
|
||||
fn(8.5); tc(75,85,99);
|
||||
// Accent: #2f6fed = rgb(47,111,237) Muted text: rgb(107,114,128) Body: rgb(17,24,39)
|
||||
const ACCENT = [47,111,237];
|
||||
const BODY = [17,24,39];
|
||||
const MUTED = [107,114,128];
|
||||
const BORDER = [209,213,219];
|
||||
const WHITE = [255,255,255];
|
||||
const STRIPE = [249,250,251];
|
||||
|
||||
if (sName) { fb(13); tc(...ACCENT); tL(sName, ML, ly); ly += 6; }
|
||||
fn(8.5); tc(...MUTED);
|
||||
[...sAddr, sCntry].filter(Boolean).forEach(l => { tL(l, ML, ly); ly += 4.5; });
|
||||
if (sPh || sEm || sTax) {
|
||||
const parts = [];
|
||||
if (sPh) parts.push(`${td("sender-phone")}: ${sPh}`);
|
||||
if (sEm) parts.push(`${td("sender-email")}: ${sEm}`);
|
||||
if (sTax) parts.push(`${td("vat-id")}: ${sTax}`);
|
||||
fn(8); tc(107,114,128);
|
||||
fn(8); tc(...MUTED);
|
||||
sp(parts.join(" "), LW).forEach(l => { tL(l, ML, ly); ly += 4; }); ly += 1;
|
||||
}
|
||||
|
||||
fb(24); tc(30,45,69); tR(td("invoice"), XR, ry); ry += 10;
|
||||
fb(24); tc(...ACCENT); tR(td("invoice"), XR, ry); ry += 10;
|
||||
const metaRows = [
|
||||
iNo ? [td("invoice-no"), iNo] : null,
|
||||
iDate ? [td("invoice-date"), fmtDate(iDate)] : null,
|
||||
|
|
@ -1633,20 +1641,20 @@ function buildPDF() {
|
|||
iCur ? [td("invoice-currency"), iCur] : null,
|
||||
].filter(Boolean);
|
||||
metaRows.forEach(([lbl, val]) => {
|
||||
fn(8.5); tc(107,114,128); tR(lbl + ":", XR - 42, ry);
|
||||
fb(8.5); tc(17,24,39); tR(val, XR, ry);
|
||||
fn(8.5); tc(...MUTED); tR(lbl + ":", XR - 42, ry);
|
||||
fb(8.5); tc(...BODY); tR(val, XR, ry);
|
||||
ry += 5;
|
||||
});
|
||||
|
||||
const row1Y = Math.max(ly, ry) + 4;
|
||||
dc(209,213,219); doc.setLineWidth(0.3);
|
||||
dc(...BORDER); doc.setLineWidth(0.3);
|
||||
doc.line(ML, row1Y, XR, row1Y);
|
||||
|
||||
let ly2 = row1Y + 5, ry2 = row1Y + 5;
|
||||
fb(7); tc(107,114,128); tL(td("charge-to").toUpperCase(), ML, ly2); ly2 += 5;
|
||||
fb(7); tc(...MUTED); tL(td("charge-to").toUpperCase(), ML, ly2); ly2 += 5;
|
||||
if (ctName) {
|
||||
fb(10); tc(30,45,69); tL(ctName, ML, ly2); ly2 += 5.5;
|
||||
fn(8.5); tc(17,24,39);
|
||||
fb(10); tc(...ACCENT); tL(ctName, ML, ly2); ly2 += 5.5;
|
||||
fn(8.5); tc(...BODY);
|
||||
[...ctAddr, ctCntry].filter(Boolean).forEach(l => { tL(l, ML, ly2); ly2 += 4.5; });
|
||||
const ctParts = [];
|
||||
if (ctPh) ctParts.push(`${td("charge-to-phone")}: ${ctPh}`);
|
||||
|
|
@ -1654,16 +1662,16 @@ function buildPDF() {
|
|||
if (ctVat) ctParts.push(`${td("vat-id")}: ${ctVat}`);
|
||||
if (ctReg) ctParts.push(`${td("registration-no")}: ${ctReg}`);
|
||||
if (ctParts.length) {
|
||||
fn(8); tc(107,114,128);
|
||||
fn(8); tc(...MUTED);
|
||||
sp(ctParts.join(" "), LW).forEach(l => { tL(l, ML, ly2); ly2 += 4; }); ly2 += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (pTerm > 0 || showBank) {
|
||||
fb(7); tc(107,114,128); tL(td("payment").toUpperCase(), XM_L, ry2); ry2 += 5;
|
||||
fb(7); tc(...MUTED); tL(td("payment").toUpperCase(), XM_L, ry2); ry2 += 5;
|
||||
if (pTerm > 0) {
|
||||
const ts = `${td("payment-terms")}: ${pTerm} ${td("payment-days")}${pPayBy ? ` ${td("pay-by")}: ${pPayBy}` : ""}`;
|
||||
fn(8.5); tc(17,24,39); tL(ts, XM_L, ry2); ry2 += 5;
|
||||
fn(8.5); tc(...BODY); tL(ts, XM_L, ry2); ry2 += 5;
|
||||
}
|
||||
if (showBank) {
|
||||
const LLBL = 46;
|
||||
|
|
@ -1674,22 +1682,22 @@ function buildPDF() {
|
|||
(pBadr1||pBadr2) ? [td("bank-address"), [pBadr1,pBadr2].filter(Boolean).join(", ")] : null,
|
||||
].filter(Boolean);
|
||||
payRows.forEach(([lbl, val]) => {
|
||||
fn(8); tc(107,114,128); tL(lbl + ":", XM_L, ry2);
|
||||
fn(8.5); tc(17,24,39);
|
||||
fn(8); tc(...MUTED); tL(lbl + ":", XM_L, ry2);
|
||||
fn(8.5); tc(...BODY);
|
||||
const wrapped = sp(val, LW - LLBL - 2);
|
||||
wrapped.forEach((line, i) => tL(line, XM_L + LLBL, ry2 + i * 4));
|
||||
ry2 += Math.max(4.5, wrapped.length * 4);
|
||||
});
|
||||
if (pRef) {
|
||||
fn(8); tc(107,114,128); tL(td("payment-ref") + ":", XM_L, ry2);
|
||||
fb(8.5); tc(17,24,39); tL(pRef, XM_L + LLBL, ry2); ry2 += 5;
|
||||
fn(8); tc(...MUTED); tL(td("payment-ref") + ":", XM_L, ry2);
|
||||
fb(8.5); tc(...BODY); tL(pRef, XM_L + LLBL, ry2); ry2 += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
y = Math.max(ly2, ry2) + 5;
|
||||
|
||||
dc(30,45,69); doc.setLineWidth(0.6);
|
||||
dc(...ACCENT); doc.setLineWidth(0.6);
|
||||
doc.line(ML, y, XR, y); y += 6;
|
||||
|
||||
// ── LINE ITEMS TABLE ──────────────────────────────────────────────────────
|
||||
|
|
@ -1700,9 +1708,9 @@ function buildPDF() {
|
|||
|
||||
if (y + TH > PH - 40) { doc.addPage(); y = MT; }
|
||||
|
||||
fc(30,45,69); dc(255,255,255); doc.setLineWidth(0);
|
||||
fc(...ACCENT); dc(...WHITE); doc.setLineWidth(0);
|
||||
doc.rect(ML, y, CW, TH, "F");
|
||||
fb(8); tc(255,255,255);
|
||||
fb(8); tc(...WHITE);
|
||||
tL(td("qty"), xQ+2, y+4.8);
|
||||
tL(td("uom"), xU+2, y+4.8);
|
||||
tL(td("description"), xD+2, y+4.8);
|
||||
|
|
@ -1727,27 +1735,27 @@ function buildPDF() {
|
|||
if (y + rh > PH - 30) { doc.addPage(); y = MT; }
|
||||
|
||||
if (idx % 2 === 1) {
|
||||
fc(249,250,251); dc(255,255,255); doc.setLineWidth(0);
|
||||
fc(...STRIPE); dc(...WHITE); doc.setLineWidth(0);
|
||||
doc.rect(ML, y, CW, rh, "F");
|
||||
}
|
||||
|
||||
dc(209,213,219); doc.setLineWidth(0.1);
|
||||
dc(...BORDER); doc.setLineWidth(0.1);
|
||||
doc.line(ML, y+rh, XR, y+rh);
|
||||
|
||||
const yt = y + 5;
|
||||
const qStr = row.qty % 1 === 0 ? String(row.qty) : fmt(row.qty);
|
||||
|
||||
fn(8.5); tc(17,24,39);
|
||||
fn(8.5); tc(...BODY);
|
||||
tL(qStr, xQ+2, yt);
|
||||
tL(row.uomLbl, xU+2, yt);
|
||||
dLines.forEach((dline, li) => tL(dline, xD+2, yt + li*3.8));
|
||||
fn(8.5); tc(17,24,39); tR(fmt(row.price), xP+CP-2, yt);
|
||||
fb(8.5); tc(17,24,39); tR(fmt(row.tot), XR-2, yt);
|
||||
fn(8.5); tc(...BODY); tR(fmt(row.price), xP+CP-2, yt);
|
||||
fb(8.5); tc(...BODY); tR(fmt(row.tot), XR-2, yt);
|
||||
|
||||
if (row.fxNote) {
|
||||
const fxStr = `${td("per-item")}: ${row.fxNote.cur} ${fmt(row.fxNote.per)} `
|
||||
+ `(${(+row.fxNote.rate).toFixed(5)} ${row.fxNote.cur} = 1 ${iCur})`;
|
||||
fn(7); tc(107,114,128);
|
||||
fn(7); tc(...MUTED);
|
||||
const fxLines = sp(fxStr, CD + CP - 4);
|
||||
fxLines.forEach((fl, fi) => tL(fl, xD+2, y + ROW_H + descH + 3.2 + fi * 3.5));
|
||||
}
|
||||
|
|
@ -1771,21 +1779,26 @@ function buildPDF() {
|
|||
|
||||
totRows.forEach(([lbl, val]) => {
|
||||
if (y + TRH > PH - 20) { doc.addPage(); y = MT; }
|
||||
fn(8.5); tc(107,114,128); tR(lbl, TLBX, y+4.5);
|
||||
fn(8.5); tc(17,24,39); tR(val, XR-2, y+4.5);
|
||||
dc(209,213,219); doc.setLineWidth(0.1);
|
||||
fn(8.5); tc(...MUTED); tR(lbl, TLBX, y+4.5);
|
||||
fn(8.5); tc(...BODY); tR(val, XR-2, y+4.5);
|
||||
dc(...BORDER); doc.setLineWidth(0.1);
|
||||
doc.line(TX, y+TRH, XR, y+TRH);
|
||||
y += TRH;
|
||||
});
|
||||
|
||||
if (y + 9 > PH - 10) { doc.addPage(); y = MT; }
|
||||
fc(30,45,69); dc(255,255,255); doc.setLineWidth(0);
|
||||
fc(...ACCENT); dc(...WHITE); doc.setLineWidth(0);
|
||||
doc.rect(TX, y, TW, 9, "F");
|
||||
fn(9); tc(180,195,215); tR(td("to-pay"), TLBX, y+5.8);
|
||||
fb(11); tc(255,255,255); tR(fmt(toPay), XR-2, y+5.8);
|
||||
fn(9); tc(180,210,255); tR(td("to-pay"), TLBX, y+5.8);
|
||||
fb(11); tc(...WHITE); tR(fmt(toPay), XR-2, y+5.8);
|
||||
y += 9;
|
||||
|
||||
doc.save(iNo ? `invoice-${iNo}.pdf` : "invoice.pdf");
|
||||
const safeName = s => (s || "").replace(/[^a-zA-Z0-9_\-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
||||
const fnIssuer = safeName(sName);
|
||||
const fnDate = (iDate || "").replace(/-/g, "");
|
||||
const fnNo = safeName(iNo);
|
||||
const parts = [fnIssuer, fnDate, fnNo].filter(Boolean);
|
||||
doc.save(parts.length ? parts.join("_") + ".pdf" : "invoice.pdf");
|
||||
}
|
||||
|
||||
// ── Update FX labels when currency or invoice currency changes ────────────────
|
||||
|
|
|
|||
Loading…
Reference in a new issue