Compare commits
7
Commits
v1.0.5
..
6d1b114b78
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d1b114b78 | ||
|
|
b4d1e25e50 | ||
|
|
a9b6203dec | ||
|
|
36f49b216f | ||
|
|
796245fefe | ||
|
|
0bbb166b32 | ||
|
|
fe6b94aa7f |
@@ -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
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -10,7 +10,7 @@ Eigen vermogen · Schulden · Voortgang · Gebruikersbeheer
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
<img src="docs/screenshot-dashboard.png" alt="VibeFinance dashboard" width="820">
|
||||
@@ -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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vibefinance-frontend",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
@@ -113,7 +123,21 @@ export default function DashboardTab() {
|
||||
|
||||
{/* EV Grafiek */}
|
||||
<VCard style={{ background: T.card, border: `1px solid ${T.border}`, marginBottom: 20 }}>
|
||||
<div style={{ fontWeight: 600, marginBottom: 16 }}>Ontwikkeling van Eigen Vermogen</div>
|
||||
<div style={{
|
||||
display: "flex", alignItems: "center", justifyContent: "space-between",
|
||||
gap: 12, flexWrap: "wrap", marginBottom: 16,
|
||||
}}>
|
||||
<div style={{ fontWeight: 600 }}>Ontwikkeling van Eigen Vermogen</div>
|
||||
{vData.length >= 2 && (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 14, fontSize: 11, color: T.muted }}>
|
||||
<Legenda kleur="linear-gradient(90deg, #0eddd4, #8b5cf6)" label="Eigen vermogen" />
|
||||
<Legenda
|
||||
kleur={`repeating-linear-gradient(90deg, ${GOLD} 0 5px, transparent 5px 9px)`}
|
||||
label="Geïnvesteerde fiat"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{vData.length < 2 ? (
|
||||
<div style={{ textAlign: "center", padding: "32px 0", color: T.muted }}>
|
||||
<div style={{ fontSize: 28, marginBottom: 8 }}>📈</div>
|
||||
@@ -122,7 +146,7 @@ export default function DashboardTab() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<MiniLineChart vData={vData} darkMode={darkMode} />
|
||||
<MiniLineChart vData={vData} fiatData={fiatData} darkMode={darkMode} />
|
||||
)}
|
||||
</VCard>
|
||||
|
||||
@@ -130,6 +154,16 @@ export default function DashboardTab() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── Legenda-item bij de grafiek ─────────────────────────────────────────────
|
||||
function Legenda({ kleur, label }) {
|
||||
return (
|
||||
<span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
|
||||
<span style={{ width: 16, height: 3, borderRadius: 2, background: kleur }} />
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// ── 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 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 (
|
||||
<svg
|
||||
@@ -214,6 +259,11 @@ function MiniLineChart({ vData, darkMode }) {
|
||||
{/* Vlakkleur met gradient + fade */}
|
||||
<path d={areaPath} fill="url(#chartLineGrad)" mask="url(#chartAreaMask)" opacity="0.4" />
|
||||
|
||||
{/* Geïnvesteerde fiat — gestippeld, zodat het als referentielijn leest
|
||||
en de EV-lijn de hoofdrol houdt */}
|
||||
<path d={fiatPath} fill="none" stroke={GOLD} strokeWidth="1.8"
|
||||
strokeDasharray="5,4" strokeLinecap="round" strokeLinejoin="round" opacity="0.85" />
|
||||
|
||||
{/* Lijn */}
|
||||
<path d={linePath} fill="none" stroke="url(#chartLineGrad)"
|
||||
strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
@@ -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 && (
|
||||
<>
|
||||
<circle cx={xs[h]} cy={fYs[h]} r="4.5"
|
||||
fill={GOLD} stroke={darkMode ? "#151c2e" : "#fff"} strokeWidth="2.5" />
|
||||
<circle cx={xs[h]} cy={ys[h]} r="5.5"
|
||||
fill="#0eddd4" stroke={darkMode ? "#151c2e" : "#fff"} strokeWidth="2.5" />
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Tooltip */}
|
||||
@@ -239,10 +293,24 @@ function MiniLineChart({ vData, darkMode }) {
|
||||
<text x={tipX + tipW / 2} y={tipY + 16} fill={lc} fontSize="9" textAnchor="middle">
|
||||
{fmtDatum(vData[h].datum)}
|
||||
</text>
|
||||
<text x={tipX + tipW / 2} y={tipY + 34} fill="#0eddd4" fontSize="11"
|
||||
fontWeight="700" textAnchor="middle">
|
||||
<text x={tipX + 10} y={tipY + 33} fill={lc} fontSize="9">Vermogen</text>
|
||||
<text x={tipX + tipW - 10} y={tipY + 33} fill="#0eddd4" fontSize="10"
|
||||
fontWeight="700" textAnchor="end">
|
||||
{fmt(vals[h])}
|
||||
</text>
|
||||
<text x={tipX + 10} y={tipY + 48} fill={lc} fontSize="9">Ingelegd</text>
|
||||
<text x={tipX + tipW - 10} y={tipY + 48} fill={GOLD} fontSize="10"
|
||||
fontWeight="700" textAnchor="end">
|
||||
{fmt(fVals[h])}
|
||||
</text>
|
||||
<line x1={tipX + 10} y1={tipY + 55} x2={tipX + tipW - 10} y2={tipY + 55}
|
||||
stroke={gc} strokeWidth="1" />
|
||||
<text x={tipX + 10} y={tipY + 68} fill={lc} fontSize="9">Resultaat</text>
|
||||
<text x={tipX + tipW - 10} y={tipY + 68}
|
||||
fill={resultaat >= 0 ? GREEN : RED} fontSize="10"
|
||||
fontWeight="700" textAnchor="end">
|
||||
{`${resultaat >= 0 ? "+" : ""}${fmt(resultaat)}`}
|
||||
</text>
|
||||
</g>
|
||||
)}
|
||||
|
||||
|
||||
+1
-1
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user