diff --git a/app/index.html b/app/index.html index f757bff..59f0255 100644 --- a/app/index.html +++ b/app/index.html @@ -948,14 +948,14 @@ async function generatePDF() { y -= lh + 6; // Divider - pg.drawLine({start:{x:M.left,y}, end:{x:M.left+W,y}, thickness:1.5, color:accent}); + pg.drawLine({start:{x:M.left,y}, end:{x:M.left+W,y}, thickness:3, color:accent}); y -= lh; // Items state.items.forEach(item => { // Item header needSpace(lh * 6); // need room for at least header + one line - pg.drawText('ITEM / PROJECT / TRAVEL', {x:M.left, y, size:szSm, font:fontBold, color:accent}); + pg.drawText('ITEM / PROJECT / TRAVEL', {x:M.left, y, size:sz, font:fontBold, color:accent}); const subStr = `Subtotal: ${baseCur} ${fmtAmt(item._subtotal)}`; const subW = fontBold.widthOfTextAtSize(subStr, sz); pg.drawText(subStr, {x:M.left+W-subW, y, size:sz, font:fontBold, color:accent}); @@ -966,58 +966,57 @@ async function generatePDF() { // Lines item.lines.forEach((ln, li) => { needSpace(lh * 8); - if (li > 0 && !justBroke) { - y -= 4; - pg.drawLine({start:{x:M.left,y}, end:{x:M.left+W,y}, thickness:0.3, color:lineCol}); - y -= 8; + + // Thin grey rule above each vendor line + if (!justBroke) { + if (li > 0) y -= 6; + pg.drawLine({start:{x:M.left, y:y+lh+1}, end:{x:M.left+W, y:y+lh+1}, thickness:0.3, color:lineCol}); } - const boxW = W * 0.65; // width of vendor / description boxes - const boxH = lh; // box height matches line height - const boxFill = rgb(0.97, 0.97, 0.97); + const boxW = W * 0.65; // text width for vendor / description - // Line 1: [Vendor box] LCC amount + // Line 1: Vendor FXC amount LCC amount const baseAmt = (() => { const a=parseFloat(ln.amount)||0, r=parseFloat(ln.fxRate)||1; return r>0?a/r:0; })(); const baseAmtStr = `${baseCur} ${fmtAmt(baseAmt)}`; const baseAmtW = fontBold.widthOfTextAtSize(baseAmtStr, sz); - pg.drawRectangle({x:M.left, y:y-2, width:boxW, height:boxH, borderColor:lineCol, borderWidth:0.5, color:boxFill}); - pg.drawText(truncate(ln.vendor||'–', fontBody, sz, boxW-8), {x:M.left+4, y, size:sz, font:fontBody, color:black}); + pg.drawText(truncate(ln.vendor||'–', fontBody, sz, boxW-4), {x:M.left, y, size:sz, font:fontBody, color:black}); + if (ln.currency && ln.currency !== baseCur) { + const fxAmtStr = `${ln.currency} ${fmtAmt(ln.amount)}`; + const fxAmtW = fontBody.widthOfTextAtSize(fxAmtStr, szSm); + pg.drawText(fxAmtStr, {x:M.left+W-baseAmtW-fxAmtW-6, y, size:szSm, font:fontBody, color:gray}); + } pg.drawText(baseAmtStr, {x:M.left+W-baseAmtW, y, size:sz, font:fontBold, color:black}); - y -= boxH + 4; + y -= lh + 2; - // Line 2: [Description box] Date: YYYY-MM-DD + // Line 2: Description Date: YYYY-MM-DD const dateInPeriod = isDateInPeriod(ln.date); const dateColor = dateInPeriod ? black : rgb(0.9, 0.33, 0); const dateStr = `Date: ${ln.date||'–'}${dateInPeriod ? '' : ' (!)'}`; const dateStrW = fontBody.widthOfTextAtSize(dateStr, szSm); - pg.drawRectangle({x:M.left, y:y-2, width:boxW, height:boxH, borderColor:lineCol, borderWidth:0.5, color:boxFill}); - pg.drawText(truncate(ln.description||'–', fontBody, sz, boxW-8), {x:M.left+4, y, size:sz, font:fontBody, color:black}); + pg.drawText(truncate(ln.description||'–', fontBody, sz, boxW-4), {x:M.left, y, size:sz, font:fontBody, color:black}); pg.drawText(dateStr, {x:M.left+W-dateStrW, y, size:szSm, font:fontBody, color:dateColor}); - y -= boxH + 4; + y -= lh + 2; - // Line 3: Receipt: … (left, backfilled) FXC amount – FXC rate per LCC (right) + // Line 3: Receipt ref (backfilled) or explanation FXC rate detail (right) if (ln.currency && ln.currency !== baseCur) { - const fxDetail = `${ln.currency} ${fmtAmt(ln.amount)} – ${ln.currency} ${parseFloat(ln.fxRate||'0').toFixed(5)} per ${baseCur}`; + const fxDetail = `${ln.currency} ${parseFloat(ln.fxRate||'0').toFixed(5)} per ${baseCur}`; const fxDetailW = fontBody.widthOfTextAtSize(fxDetail, szSm); pg.drawText(fxDetail, {x:M.left+W-fxDetailW, y, size:szSm, font:fontBody, color:gray}); } if (ln.hasReceipt && ln.receipts.length > 0) { ln.receipts.forEach((r, ri) => { const key = `${ln.id}-${ri}`; - receiptRefs.push({pageIdx: pages.length-1, x:M.left, y, key, prefix:'Receipt: '}); + receiptRefs.push({pageIdx: pages.length-1, x:M.left, y, key, prefix:''}); y -= lh; }); } else if (!ln.hasReceipt) { - pg.drawText('Receipt: No receipt', {x:M.left, y, size:sz, font:fontBody, color:black}); - y -= lh; - const explLines = wrapText(ln.noReceiptExplanation||'–', fontBody, szSm, boxW); + const explLines = wrapText(ln.noReceiptExplanation||'–', fontBody, szSm, W * 0.6); explLines.forEach(line => { needSpace(lh); - pg.drawText(` ${line}`, {x:M.left, y, size:szSm, font:fontBody, color:gray}); + pg.drawText(line, {x:M.left, y, size:szSm, font:fontBody, color:gray}); y -= lh; }); } else { - pg.drawText('Receipt: –', {x:M.left, y, size:sz, font:fontBody, color:gray}); y -= lh; }