Repository navigation
Expand file tree
/
Copy pathapp.js
More file actions
15 lines (10 loc) · 3.51 KB
/
Copy pathapp.js
File metadata and controls
15 lines (10 loc) · 3.51 KB
1
2
3
4
5
6
7
8
9
10
const KEY = "pebblebudget.v1";
const categories = {food:["Boodschappen","🛒"],home:["Wonen","⌂"],travel:["Vervoer","↗"],fun:["Vrije tijd","✳"],other:["Overig","•"]};
let data = { budget: 1000, expenses: [] };
try { const saved = JSON.parse(localStorage.getItem(KEY)); if (saved && Array.isArray(saved.expenses)) data = {budget:Number(saved.budget)||1000,expenses:saved.expenses.filter(x=>x&&Number.isFinite(Number(x.amount)))}; } catch {}
function makeId(){return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}
const el = s => document.querySelector(s), currency = n => new Intl.NumberFormat("nl-BE",{style:"currency",currency:"EUR"}).format(n), monthInput=el("#month");
const today = new Date(), localDate = new Date(today.getTime()-today.getTimezoneOffset()*60000).toISOString().slice(0,10); monthInput.value = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,"0")}`; el("#date").value = localDate;
function save(){localStorage.setItem(KEY,JSON.stringify(data));render();}
function render(){const month=monthInput.value, items=data.expenses.filter(x=>x.date.startsWith(month)).sort((a,b)=>b.date.localeCompare(a.date));const spent=items.reduce((s,x)=>s+Number(x.amount),0);el("#budgetValue").textContent=currency(data.budget);el("#spentValue").textContent=currency(spent);el("#remainingValue").textContent=currency(data.budget-spent);el("#budgetInput").value=data.budget;el("#budgetMeter").style.width=`${Math.min(data.budget?spent/data.budget*100:0,100)}%`;el("#budgetHint").textContent=spent===0?"Nog geen uitgaven":spent>data.budget?`Je zit ${currency(spent-data.budget)} boven je budget`:`${Math.round(spent/data.budget*100)}% van je budget gebruikt`;const list=el("#list");list.replaceChildren();el("#empty").hidden=items.length>0;for(const x of items){const li=document.createElement("li");li.className="item";const [label,icon]=categories[x.category]||categories.other;const dot=document.createElement("span");dot.className="dot";dot.textContent=icon;const copy=document.createElement("div");copy.className="item-copy";const title=document.createElement("strong");title.textContent=x.title;const meta=document.createElement("small");meta.textContent=`${label} · ${new Date(`${x.date}T12:00:00`).toLocaleDateString("nl-BE")}`;copy.append(title,meta);const amount=document.createElement("span");amount.className="amount";amount.textContent=`− ${currency(x.amount)}`;const remove=document.createElement("button");remove.className="delete";remove.setAttribute("aria-label",`${x.title} verwijderen`);remove.textContent="×";remove.addEventListener("click",()=>{data.expenses=data.expenses.filter(t=>t.id!==x.id);save();});li.append(dot,copy,amount,remove);list.append(li);}}
el("#expenseForm").addEventListener("submit",e=>{e.preventDefault();const title=el("#title").value.trim(),amount=Number(el("#amount").value),date=el("#date").value;if(!title||!Number.isFinite(amount)||amount<=0||!date)return;data.expenses.push({id:makeId(),title,amount,category:el("#category").value,date});e.target.reset();el("#date").value=localDate;save();});el("#saveBudget").addEventListener("click",()=>{const value=Number(el("#budgetInput").value);if(Number.isFinite(value)&&value>=0){data.budget=value;save();}});monthInput.addEventListener("change",render);el("#export").addEventListener("click",()=>{const blob=new Blob([JSON.stringify(data,null,2)],{type:"application/json"}),url=URL.createObjectURL(blob),a=document.createElement("a");a.href=url;a.download="pebblebudget-data.json";a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);});render();