'use client' import { useState, useEffect } from 'react' interface TimeSeriesPoint { period: string given: number updated: number withdrawn: number total: number opt_in_rate: number } interface CategoryStats { [key: string]: { count: number; total: number; rate: number } } interface DeviceStats { desktop: number mobile: number tablet: number unknown: number } interface OverviewStats { period_days: number total_interactions: number consents_given: number consents_updated: number consents_withdrawn: number opt_in_rate: number } const PERIODS = [ { value: 7, label: '7 Tage' }, { value: 30, label: '30 Tage' }, { value: 90, label: '90 Tage' }, ] const CAT_COLORS: Record = { necessary: '#22c55e', statistics: '#eab308', marketing: '#ef4444', functional: '#3b82f6', preferences: '#8b5cf6', } export function AnalyticsDashboard({ siteId }: { siteId?: string }) { const [days, setDays] = useState(30) const [overview, setOverview] = useState(null) const [timeSeries, setTimeSeries] = useState([]) const [categories, setCategories] = useState({}) const [devices, setDevices] = useState({ desktop: 0, mobile: 0, tablet: 0, unknown: 0 }) const [loading, setLoading] = useState(true) const sid = siteId || 'preview-test-site' useEffect(() => { setLoading(true) const base = `/api/sdk/v1/compliance/banner/analytics/${sid}` Promise.all([ fetch(`${base}/overview?days=${days}`).then(r => r.ok ? r.json() : null), fetch(`${base}/time-series?days=${days}&period=daily`).then(r => r.ok ? r.json() : []), fetch(`${base}/categories?days=${days}`).then(r => r.ok ? r.json() : {}), fetch(`${base}/devices?days=${days}`).then(r => r.ok ? r.json() : {}), ]).then(([o, ts, cats, devs]) => { setOverview(o) setTimeSeries(ts || []) setCategories(cats || {}) setDevices(devs || { desktop: 0, mobile: 0, tablet: 0, unknown: 0 }) }).catch(() => {}).finally(() => setLoading(false)) }, [sid, days]) const deviceTotal = devices.desktop + devices.mobile + devices.tablet + devices.unknown if (loading) return
Lade Analytik...
return (
{/* Period Selector */}
{PERIODS.map(p => ( ))}
{/* Overview KPIs */} {overview && (
Opt-In-Rate
{overview.opt_in_rate}%
Einwilligungen
{overview.consents_given}
Aktualisiert
{overview.consents_updated}
Widerrufen
{overview.consents_withdrawn}
)} {/* Time Series (simple bar visualization) */} {timeSeries.length > 0 && (

Opt-In-Rate im Zeitverlauf

{timeSeries.map((pt, i) => { const height = Math.max(pt.opt_in_rate, 2) const date = new Date(pt.period) return (
{i % Math.max(1, Math.floor(timeSeries.length / 6)) === 0 && ( {date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' })} )}
) })}
)}
{/* Category Acceptance */}

Akzeptanz nach Kategorie

{Object.entries(categories).map(([cat, stats]) => (
{cat} {stats.rate}%
))} {Object.keys(categories).length === 0 && (

Noch keine Daten vorhanden

)}
{/* Device Breakdown */}

Geraete-Verteilung

{[ { key: 'desktop', label: 'Desktop', color: 'bg-blue-500' }, { key: 'mobile', label: 'Mobile', color: 'bg-green-500' }, { key: 'tablet', label: 'Tablet', color: 'bg-purple-500' }, ].map(d => { const count = devices[d.key as keyof DeviceStats] const pct = deviceTotal > 0 ? Math.round(count / deviceTotal * 100) : 0 return (
{d.label} {pct}% ({count})
) })} {deviceTotal === 0 && (

Noch keine Geraetedaten vorhanden

)}
) }