feat: Vendor-level consent + Consent analytics (F4 + F6)

F4: Granular Vendor-Level Consent
- Migration 113: vendor_consents JSONB on banner_consents + audit_log
- ConsentCreate schema + BannerConsentDB model extended
- banner_consent_service stores vendor_consents alongside categories
- Audit trail includes vendor-level decisions + user_agent

F6: Consent Rate Analytics
- Migration 114: user_agent on audit_log + time-series index
- BannerAnalyticsService: time series, category breakdown, device stats
- banner_analytics_routes: 4 endpoints (overview, time-series, categories, devices)
- AnalyticsDashboard.tsx: KPIs, bar chart, category bars, device breakdown
- New "Analytik" tab in cookie-banner page

[migration-approved]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-03 20:58:06 +02:00
parent 36d9f929c6
commit c3fcfe88ee
12 changed files with 432 additions and 1 deletions
@@ -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 => (
<button key={tab.id} onClick={() => setActiveTab(tab.id)}
className={`px-4 py-3 text-sm font-medium border-b-2 -mb-px transition-colors ${
@@ -91,6 +93,9 @@ export default function CookieBannerPage() {
{/* Tab: Einbettung */}
{activeTab === 'embed' && <EmbeddableVendorHTML siteId={activeSiteId || undefined} />}
{/* Tab: Analytik */}
{activeTab === 'analytics' && <AnalyticsDashboard siteId={activeSiteId || undefined} />}
{/* Tab: Konfiguration */}
{activeTab !== 'config' ? null : (<>
{/* Stats */}