Add selectable base currency to form header

Replace static base currency badge with a currency dropdown (makeCDD).
Changing the selection updates state.baseCurrency, re-renders all item/line
blocks so closures capture the new base, and recalculates totals. All
dynamic references to CFG['currency-base'] replaced with state.baseCurrency.

https://claude.ai/code/session_014uUwDBtG5y5FuWcy5zqVD1
This commit is contained in:
Claude 2026-05-13 09:15:54 +00:00
parent d35429314d
commit 09ceaaf360
No known key found for this signature in database

View file

@ -140,12 +140,12 @@ async function loadConfig() {
} }
// ========== STATE ========== // ========== STATE ==========
const state = { staff: '', periodFrom: '', periodTo: '', items: [], _grandTotal: 0 }; const state = { staff: '', periodFrom: '', periodTo: '', baseCurrency: CFG['currency-base'], items: [], _grandTotal: 0 };
function newItem() { return { id: uid(), name: '', lines: [newLine()], _subtotal: 0 }; } function newItem() { return { id: uid(), name: '', lines: [newLine()], _subtotal: 0 }; }
function newLine() { function newLine() {
return { return {
id: uid(), date: '', description: '', currency: CFG['currency-base'], id: uid(), date: '', description: '', currency: state.baseCurrency,
fxRate: '1.00000', vendor: '', hasReceipt: true, receipts: [], fxRate: '1.00000', vendor: '', hasReceipt: true, receipts: [],
noReceiptExplanation: '', amount: '', account: '', program: '', programOther: '' noReceiptExplanation: '', amount: '', account: '', program: '', programOther: ''
}; };
@ -164,11 +164,11 @@ function recalc() {
item._subtotal = sub; item._subtotal = sub;
grand += sub; grand += sub;
const se = $(`#sub-${item.id}`); const se = $(`#sub-${item.id}`);
if (se) se.textContent = `${CFG['currency-base']} ${fmtAmt(sub)}`; if (se) se.textContent = `${state.baseCurrency} ${fmtAmt(sub)}`;
}); });
state._grandTotal = grand; state._grandTotal = grand;
const ge = $('#grand-total'); const ge = $('#grand-total');
if (ge) ge.textContent = `${CFG['currency-base']} ${fmtAmt(grand)}`; if (ge) ge.textContent = `${state.baseCurrency} ${fmtAmt(grand)}`;
} }
// ========== CURRENCY DROPDOWN ========== // ========== CURRENCY DROPDOWN ==========
@ -246,13 +246,21 @@ function render() {
const toInput = el('input', {type:'date', value: state.periodTo}); const toInput = el('input', {type:'date', value: state.periodTo});
toInput.addEventListener('change', () => { state.periodTo = toInput.value; }); toInput.addEventListener('change', () => { state.periodTo = toInput.value; });
const baseBadge = el('span', {style:{fontWeight:'600', fontSize:'14px', padding:'7px 0'}}, CFG['currency-base']); const baseCurDD = makeCDD(CFG.currencies || [], state.baseCurrency, code => {
state.baseCurrency = code;
const box = $('#items-box');
if (box) {
box.innerHTML = '';
state.items.forEach(item => box.appendChild(renderItem(item)));
}
recalc();
});
wrap.appendChild(el('div', {className:'frow'}, [ wrap.appendChild(el('div', {className:'frow'}, [
el('div', {className:'fgrp grow'}, [el('label', null, 'Staff'), staffInput]), el('div', {className:'fgrp grow'}, [el('label', null, 'Staff'), staffInput]),
el('div', {className:'fgrp'}, [el('label', null, 'Period from'), fromInput]), el('div', {className:'fgrp'}, [el('label', null, 'Period from'), fromInput]),
el('div', {className:'fgrp'}, [el('label', null, 'to'), toInput]), el('div', {className:'fgrp'}, [el('label', null, 'to'), toInput]),
el('div', {className:'fgrp'}, [el('label', null, 'Base currency'), baseBadge]), el('div', {className:'fgrp'}, [el('label', null, 'Base currency'), baseCurDD]),
])); ]));
wrap.appendChild(el('hr', {className:'divider'})); wrap.appendChild(el('hr', {className:'divider'}));
@ -274,7 +282,7 @@ function render() {
addItemBtn, addItemBtn,
el('div', {className:'grand-total'}, [ el('div', {className:'grand-total'}, [
'Total reimbursement claim: ', 'Total reimbursement claim: ',
el('span', {id:'grand-total'}, `${CFG['currency-base']} ${fmtAmt(0)}`) el('span', {id:'grand-total'}, `${state.baseCurrency} ${fmtAmt(0)}`)
]) ])
])); ]));
@ -301,7 +309,7 @@ function renderItem(item) {
blk.appendChild(el('div', {className:'item-hdr'}, [ blk.appendChild(el('div', {className:'item-hdr'}, [
el('label', null, 'Item / Project / Travel'), el('label', null, 'Item / Project / Travel'),
el('span', {className:'item-subtotal'}, [ el('span', {className:'item-subtotal'}, [
'Subtotal: ', el('span', {id:`sub-${item.id}`}, `${CFG['currency-base']} ${fmtAmt(0)}`) 'Subtotal: ', el('span', {id:`sub-${item.id}`}, `${state.baseCurrency} ${fmtAmt(0)}`)
]), ]),
rmBtn rmBtn
])); ]));
@ -327,7 +335,7 @@ function renderLine(ln, item) {
const blk = el('div', {className:'line-blk', id:`line-${ln.id}`}); const blk = el('div', {className:'line-blk', id:`line-${ln.id}`});
const currencies = CFG.currencies || []; const currencies = CFG.currencies || [];
const baseCur = CFG['currency-base']; const baseCur = state.baseCurrency;
// Row 1: Date, Description, Currency, FX Rate // Row 1: Date, Description, Currency, FX Rate
const dateIn = el('input', {type:'date', value: ln.date, style:{width:'140px'}}); const dateIn = el('input', {type:'date', value: ln.date, style:{width:'140px'}});
@ -461,7 +469,7 @@ function validate() {
if (!ln.vendor.trim()) errs.push(`${lx}: Vendor is required.`); if (!ln.vendor.trim()) errs.push(`${lx}: Vendor is required.`);
const amt = parseFloat(ln.amount); const amt = parseFloat(ln.amount);
if (isNaN(amt) || amt <= 0) errs.push(`${lx}: Amount must be a positive number.`); if (isNaN(amt) || amt <= 0) errs.push(`${lx}: Amount must be a positive number.`);
if (ln.currency !== CFG['currency-base']) { if (ln.currency !== state.baseCurrency) {
const rate = parseFloat(ln.fxRate); const rate = parseFloat(ln.fxRate);
if (isNaN(rate) || rate <= 0) errs.push(`${lx}: FX rate must be a positive number.`); if (isNaN(rate) || rate <= 0) errs.push(`${lx}: FX rate must be a positive number.`);
} }
@ -499,7 +507,7 @@ async function generatePDF() {
const black = rgb(0.13, 0.13, 0.13); const black = rgb(0.13, 0.13, 0.13);
const gray = rgb(0.45, 0.45, 0.45); const gray = rgb(0.45, 0.45, 0.45);
const lineCol = rgb(0.75, 0.75, 0.75); const lineCol = rgb(0.75, 0.75, 0.75);
const baseCur = CFG['currency-base']; const baseCur = state.baseCurrency;
// Logo // Logo
let logoImage = null; let logoImage = null;