FEAT-7: Statistik page - Wuerfe/Jahr + avg Wurfstaerke bars, Farbschlag h-bars (FEAT-4 chip colors), Bestandsentwicklung line, Verluste/Jahr + /statistik route & nav entry (Mehr overflow follows via Kevin)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 00:41:21 +02:00
parent 1b776cd994
commit 34911fdee1
7 changed files with 419 additions and 0 deletions

View File

@@ -8,6 +8,9 @@ const navItems = [
{ to: '/becken', label: de.nav.enclosures, icon: '🛁', end: false },
{ to: '/kontakte', label: de.nav.contacts, icon: '📇', end: false },
{ to: '/genetik', label: de.nav.genetics, icon: '🧬', end: false },
// FEAT-7: vorerst 7. Tab; „Mehr“-Overflow für das Smartphone ist mit Kevin
// abgestimmt (FEAT-7-Konversation) und ersetzt diesen Eintrag dann.
{ to: '/statistik', label: de.nav.statistics, icon: '📊', end: false },
]
/**

View File

@@ -0,0 +1,28 @@
/* FEAT-7: Stile des SVG-Balkendiagramms (BarChart.tsx). */
.bar-chart {
width: 100%;
height: auto;
display: block;
}
.bar-chart__axis {
stroke: var(--color-border);
stroke-width: 1;
}
.bar-chart__bar {
fill: var(--color-accent);
opacity: 0.85;
}
.bar-chart__value {
font-size: 12px;
fill: var(--color-text);
font-weight: 600;
}
.bar-chart__label {
font-size: 12px;
fill: var(--color-text-muted);
}

View File

@@ -0,0 +1,79 @@
/**
* FEAT-7: Schlichtes, responsives SVG-Balkendiagramm (eine Serie) —
* Geschwister von LineChart (FEAT-6), gleiche Konventionen: kein
* Chart-Paket, viewBox-skaliert, __axis/__grid/__label-Klassen.
* Eigene CSS-Datei (Standing-Rule-2-Muster: seitennahe Stile kapseln).
*/
import './BarChart.css'
export interface Bar {
label: string
value: number
}
interface Props {
bars: Bar[]
/** Zugänglicher Titel des Diagramms. */
title: string
/** Nachkommastellen der Wertbeschriftung (Standard 0). */
decimals?: number
}
const W = 600
const H = 240
const PAD = { top: 24, right: 12, bottom: 28, left: 12 }
export default function BarChart({ bars, title, decimals = 0 }: Props) {
const innerW = W - PAD.left - PAD.right
const innerH = H - PAD.top - PAD.bottom
const max = Math.max(...bars.map((b) => b.value), 1)
const slot = innerW / bars.length
const barW = Math.min(56, slot * 0.6)
const format = (v: number) =>
v.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: decimals })
return (
<svg
className="bar-chart"
viewBox={`0 0 ${W} ${H}`}
role="img"
aria-label={title}
preserveAspectRatio="xMidYMid meet"
>
<line
x1={PAD.left}
y1={H - PAD.bottom}
x2={W - PAD.right}
y2={H - PAD.bottom}
className="bar-chart__axis"
/>
{bars.map((bar, i) => {
const h = (bar.value / max) * innerH
const x = PAD.left + i * slot + (slot - barW) / 2
const y = H - PAD.bottom - h
return (
<g key={bar.label}>
<rect x={x} y={y} width={barW} height={h} rx={3} className="bar-chart__bar" />
<text
x={x + barW / 2}
y={y - 6}
textAnchor="middle"
className="bar-chart__value"
>
{format(bar.value)}
</text>
<text
x={x + barW / 2}
y={H - PAD.bottom + 18}
textAnchor="middle"
className="bar-chart__label"
>
{bar.label}
</text>
</g>
)
})}
</svg>
)
}