Files
breakpilot-compliance/admin-compliance/app/sdk/agent/snapshots/[snapshotId]/page.tsx
T
Benjamin Admin d720db07dd feat(audit-report): deterministischer Textreport je Audit (MD + PDF) + Bericht-Tab
Firmen-tauglicher Bericht aus den Snapshot-Modulergebnissen (kein Re-Crawl, kein
LLM): Einleitung, Testumfang+Methodik, Management-Summary (4-Status), Detail-
befunde je Modul, Maßnahmen, Rechtlicher Hinweis. Co-Pilot-Tonalität, Tracking-
statt Cookie-Rohzahl, Norm nur referenziert (kein Normtext).
- audit_report.py: assemble_report (pur) + render_markdown + render_pdf (reportlab)
- snapshot_check_routes: GET /report (struktur+md) + GET /report.pdf
- Frontend: AuditReportTab + Proxys (report, report/pdf) + "Bericht"-Tab
- Tests: 5 Assembler (compliance/tests → CI-geprüft) + 1 Vitest

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-13 14:50:45 +02:00

156 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
/**
* Snapshot-Detail — öffnet einen gespeicherten Check aus der Historie und
* zeigt die Ergebnis-Views aus den Rohdaten (kein Re-Crawl), als Modul-Tabs:
* Cookies & Tracking + Impressum + Datenschutzerklärung (AGB folgen).
* Doc-Agenten (Impressum/DSE) laufen beim Öffnen des Tabs auf dem gespeicherten
* Text — generisch via AgentModuleTab.
*/
import React, { use as useUnwrap, useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import { CookieLibraryPanel } from '../../_components/CookieLibraryPanel'
import { CookieDeclarationDiff } from '../../_components/CookieDeclarationDiff'
import { CookieResultView } from '../../_components/CookieResultView'
import { AgentModuleTab } from '../../_components/AgentModuleTab'
import { BrowserBehaviorView } from '../../_components/BrowserBehaviorView'
import { AuditReportTab } from '../../_components/AuditReportTab'
export default function SnapshotDetail(
{ params }: { params: Promise<{ snapshotId: string }> },
) {
const { snapshotId } = useUnwrap(params)
const [snap, setSnap] = useState<any>(null)
const [check, setCheck] = useState<any>(null) // cookie-check
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [tab, setTab] = useState<string>('')
useEffect(() => {
let cancelled = false
fetch(`/api/sdk/v1/agent/snapshots/${snapshotId}`)
.then(r => r.json())
.then(d => {
if (cancelled) return
if (d?.error) setError(d.error); else setSnap(d)
})
.catch(e => { if (!cancelled) setError(String(e)) })
.finally(() => { if (!cancelled) setLoading(false) })
return () => { cancelled = true }
}, [snapshotId])
// Cookie-Abgleich einmal laden (Findings + cookie_categories für beide Views).
useEffect(() => {
let cancelled = false
fetch(`/api/sdk/v1/agent/snapshots/${snapshotId}/cookie-check`)
.then(r => r.json())
.then(d => { if (!cancelled) setCheck(d) })
.catch(() => { if (!cancelled) setCheck(null) })
return () => { cancelled = true }
}, [snapshotId])
const docs = snap?.doc_entries || []
const hasCookies = (snap?.cmp_vendors?.length ?? 0) > 0
const hasDoc = (dt: string) => docs.some(
(e: any) => e.doc_type === dt && (e.text || e.content || '').length > 100)
// Browser-Verhalten braucht nur eine scanbare URL (on-demand-Live-Lauf).
const hasSite = docs.some((e: any) => (e.url || '').trim())
|| (!!snap?.site_domain && snap.site_domain !== 'unknown')
const modules = useMemo(() => [
...(hasCookies ? [{ key: 'cookie', label: 'Cookies & Tracking' }] : []),
...(hasDoc('impressum') ? [{ key: 'impressum', label: 'Impressum' }] : []),
...(hasDoc('dse') ? [{ key: 'dse', label: 'Datenschutzerklärung' }] : []),
...(hasDoc('agb') ? [{ key: 'agb', label: 'AGB' }] : []),
...(hasSite ? [{ key: 'browser', label: 'Browser-Verhalten' }] : []),
{ key: 'bericht', label: 'Bericht' },
// eslint-disable-next-line react-hooks/exhaustive-deps
], [snap])
useEffect(() => {
if (!tab && modules.length) setTab(modules[0].key)
}, [modules, tab])
const tabBtn = (key: string, label: string) => (
<button key={key} onClick={() => setTab(key)}
className={`px-3 py-1.5 text-sm border-b-2 -mb-px ${tab === key ? 'border-blue-600 text-blue-700 font-medium' : 'border-transparent text-gray-500 hover:text-gray-700'}`}>
{label}
</button>
)
return (
<div className="p-6 max-w-6xl space-y-4">
<Link href="/sdk/agent/snapshots" className="text-xs text-blue-700 hover:underline">
Zurück zur Historie
</Link>
{loading ? (
<div className="text-sm text-gray-500">Lade Snapshot</div>
) : error || !snap ? (
<div className="text-sm text-red-600">Snapshot nicht gefunden.</div>
) : modules.length === 0 ? (
<div className="text-sm text-gray-500">
Dieser Snapshot enthält keine auswertbaren Daten.
</div>
) : (
<>
<div className="flex items-center justify-between gap-3 flex-wrap">
<div>
<h1 className="text-lg font-semibold text-gray-900">
{snap.site_label || snap.site_domain || 'Snapshot'}
</h1>
<p className="text-xs text-gray-500">
{snap.site_domain}
{snap.created_at ? ` · ${String(snap.created_at).slice(0, 16).replace('T', ' ')}` : ''}
</p>
</div>
{snap.check_id && (
<a
href={`/sdk/agent/audit/${snap.check_id}`}
target="_blank"
rel="noopener"
className="px-3 py-1.5 text-sm rounded-lg border border-blue-200 text-blue-700 hover:bg-blue-50 whitespace-nowrap"
>
Voll-Audit öffnen (alle MCs)
</a>
)}
</div>
<div className="flex gap-1 border-b border-gray-200">
{modules.map(m => tabBtn(m.key, m.label))}
</div>
{tab === 'cookie' && hasCookies && (
<div className="space-y-4">
<CookieDeclarationDiff data={check?.declaration_diff} />
<CookieLibraryPanel snapshotId={snapshotId} data={check ?? undefined} />
<CookieResultView snapshot={snap} cookieCategories={check?.cookie_categories} storageTypes={check?.storage_inventory?.per_cookie} />
</div>
)}
{tab === 'impressum' && (
<AgentModuleTab snapshotId={snapshotId} docType="impressum" label="Impressum" />
)}
{tab === 'dse' && (
<AgentModuleTab snapshotId={snapshotId} docType="dse" label="Datenschutzerklärung" />
)}
{tab === 'agb' && (
<AgentModuleTab snapshotId={snapshotId} docType="agb" label="AGB" />
)}
{tab === 'browser' && (
<BrowserBehaviorView snapshotId={snapshotId} />
)}
{tab === 'bericht' && (
<AuditReportTab snapshotId={snapshotId} />
)}
</>
)}
</div>
)
}