Donut-hover met percentages en werkende update-notificatie
- Donut: naadloze segmenten (donkere spleten weg), hover-tooltip per categorie met naam, bedrag en aandeel, middenwaarde schaalt mee - Update-check via de eigen backend (/api/update-check) i.p.v. direct naar de Gitea API, die geen CORS-headers stuurt - Semver-vergelijking, herhaalde check en release-notes in de melding - release.ps1 maakt de Gitea-release aan (stap 4) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e45d5b73fb
commit
3c67381de9
@@ -3,10 +3,14 @@
|
||||
* Wordt gebruikt door alle componenten om consistent kleuren te krijgen.
|
||||
*/
|
||||
export function useTheme(darkMode) {
|
||||
const bg = darkMode ? "#241A39" : "#eef1f8";
|
||||
const card = darkMode ? "rgba(22,23,27,0.30)" : "#ffffff";
|
||||
return {
|
||||
// Exacte Finex kleuren (dark) / licht alternatief
|
||||
bg: darkMode ? "#241A39" : "#eef1f8",
|
||||
card: darkMode ? "rgba(22,23,27,0.30)" : "#ffffff",
|
||||
bg,
|
||||
card,
|
||||
// Ondoorzichtige achtergrond voor popups/modals: kaartkleur over de pagina-bg.
|
||||
popupBg: `linear-gradient(${card}, ${card}), ${bg}`,
|
||||
sidebar: darkMode ? "#16171B" : "#f8faff",
|
||||
border: darkMode ? "rgba(139,92,246,0.15)" : "rgba(0,0,0,0.07)",
|
||||
muted: darkMode ? "#8b8fa8" : "#94a3b8",
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
// De check loopt via de eigen backend (/api/update-check): de Gitea API stuurt
|
||||
// geen CORS-headers, dus de browser mag die niet direct bevragen.
|
||||
const POLL_MS = 30 * 60 * 1000; // periodiek opnieuw kijken
|
||||
const MIN_GAP_MS = 5 * 60 * 1000; // niet vaker dan dit, ook niet bij focus
|
||||
|
||||
/**
|
||||
* Vergelijkt twee semver-strings. Een pre-release suffix (`-dev`) telt als
|
||||
* lager dan dezelfde versie zonder suffix.
|
||||
*/
|
||||
export function isNieuwer(latest, huidig) {
|
||||
const parse = (v) => {
|
||||
const [core, pre] = String(v ?? "").replace(/^v/, "").split("-");
|
||||
const n = core.split(".").map((x) => parseInt(x, 10) || 0);
|
||||
return { n: [n[0] ?? 0, n[1] ?? 0, n[2] ?? 0], pre: pre || null };
|
||||
};
|
||||
const a = parse(latest), b = parse(huidig);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (a.n[i] !== b.n[i]) return a.n[i] > b.n[i];
|
||||
}
|
||||
return !a.pre && !!b.pre;
|
||||
}
|
||||
|
||||
/**
|
||||
* Geeft de nieuwste release terug zodra die nieuwer is dan de draaiende versie,
|
||||
* anders `null`. Vorm: { latest, naam, notes, url, publishedAt }.
|
||||
*/
|
||||
export function useUpdateCheck() {
|
||||
const [update, setUpdate] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Dev-builds lopen per definitie voor op de laatste release — geen melding.
|
||||
// Met ?updatecheck in de URL forceer je de check alsnog (voor testen).
|
||||
const forceer = new URLSearchParams(window.location.search).has("updatecheck");
|
||||
if (version.includes("-") && !forceer) return;
|
||||
|
||||
let gestopt = false;
|
||||
let laatste = 0;
|
||||
|
||||
const check = async () => {
|
||||
if (gestopt || Date.now() - laatste < MIN_GAP_MS) return;
|
||||
laatste = Date.now();
|
||||
try {
|
||||
const res = await fetch("/api/update-check");
|
||||
if (!res.ok || gestopt) return;
|
||||
const rel = await res.json();
|
||||
if (gestopt) return;
|
||||
setUpdate(rel?.latest && isNieuwer(rel.latest, version) ? rel : null);
|
||||
} catch {
|
||||
// Backend onbereikbaar of offline: gewoon geen melding tonen.
|
||||
}
|
||||
};
|
||||
|
||||
check();
|
||||
const iv = setInterval(check, POLL_MS);
|
||||
window.addEventListener("focus", check);
|
||||
return () => {
|
||||
gestopt = true;
|
||||
clearInterval(iv);
|
||||
window.removeEventListener("focus", check);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return update;
|
||||
}
|
||||
Reference in New Issue
Block a user