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 (
)
}
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) => (
))}
)
}