mirror of
https://github.com/kbenestad/reimburse.git
synced 2026-06-18 08:04:31 +00:00
- Header: remove initials box, match timesheet style (org name left, REIMBURSEMENT right, same size, muted subtitles below each) - Replace thin hairline after header with thick accent-colour divider (2.5pt) - Info strip (Staff/Period/Currency): fix unequal vertical padding — labels were crowded to top border; now symmetric using cap-height maths - Item section header: replace bare left stripe with full-width surface-2 strip, equal visual padding above and below text, accent bar full height of strip - Reduce excessive whitespace after receipt refs and before grand total divider - Apply same equal-padding fix to continuation header (page 2+) https://claude.ai/code/session_01JyuActqTJG5tuRQNLmT7fZ
This commit is contained in:
parent
1b6ea1b875
commit
df8816dfb4
1 changed files with 38 additions and 44 deletions
|
|
@ -1381,12 +1381,13 @@ async function generatePDF() {
|
|||
|
||||
// Continuation header: light strip with staff + period
|
||||
function drawContHeader() {
|
||||
const stripH = szXs + lh + 14;
|
||||
const cPad = 7;
|
||||
const stripH = 2 * cPad + szXs + 5 + sz;
|
||||
pg.drawRectangle({ x: 0, y: y - stripH, width: pageW, height: stripH, color: clrSurface2 });
|
||||
pg.drawLine({ start:{x:0, y}, end:{x:pageW, y}, thickness:0.5, color:clrBorder });
|
||||
pg.drawLine({ start:{x:0, y:y-stripH}, end:{x:pageW, y:y-stripH}, thickness:0.5, color:clrBorder });
|
||||
|
||||
const labY = y - 7;
|
||||
const labY = y - Math.round(cPad + szXs * 0.72);
|
||||
const valY = labY - szXs - 5;
|
||||
lbl('Staff', M.left, labY);
|
||||
lbl('Period', M.left + W * 0.45, labY);
|
||||
|
|
@ -1399,47 +1400,38 @@ async function generatePDF() {
|
|||
// ── Page 1 ────────────────────────────────────────────────────────────────
|
||||
addPage(true);
|
||||
|
||||
// Header: brand block left | doc title right
|
||||
// Header: org name left | REIMBURSEMENT right (timesheet style)
|
||||
const hdrTopY = y;
|
||||
const boxSize = 44;
|
||||
const nowD = new Date();
|
||||
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||
|
||||
const hdrNameSize = szLg; // org name and title same size
|
||||
const hdrNameY = hdrTopY - Math.round(hdrNameSize * 0.28); // cap top near hdrTopY
|
||||
|
||||
if (logoImage) {
|
||||
const maxWLogo = (CFG['logo-maxwidth'] || 4) * 28.3465;
|
||||
const scale = Math.min(maxWLogo / logoImage.width, boxSize / logoImage.height, 1);
|
||||
const scale = Math.min(maxWLogo / logoImage.width, hdrNameSize * 1.6 / logoImage.height, 1);
|
||||
const lw = logoImage.width * scale, lhImg = logoImage.height * scale;
|
||||
pg.drawImage(logoImage, { x: M.left, y: hdrTopY - lhImg, width: lw, height: lhImg });
|
||||
} else {
|
||||
// Initials box (surface-2 fill, border, accent initials)
|
||||
const raw = (CFG.organization || 'ORG').trim();
|
||||
const initials = raw.split(/\s+/).map(w => w[0] || '').join('').toUpperCase().slice(0, 2) || '??';
|
||||
pg.drawRectangle({ x: M.left, y: hdrTopY - boxSize, width: boxSize, height: boxSize,
|
||||
color: clrSurface2, borderColor: clrBorder, borderWidth: 0.5 });
|
||||
const initW = fontBold.widthOfTextAtSize(initials, szLg);
|
||||
pg.drawText(initials, { x: M.left + (boxSize - initW) / 2, y: hdrTopY - boxSize / 2 - szLg * 0.35,
|
||||
size: szLg, font: fontBold, color: clrAccent });
|
||||
pg.drawText(CFG.organization || '', { x: M.left, y: hdrNameY, size: hdrNameSize, font: fontBold, color: clrText });
|
||||
}
|
||||
const hdrSubtY = hdrNameY - hdrNameSize - 5;
|
||||
pg.drawText('Expense reimbursement', { x: M.left, y: hdrSubtY, size: szSm, font: fontBody, color: clrMuted });
|
||||
|
||||
// Org name + subtitle
|
||||
const orgX = M.left + boxSize + 11;
|
||||
pg.drawText(CFG.organization || '', { x: orgX, y: hdrTopY - 15, size: sz + 2, font: fontBold, color: clrText });
|
||||
pg.drawText('Expense reimbursement', { x: orgX, y: hdrTopY - 15 - (sz + 2) - 4, size: szSm, font: fontBody, color: clrMuted });
|
||||
const titleStr = 'REIMBURSEMENT';
|
||||
const titleW = fontBold.widthOfTextAtSize(titleStr, hdrNameSize);
|
||||
pg.drawText(titleStr, { x: M.left + W - titleW, y: hdrNameY, size: hdrNameSize, font: fontBold, color: clrText });
|
||||
|
||||
// Title + claim date (right-aligned)
|
||||
const titleStr = 'Reimbursement';
|
||||
const titleW = fontBold.widthOfTextAtSize(titleStr, szLg);
|
||||
pg.drawText(titleStr, { x: M.left + W - titleW, y: hdrTopY - 15, size: szLg, font: fontBold, color: clrText });
|
||||
|
||||
const nowD = new Date();
|
||||
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||
const claimDate = `Claim · ${nowD.getDate()} ${MONTHS[nowD.getMonth()]} ${nowD.getFullYear()}`;
|
||||
const claimDateW = fontBody.widthOfTextAtSize(claimDate, szSm);
|
||||
pg.drawText(claimDate, { x: M.left + W - claimDateW, y: hdrTopY - 15 - szLg - 4, size: szSm, font: fontBody, color: clrMuted });
|
||||
pg.drawText(claimDate, { x: M.left + W - claimDateW, y: hdrSubtY, size: szSm, font: fontBody, color: clrMuted });
|
||||
|
||||
y = hdrTopY - boxSize - 10;
|
||||
y = hdrSubtY - szSm - 12;
|
||||
|
||||
// Hairline below header
|
||||
pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:0.5, color:clrBorder });
|
||||
y -= 12;
|
||||
// Thick accent divider below header
|
||||
pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:2.5, color:clrAccent });
|
||||
y -= 16;
|
||||
|
||||
// Intro text
|
||||
if (CFG.intro) {
|
||||
|
|
@ -1448,13 +1440,14 @@ async function generatePDF() {
|
|||
y -= 6;
|
||||
}
|
||||
|
||||
// Staff / Period / Currency info block
|
||||
const infoH = szXs + lh + 14;
|
||||
// Staff / Period / Currency info block — equal visual padding top and bottom
|
||||
const infoPad = 7;
|
||||
const infoH = 2 * infoPad + szXs + 5 + sz;
|
||||
pg.drawRectangle({ x: M.left - 8, y: y - infoH, width: W + 16, height: infoH, color: clrSurface2 });
|
||||
pg.drawLine({ start:{x:M.left-8, y}, end:{x:M.left+W+8, y}, thickness:0.5, color:clrBorder });
|
||||
pg.drawLine({ start:{x:M.left-8, y:y-infoH}, end:{x:M.left+W+8, y:y-infoH}, thickness:0.5, color:clrBorder });
|
||||
|
||||
const iLabY = y - 7;
|
||||
const iLabY = y - Math.round(infoPad + szXs * 0.72);
|
||||
const iValY = iLabY - szXs - 5;
|
||||
const iC2 = W * 0.45, iC3 = W * 0.78;
|
||||
|
||||
|
|
@ -1472,17 +1465,18 @@ async function generatePDF() {
|
|||
state.items.forEach(item => {
|
||||
needSpace(lh * 7);
|
||||
|
||||
// Section header: accent stripe + item name left, subtotal right
|
||||
accentStripe(y);
|
||||
pg.drawText(item.name || '(no name)', { x: M.left + 8, y, size: sz, font: fontBold, color: clrTextSoft });
|
||||
// Section header strip — equal visual padding, accent left bar
|
||||
const secH = sz + 16;
|
||||
const secTextY = Math.round(y - secH / 2 + sz * 0.22);
|
||||
pg.drawRectangle({ x: M.left, y: y - secH, width: W, height: secH, color: clrSurface2 });
|
||||
pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:0.5, color:clrBorder });
|
||||
pg.drawLine({ start:{x:M.left, y:y-secH}, end:{x:M.left+W, y:y-secH}, thickness:0.5, color:clrBorder });
|
||||
pg.drawRectangle({ x: M.left, y: y - secH, width: 3, height: secH, color: clrAccent });
|
||||
pg.drawText(item.name || '(no name)', { x: M.left + 10, y: secTextY, size: sz, font: fontBold, color: clrTextSoft });
|
||||
const subStr = `${baseCur} ${fmtAmt(item._subtotal)}`;
|
||||
const subW = fontBold.widthOfTextAtSize(subStr, sz);
|
||||
pg.drawText(subStr, { x: M.left + W - subW, y, size: sz, font: fontBold, color: clrAccent });
|
||||
y -= lh + 2;
|
||||
|
||||
// Hairline below section header
|
||||
pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:0.5, color:clrBorder });
|
||||
y -= 14;
|
||||
pg.drawText(subStr, { x: M.left + W - subW, y: secTextY, size: sz, font: fontBold, color: clrAccent });
|
||||
y -= secH + 14;
|
||||
|
||||
// Lines
|
||||
item.lines.forEach((ln, li) => {
|
||||
|
|
@ -1571,15 +1565,15 @@ async function generatePDF() {
|
|||
explLines.forEach(line => { needSpace(lh); val(line, M.left, y); y -= lh; });
|
||||
}
|
||||
|
||||
y -= 6;
|
||||
y -= 2;
|
||||
});
|
||||
|
||||
y -= 16;
|
||||
y -= 10;
|
||||
});
|
||||
|
||||
// Grand total
|
||||
needSpace(lh * 3 + 6);
|
||||
y -= 6;
|
||||
y -= 4;
|
||||
pg.drawLine({ start:{x:M.left, y}, end:{x:M.left+W, y}, thickness:1.5, color:clrBorderStrong });
|
||||
y -= lh;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue