'use client' import React, { useEffect, useState } from 'react' type Phase = { cookies?: string[] scripts?: string[] tracking_services?: (string | { name?: string })[] new_tracking?: unknown[] violations?: Array<{ severity?: string; text?: string }> undocumented?: unknown[] } type CategoryTest = { category: string category_label: string tracking_services?: (string | { name?: string })[] cookies_set?: string[] provider_details_visible?: boolean violations?: Array<{ severity?: string; text?: string; legal_ref?: string }> } type BannerViolation = { severity?: string text?: string legal_ref?: string } type StructuredCheck = { id: string label: string passed: boolean skipped?: boolean severity: string level?: number hint?: string } type BannerResp = { found: boolean check_id: string banner?: { banner_provider?: string banner_detected?: boolean completeness_pct?: number correctness_pct?: number phases?: Record banner_checks?: { violations?: BannerViolation[] } category_tests?: CategoryTest[] structured_checks?: StructuredCheck[] summary?: Record } } const PHASE_LABEL: Record = { before_consent: 'Vor Consent', after_reject: 'Nach Ablehnung', after_accept: 'Nach Annahme', } const SEV_BADGE: Record = { CRITICAL: 'bg-red-600 text-white', HIGH: 'bg-red-100 text-red-800', MEDIUM: 'bg-amber-100 text-amber-800', LOW: 'bg-blue-100 text-blue-800', INFO: 'bg-gray-100 text-gray-600', } function pctColor(pct?: number): string { if (pct === undefined || pct === null) return 'text-gray-400' return pct >= 80 ? 'text-green-700' : pct >= 50 ? 'text-amber-700' : 'text-red-700' } export default function BannerTab({ checkId }: { checkId: string }) { const [data, setData] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [checkFilter, setCheckFilter] = useState<'all' | 'fail' | 'critical'>('fail') useEffect(() => { let cancelled = false setLoading(true) fetch(`/api/sdk/v1/agent/banner/${checkId}`) .then(r => r.json()) .then(d => { if (!cancelled) setData(d) }) .catch(e => { if (!cancelled) setError(String(e)) }) .finally(() => { if (!cancelled) setLoading(false) }) return () => { cancelled = true } }, [checkId]) if (loading) return
Lade Banner-Daten…
if (error) return
Fehler: {error}
if (!data?.found || !data.banner) { return
Keine Banner-Daten zu diesem Check.
} const b = data.banner const phases = b.phases || {} const cats = b.category_tests || [] const violations = b.banner_checks?.violations || [] const checks = b.structured_checks || [] const summary = b.summary || {} const filteredChecks = checks.filter(c => { if (checkFilter === 'all') return true if (checkFilter === 'fail') return !c.passed && !c.skipped return !c.passed && !c.skipped && ['CRITICAL', 'HIGH'].includes(c.severity) }) return (
{/* Quality Cards */}
Vollstaendigkeit
{b.completeness_pct ?? '–'}{b.completeness_pct !== undefined && '%'}
Korrektheit
{b.correctness_pct ?? '–'}{b.correctness_pct !== undefined && '%'}
Verstoesse
{summary.total_violations ?? violations.length}
crit:{summary.critical ?? 0} · high:{summary.high ?? 0}
CMP
{b.banner_provider || 'unbekannt'}
{b.banner_detected ? 'Banner erkannt' : 'kein Banner'}
{/* Phases */}
Cookie-Setzungen pro Phase (echter Browser-Test)
{(['before_consent', 'after_reject', 'after_accept'] as const).map(key => { const p = phases[key] || {} const nc = (p.cookies || []).length const nt = (p.tracking_services || []).length const issues: string[] = [] if (p.violations?.length) issues.push(`${p.violations.length} Verstoss`) if (p.new_tracking?.length) issues.push(`${p.new_tracking.length} neue Tracker`) if (p.undocumented?.length) issues.push(`${p.undocumented.length} undokumentiert`) const color = key === 'before_consent' ? (nc === 0 ? 'text-green-600' : 'text-red-600') : key === 'after_reject' ? (nc <= 1 ? 'text-green-600' : 'text-amber-600') : 'text-gray-700' return ( ) })}
Phase Cookies Tracker Auffaelligkeiten
{PHASE_LABEL[key]} {nc} {nt} {issues.join(', ') || '—'}
{/* Per-Category */} {cats.length > 0 && (
Provider-Listing pro Kategorie (P19 Click-Through-Test)
{cats.map(c => { const pdv = c.provider_details_visible const pdv_label = pdv === true ? 'Ja' : pdv === false ? 'Nein' : '–' const pdv_color = pdv === false ? 'text-red-700' : pdv === true ? 'text-green-700' : 'text-gray-400' return ( ) })}
Kategorie Anbieter sichtbar Tracker erkannt Violations
{c.category_label} {pdv_label} {(c.tracking_services || []).length} {(c.violations || []).map(v => v.text?.slice(0, 80)).join('; ') || '—'}
)} {/* Banner-Checks Violations */} {violations.length > 0 && (
Banner-Verstoesse ({violations.length})
    {violations.map((v, i) => { const sev = (v.severity || 'MEDIUM').toUpperCase() return (
  • {sev}
    {v.text}
    {v.legal_ref &&
    Quelle: {v.legal_ref}
    }
  • ) })}
)} {/* 46 structured_checks Drilldown */}
Banner-Checks ({checks.length})
{(['all', 'fail', 'critical'] as const).map(f => ( ))}
{filteredChecks.map(c => ( ))} {filteredChecks.length === 0 && ( )}
Status Sev Check
{c.passed ? : c.skipped ? : } {c.severity}
{c.label}
{c.hint && !c.passed && (
{c.hint.slice(0, 200)}
)}
Keine Checks fuer den Filter.
) }