From 6263462ba3fc00a53ee8b4a0c39c8094b392cd16 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 21 May 2026 23:44:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20Tab-Layout=20f=C3=BCr=20Audit?= =?UTF-8?q?-Ergebnisse=20+=20cookie=5Faudit=20in=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ResultsTabsView.tsx — neue Komponente mit 7 Tabs: 1. Übersicht (KPIs: Docs, Findings, Vendors, Score) 2. Cookies & VVT (3-Quellen-Compliance-Vergleich + undokumentiert/compliant/nicht-geladen + deduplizierte Vendor-Tabelle) 3. Datenschutzerklärung (DSE-Findings via ChecklistView) 4. Impressum 5. AGB / Widerruf (zwei Sections in einem Tab) 6. Cookie-Banner (Verstoesse + Phasen-KPIs) 7. Mail-Vorschau (PDF-Download-Link) Sticky Tab-Header oben, Content scrollt darunter. Lange Scroll-Mail ist damit verschwunden. DocCheckTab nutzt ResultsTabsView statt der alten Inline-ChecklistView. Backend liefert jetzt cookie_audit-dict in der Response (zusaetzlich zu cmp_vendors + banner_result) damit das Cookie-Tab die 3 Listen (undokumentiert / compliant / nicht-geladen) rendern kann. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../app/sdk/agent/_components/DocCheckTab.tsx | 37 +- .../sdk/agent/_components/ResultsTabsView.tsx | 353 ++++++++++++++++++ .../api/agent_compliance_check_routes.py | 1 + 3 files changed, 357 insertions(+), 34 deletions(-) create mode 100644 admin-compliance/app/sdk/agent/_components/ResultsTabsView.tsx diff --git a/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx b/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx index c92b8e3a..a813346d 100644 --- a/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx +++ b/admin-compliance/app/sdk/agent/_components/DocCheckTab.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react' import { ChecklistView } from './ChecklistView' +import { ResultsTabsView } from './ResultsTabsView' import { PreScanWizard, useScanContext, isContextComplete } from './PreScanWizard' import { safeSetItem } from './storageHelpers' @@ -312,41 +313,9 @@ export function DocCheckTab() {
{error}
)} - {/* Results */} + {/* Results — als Tab-Ansicht (Übersicht/Cookies/DSE/Impressum/AGB/Banner/Mail) */} {results && results.results && ( -
- - - {/* Cookie Banner Result */} - {results.cookie_banner_result && ( -
-

Cookie-Banner

-
- {results.cookie_banner_result.banner_detected - ? `Banner erkannt: ${results.cookie_banner_result.banner_provider || 'unbekannt'}` - : 'Kein Banner erkannt'} -
- {results.cookie_banner_result.banner_checks?.violations?.length > 0 && ( -
- {results.cookie_banner_result.banner_checks.violations.map((v: any, i: number) => ( -
- !! - {v.text} -
- ))} -
- )} -
- )} - - {/* Email Status */} - {results.email_status && ( -
- - E-Mail: {results.email_status === 'sent' ? 'Gesendet' : results.email_status} -
- )} -
+ )} {/* History */} diff --git a/admin-compliance/app/sdk/agent/_components/ResultsTabsView.tsx b/admin-compliance/app/sdk/agent/_components/ResultsTabsView.tsx new file mode 100644 index 00000000..6e5604cd --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/ResultsTabsView.tsx @@ -0,0 +1,353 @@ +'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) => ( + + + + + + + ))} + +
VendorKategorieQuelleCookies
{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. +
+ ) +} diff --git a/backend-compliance/compliance/api/agent_compliance_check_routes.py b/backend-compliance/compliance/api/agent_compliance_check_routes.py index 7e91c9d2..ec905ef5 100644 --- a/backend-compliance/compliance/api/agent_compliance_check_routes.py +++ b/backend-compliance/compliance/api/agent_compliance_check_routes.py @@ -1570,6 +1570,7 @@ async def _run_compliance_check(check_id: str, req: ComplianceCheckRequest): } if banner_result else None), "tcf_vendors": vvt_entries if tcf_vendors else [], "cmp_vendors": cmp_vendors, + "cookie_audit": cookie_audit if cookie_audit else None, "total_documents": len(results), "total_findings": total_findings, "email_status": email_result.get("status", "failed"),