import type { ReactElement, ReactNode } from "react"; import type { StatisticsMetric, StatisticsProviderScope, StatisticsRange, StatisticsViewModel } from "./statistics-model"; import { SlidingSelection } from "../../ui/SlidingSelection"; import "./statistics.css"; export interface StatisticsViewActions { onRangeChange: (range: StatisticsRange) => void; onResetSession: () => void; onResetAll: () => void; onResetErrors: () => void; } export interface StatisticsViewProps { model: StatisticsViewModel; actions: StatisticsViewActions; chart: ReactNode; } const rangeItems: Array<{ id: StatisticsRange; label: string }> = [ { id: "session", label: "Sitzung" }, { id: "today", label: "Heute" }, { id: "week", label: "Sieben Tage" }, { id: "month", label: "30 Tage" }, { id: "all", label: "Gesamt" } ]; const numberFormatter = new Intl.NumberFormat("de-DE", { maximumFractionDigits: 1 }); function formatBytes(bytes: number): string { const safe = Math.max(0, Number.isFinite(bytes) ? bytes : 0); if (safe < 1024) { return `${Math.round(safe)} B`; } const units = ["KB", "MB", "GB", "TB", "PB"]; let value = safe / 1024; let unitIndex = 0; while (value >= 1024 && unitIndex < units.length - 1) { value /= 1024; unitIndex += 1; } return `${numberFormatter.format(value)} ${units[unitIndex]}`; } function formatMetric(metric: StatisticsMetric, kind: "bytes" | "count" | "percent" | "speed"): string { if (!metric.available || metric.value === null) { return "–"; } if (kind === "bytes") { return formatBytes(metric.value); } if (kind === "speed") { return `${formatBytes(metric.value)}/s`; } if (kind === "percent") { return `${numberFormatter.format(metric.value)} %`; } return numberFormatter.format(metric.value); } function providerScopeLabel(scope: StatisticsProviderScope | null): string { if (scope === "current-queue") { return "Aktuelle Queue"; } if (scope === "today") { return "Heute"; } if (scope === "all") { return "Gesamt"; } return "Nicht verfügbar"; } function emptyProviderMessage(model: StatisticsViewModel): string { if (model.coverage === "unavailable") { return model.message; } if (model.providerScope === "today") { return "Heute wurden noch keine Providerbytes erfasst."; } if (model.providerScope === "all") { return "Noch keine gespeicherten Providerbytes vorhanden."; } return "In der aktuellen Queue sind noch keine Providerwerte vorhanden."; } function StatisticsMetricCard({ label, metric, kind }: { label: string; metric: StatisticsMetric; kind: "bytes" | "count" | "percent" | "speed"; }): ReactElement { return ( {label} {formatMetric(metric, kind)} {metric.sourceLabel} ); } export function StatisticsSidebar({ model, actions }: Pick): ReactElement { return ( Zeitraum {rangeItems.map((item) => ( actions.onRangeChange(item.id)} type="button" >{item.label} ))} {model.message} ); } export function StatisticsSidebarStatus({ model }: Pick): ReactElement | null { const metrics = model.metrics; const rows = [ metrics.downloadedBytes.available ? `Daten: ${formatMetric(metrics.downloadedBytes, "bytes")}` : null, metrics.files.available ? `Dateien: ${formatMetric(metrics.files, "count")}` : null, metrics.successRate.available ? `Erfolg: ${formatMetric(metrics.successRate, "percent")}` : null, metrics.errors.available ? `Fehler: ${formatMetric(metrics.errors, "count")}` : null, model.providerScope ? `Provider: ${model.providers.length}` : null ].filter((value): value is string => value !== null); if (rows.length === 0) { return null; } return ( {rows.map((row) => {row})} ); } export function StatisticsContent({ model, actions, chart }: StatisticsViewProps): ReactElement { return ( Statistiken {model.message} Sitzung zurücksetzen Gesamt zurücksetzen Fehler zurücksetzen Bandbreitenverlauf Live aus der aktuellen Renderer-Sitzung {chart} Provider {providerScopeLabel(model.providerScope)} Provider Daten Ergebnisse {model.providers.length > 0 ? model.providers.map((provider) => ( {provider.label} {formatBytes(provider.bytes)} 0 ? "statistics-provider-errors" : undefined} role="cell"> {provider.completed === null || provider.failed === null ? "–" : `${provider.completed} fertig · ${provider.failed} Fehler`} )) : ( {emptyProviderMessage(model)} )} ); } export function StatisticsView({ model, actions, chart }: StatisticsViewProps): ReactElement { return ( ); }
{model.message}