"use client"; import React from "react"; import { PageHeader } from "../components/layout/PageHeader"; import { PageContainer } from "../components/layout/PageContainer"; import { KPICard } from "../components/cards/KPICard"; import { ChartCard } from "../components/cards/ChartCard"; import { AlertList } from "../components/cards/AlertList"; import { StatZeile } from "../components/cards/StatZeile"; import { AreaVerlauf } from "../components/charts/AreaVerlauf"; import { BalkenVertikal } from "../components/charts/BalkenVertikal"; import { BalkenHorizontal } from "../components/charts/BalkenHorizontal"; import { Donut } from "../components/charts/Donut"; import { Trichter } from "../components/charts/Trichter"; import { AuslastungsBalken } from "../components/charts/AuslastungsBalken"; import { ChartLeer } from "../components/charts/ChartLeer"; import { ChartLegende } from "../components/charts/ChartLegende"; import { AuftragBadge, RechnungBadge } from "../components/tables/StatusBadge"; import { SeitenZustand } from "../components/layout/SeitenZustand"; import { WidgetKachel } from "../components/dashboard/WidgetKachel"; import { ReservierteKachel } from "../components/dashboard/ReservierteKachel"; import { RESERVIERTE_BEREICHE } from "../view-models/dashboard.vm"; import { Begruessung } from "../components/dashboard/Begruessung"; import type { VMState } from "../view-models"; import { KATEGORIE_FARBEN, MAX_KATEGORIEN, PRIMAER_FARBE, STATUS_FARBEN, VERGLEICH_FARBE, } from "../utils/chartTheme"; import { formatEuro, formatEuroAchse, formatEuroKompakt, formatProzent, formatZahl, } from "../utils/formatters"; import { DATENSATZ_INFO } from "../data/seed"; /** Beispieldaten, damit die Galerie unabhängig von Filtern immer etwas zeigt. */ const BEISPIEL_REIHE = Array.from({ length: 12 }, (_, i) => ({ datum: `2026-${String(i + 1).padStart(2, "0")}`, label: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"][i], value: [148, 162, 171, 158, 166, 149, 152, 161, 189, 205, 198, 176][i] * 1000, vergleich: [131, 140, 152, 146, 151, 138, 141, 149, 168, 181, 176, 158][i] * 1000, })); const BEISPIEL_KATEGORIEN = [ { name: "Heizung", value: 842_000 }, { name: "Sanitär", value: 486_000 }, { name: "Photovoltaik", value: 421_000 }, { name: "Klima & Lüftung", value: 268_000 }, { name: "Elektro", value: 194_000 }, { name: "Sonstiges", value: 61_000 }, ]; export function KomponentenSeite() { return ( <>
{KATEGORIE_FARBEN.map((farbe, index) => (
{index + 1}. {farbe}
))}
{Object.entries(STATUS_FARBEN).map(([name, farbe]) => ( Status „{name}“ · {farbe} ))}

Kategorien nutzen die Carbon-Palette, Statusaussagen die Ampelfarben. Beides nie mischen: eine grüne Kategorie neben einem grünen Positiv-Status ist nicht mehr lesbar. Maximal {MAX_KATEGORIEN} Segmente je Chart, der Rest wird zu „Sonstige“ zusammengefasst.

} >
({ label: p.label, value: p.value / 1000 }))} formatWert={(w) => formatZahl(w)} hoehe={200} nurGanzzahlen /> formatZahl(w)} />
{([ ["Lädt", { status: "loading" } as VMState], ["Leer: Filter schneiden alles weg", { status: "empty" } as VMState], [ "Fehler", { status: "error", error: { messageKey: "Zeitüberschreitung beim Laden der Kennzahlen" }, } as VMState, ], ] as const).map(([titel, vm]) => (
{titel} {/* -mx-4: SeitenZustand bringt seinen eigenen Seitenrand mit, weil er im Normalfall die ganze Seite füllt. Hier steht er in einer Rasterspalte und braucht ihn nicht. Ohne zusätzlichen Rahmen: Die Komponente hat bereits eine gestrichelte Kante. Ein Kasten darum wäre ein zweiter Rahmen und zeigte den Baustein anders, als er in der Anwendung aussieht. */}
))}
{/* Wie bei den Seitenzuständen: Die Begrüßung trägt ihren eigenen Seitenrand, weil sie im Dashboard direkt auf der Seite steht. In der Galerie liegt sie schon im Innenabstand des Containers und wäre sonst doppelt eingerückt. */}
{RESERVIERTE_BEREICHE.map((bereich) => ( ))}
Auftrag
Rechnung
    {[ "Nur horizontale Gridlines in #e0e0e0. Vertikale Linien lenken vom Verlauf ab.", "Achsenbeschriftungen 12 px in #525252, keine Tick-Marks, Achsenlinie #8d8d8d.", "Ein Tooltip für alle Charts: weiße Fläche #ffffff, Text #161616, Schatten statt Rahmen. Seit Carbon v11 ist er hell, nicht mehr dunkel.", "Maximal fünf Kategorien je Chart, alles darüber wird zu „Sonstige“ zusammengefasst.", "Balkenabstand mindestens halbe Balkenbreite; Balken nie breiter als 48 px.", "Zahlen rechtsbündig und in Tabellenziffern, Text linksbündig.", "Karten: 16 px Innenabstand, 16 px Abstand zwischen Karten; innen nie größer als außen.", "Jede Karte braucht einen Leerzustand. Filter können jedes Chart leer laufen lassen.", "Karten einer Zeile nur strecken, wenn sie dieselbe Bauart haben. Ein Diagramm mit fester Höhe neben einer Balkenliste bleibt oben bündig, sonst wird aus einer Karte mit einem Balken eine 427 px hohe Fläche.", ].map((regel) => (
  • {regel}
  • ))}
); } function Abschnitt({ titel, text }: { titel: string; text: string }) { return (

{titel}

{/* break-words: Diese Absätze beginnen mit einem Dateipfad, und ein Pfad ist ein einziges Wort ohne Trennstelle. Bei 375 px lief "features/dashboard/components/layout/SeitenZustand.tsx" neun Pixel über den Rand. */}

{text}

); } function Hinweis({ text }: { text: string }) { return (

{text}

); }