Budget Buddy Pro
Local & private • Saved to your browser

Overview (This Month)

Income
$0
Fixed
$0
Variable
$0
Leftover
$0

By Category (with Rollovers)

Quick Add (Chat)

Examples: "12.99 dining Starbucks USD 2025-08-07", "income 2500 USD 2025-08-01 Salary"

Top Merchants

Transactions

DateMerchant / NoteAmount (base)OrigCategorySource

Categories & Monthly Budgets

Auto-Classification Rules

Map merchant keywords/regex to categories. Example: "/^UBER/i"Transport

Add Income

DateSourceAmount (base)Orig

Fixed Monthly Expenses

DayNameAmount (base)Orig

Receipt OCR (Images)

Drop or select images of receipts. We'll OCR, detect merchant & total, suggest a category, and add.

Import CSV / Assistant Paste

Upload CSV (Date, Description, Amount[, Currency]) or paste ASSISTANT_TRANSACTIONS_V1 below.

Paste from Assistant

Base Currency & FX Rates

Monthly Closeout & PDF

Backup / Restore

Live Ingest (Optional Webhook)

Data Reset

`); win.document.close(); } // Auto-backup (File System Access API) async function setupAutoBackup(){ try { const handle = await window.showSaveFilePicker({ suggestedName: "budget-buddy-pro-backup.json", types:[{description:"JSON", accept:{ "application/json":[".json"]}}] }); state.backupHandle = handle; $$("#backupStatus").textContent = "Auto-backup ready."; await save(); } catch(e){ $$("#backupStatus").textContent = "Auto-backup not set."; } } async function autoBackup(){ try { if (!state.backupHandle) return; const writable = await state.backupHandle.createWritable(); await writable.write(JSON.stringify(state, null, 2)); await writable.close(); $$("#backupStatus").textContent = "Auto-backup saved."; } catch(e){ $$("#backupStatus").textContent = "Auto-backup failed: " + (e && e.message ? e.message : e); } } // Webhook polling async function fetchWebhookOnce(){ const url = (state.webhook && state.webhook.url) || $$("#webhook-url").value; if (!url) { $$("#webhook-status").textContent = "No URL set."; return; } $$("#webhook-status").textContent = "Fetching…"; try { const resp = await fetch(url, {cache:"no-store"}); const text = await resp.text(); if (!/^ASSISTANT_TRANSACTIONS_V1/.test(text.trim())) { $$("#webhook-status").textContent = "No data (missing header)."; return; } const added = assistantImport(text); $$("#webhook-status").textContent = "Imported " + added + " items."; } catch(e){ $$("#webhook-status").textContent = "Fetch failed (CORS or network)."; } } let webhookTimer = null; function applyWebhookAuto(){ const auto = $$("#webhook-auto").checked; state.webhook = { url: $$("#webhook-url").value, auto }; if (webhookTimer) { clearInterval(webhookTimer); webhookTimer = null; } if (auto && state.webhook.url) { webhookTimer = setInterval(fetchWebhookOnce, 10*60*1000); } save(); } // ---------- Wiring ---------- function wire(){ // Tabs // (setupTabs called in init) // Buttons & Inputs $$("#cat-add").onclick = addCategory; $$("#rule-add").onclick = addRule; $$("#inc-add").onclick = $onIncAdd; $$("#fx-add").onclick = $onFxAdd; $$("#quickBtn").onclick = () => addQuick($$("#quickInput").value); $$("#quickInput").addEventListener("keydown", e => { if (e.key === "Enter") addQuick($$("#quickInput").value) }); $$$("#exportData").forEach(btn => btn.onclick = exportData); $$("#importDataBtn").onclick = () => $$("#importDataFile").click(); $$("#importDataFile").onchange = (e) => { const file = e.target.files[0]; if (file) importData(file); e.target.value=""; }; $$("#clearAll").onclick = () => { if (confirm("This will DELETE all local data. Continue?")) { localStorage.removeItem(LS_KEY); load(); save(); } }; $$("#parse-file").onclick = () => { const file = $$("#file-input").files[0]; if (!file) return alert("Choose a file first"); const reader = new FileReader(); reader.onload = () => { const items = parseCSV(reader.result); showImportPreview(items); }; reader.readAsText(file); }; $$("#assistant-import").onclick = () => { const txt = $$("#assistant-paste").value; const added = assistantImport(txt); if (added) { $$("#assistant-paste").value = ""; alert("Imported " + added + " transactions."); } }; $$("#assistant-format-btn").onclick = () => { $$("#assistant-format").classList.toggle("hidden"); }; // OCR $$("#ocr-run").onclick = runOcr; // FX $$("#fx-set").onclick = setFx; $$("#base-currency").onchange = setBaseCurrency; // Closeout $$("#close-preview").onclick = showCloseoutPreview; $$("#close-pdf").onclick = generatePdf; $$("#close-month").value = monthKey(); // Backup $$("#setupAutoBackup").onclick = setupAutoBackup; // Webhook $$("#webhook-test").onclick = fetchWebhookOnce; $$("#webhook-auto").onchange = applyWebhookAuto; $$("#webhook-url").oninput = () => { state.webhook.url = $$("#webhook-url").value; save(); }; } // ---------- Init ---------- load(); setupTabs(); wire(); renderAll();