/** * Uebersicht der Anlage. * * Der Plan (§14) nennt zehn Kennzahlen. Sieben haben eine Datengrundlage, drei * nicht — und die drei erscheinen trotzdem, mit der Angabe, was fehlt. Ein * weggelassenes Feld sieht aus wie ein vergessenes; ein mit einer Null * gefuelltes waere eine erfundene Statistik (PROMPT.md §139). */ import { useCallback } from 'react'; import { useApiResource } from '../../api/useApiResource'; import { ErrorState, LoadingState } from '../../components/PageState'; import { fetchDashboard } from './dashboardApi'; import type { Dashboard, DashboardWidget } from './dashboardApi'; /** Zeigt die Uebersicht. */ export function DashboardPage(): React.JSX.Element { const loadDashboard = useCallback((abortSignal: AbortSignal) => fetchDashboard(abortSignal), []); const { loadState, data, loadError, reload } = useApiResource(loadDashboard); return (

Uebersicht

{data !== null && ( {data.available_count} von {data.widgets.length} Kennzahlen haben eine Datengrundlage )}
{loadState === 'loading' && } {loadState === 'failed' && loadError !== null && ( )} {loadState === 'loaded' && data !== null && (
{data.widgets.map((dashboardWidget) => ( ))}
)}
); } /** Eigenschaften einer Kennzahlkachel. */ interface WidgetCardProperties { /** Die dargestellte Kennzahl. */ readonly widget: DashboardWidget; } /** Zeigt eine einzelne Kennzahl. */ function WidgetCard({ widget }: WidgetCardProperties): React.JSX.Element { if (!widget.available) { return (

{widget.title}

noch nicht verfuegbar

{widget.unavailable_reason}

); } return (

{widget.title}

{widget.value === undefined ? ( // Kein Wert heisst nicht null. Eine Kennzahl, die sich nicht bilden // laesst, sagt das — der Grund steht im Detailtext darunter. nicht bezifferbar ) : ( <> {formatWidgetValue(widget.value)} {widget.unit !== undefined && {widget.unit}} )}

{widget.detail !== undefined &&

{widget.detail}

} {widget.breakdown !== undefined && (
{Object.entries(widget.breakdown).map(([breakdownKey, breakdownValue]) => (
{formatBreakdownLabel(breakdownKey)}
{breakdownValue.toLocaleString('de-DE')}
))}
)}
); } /** Formatiert die Hauptzahl einer Kennzahl. */ function formatWidgetValue(widgetValue: number): string { // Ein kleiner Wert wird nicht auf null gerundet: „0 %" belegter Speicher liest // sich wie „nichts abgelegt", obwohl Daten da sind. Dieselbe Ueberlegung wie // bei der Recovery Assurance — eine Zahl darf nicht mehr behaupten, als sie // weiss, und auch nicht weniger. if (widgetValue > 0 && widgetValue < 0.1) { return '< 0,1'; } // Ganze Zahlen ohne Nachkommastellen, gebrochene mit einer: „99,7 %" sagt // mehr als „100 %", wenn drei von tausend Laeufen scheiterten. if (Number.isInteger(widgetValue)) { return widgetValue.toLocaleString('de-DE'); } return widgetValue.toLocaleString('de-DE', { maximumFractionDigits: 1 }); } /** Macht aus einem Schluessel der Aufschluesselung eine Beschriftung. */ function formatBreakdownLabel(breakdownKey: string): string { const readableLabel = breakdownKey.replace(/_/g, ' '); return readableLabel.charAt(0).toUpperCase() + readableLabel.slice(1); }