import { useState } from "react"; import { useApp } from "../context/AppContext.jsx"; import { fmt, pct } from "../utils/format.js"; import { PURPLE, PURPLE_LIGHT, RED, GREEN } from "../constants/index.js"; import { VCard, DonutChart, Overlay, ModalBox, ModalHeader, EmojiPicker, AppIcon, ConfirmDeleteDialog } from "../components/ui/index.jsx"; // ── SchuldAanpassenPopup ────────────────────────────────────────────────────── function SchuldAanpassenPopup({ blok, onClose }) { const { data, setData, schuld, schuldBlokken, darkMode, T } = useApp(); const [items, setItems] = useState(blok.items.map((i) => ({ ...i }))); const [blokNaam, setBlokNaam] = useState(blok.naam); const [blokIcon, setBlokIcon] = useState(blok.icon || ""); const [showEmoji, setShowEmoji] = useState(false); const [nieuwNaam, setNieuwNaam] = useState(""); const [nieuwVal, setNieuwVal] = useState(""); const [nieuwErr, setNieuwErr] = useState(""); const [confirmDel, setConfirmDel] = useState(false); const is = inputStyle(T); const updateItem = (id, field, val) => setItems((p) => p.map((i) => i.id !== id ? i : { ...i, [field]: val })); const removeItemLocal = (id) => { if (items.length <= 1) return; setItems((p) => p.filter((i) => i.id !== id)); }; const addNieuw = () => { if (!nieuwNaam.trim()) { setNieuwErr("Naam is verplicht."); setTimeout(() => setNieuwErr(""), 3000); return; } setItems((p) => [...p, { id: `si${Date.now()}`, naam: nieuwNaam.trim(), waarde: parseFloat(nieuwVal) || 0 }]); setNieuwNaam(""); setNieuwVal(""); setNieuwErr(""); }; const handleSave = () => { setData({ ...data, schuld: { ...schuld, blokken: schuldBlokken.map((b) => b.id !== blok.id ? b : { ...b, naam: blokNaam.trim() || b.naam, icon: blokIcon, items: items.map((i) => ({ ...i, waarde: parseFloat(i.waarde) || 0 })), } ), }, }); onClose(); }; const handleDelete = () => { setData({ ...data, schuld: { ...schuld, blokken: schuldBlokken.filter((b) => b.id !== blok.id) } }); onClose(); }; return (
{/* Naam + emoji */}
setBlokNaam(e.target.value)} style={{ ...is, flex: 1, borderLeft: `3px solid ${blok.color}` }} placeholder="Naam categorie" />
{showEmoji && ( { setBlokIcon(e); setShowEmoji(false); }} onClear={() => { setBlokIcon(""); setShowEmoji(false); }} /> )}
{/* Items */} {items.map((item) => (
updateItem(item.id, "naam", e.target.value)} style={{ ...is, flex: 1 }} placeholder="Naam" /> updateItem(item.id, "waarde", v)} is={is} T={T} /> {items.length > 1 && ( )}
))} {/* Nieuw item */}
{ setNieuwNaam(e.target.value); setNieuwErr(""); }} placeholder="Naam" style={{ ...is, flex: 1 }} />
{nieuwErr && }
{/* Footer */}
{confirmDel ? ( setConfirmDel(false)} onConfirm={handleDelete} /> ) : (
)}
); } // ── SchuldKaart ─────────────────────────────────────────────────────────────── function SchuldKaart({ blok }) { const { locked, T } = useApp(); const [showAanpassen, setShowAanpassen] = useState(false); const subtot = blok.items.reduce((a, i) => a + (i.waarde || 0), 0); return ( {showAanpassen && setShowAanpassen(false)} />}
{blok.icon && } {blok.naam} {!locked && ( )}
{blok.items.map((item) => (
{item.naam} {fmt(item.waarde)}
))}
{fmt(subtot)}
); } // ── AflossingProgressie ─────────────────────────────────────────────────────── function AflossingProgressie() { const { data, setData, schuld, totSchuld, locked, darkMode, T } = useApp(); const init = schuld.initieleSchuld || 0; const betaald = Math.min(Math.max(init - totSchuld, 0), init); const aflossingPct = init > 0 ? betaald / init : 0; const [editOpen, setEditOpen] = useState(false); const [editVal, setEditVal] = useState(schuld.initieleSchuld || ""); const saveInitiele = () => { setData({ ...data, schuld: { ...schuld, initieleSchuld: parseFloat(editVal) || 0 } }); setEditOpen(false); }; return ( {editOpen && ( setEditOpen(false)}> setEditOpen(false)} headerBg={darkMode ? `${RED}11` : `${RED}09`} T={T} />
Bedrag (€)
setEditVal(e.target.value)} autoFocus style={{ background: T.inputBg, border: `1px solid ${T.inputBorder}`, borderRadius: 10, color: T.text, padding: "10px 12px 10px 28px", fontSize: 14, outline: "none", width: "100%", boxSizing: "border-box" }} />
)}
Aflossing progressie
Initiële schuld: {fmt(schuld.initieleSchuld)} {!locked && ( )}
€0 {pct(aflossingPct)} afgelost {fmt(init)}
); } // ── NieuweCategoriepopup ────────────────────────────────────────────────────── function NieuweCategoriepopup({ onSave, onClose, T, darkMode }) { const is = { background: T.inputBg, border: `1px solid ${T.inputBorder}`, borderRadius: 8, color: T.text, padding: "10px 12px", fontSize: 13, outline: "none", boxSizing: "border-box", width: "100%" }; const COLORS = ["#ef4444","#f97316","#eab308","#a855f7","#06b6d4","#10b981","#3b82f6","#ec4899","#6b7280"]; const [naam, setNaam] = useState(""); const [color, setColor] = useState(COLORS[0]); const save = () => { if (!naam.trim()) return; onSave({ naam: naam.trim(), color }); onClose(); }; return (
Naam *
setNaam(e.target.value)} onKeyDown={(e) => e.key === "Enter" && save()} placeholder="Bijv. Hypotheek" style={{ ...is, marginBottom: 16 }} />
Kleur
{COLORS.map((c) => (
); } // ── SchuldTab (pagina) ──────────────────────────────────────────────────────── export default function SchuldTab() { const { data, setData, schuld, schuldBlokken, locked, darkMode, T } = useApp(); const [showNieuweCategorie, setShowNieuweCategorie] = useState(false); const schuldCats = schuldBlokken.map((b) => ({ ...b, subtot: b.items.reduce((a, i) => a + (i.waarde || 0), 0), })); const donutTotal = schuldCats.reduce((a, b) => a + b.subtot, 0); const addSchuldBlok = ({ naam, color }) => { setData({ ...data, schuld: { ...schuld, blokken: [...schuldBlokken, { id: `s${Date.now()}`, naam, color, items: [{ id: `si${Date.now()}`, naam: "Item 1", waarde: 0 }], }], }, }); }; return (

Schuld

{schuldCats.length > 0 && (
{/* Verdeling lijst */}
Verdeling Schuld
{schuldCats.map((b) => (
{b.naam} {fmt(b.subtot)}
))}
Totaal {fmt(donutTotal)}
{/* Donut */}
Verdeling Schuld
({ val: b.subtot, color: b.color }))} total={donutTotal} size={160} />
{schuldCats.map((b) => (
{b.naam}
))}
)} {!locked && ( )} {showNieuweCategorie && ( setShowNieuweCategorie(false)} T={T} darkMode={darkMode} /> )}
{schuldCats.map((blok) => )}
); } // ── kleine helpers ──────────────────────────────────────────────────────────── function inputStyle(T) { return { background: T.inputBg, border: `1px solid ${T.inputBorder}`, borderRadius: 8, color: T.text, padding: "8px 10px", fontSize: 13, outline: "none", boxSizing: "border-box", }; } function FieldLabel({ label, T }) { return (
{label}
); } function EuroInput({ value, onChange, is, T }) { return (
onChange(e.target.value)} placeholder="0" style={{ ...is, paddingLeft: 22, width: "100%" }} />
); } function ErrMsg({ msg }) { return
{msg}
; } const deleteSmBtn = { padding: "4px 8px", background: "rgba(239,68,68,0.1)", border: "1px solid rgba(239,68,68,0.25)", borderRadius: 6, color: RED, cursor: "pointer", fontSize: 13, flexShrink: 0 }; const trashBtn = { padding: "10px 14px", background: "rgba(239,68,68,0.08)", border: "1px solid rgba(239,68,68,0.25)", borderRadius: 10, color: RED, cursor: "pointer", fontWeight: 600, fontSize: 13 }; const addBtn = { padding: "6px 14px", background: `${PURPLE}22`, border: `1px solid ${PURPLE}44`, borderRadius: 8, color: PURPLE_LIGHT, cursor: "pointer", fontSize: 12, fontWeight: 700, flexShrink: 0 }; const cancelBtn = (T) => ({ padding: "10px", background: "transparent", border: `1px solid ${T.border}`, borderRadius: 10, color: T.muted, cursor: "pointer", fontWeight: 600, fontSize: 13 });