import { useState } from "react"; import { useApp } from "../context/AppContext.jsx"; import { fmt, fmtDatum } from "../utils/format.js"; import { PURPLE, PURPLE_LIGHT, GREEN, RED, CAT_COLORS } from "../constants/index.js"; import { VCard, DonutChart, Overlay, ModalBox, ModalHeader, ModalFooter, EmojiPicker, AppIcon, ConfirmDeleteDialog, EmptyState, } from "../components/ui/index.jsx"; // ── AanpassenPopup (EV categorie) ───────────────────────────────────────────── function AanpassenPopup({ cat, onClose }) { const { data, setData, ev, cats, darkMode, T } = useApp(); const [items, setItems] = useState(cat.items.map((i) => ({ ...i }))); const [catNaam, setCatNaam] = useState(cat.naam); const [catColor, setCatColor] = useState(cat.color); const [catIcon, setCatIcon] = useState(cat.icon || ""); const [showEmoji, setShowEmoji] = useState(false); const [nieuwNaam, setNieuwNaam] = useState(""); const [nieuwVal, setNieuwVal] = useState(""); const [err, setErr] = 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()) { setErr("Naam is verplicht."); return; } setItems((p) => [...p, { id: `i${Date.now()}`, naam: nieuwNaam.trim(), waarde: parseFloat(nieuwVal) || 0 }]); setNieuwNaam(""); setNieuwVal(""); setErr(""); }; const handleSave = () => { const updated = { ...data, eigenVermogen: { ...ev, categories: cats.map((c) => c.id !== cat.id ? c : { ...c, naam: catNaam.trim() || c.naam, color: catColor, icon: catIcon, items: items.map((i) => ({ ...i, waarde: parseFloat(i.waarde) || 0 })), } ), }, }; setData(updated); onClose(); }; const handleDelete = () => { setData({ ...data, eigenVermogen: { ...ev, categories: cats.filter((c) => c.id !== cat.id) } }); onClose(); }; return (
{/* Naam + emoji */}
setCatNaam(e.target.value)} style={{ ...is, flex: 1, borderLeft: `3px solid ${cat.color}` }} placeholder="Naam categorie" />
{showEmoji && ( { setCatIcon(e); setShowEmoji(false); }} onClear={() => { setCatIcon(""); setShowEmoji(false); }} /> )}
{/* Kleur */}
{CAT_COLORS.map((c) => (
{/* Bestaande 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); setErr(""); }} placeholder="Naam" style={{ ...is, flex: 1 }} />
{err && }
{confirmDel ? ( setConfirmDel(false)} onConfirm={handleDelete} /> ) : ( <> )}
); } // ── CatKaart ────────────────────────────────────────────────────────────────── function CatKaart({ cat }) { const { locked, T } = useApp(); const [showAanpassen, setShowAanpassen] = useState(false); return ( {showAanpassen && setShowAanpassen(false)} />}
{cat.icon && } {cat.naam} {!locked && ( )}
{cat.items.map((item) => (
{item.naam} {fmt(item.waarde)}
))}
{fmt(cat.items.reduce((a, i) => a + (i.waarde || 0), 0))}
); } // ── EVVoortgangTabel ────────────────────────────────────────────────────────── function EVVoortgangTabel() { const { evRows, setEvRows, cats, locked, darkMode, T } = useApp(); const empty = () => ({ datum: "", nettoWaarde: "", opmerkingen: "", ...cats.reduce((a, c) => ({ ...a, [c.id]: "" }), {}), }); const [showModal, setShowModal] = useState(false); const [input, setInput] = useState(empty); const [editDatum, setEditDatum] = useState(null); const [err, setErr] = useState(""); const is = inputStyle(T); const openModal = () => { setInput(empty()); setEditDatum(null); setErr(""); setShowModal(true); }; const openEdit = (row) => { setInput({ ...row, opmerkingen: row.opmerkingen || "" }); setEditDatum(row.datum); setErr(""); setShowModal(true); }; const closeModal = () => { setShowModal(false); setErr(""); setEditDatum(null); }; const addRow = () => { if (!input.datum) { setErr("Datum is verplicht."); return; } const parsedCats = cats.reduce((a, c) => ({ ...a, [c.id]: parseFloat(input[c.id]) || 0 }), {}); const hasValue = cats.some((c) => parsedCats[c.id] > 0); if (!hasValue) { setErr("Vul minimaal één bedrag in."); return; } const row = { ...input, ...parsedCats, nettoWaarde: parseFloat(input.nettoWaarde) || 0, }; const updated = [...evRows.filter((r) => r.datum !== row.datum), row] .sort((a, b) => b.datum.localeCompare(a.datum)); setEvRows(updated); closeModal(); }; const removeRow = (datum) => setEvRows(evRows.filter((r) => r.datum !== datum)); const th = { padding: "8px 10px", fontSize: 11, color: T.muted, fontWeight: 600, textAlign: "left", whiteSpace: "nowrap", background: T.card, position: "sticky", top: 0, zIndex: 1, borderBottom: `1px solid ${T.border}` }; const td = { padding: "7px 10px", fontSize: 12, color: T.text, borderBottom: `1px solid ${T.border}` }; return (
{/* Modal */} {showModal && (
setInput((s) => ({ ...s, datum: e.target.value }))} style={{ ...is, width: "100%", marginBottom: 16 }} />
{cats.map((c) => (
{c.naam} setInput((s) => ({ ...s, [c.id]: v }))} is={is} T={T} />
))}
setInput((s) => ({ ...s, nettoWaarde: v }))} is={is} T={T} />
setInput((s) => ({ ...s, opmerkingen: e.target.value }))} placeholder="Optionele notitie..." style={{ ...is, width: "100%" }} /> {err && }
)}
Geïnvesteerde Fiat
{!locked && ( )}
{evRows.length > 0 ? (
{cats.map((c) => )} {!locked && } {evRows.map((row, i) => { const vorigeNW = i < evRows.length - 1 ? (evRows[i + 1].nettoWaarde || 0) : null; const verschil = vorigeNW !== null ? (row.nettoWaarde || 0) - vorigeNW : null; const vColor = verschil === null ? T.muted : verschil > 0 ? GREEN : verschil < 0 ? RED : T.muted; const vLabel = verschil === null ? "—" : `${verschil >= 0 ? "+" : ""}${fmt(verschil)}`; return ( {cats.map((c) => )} {!locked && ( )} ); })}
Datum{c.naam}Netto Waarde Verschil Opmerkingen
{fmtDatum(row.datum)}{fmt(row[c.id] || 0)}{fmt(row.nettoWaarde || 0)} {vLabel} {row.opmerkingen || "—"}
) : ( )}
); } // ── DoelProgressie ──────────────────────────────────────────────────────────── function DoelProgressie({ ev, data, setData, totVerdeling, locked, T }) { const [editing, setEditing] = useState(false); const [val, setVal] = useState(""); const doel = ev.doel || 1; const progressPct = Math.min(totVerdeling / doel, 1); const openEdit = () => { setVal(String(doel)); setEditing(true); }; const save = () => { const n = parseFloat(val); if (n > 0) setData({ ...data, eigenVermogen: { ...ev, doel: n } }); setEditing(false); }; return (
🎯 Gewenst doel EV
{fmt(totVerdeling)} {editing ? (
/ setVal(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") save(); if (e.key === "Escape") setEditing(false); }} style={{ width: 130, padding: "4px 8px", borderRadius: 8, fontSize: 13, background: T.inputBg, border: `1px solid ${PURPLE}`, color: T.text, outline: "none", }} />
) : ( <> / {fmt(doel)} {!locked && ( )} )} {Math.round(progressPct * 100)}%
); } // ── EigenVermogenTab (pagina) ───────────────────────────────────────────────── export default function EigenVermogenTab() { const { cats, catTotals, ev, data, setData, locked, darkMode, T, evRows } = useApp(); const [showAddCat, setShowAddCat] = useState(false); const [newNaam, setNewNaam] = useState(""); const [newColor, setNewColor] = useState(CAT_COLORS[0]); const totVerdeling = catTotals.reduce((a, c) => a + c.tot, 0); const sumCat = (id) => evRows.reduce((a, r) => a + (r[id] || 0), 0); const totGeinv = cats.reduce((a, c) => a + sumCat(c.id), 0); const iStyle2 = inputStyle(T); const addCat = () => { if (!newNaam.trim()) return; setData({ ...data, eigenVermogen: { ...ev, categories: [...cats, { id: `c${Date.now()}`, naam: newNaam, color: newColor, items: [{ id: `i${Date.now()}`, naam: "Item 1", waarde: 0 }], }], }, }); setNewNaam(""); setShowAddCat(false); }; const summaryBlocks = [ { title: "Verdeling Eigen Vermogen", accent: GREEN, rows: catTotals.map((c) => ({ label: c.naam, value: fmt(c.tot), color: c.color })), total: fmt(totVerdeling), totalColor: GREEN, }, { title: "Geïnvesteerde Fiat", accent: PURPLE_LIGHT, rows: cats.map((c) => ({ label: c.naam, value: evRows.length > 0 ? fmt(sumCat(c.id)) : "—", color: c.color })), total: evRows.length > 0 ? fmt(totGeinv) : "—", totalColor: PURPLE_LIGHT, }, { title: "Rendement", accent: "#f59e0b", rows: cats.map((c) => { const ev2 = catTotals.find((ct) => ct.id === c.id)?.tot ?? 0; const g = sumCat(c.id); const r = g > 0 ? (ev2 - g) / g : null; return { label: c.naam, value: r === null ? "—" : `${(r * 100).toFixed(1)}%`, color: r === null ? T.muted : r >= 0 ? GREEN : RED }; }), total: (() => { const et = totVerdeling; const gt = totGeinv; return gt > 0 ? `${(((et - gt) / gt) * 100).toFixed(1)}%` : "—"; })(), totalColor: (() => { return totGeinv > 0 ? (totVerdeling >= totGeinv ? GREEN : RED) : T.muted; })(), }, ]; return (

Eigen Vermogen

{/* Doel progressie */} {/* Summary blokken */}
{summaryBlocks.map((block) => (
{block.title}
{block.rows.map(({ label, value, color }) => (
{label} {value}
))}
Totaal {block.total}
))} {totVerdeling > 0 && (
({ val: c.tot, color: c.color, naam: c.naam }))} total={totVerdeling} size={200} label="Verdeling" centerVal={fmt(totVerdeling)} />
{catTotals.map((c) => (
{c.naam}
))}
)}
{/* Nieuwe categorie */} {!locked && (
)} {showAddCat && ( setShowAddCat(false)}> setShowAddCat(false)} headerBg={darkMode ? `${PURPLE}08` : "#f8f5ff"} T={T} />
Naam *
setNewNaam(e.target.value)} onKeyDown={(e) => e.key === "Enter" && addCat()} style={{ ...iStyle2, marginBottom: 16, width: "100%", boxSizing: "border-box" }} />
Kleur
{CAT_COLORS.map((c) => (
)} {/* Categorie kaarten */}
{cats.map((cat) => )}
{/* Voortgang tabel */}
); } // ── Gedeelde 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 primaryBtn = { padding: "10px 20px", background: PURPLE, border: "none", borderRadius: 8, color: "#fff", fontWeight: 700, cursor: "pointer", fontSize: 13 }; const editBtn = { padding: "2px 10px", background: `${PURPLE}22`, border: `1px solid ${PURPLE}44`, borderRadius: 6, color: PURPLE_LIGHT, cursor: "pointer", fontSize: 12, fontWeight: 600 }; const deleteSmBtn = { padding: "2px 8px", background: "rgba(239,68,68,0.1)", border: "1px solid rgba(239,68,68,0.25)", borderRadius: 6, color: RED, cursor: "pointer", fontSize: 12 }; 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 });