diff --git a/fonts/GnuMICR.otf b/fonts/GnuMICR.otf new file mode 100644 index 0000000..8fd1823 Binary files /dev/null and b/fonts/GnuMICR.otf differ diff --git a/public/css/style.css b/public/css/style.css index 65b7af6..4ec67d1 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -53,6 +53,17 @@ header { cursor: pointer; } .account-switcher option { background: var(--header-bg); color: #fff; } +.btn-header-icon { + background: rgba(255,255,255,0.15); + border: 1px solid rgba(255,255,255,0.3); + color: #fff; + border-radius: 4px; + padding: 2px 7px; + font-size: 14px; + cursor: pointer; + line-height: 1.4; +} +.btn-header-icon:hover { background: rgba(255,255,255,0.28); } /* ── Toolbar ── */ .toolbar { @@ -492,8 +503,35 @@ input[type="file"] { .wizard-footer .btn-ghost { margin-right: auto; font-size: 12px; } .form-row-3 { grid-template-columns: 1fr 80px 100px; } +.form-row-4 { grid-template-columns: repeat(4, 1fr); } .field-hint { font-size: 11px; color: var(--text-muted); margin-top: 2px; } + +/* ── Account settings modal ── */ +.settings-section-label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); + padding-bottom: 4px; + border-bottom: 1px solid var(--border); + margin-top: 4px; +} +.logo-preview { + margin-top: 6px; +} +.logo-preview img { + max-height: 60px; + max-width: 200px; + border: 1px solid var(--border); + border-radius: 3px; + padding: 3px; +} +#acct-settings-modal .modal-body { + max-height: calc(100vh - 160px); + overflow-y: auto; +} diff --git a/public/index.html b/public/index.html index cdefa7b..9a7cc99 100644 --- a/public/index.html +++ b/public/index.html @@ -11,6 +11,7 @@
ezcheck +
Next check: @@ -238,6 +239,101 @@ + + + + diff --git a/public/js/app.js b/public/js/app.js index 2ba1ab7..7487990 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -512,6 +512,101 @@ async function runImport() { } } +// ── Account settings modal ─────────────────────────────────────────────────── + +const acctSettings = { logoData: null }; + +function openAccountSettings() { + const a = state.account; + if (!a) return; + + acctSettings.logoData = null; + + const f = document.getElementById('acct-settings-form'); + f.elements.company1.value = a.company1 || ''; + f.elements.company2.value = a.company2 || ''; + f.elements.company3.value = a.company3 || ''; + f.elements.company4.value = a.company4 || ''; + f.elements.bank_name.value = a.bank_name || ''; + f.elements.bank_info1.value = a.bank_info1 || ''; + f.elements.bank_info2.value = a.bank_info2 || ''; + f.elements.transit_code.value = a.transit_code || ''; + f.elements.routing_number.value = a.routing_number || ''; + f.elements.account_number.value = a.account_number || ''; + f.elements.offset_left.value = a.offset_left || 0; + f.elements.offset_right.value = a.offset_right || 0; + f.elements.offset_up.value = a.offset_up || 0; + f.elements.offset_down.value = a.offset_down || 0; + + document.getElementById('as-logo').value = ''; + document.getElementById('as-logo-preview').hidden = true; + document.getElementById('acct-settings-error').hidden = true; + document.getElementById('btn-save-acct-settings').disabled = false; + document.getElementById('btn-save-acct-settings').textContent = 'Save Changes'; + + document.getElementById('acct-settings-overlay').classList.add('open'); + document.getElementById('acct-settings-modal').classList.add('open'); + f.elements.company1.focus(); +} + +function closeAccountSettings() { + document.getElementById('acct-settings-overlay').classList.remove('open'); + document.getElementById('acct-settings-modal').classList.remove('open'); +} + +async function saveAccountSettings() { + const f = document.getElementById('acct-settings-form'); + const errEl = document.getElementById('acct-settings-error'); + errEl.hidden = true; + + const payload = { + company1: f.elements.company1.value.trim(), + company2: f.elements.company2.value.trim() || null, + company3: f.elements.company3.value.trim() || null, + company4: f.elements.company4.value.trim() || null, + bank_name: f.elements.bank_name.value.trim(), + bank_info1: f.elements.bank_info1.value.trim() || null, + bank_info2: f.elements.bank_info2.value.trim() || null, + transit_code: f.elements.transit_code.value.trim() || null, + routing_number: f.elements.routing_number.value.trim(), + account_number: f.elements.account_number.value.trim(), + offset_left: parseFloat(f.elements.offset_left.value) || 0, + offset_right: parseFloat(f.elements.offset_right.value) || 0, + offset_up: parseFloat(f.elements.offset_up.value) || 0, + offset_down: parseFloat(f.elements.offset_down.value) || 0, + logo_data: acctSettings.logoData || null, + }; + + if (!payload.company1) { + errEl.textContent = 'Organization name is required.'; + errEl.hidden = false; + f.elements.company1.focus(); + return; + } + if (!payload.routing_number || !payload.account_number) { + errEl.textContent = 'Routing number and account number are required.'; + errEl.hidden = false; + return; + } + + const btn = document.getElementById('btn-save-acct-settings'); + btn.disabled = true; + btn.textContent = 'Saving…'; + + try { + state.account = await apiFetch('PUT', `/api/account/${state.activeAccountId}`, payload); + // Refresh account in the accounts list (for the switcher label) + await loadAccounts(); + renderHeader(); + closeAccountSettings(); + } catch (err) { + errEl.textContent = err.message; + errEl.hidden = false; + btn.disabled = false; + btn.textContent = 'Save Changes'; + } +} + // ── Utilities ──────────────────────────────────────────────────────────────── function escHtml(str) { @@ -600,6 +695,25 @@ function init() { switchAccount(parseInt(e.target.value, 10)); }); + // Account settings modal + document.getElementById('btn-account-settings').addEventListener('click', openAccountSettings); + document.getElementById('btn-close-acct-settings').addEventListener('click', closeAccountSettings); + document.getElementById('btn-cancel-acct-settings').addEventListener('click', closeAccountSettings); + document.getElementById('acct-settings-overlay').addEventListener('click', closeAccountSettings); + document.getElementById('btn-save-acct-settings').addEventListener('click', saveAccountSettings); + document.getElementById('as-logo').addEventListener('change', e => { + const file = e.target.files[0]; + if (!file) { acctSettings.logoData = null; return; } + const reader = new FileReader(); + reader.onload = ev => { + acctSettings.logoData = ev.target.result; + const preview = document.getElementById('as-logo-preview'); + preview.innerHTML = `Logo preview`; + preview.hidden = false; + }; + reader.readAsDataURL(file); + }); + // Initial data load loadAccounts(); } diff --git a/src/app.js b/src/app.js index 46f6e4c..708c91c 100644 --- a/src/app.js +++ b/src/app.js @@ -26,6 +26,50 @@ app.get('/api/accounts', (req, res) => { res.json(accounts); }); +// PUT /api/account/:id - update account settings +app.put('/api/account/:id', (req, res) => { + const db = require('./db/database'); + const account = db.prepare('SELECT id FROM account WHERE id = ?').get(req.params.id); + if (!account) return res.status(404).json({ error: 'Account not found.' }); + + const { + company1, company2, company3, company4, + bank_name, bank_info1, bank_info2, bank_info3, transit_code, + routing_number, account_number, + offset_left, offset_right, offset_up, offset_down, + logo_data, + } = req.body; + + if (!company1 || !routing_number || !account_number) { + return res.status(400).json({ error: 'Organization name, routing number, and account number are required.' }); + } + + db.prepare(` + UPDATE account SET + company1 = ?, company2 = ?, company3 = ?, company4 = ?, + bank_name = ?, bank_info1 = ?, bank_info2 = ?, bank_info3 = ?, transit_code = ?, + routing_number = ?, account_number = ?, + offset_left = ?, offset_right = ?, offset_up = ?, offset_down = ?, + logo_data = CASE WHEN ? IS NOT NULL THEN ? ELSE logo_data END, + updated_at = datetime('now') + WHERE id = ? + `).run( + company1 || null, company2 || null, company3 || null, company4 || null, + bank_name || '', bank_info1 || null, bank_info2 || null, bank_info3 || null, transit_code || null, + routing_number, account_number, + parseFloat(offset_left) || 0, parseFloat(offset_right) || 0, + parseFloat(offset_up) || 0, parseFloat(offset_down) || 0, + logo_data || null, logo_data || null, + req.params.id + ); + + res.json(db.prepare( + 'SELECT id, bank_name, bank_info1, bank_info2, bank_info3, transit_code, ' + + 'routing_number, account_number, current_check_no, ' + + 'company1, company2, company3, company4, check_position FROM account WHERE id = ?' + ).get(req.params.id)); +}); + // GET /api/account/:id - get full account by id app.get('/api/account/:id', (req, res) => { const db = require('./db/database');