import { useQuery } from "@tanstack/react-query"; import { api, errMsg } from "../api"; import type { StatsDetail } from "../types"; // Statistik-Seite: Zahlen + einfache CSS-Balken (keine Chart-Library). // Balkenbreiten setzt React per CSSOM (element.style), nicht als // style-Attribut im HTML -> von der strikten CSP nicht betroffen. // NumCard ist eine Kennzahl-Kachel: große Zahl + Beschriftung darunter. function NumCard({ value, label }: { value: number; label: string }) { return (
{value} {label}
); } // Bars zeichnet eine Liste {label, value} als horizontale Balken. Die Breite // ist relativ zum größten Wert. function Bars({ items, formatLabel }: { items: { label: string; value: number }[]; formatLabel?: (label: string) => string; }) { if (items.length === 0) return

Noch keine Daten.

; const max = Math.max(...items.map((it) => it.value), 1); // Wertespalte für alle Zeilen gleich breit: an der längsten Zahl ausrichten // (in ch), damit die Tracks bündig sind und nicht je nach Stellenzahl wandern. const valWidth = Math.max(...items.map((it) => String(it.value).length)); return items.map((it) => (
{formatLabel ? formatLabel(it.label) : it.label}
{it.value}
)); } const dayAxis = (day: string) => day.slice(5); // "MM-DD" reicht auf der Achse export function StatsPage() { const stats = useQuery({ queryKey: ["stats"], queryFn: () => api.get("/stats/detail"), }); const s = stats.data; // Backend liefert die Tage absteigend -> für die Achse drehen. const daily = s ? s.daily.slice().reverse() : []; return (

Statistik

Auf einen Blick

{stats.isError &&

{errMsg(stats.error, "Statistik konnte nicht geladen werden.")}

} {s && ( <> )}

Reichweite

Seitenaufrufe werden mit anonymisierten Netzen gezählt (IPv4 /16, IPv6 /32). „Netze" ist eine grobe Annäherung an Besucher, keine Personenzählung.

{s && ( <> )}

Netze nach Betreiber

Aufrufe je autonomem System (Netzbetreiber), aufgelöst über GeoLite2-ASN.

{s && ({ label: a.asn, value: a.hits }))} />}

Aufrufe pro Tag

{s && ({ label: d.day, value: d.impressions }))} formatLabel={dayAxis} />}

Verschiedene Netze pro Tag

{s && ({ label: d.day, value: d.visitors }))} formatLabel={dayAxis} />}
); }