/* ── Bills management page ── */ const BillsPage = (() => { let categories = []; const CYCLE_LABELS = { monthly: 'Monthly', quarterly: 'Quarterly', annually: 'Annually', irregular: 'Irregular', }; function fmt(amount) { return '$' + Number(amount || 0).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function escHtml(str) { return String(str || '') .replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } async function init(container) { container.innerHTML = `
Loading...
`; try { [categories] = await Promise.all([API.categories()]); render(container); } catch (e) { container.innerHTML = `

Failed to load: ${e.message}

`; } } async function render(container) { const bills = await API.allBills(); const active = bills.filter(b => b.active); const inactive = bills.filter(b => !b.active); container.innerHTML = `
${active.map(b => renderCard(b)).join('')} ${inactive.length ? `
INACTIVE
${inactive.map(b => renderCard(b, true)).join('')}
` : ''} ${bills.length === 0 ? `

No bills yet. Add your first bill!

` : ''}
`; document.getElementById('add-bill-btn').onclick = () => openBillModal(null, () => render(container)); container.querySelectorAll('.btn-edit-bill').forEach(btn => { btn.onclick = async () => { const bill = await API.bill(btn.dataset.id); openBillModal(bill, () => render(container)); }; }); container.querySelectorAll('.btn-toggle-bill').forEach(btn => { btn.onclick = async () => { const active = btn.dataset.active === '1'; if (!active || confirm('Deactivate this bill? It will be hidden from the tracker.')) { await API.updateBill(btn.dataset.id, { active: active ? 0 : 1 }); render(container); } }; }); } function renderCard(bill, inactive = false) { const catName = categories.find(c => c.id === bill.category_id)?.name || ''; return `
${escHtml(bill.name)}
Day ${bill.due_day} ${catName ? ` · ${escHtml(catName)}` : ''} · ${CYCLE_LABELS[bill.billing_cycle] || bill.billing_cycle} ${bill.autopay_enabled ? ' · Autopay' : ''}
${fmt(bill.expected_amount)}
`; } function openBillModal(bill, onSave) { const modal = document.getElementById('bill-modal'); const isNew = !bill; document.getElementById('bill-modal-title').textContent = isNew ? 'Add Bill' : 'Edit Bill'; document.getElementById('bill-id').value = bill?.id || ''; document.getElementById('bill-name').value = bill?.name || ''; document.getElementById('bill-due-day').value = bill?.due_day || ''; document.getElementById('bill-expected').value = bill?.expected_amount || ''; document.getElementById('bill-interest-rate').value = bill?.interest_rate ?? ''; document.getElementById('bill-cycle').value = bill?.billing_cycle || 'monthly'; document.getElementById('bill-autopay').checked = !!bill?.autopay_enabled; document.getElementById('bill-2fa').checked = !!bill?.has_2fa; document.getElementById('bill-website').value = bill?.website || ''; document.getElementById('bill-username').value = bill?.username || ''; document.getElementById('bill-account-info').value = bill?.account_info || ''; document.getElementById('bill-notes').value = bill?.notes || ''; // Populate category select const catSelect = document.getElementById('bill-category'); catSelect.innerHTML = '' + categories.map(c => ``).join(''); modal.classList.remove('hidden'); const close = () => modal.classList.add('hidden'); document.getElementById('bill-modal-close').onclick = close; document.getElementById('bill-modal-cancel').onclick = close; modal.querySelector('.modal-overlay').onclick = close; document.getElementById('bill-form').onsubmit = async (e) => { e.preventDefault(); const data = { name: document.getElementById('bill-name').value.trim(), category_id: document.getElementById('bill-category').value || null, due_day: parseInt(document.getElementById('bill-due-day').value, 10), expected_amount: parseFloat(document.getElementById('bill-expected').value) || 0, interest_rate: document.getElementById('bill-interest-rate').value === '' ? null : parseFloat(document.getElementById('bill-interest-rate').value), billing_cycle: document.getElementById('bill-cycle').value, autopay_enabled: document.getElementById('bill-autopay').checked, has_2fa: document.getElementById('bill-2fa').checked, website: document.getElementById('bill-website').value || null, username: document.getElementById('bill-username').value || null, account_info: document.getElementById('bill-account-info').value || null, notes: document.getElementById('bill-notes').value || null, }; try { if (isNew) { await API.createBill(data); showToast('Bill added', 'success'); } else { await API.updateBill(bill.id, data); showToast('Bill updated', 'success'); } close(); onSave(); } catch (err) { showToast('Error: ' + err.message, 'error'); } }; } return { init }; })();