import { useEffect, useState, type ReactNode } from 'react' import { fetchAdminMe, fetchAdminSummary, fetchAdminTimeSeries, type AdminSummary, type AdminTimeSeriesResponse, type AdminTimeBucket } from '../services/adminApi.js' import { BarChart2, Bookmark, ChevronLeft, Database, Image, MapPin, Mic, Users } from 'lucide-react' function formatNumber(value: number): string { return value.toLocaleString() } function formatBytes(bytes: number | undefined): string { if (bytes === undefined || bytes === null) return '—' if (bytes === 0) return '0 B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) const num = bytes / Math.pow(k, i) return `${num.toFixed(1)} ${sizes[i]}` } function KpiCard({ icon, label, value }: { icon: ReactNode label: string value: number | string }) { return (
{icon}
{label} {typeof value === 'number' ? formatNumber(value) : value}
) } function TimeSeriesChart({ title, seriesKey, data }: { title: string seriesKey: string data: AdminTimeSeriesResponse | null }) { if (!data) { return null } const metric = data.series.find((s) => s.metric === seriesKey) if (!metric || metric.points.length === 0) { return (

{title}

Keine Daten im gewählten Zeitraum.

) } const max = metric.points.reduce((acc, p) => (p.count > acc ? p.count : acc), 0) || 1 return (

{title}

{metric.points.map((point) => { const heightPct = Math.max(2, (point.count / max) * 100) return (
{point.count > 0 ? String(point.count) : ''}
{point.date.slice(5)}
) })}
) } interface AdminDashboardProps { onBack: () => void } export default function AdminDashboard({ onBack }: AdminDashboardProps) { const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [summary, setSummary] = useState(null) const [timeSeries, setTimeSeries] = useState(null) const [bucket, setBucket] = useState('day') const [windowDays, setWindowDays] = useState(90) useEffect(() => { let cancelled = false async function load() { try { setLoading(true) setError(null) await fetchAdminMe() const [summaryRes, tsRes] = await Promise.all([ fetchAdminSummary(), fetchAdminTimeSeries({ bucket, windowDays }) ]) if (!cancelled) { setSummary(summaryRes) setTimeSeries(tsRes) } } catch (err: unknown) { if (!cancelled) { const message = err instanceof Error && err.message ? err.message : 'Fehler beim Laden des Admin-Dashboards' setError(message) } } finally { if (!cancelled) { setLoading(false) } } } void load() return () => { cancelled = true } }, [bucket, windowDays]) if (loading && !summary) { return (

Admin-Dashboard wird geladen…

) } if (error) { return (

{error}

) } if (!summary) { return (

Keine Admin-Daten verfügbar.

) } return (

Admin-Dashboard

Übersicht über Nutzung und Wachstum von Kapteins Daagbok.

} label="Registrierte Benutzer" value={summary.totalUsers} /> } label="Logbücher" value={summary.totalLogbooks} /> } label="Fotos" value={summary.totalPhotos} /> } label="Sprachmemos" value={summary.totalVoiceMemos} /> } label="GPS-Tracks" value={summary.totalGpsTracks} /> } label="Einträge mit AI-Zusammenfassung" value={summary.aiSummaryEntries} /> } label="Datenbankgröße" value={formatBytes(summary.dbSize)} />
Zeitraum
{[30, 90, 365].map((days) => ( ))}
Aggregation
{(['day', 'week', 'month'] as AdminTimeBucket[]).map((b) => ( ))}
) }