'use client' /** * ResultsTabsView — strukturierte Tab-Ansicht der Audit-Ergebnisse. * * Statt einer langen Scroll-Seite gibt es: * 1. Übersicht (Score + GF-Kurzfassung) * 2. Cookies (3-Quellen-Compliance-Vergleich + Vendor-/Cookie-Listen) * 3. Datenschutzerklärung * 4. Impressum * 5. AGB / Widerruf * 6. Banner (Cookie-Banner-Checks) * 7. Vollständige Mail (HTML-Preview) * * Tab-Headers sticky oben, Content scrollbar unten. */ import React, { useState, useMemo } from 'react' import { ChecklistView } from './ChecklistView' interface ResultsTabsViewProps { results: any } type TabId = 'overview' | 'cookies' | 'dse' | 'impressum' | 'agb' | 'banner' | 'mail' const TABS: { id: TabId; label: string; icon: string }[] = [ { id: 'overview', label: 'Übersicht', icon: '◉' }, { id: 'cookies', label: 'Cookies & VVT', icon: '🍪' }, { id: 'dse', label: 'Datenschutzerkl.', icon: '📄' }, { id: 'impressum', label: 'Impressum', icon: '🏢' }, { id: 'agb', label: 'AGB / Widerruf', icon: '⚖️' }, { id: 'banner', label: 'Cookie-Banner', icon: '🎛' }, { id: 'mail', label: 'Mail-Vorschau', icon: '✉️' }, ] export function ResultsTabsView({ results }: ResultsTabsViewProps) { const [active, setActive] = useState('overview') const r = results || {} const docs: any[] = r.results || [] const banner = r.banner_result || r.cookie_banner_result || {} const cmpVendors: any[] = r.cmp_vendors || [] const cookieAudit = r.cookie_audit || {} const docsByType = useMemo(() => { const m: Record = {} for (const d of docs) { const t = (d.doc_type || '').toLowerCase() if (!m[t]) m[t] = d } return m }, [docs]) return (
{/* Sticky Tab-Header */}
{TABS.map(t => ( ))}
{/* Tab-Content */}
{active === 'overview' && } {active === 'cookies' && ( )} {active === 'dse' && } {active === 'impressum' && } {active === 'agb' && } {active === 'banner' && } {active === 'mail' && }
) } // ── Übersicht ────────────────────────────────────────────────────────── function OverviewTab({ results }: { results: any }) { const totalDocs = results.total_documents || (results.results?.length ?? 0) const totalFindings = results.total_findings ?? 0 const banner = results.banner_result || results.cookie_banner_result || {} const score = banner.compliance_score ?? banner.completeness_pct ?? null const emailStatus = results.email_status return (
5 ? 'warn' : 'ok'} /> = 80 ? 'ok' : score >= 60 ? 'warn' : 'bad'} />
{emailStatus && (
E-Mail: {emailStatus === 'sent' ? '✓ Gesendet an Empfänger' : emailStatus}
)}
Wo welcher Inhalt steckt: in den Tabs oben findest du die Detail-Auswertung pro Doc-Typ. Im Cookie-Tab steht der 3-Quellen-Compliance- Vergleich (deklariert vs Browser vs Library) — das ist der wichtigste rechtliche Knackpunkt. Banner-Tab zeigt die echten Browser-Phasen-Checks.
) } function Kpi({ label, value, tone = 'neutral' }: { label: string; value: any; tone?: string }) { const colors: Record = { ok: 'text-green-700 bg-green-50 border-green-200', warn: 'text-amber-700 bg-amber-50 border-amber-200', bad: 'text-red-700 bg-red-50 border-red-200', neutral: 'text-gray-700 bg-gray-50 border-gray-200', } return (
{label}
{value}
) } // ── Cookies & VVT ────────────────────────────────────────────────────── function CookiesTab({ audit, vendors, banner }: { audit: any; vendors: any[]; banner: any }) { const declared = audit?.declared_count ?? 0 const browser = audit?.browser_count ?? 0 const both = (audit?.compliant ?? []).length const undecl = (audit?.undeclared_in_browser ?? []).length const decOnly = (audit?.declared_not_loaded ?? []).length return (
{/* Top-Bar mit Counts */}
0 ? 'bad' : 'ok'} /> 0 ? 'warn' : 'neutral'} />
{/* 3-Spalten-Vergleichstabelle */}
{/* Vendor-Liste (deduped) */}

Vendor-Liste ({vendors.length} unique nach Deduplizierung)

{vendors.map((v, i) => ( ))}
Vendor Kategorie Quelle Cookies
{v.name} {v.category || '—'} {v.source || '—'} {(v.cookies || []).length}
) } function CookieColumn({ title, tone, subtitle, cookies }: { title: string; tone: string; subtitle: string; cookies: string[] }) { const colors: Record = { bad: 'bg-red-50 border-red-200 text-red-900', ok: 'bg-green-50 border-green-200 text-green-900', warn: 'bg-amber-50 border-amber-200 text-amber-900', } return (
{title}
{subtitle}
{cookies.length === 0 && — keine —} {cookies.map((c, i) => (
{c}
))}
) } // ── Generic Doc-Tab ──────────────────────────────────────────────────── function DocTab({ doc, label }: { doc: any; label: string }) { if (!doc) return const checks = doc.checks || [] const failed = checks.filter((c: any) => !c.passed && !c.skipped) const passed = checks.filter((c: any) => c.passed) return (

{label}

{doc.word_count?.toLocaleString('de-DE') || 0} Wörter ·{' '} {failed.length} Findings ·{' '} {passed.length} OK
{doc.url && ( {doc.url} )}
) } function AgbWiderrufTab({ docs }: { docs: Record }) { const agb = docs['agb'] || docs['nutzungsbedingungen'] const wid = docs['widerruf'] return (

AGB / Nutzungsbedingungen

{agb ? : }

Widerrufsbelehrung

{wid ? : }
) } function BannerTab({ banner }: { banner: any }) { if (!banner || Object.keys(banner).length === 0) return const phases = banner.phases || {} const violations = banner.banner_checks?.violations || [] return (
Banner erkannt: {banner.banner_detected ? 'Ja' : 'Nein'} ·{' '} Provider: {banner.banner_provider || '—'} ·{' '} Verstöße: {violations.length}
{violations.length > 0 && (
Verstöße
    {violations.map((v: any, i: number) => (
  • • {v.label || v.message || JSON.stringify(v)}
  • ))}
)}
{Object.entries(phases).map(([name, ph]: [string, any]) => (
{name}
Cookies: {ph.cookies?.length || 0}
Vendors: {ph.vendors?.length || 0}
))}
) } function MailPreviewTab({ results }: { results: any }) { return (

Die vollständige Mail wurde {results.email_status === 'sent' ? 'gesendet' : 'erstellt'}. Snapshot-ID:{' '} {results.check_id || '—'}

{results.check_id && ( → PDF der Mail herunterladen )}
) } function Empty({ label, inline }: { label: string; inline?: boolean }) { return (
Keine Daten für „{label}" in diesem Lauf.
) }