diff --git a/admin-compliance/app/sdk/cookie-banner/_components/AnalyticsDashboard.tsx b/admin-compliance/app/sdk/cookie-banner/_components/AnalyticsDashboard.tsx new file mode 100644 index 0000000..03409c3 --- /dev/null +++ b/admin-compliance/app/sdk/cookie-banner/_components/AnalyticsDashboard.tsx @@ -0,0 +1,191 @@ +'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

+ )} +
+
+
+
+ ) +} diff --git a/admin-compliance/app/sdk/cookie-banner/page.tsx b/admin-compliance/app/sdk/cookie-banner/page.tsx index 4cae9c5..7335045 100644 --- a/admin-compliance/app/sdk/cookie-banner/page.tsx +++ b/admin-compliance/app/sdk/cookie-banner/page.tsx @@ -9,8 +9,9 @@ import { CategoryCard } from './_components/CategoryCard' import { VendorTable } from './_components/VendorTable' import { EmbeddableVendorHTML } from './_components/EmbeddableVendorHTML' import { SiteSelector } from './_components/SiteSelector' +import { AnalyticsDashboard } from './_components/AnalyticsDashboard' -type BannerTab = 'config' | 'vendors' | 'embed' +type BannerTab = 'config' | 'vendors' | 'embed' | 'analytics' export default function CookieBannerPage() { const { state } = useSDK() @@ -75,6 +76,7 @@ export default function CookieBannerPage() { { id: 'config' as const, label: 'Konfiguration' }, { id: 'vendors' as const, label: 'Verarbeiter' }, { id: 'embed' as const, label: 'Einbettung' }, + { id: 'analytics' as const, label: 'Analytik' }, ]).map(tab => (