diff --git a/BEHEER.md b/BEHEER.md index 8efa72b..0972e1c 100644 --- a/BEHEER.md +++ b/BEHEER.md @@ -245,9 +245,11 @@ tag op een Raspberry Pi en op een x86-server werkt. | `README.md` | de `versie-x.x.x`-badge bovenaan | vóór de build | | `package.json` (root) | versie → `x.x.(x+1)-dev` voor de volgende cyclus | **ná** het publiceren | -Een `-DryRun` meldt alle vier zonder te schrijven. Ze blijven na een echte -release als wijziging in je working tree staan; neem ze mee in de volgende -commit. +Een `-DryRun` meldt de vier bestandswijzigingen zonder ze te schrijven (de +Git-tag is de uitzondering in die tabel: die hoort bij stap 4, niet bij de +auto-updates). Na een echte release blijven die vier als wijziging in je working +tree staan; neem ze mee in de volgende commit — de tag staat dan op de commit +ervóór, dus de release bevat ze nog niet. De laatste staat bewust ná het publiceren: breekt de release eerder af, dan schuift het versienummer niet vooruit. Bijeffect dat je moet kennen — na een diff --git a/Changelog.md b/Changelog.md index 9d4ae8f..ab0ecc7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,38 @@ ## [Niet vrijgegeven] +### Toegevoegd +- De grafiek "Ontwikkeling van Eigen Vermogen" op het dashboard heeft een tweede + lijn: de **cumulatief geïnvesteerde fiat**. Die is opgebouwd uit dezelfde + gegevens als de KPI's erboven — het oudste voortgangsmoment telt als inleg, + elke latere rij telt zijn stortingen erbij op — zodat het laatste punt van de + lijn exact gelijk is aan "Totaal geïnvesteerde fiat". Het verschil tussen de + twee lijnen is je rendement, en dat staat nu ook in de tooltip: vermogen, + ingelegd en resultaat per datum +- Legenda boven de grafiek, nodig zodra er meer dan één lijn in staat + +### Gewijzigd +- De inleglijn wordt met rechte segmenten getekend en niet met de + Catmull-Rom-afronding van de vermogenslijn. Cumulatieve inleg loopt nooit + terug, maar de bezier-afronding trekt tussen twee ongelijke stortingen een + doorbuiging onder het vorige punt — dan zou de grafiek een daling tonen die + in de data niet bestaat +- De y-as schaalt op beide reeksen. Op alleen de vermogenswaarden zou de + inleglijn buiten beeld lopen zodra je meer hebt ingelegd dan het vermogen + waard is +- De schermentabel in `README.md` beschreef het Dashboard als "totaal eigen + vermogen, schulden en netto vermogen". Dat klopte niet: `DashboardTab.jsx` + leest `totSchuld` helemaal niet en toont geen schuld en geen nettowaarde — dat + staat op het Voortgang-tabblad. De rij noemt nu wat er wél staat (KPI's over + inleg, vermogen, winst en ROI, de doelbalk en de grafiek), met een alinea + eronder over de twee lijnen + +### Bekend +- De screenshot bovenaan `README.md` is van vóór deze wijziging en toont de + grafiek nog met één lijn + +## [1.0.5] - 2026-09-11 + ### Gewijzigd - De documentatie is gelijkgetrokken met de code. `README.md`: de bestandsboom miste `backend/src/logger.js`, `docker/check-updates.mjs`, diff --git a/README.md b/README.md index b314a1a..392cfd1 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Eigen vermogen · Schulden · Voortgang · Gebruikersbeheer ![Node.js](https://img.shields.io/badge/Node.js_22-339933?style=flat&logo=node.js&logoColor=white) ![PostgreSQL](https://img.shields.io/badge/PostgreSQL_18-4169E1?style=flat&logo=postgresql&logoColor=white) ![Docker](https://img.shields.io/badge/Docker_Compose-2496ED?style=flat&logo=docker&logoColor=white) -![Version](https://img.shields.io/badge/versie-1.0.4-8b5cf6?style=flat) +![Version](https://img.shields.io/badge/versie-1.0.5-8b5cf6?style=flat) ![Built with Claude](https://img.shields.io/badge/Gebouwd_met-Claude.ai-8b5cf6?style=flat&logo=anthropic&logoColor=white) VibeFinance dashboard @@ -166,13 +166,21 @@ build — anders zou de release met een placeholder als notes online komen. | Scherm | Wat het doet | |---|---| -| 📊 **Dashboard** | Overzicht van totaal eigen vermogen, schulden en netto vermogen | +| 📊 **Dashboard** | KPI's over inleg, vermogen, winst en ROI, de voortgang naar je doel, en de grafiek met vermogen tegen inleg | | 💰 **Eigen vermogen** | Vermogensposten per categorie beheren + geïnvesteerde fiat historiek | | 📉 **Schuld** | Schulden bijhouden per blok met afbetalingsoverzicht | | 📈 **Voortgang Schuld & EV** | Grafieken en trends van EV en schuld over tijd (apart bij te houden) | | 👥 **Gebruikers** | Accounts aanmaken, bewerken en beheren (Admin-rol vereist) | | ⚙️ **Instellingen** | Licht/donker thema, backup downloaden en backup terugzetten | +De grafiek "Ontwikkeling van Eigen Vermogen" op het dashboard tekent twee lijnen +op één as: je netto waarde per voortgangsmoment, en daaronder de cumulatief +geïnvesteerde fiat. Het verschil tussen de twee is je rendement; de tooltip zet +vermogen, inleg en resultaat per datum onder elkaar. Het laatste punt van de +inleglijn is per definitie gelijk aan de KPI "Totaal geïnvesteerde fiat" — beide +rekenen het oudste voortgangsmoment als startinleg en tellen daar de stortingen +per rij bij op. + De kolommen in de tabellen op Eigen vermogen en Voortgang zijn versleepbaar: pak de rechterrand van een kolomkop. De breedtes worden per tabel in de browser bewaard; dubbelklik op de greep voor de standaardbreedte. diff --git a/frontend/package.json b/frontend/package.json index a5babf7..f1f922b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance-frontend", - "version": "1.0.4", + "version": "1.0.5", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/pages/DashboardTab.jsx b/frontend/src/pages/DashboardTab.jsx index db33a85..ac149f2 100644 --- a/frontend/src/pages/DashboardTab.jsx +++ b/frontend/src/pages/DashboardTab.jsx @@ -1,7 +1,7 @@ import { useState } from "react"; import { useApp } from "../context/AppContext.jsx"; import { fmt, pct, fmtDatum } from "../utils/format.js"; -import { GREEN, RED } from "../constants/index.js"; +import { GREEN, RED, GOLD } from "../constants/index.js"; const TEAL = "#a855f7"; import { VCard, VBadge } from "../components/ui/index.jsx"; @@ -38,6 +38,16 @@ export default function DashboardTab() { // Minigrafiek data const vData = [...evRows].sort((a, b) => a.datum.localeCompare(b.datum)); + // Cumulatief geïnvesteerde fiat, op dezelfde datums als vData. Dezelfde + // opbouw als de KPI's hierboven: het oudste punt is de start-EV (daar is de + // inleg per definitie gelijk aan de waarde), elk volgend punt telt de + // stortingen van die rij erbij op. Het laatste punt is daarmee gelijk aan + // "Totaal geïnvesteerde fiat". + const fiatData = vData.reduce((acc, row, i) => { + acc.push(i === 0 ? (row.nettoWaarde || 0) : acc[i - 1] + catSum(row)); + return acc; + }, []); + return (
@@ -113,7 +123,21 @@ export default function DashboardTab() { {/* EV Grafiek */} -
Ontwikkeling van Eigen Vermogen
+
+
Ontwikkeling van Eigen Vermogen
+ {vData.length >= 2 && ( +
+ + +
+ )} +
{vData.length < 2 ? (
📈
@@ -122,7 +146,7 @@ export default function DashboardTab() {
) : ( - + )} @@ -130,6 +154,16 @@ export default function DashboardTab() { ); } +// ── Legenda-item bij de grafiek ───────────────────────────────────────────── +function Legenda({ kleur, label }) { + return ( + + + {label} + + ); +} + // ── Smooth Catmull-Rom bezier path ────────────────────────────────────────── function crPath(xs, ys, t = 0.4) { if (xs.length < 2) return ""; @@ -149,18 +183,25 @@ function crPath(xs, ys, t = 0.4) { } // ── Mini inline SVG grafiek ──────────────────────────────────────────────────── -function MiniLineChart({ vData, darkMode }) { +function MiniLineChart({ vData, fiatData, darkMode }) { const [hovered, setHovered] = useState(null); const W = 700, H = 230, pl = 62, pr = 16, pt = 16, pb = 52; - const vals = vData.map((d) => d.nettoWaarde || 0); - const mn = Math.min(...vals) * 0.95; - const mx = Math.max(...vals) * 1.05; + const vals = vData.map((d) => d.nettoWaarde || 0); + const fVals = fiatData; + // Beide reeksen staan in euro's op dezelfde as, dus het bereik moet ze allebei + // omvatten — anders loopt de inleglijn buiten beeld zodra die boven de EV uitkomt. + const mn = Math.min(...vals, ...fVals) * 0.95; + const mx = Math.max(...vals, ...fVals) * 1.05; const rng = mx - mn || 1; const iW = W - pl - pr, iH = H - pt - pb; const xs = vData.map((_, i) => pl + (i / Math.max(vData.length - 1, 1)) * iW); const ys = vals.map((v) => pt + (1 - (v - mn) / rng) * iH); + const fYs = fVals.map((v) => pt + (1 - (v - mn) / rng) * iH); const linePath = crPath(xs, ys); const areaPath = `${linePath} L ${xs[xs.length-1]},${pt+iH} L ${xs[0]},${pt+iH} Z`; + // Rechte segmenten, geen crPath: de inleg loopt nooit terug, en de bezier- + // afronding zou tussen twee stortingen een dip tekenen die er niet is. + const fiatPath = xs.map((x, i) => `${i ? "L" : "M"} ${x},${fYs[i]}`).join(" "); const gc = darkMode ? "rgba(255,255,255,0.07)" : "rgba(0,0,0,0.06)"; const lc = darkMode ? "#6b7280" : "#94a3b8"; @@ -172,9 +213,13 @@ function MiniLineChart({ vData, darkMode }) { }; const h = hovered; - const tipW = 114, tipH = 46; + const tipW = 150, tipH = 76; const tipX = h !== null ? Math.min(Math.max(xs[h] - tipW / 2, pl), W - tipW - 4) : 0; - const tipY = h !== null ? (ys[h] - tipH - 14 < pt ? ys[h] + 14 : ys[h] - tipH - 14) : 0; + // Ankeren op de hoogste van de twee punten, zodat de tooltip nooit over de + // andere lijn heen valt. + const tipAnker = h !== null ? Math.min(ys[h], fYs[h]) : 0; + const tipY = h !== null ? (tipAnker - tipH - 14 < pt ? Math.max(ys[h], fYs[h]) + 14 : tipAnker - tipH - 14) : 0; + const resultaat = h !== null ? vals[h] - fVals[h] : 0; return ( + {/* Geïnvesteerde fiat — gestippeld, zodat het als referentielijn leest + en de EV-lijn de hoofdrol houdt */} + + {/* Lijn */} @@ -224,10 +274,14 @@ function MiniLineChart({ vData, darkMode }) { stroke="rgba(255,255,255,0.25)" strokeWidth="1" strokeDasharray="4,3" /> )} - {/* Hover punt */} + {/* Hover punten */} {h !== null && ( - + <> + + + )} {/* Tooltip */} @@ -239,10 +293,24 @@ function MiniLineChart({ vData, darkMode }) { {fmtDatum(vData[h].datum)} - + Vermogen + {fmt(vals[h])} + Ingelegd + + {fmt(fVals[h])} + + + Resultaat + = 0 ? GREEN : RED} fontSize="10" + fontWeight="700" textAnchor="end"> + {`${resultaat >= 0 ? "+" : ""}${fmt(resultaat)}`} + )} diff --git a/package.json b/package.json index 1c8e2ad..420e94b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance", - "version": "1.0.5", + "version": "1.0.6", "description": "VibeFinance — persoonlijk vermogensbeheer", "private": true, "repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance",