diff --git a/BEHEER.md b/BEHEER.md index 0972e1c..15b33ca 100644 --- a/BEHEER.md +++ b/BEHEER.md @@ -32,6 +32,32 @@ Vite proxyt `/api` naar `localhost:3001` binnen dezelfde container. > ⚠️ Alleen `.\dev.ps1 down` wist de development-database (`docker compose down -v`). > `.\dev.ps1 up` doet een gewone `down` en laat het volume staan. +### Hot reload pakt wijzigingen van de host niet op + +De naam "hot reload" in de tabel hierboven is optimistischer dan de praktijk. +`docker-compose.dev.yml` mount `./frontend` de container in, maar over die +bind-mount komen op Windows geen inotify-events aan. Vite's bestandswatcher ziet +je wijziging dus niet, houdt de oude versie van de module in zijn geheugen, en +blijft die serveren — ook na een harde herlaad in de browser. + +Zo zie je het verschil tussen "de browser cachet" en "Vite serveert oud": + +```powershell +# Haalt de module rechtstreeks bij Vite op, langs de browser om +(Invoke-WebRequest "http://localhost/src/pages/DashboardTab.jsx" -UseBasicParsing).Content -match "jouw-nieuwe-code" +``` + +Komt daar `False` uit terwijl het wél in het bestand staat, dan is het de +watcher. Een `touch` binnen de container helpt niet; herstarten wel: + +```powershell +docker compose -f docker-compose.dev.yml restart app +``` + +Wil je het structureel oplossen, dan is `server: { watch: { usePolling: true, +interval: 300 } }` in `frontend/vite.config.js` de knop daarvoor. Polling kost +wel constant CPU, vandaar dat het er niet standaard in staat. + ### Welke versie de dev-app toont De versie in "Over VibeFinance" komt uit `VITE_APP_VERSION`. `dev.ps1` leest die diff --git a/Changelog.md b/Changelog.md index a10df3a..5e85575 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,63 @@ ## [Niet vrijgegeven] +### Toegevoegd +- **Kleurthema's.** Negen paletten in `frontend/src/constants/themas.js`, te kiezen + onder Instellingen naast de dark-mode schakelaar. Thema en licht/donker staan + los van elkaar: het thema levert het accent, de schakelaar de ondergrond. Dat + scheelt een compleet tweede palet per thema en laat de bestaande schakelaar + doen wat hij deed +- `useTheme(themaId, darkMode)` leidt randen, invoervelden en schaduwen af uit + het accent van het thema, dus die kleuren vanzelf mee. Nieuwe sleutels op `T`: + `accent`, `accentLicht`, `accentTekst`, `accentGradient`, `chartVan`, + `chartTot`, `darkMode`, `themaId` en `themaNaam`. De bestaande sleutels houden + hun naam en betekenis, zodat er niets hoeft te wijzigen dat ze al gebruikt +- Het accent dat tekst draagt is per ondergrond een andere stap van dezelfde + kleur — de lichte op donker, de donkere op licht. Eén stap voor beide modi + haalt aan één kant altijd te weinig contrast + +### Opgelost +- De weergavevoorkeuren overleven een herlaad. `darkMode` stond op + `useState(true)` en werd nergens bewaard: zette je de app op licht en laadde je + opnieuw, dan stond je weer in het donker. Thema en modus gaan nu naar + localStorage, net als de kolombreedtes. Ze staan los van je financiële data en + gaan dus niet mee in de backup-ZIP +- `VoortgangTab.jsx` bepaalde de modus met `T.bg === "#0f1117"`. Die kleur is de + achtergrond allang niet meer (dat is `#241A39`), dus de grafiek gebruikte + altijd de lichte rasterlijnen — ook in het donker. Leest nu `T.darkMode`, wat + met instelbare thema's sowieso de enige werkbare manier is +- De kolomkoppen van de categorieën hebben een `title`, net als de + Opmerkingen-cellen al hadden: bij de standaardbreedte kapt "Crowdfunding" af +- De dark-mode schakelaar in `InstellingenModal.jsx` was een `
` met een + klik-handler en dus niet met het toetsenbord te bedienen. Nu een + `
@@ -110,8 +110,8 @@ function AppInner() { }}>
@@ -242,7 +242,7 @@ function AppInner() { >
localStorage.getItem("vf_token"); @@ -56,11 +52,11 @@ function RolSelect({ value, onChange, opties, T, darkMode }) { {opties.map((o) => (
{ onChange(o); setOpen(false); }} style={{ padding: "9px 12px", fontSize: 13, cursor: "pointer", - color: o === value ? TEAL_COLOR : T.text, - background: o === value ? TEAL_BG : "transparent", + color: o === value ? T.accentTekst : T.text, + background: o === value ? T.inputBg : "transparent", }} onMouseEnter={(e) => e.currentTarget.style.background = "rgba(14,221,212,0.10)"} - onMouseLeave={(e) => e.currentTarget.style.background = o === value ? TEAL_BG : "transparent"} + onMouseLeave={(e) => e.currentTarget.style.background = o === value ? T.inputBg : "transparent"} > {o}
@@ -197,8 +193,8 @@ export default function GebruikersBeheer({ onClose }) { }}>
Gebruikersbeheer - {users.length} gebruiker{users.length !== 1 ? "s" : ""} @@ -236,18 +232,18 @@ export default function GebruikersBeheer({ onClose }) {
{initials}
{u.naam} {isSelf && ( jij )}
@@ -263,9 +259,9 @@ export default function GebruikersBeheer({ onClose }) {
{!isSelf && ( <> @@ -303,10 +299,10 @@ export default function GebruikersBeheer({ onClose }) { {/* Bewerken modal */} {editUser && (
-
+
{editUser.naam} bewerken
@@ -322,7 +318,7 @@ export default function GebruikersBeheer({ onClose }) { }}>Annuleren @@ -333,7 +329,7 @@ export default function GebruikersBeheer({ onClose }) { {/* Nieuwe gebruiker */} {showAdd ? (
@@ -353,7 +349,7 @@ export default function GebruikersBeheer({ onClose }) { }}>Annuleren diff --git a/frontend/src/components/InstellingenModal.jsx b/frontend/src/components/InstellingenModal.jsx index e1423ab..c3c75f5 100644 --- a/frontend/src/components/InstellingenModal.jsx +++ b/frontend/src/components/InstellingenModal.jsx @@ -1,6 +1,5 @@ import { useApp } from "../context/AppContext.jsx"; - -const TEAL = "linear-gradient(135deg, #7c3aed, #a855f7)"; +import { THEMAS } from "../constants/themas.js"; const BACKUP_ICON = ( @@ -35,7 +34,7 @@ function ActionRow({ icon, title, description, onClick }) { } export default function InstellingenModal({ onClose }) { - const { T, darkMode, setDarkMode, doBackup, doRestore } = useApp(); + const { T, darkMode, setDarkMode, thema, setThema, doBackup, doRestore } = useApp(); return (
Dark mode -
setDarkMode((d) => !d)} style={{ width: 44, height: 24, borderRadius: 12, cursor: "pointer", - background: darkMode ? TEAL : "#334155", + background: darkMode ? T.accent : "#334155", + border: "none", padding: 0, position: "relative", transition: "background 0.2s", flexShrink: 0, }} > -
+ +
+ + {/* Kleurthema */} +
+
+ Kleurthema + {T.themaNaam} +
+ {/* Drie kolommen: negen thema's vullen zo precies drie rijen, zonder + een halve rij onderaan. */} +
+ {THEMAS.map((t) => { + const gekozen = t.id === thema; + return ( +
diff --git a/frontend/src/components/NavBar.jsx b/frontend/src/components/NavBar.jsx index 47b95e7..16103d3 100644 --- a/frontend/src/components/NavBar.jsx +++ b/frontend/src/components/NavBar.jsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { TABS } from "../constants/index.js"; +import { TABS, RED } from "../constants/index.js"; import { version } from "../version.js"; import { useUpdateCheck } from "../hooks/useUpdateCheck.js"; import { useApp } from "../context/AppContext.jsx"; @@ -82,9 +82,6 @@ function SectionLabel({ children }) { ); } -const ACTIVE_BG = "#7c3aed"; -const ACTIVE_SHADOW = "0 4px 14px rgba(124,58,237,0.4)"; - function NavItem({ active, onClick, icon, label, T, danger }) { const [hovered, setHovered] = useState(false); const isLit = active || hovered; @@ -98,12 +95,12 @@ function NavItem({ active, onClick, icon, label, T, danger }) { display: "flex", alignItems: "center", gap: 10, padding: "11px 12px", borderRadius: 9, cursor: "pointer", marginBottom: 4, background: active - ? ACTIVE_BG - : hovered ? "rgba(139,92,246,0.08)" : "transparent", - color: active ? "#fff" : hovered ? T.text : (danger ? "#ef4444" : T.muted), + ? T.accent + : hovered ? T.inputBg : "transparent", + color: active ? "#fff" : hovered ? T.text : (danger ? RED : T.muted), fontWeight: active ? 600 : 400, fontSize: 15, - boxShadow: active ? ACTIVE_SHADOW : "none", + boxShadow: active ? T.cardShadow : "none", transition: "all 0.15s", }} > @@ -141,12 +138,12 @@ function OverPopup({ onClose, nieuweVersie, T }) {
- - + +
@@ -158,7 +155,7 @@ function OverPopup({ onClose, nieuweVersie, T }) {
Versie - + v{version}
@@ -198,7 +195,7 @@ function DarkModeToggle({ darkMode, setDarkMode, T }) { {/* Toggle pill */}
+ stroke={T.accentTekst} strokeOpacity="0.35" strokeWidth="1.4"/>
VibeFinance diff --git a/frontend/src/components/ProfielPopup.jsx b/frontend/src/components/ProfielPopup.jsx index 6c0b5c1..a4d010f 100644 --- a/frontend/src/components/ProfielPopup.jsx +++ b/frontend/src/components/ProfielPopup.jsx @@ -2,8 +2,6 @@ import { useState } from "react"; import { useApp } from "../context/AppContext.jsx"; import { RED } from "../constants/index.js"; -const TEAL = "linear-gradient(135deg, #7c3aed, #a855f7)"; -const TEAL_SHADOW = "0 4px 14px rgba(124,58,237,0.4)"; function initials(naam = "") { const parts = naam.trim().split(/\s+/); @@ -149,7 +147,7 @@ export default function ProfielPopup({ onClose }) {
{currentUser?.email}
@@ -189,7 +187,7 @@ export default function ProfielPopup({ onClose }) {
@@ -494,9 +496,9 @@ export default function EigenVermogenTab() { total: fmt(totVerdeling), totalColor: GREEN, }, { - title: "Geïnvesteerde Fiat", accent: PURPLE_LIGHT, + title: "Geïnvesteerde Fiat", accent: T.accentTekst, 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, + total: evRows.length > 0 ? fmt(totGeinv) : "—", totalColor: T.accentTekst, }, { title: "Rendement", accent: "#f59e0b", @@ -579,16 +581,16 @@ export default function EigenVermogenTab() { {!locked && (
)} {showAddCat && ( setShowAddCat(false)}> - setShowAddCat(false)} headerBg={darkMode ? `${PURPLE}08` : "#f8f5ff"} T={T} /> + setShowAddCat(false)} headerBg={darkMode ? `${T.accent}08` : T.inputBg} T={T} />
Naam *
- + @@ -668,9 +670,9 @@ function ErrMsg({ msg }) { }}>{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 primaryBtn = (T) => ({ padding: "10px 20px", background: T.accent, border: "none", borderRadius: 8, color: "#fff", fontWeight: 700, cursor: "pointer", fontSize: 13 }); +const editBtn = (T) => ({ padding: "2px 10px", background: `${T.accent}22`, border: `1px solid ${T.accent}44`, borderRadius: 6, color: T.accentTekst, 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 addBtn = (T) => ({ padding: "6px 14px", background: `${T.accent}22`, border: `1px solid ${T.accent}44`, borderRadius: 8, color: T.accentTekst, 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 }); diff --git a/frontend/src/pages/SchuldTab.jsx b/frontend/src/pages/SchuldTab.jsx index 962c518..324ef49 100644 --- a/frontend/src/pages/SchuldTab.jsx +++ b/frontend/src/pages/SchuldTab.jsx @@ -1,7 +1,7 @@ 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 { RED, GREEN } from "../constants/index.js"; import { VCard, DonutChart, Overlay, ModalBox, ModalHeader, EmojiPicker, AppIcon, ConfirmDeleteDialog } from "../components/ui/index.jsx"; // ── SchuldAanpassenPopup ────────────────────────────────────────────────────── @@ -108,14 +108,14 @@ function SchuldAanpassenPopup({ blok, onClose }) {
{ setNieuwNaam(e.target.value); setNieuwErr(""); }} placeholder="Naam" style={{ ...is, flex: 1 }} /> - +
{nieuwErr && }
@@ -448,5 +448,5 @@ function ErrMsg({ 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 addBtn = (T) => ({ padding: "6px 14px", background: `${T.accent}22`, border: `1px solid ${T.accent}44`, borderRadius: 8, color: T.accentTekst, 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 }); diff --git a/frontend/src/pages/VoortgangTab.jsx b/frontend/src/pages/VoortgangTab.jsx index 5001975..118e7eb 100644 --- a/frontend/src/pages/VoortgangTab.jsx +++ b/frontend/src/pages/VoortgangTab.jsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { useApp } from "../context/AppContext.jsx"; import { fmt, fmtDatum } from "../utils/format.js"; import { useKolomBreedtes } from "../hooks/useKolomBreedtes.js"; -import { PURPLE, PURPLE_LIGHT, RED, GREEN } from "../constants/index.js"; +import { RED, GREEN } from "../constants/index.js"; import { VCard, DonutChart, Overlay, ModalBox, ModalHeader, ModalFooter } from "../components/ui/index.jsx"; // ── Inline SVG line chart ───────────────────────────────────────────────────── @@ -27,8 +27,11 @@ function SvgLineChart({ pts, lines, T, H = 200 }) { const rng = mx - mn || 1; const xp = (i) => pl + (i / (pts.length - 1)) * iW; const yp = (v) => pt2 + (1 - ((v ?? 0) - mn) / rng) * iH; - const gc = T.bg === "#0f1117" ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.05)"; - const darkMode = T.bg === "#0f1117"; + // Was een vergelijking met een vaste hex (`T.bg === "#0f1117"`). Die kleur is + // de achtergrond allang niet meer, dus beide stonden altijd op licht — en met + // instelbare thema's valt er sowieso niets meer aan de bg af te lezen. + const darkMode = T.darkMode; + const gc = darkMode ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.05)"; return (
@@ -82,7 +85,7 @@ function SvgLineChart({ pts, lines, T, H = 200 }) { {isHov && ( @@ -145,12 +148,12 @@ function VoortgangModal({ onSave, onClose, T, darkMode, initialData }) {
@@ -189,7 +192,7 @@ function VoortgangModal({ onSave, onClose, T, darkMode, initialData }) { - @@ -286,8 +289,8 @@ export default function VoortgangTab() { {/* Hero netto */}
@@ -397,7 +400,7 @@ export default function VoortgangTab() {
Voortgang overzicht
{!locked && ( )} @@ -419,7 +422,7 @@ export default function VoortgangTab() { Datum Eigen Vermogen Schuld - Nettowaarde + Nettowaarde Opmerkingen {!locked && } @@ -435,7 +438,7 @@ export default function VoortgangTab() { {row.opmerkingen || "—"} {!locked && ( - + )} diff --git a/frontend/src/utils/storage.js b/frontend/src/utils/storage.js index c0c6536..f60d446 100644 --- a/frontend/src/utils/storage.js +++ b/frontend/src/utils/storage.js @@ -3,6 +3,10 @@ const KEYS = { evRows: "ev_voortgang_rows", users: "wbjw_users", exitplan: "exitplan_data", + // Weergavevoorkeuren: per browser, net als de kolombreedtes. Ze staan bewust + // los van de financiële data, dus ze gaan niet mee in de backup-ZIP. + thema: "vibefinance_thema", + darkMode: "vibefinance_darkmode", }; export function loadFromStorage(key) { diff --git a/package.json b/package.json index 5e05c69..ce20d32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vibefinance", - "version": "1.0.7", + "version": "1.0.8", "description": "VibeFinance — persoonlijk vermogensbeheer", "private": true, "repository": "https://vibehoogie.duckdns.org/vibe/VibeFinance",