From 8be37f29fe7ccb7319fb9c4487befdb8cd0b7a21 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 09:38:27 +0000 Subject: [PATCH] Add configurable date-format; replace browser date pickers with text inputs - New config key date-format (default DD/MM/YYYY) controls date display throughout the form and PDF output - formatDate(iso) converts stored YYYY-MM-DD to the configured display format - parseDate(str) converts user input back to YYYY-MM-DD for state storage - All three date inputs (line date, period from/to) switched from type=date to type=text with format placeholder, removing browser locale dependency - PDF line dates, period header, and continuation header all use formatDate() - Filename stays in YYYY-MM-DD for safe file naming https://claude.ai/code/session_014uUwDBtG5y5FuWcy5zqVD1 --- config.yml | 4 ++++ index.html | 42 +++++++++++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/config.yml b/config.yml index c13ce97..a73cb62 100644 --- a/config.yml +++ b/config.yml @@ -28,6 +28,10 @@ footer: "Confidential" # Base currency (ISO code — must appear in currencies list) currency-base: USD +# Date display format (YYYY = year, MM = month, DD = day) +# Examples: DD/MM/YYYY | YYYY-MM-DD | MM/DD/YYYY | DD.MM.YYYY +date-format: DD/MM/YYYY + # Available currencies currencies: - code: USD diff --git a/index.html b/index.html index 7deea82..527d86e 100644 --- a/index.html +++ b/index.html @@ -130,6 +130,29 @@ function defaultPeriod() { return { from: fmt(new Date(y, m, 1)), to: fmt(new Date(y, m + 1, 0)) }; } +// ========== DATE FORMATTING ========== +function formatDate(iso) { + if (!iso) return ''; + const [y, m, d] = iso.split('-'); + if (!y || !m || !d) return iso; + return (CFG['date-format'] || 'YYYY-MM-DD').replace('YYYY', y).replace('MM', m).replace('DD', d); +} +function parseDate(str) { + if (!str) return ''; + const fmt = CFG['date-format'] || 'YYYY-MM-DD'; + const sep = fmt.match(/[^YMD]/)?.[0] || '-'; + const parts = str.split(sep); + const fmtParts = fmt.split(sep); + let y = '', m = '', d = ''; + fmtParts.forEach((f, i) => { + if (f === 'YYYY') y = parts[i] || ''; + else if (f === 'MM') m = (parts[i] || '').padStart(2, '0'); + else if (f === 'DD') d = (parts[i] || '').padStart(2, '0'); + }); + if (!y || !m || !d) return ''; + return `${y}-${m}-${d}`; +} + // ========== CONFIG ========== let CFG; async function loadConfig() { @@ -241,10 +264,11 @@ function render() { const staffInput = el('input', {type:'text', value: state.staff, placeholder:'Full name'}); staffInput.addEventListener('input', () => { state.staff = staffInput.value; localStorage.setItem('reimb-staff', staffInput.value); }); - const fromInput = el('input', {type:'date', value: state.periodFrom}); - fromInput.addEventListener('change', () => { state.periodFrom = fromInput.value; }); - const toInput = el('input', {type:'date', value: state.periodTo}); - toInput.addEventListener('change', () => { state.periodTo = toInput.value; }); + const dateFmt = CFG['date-format'] || 'YYYY-MM-DD'; + const fromInput = el('input', {type:'text', value: formatDate(state.periodFrom), placeholder: dateFmt, style:{width:'120px'}}); + fromInput.addEventListener('change', () => { state.periodFrom = parseDate(fromInput.value); }); + const toInput = el('input', {type:'text', value: formatDate(state.periodTo), placeholder: dateFmt, style:{width:'120px'}}); + toInput.addEventListener('change', () => { state.periodTo = parseDate(toInput.value); }); const baseCurDD = makeCDD(CFG.currencies || [], state.baseCurrency, code => { state.baseCurrency = code; @@ -340,8 +364,8 @@ function renderLine(ln, item) { const baseCur = state.baseCurrency; // Row 1: Date, Vendor, Currency, FX Rate - const dateIn = el('input', {type:'date', value: ln.date, style:{width:'140px'}}); - dateIn.addEventListener('change', () => { ln.date = dateIn.value; }); + const dateIn = el('input', {type:'text', value: formatDate(ln.date), placeholder: CFG['date-format'] || 'YYYY-MM-DD', style:{width:'120px'}}); + dateIn.addEventListener('change', () => { ln.date = parseDate(dateIn.value); }); const vendIn = el('input', {type:'text', value: ln.vendor, placeholder:'Vendor name'}); vendIn.addEventListener('input', () => { ln.vendor = vendIn.value; }); @@ -546,7 +570,7 @@ async function generatePDF() { function drawContHeader() { pg.drawText(state.staff, { x: M.left, y, size: sz, font: fontBold, color: black }); - const periodStr = `Period: ${state.periodFrom} to ${state.periodTo}`; + const periodStr = `Period: ${formatDate(state.periodFrom)} to ${formatDate(state.periodTo)}`; const pw = fontBody.widthOfTextAtSize(periodStr, sz); pg.drawText(periodStr, { x: M.left + W - pw, y, size: sz, font: fontBody, color: gray }); y -= lh + 2; @@ -587,7 +611,7 @@ async function generatePDF() { pg.drawText('Currency', {x:M.left+col3, y, size:szSm, font:fontBold, color:gray}); y -= lh; pg.drawText(state.staff, {x:M.left, y, size:sz, font:fontBody, color:black}); - pg.drawText(`${state.periodFrom} to ${state.periodTo}`, {x:M.left+col2, y, size:sz, font:fontBody, color:black}); + pg.drawText(`${formatDate(state.periodFrom)} to ${formatDate(state.periodTo)}`, {x:M.left+col2, y, size:sz, font:fontBody, color:black}); pg.drawText(baseCur, {x:M.left+col3, y, size:sz, font:fontBold, color:black}); y -= lh + 6; @@ -620,7 +644,7 @@ async function generatePDF() { pg.drawText('FX rate', {x:M.left+c4, y, size:szSm-1, font:fontBold, color:gray}); y -= lh; // Row 1 values - pg.drawText(ln.date || '–', {x:M.left+c1, y, size:sz, font:fontBody, color:black}); + pg.drawText(formatDate(ln.date) || '–', {x:M.left+c1, y, size:sz, font:fontBody, color:black}); pg.drawText(truncate(ln.vendor, fontBody, sz, (c3-c2)-8), {x:M.left+c2, y, size:sz, font:fontBody, color:black}); pg.drawText(ln.currency, {x:M.left+c3, y, size:sz, font:fontBody, color:black}); const fxStr = ln.currency === baseCur ? '–' : parseFloat(ln.fxRate).toFixed(5);