Files
breakpilot-compliance/admin-compliance/app/sdk/agent/snapshots/[snapshotId]/page.tsx
T
Benjamin Admin 5b36b3f367 feat(impressum): Snapshot-Modul-Tab — ImpressumAgent auf gespeichertem Text
Snapshot-Detailseite wird zu Modul-Tabs (Cookies & Tracking | Impressum).
Backend GET /snapshots/{id}/impressum-check laeuft den v3 ImpressumAgent auf
dem gespeicherten Impressum-Text (kein Re-Crawl); Input-Erzeugung in
impressum_input_from_snapshot() ausgelagert (pure + getestet: Text/Scope/
company_name-Fallback/None-Pfad). Frontend laedt lazy beim Tab-Wechsel und
rendert mit dem bestehenden AgentResultTab (keine zweite Engine).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-11 11:24:44 +02:00

128 lines
4.9 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 (DSE/AGB folgen). Impressum wird beim Öffnen
* des Tabs nachgeladen (ImpressumAgent auf dem gespeicherten Text).
*/
import React, { use as useUnwrap, useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import { CookieLibraryPanel } from '../../_components/CookieLibraryPanel'
import { CookieResultView } from '../../_components/CookieResultView'
import { AgentResultTab } from '../../_components/AgentResultTab'
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 [impressum, setImpressum] = useState<any>(null) // impressum-check (lazy)
const [impLoading, setImpLoading] = useState(false)
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 hasImpressum = docs.some(
(e: any) => e.doc_type === 'impressum' && (e.text || e.content || '').length > 100)
const modules = useMemo(() => [
...(hasCookies ? [{ key: 'cookie', label: 'Cookies & Tracking' }] : []),
...(hasImpressum ? [{ key: 'impressum', label: 'Impressum' }] : []),
], [hasCookies, hasImpressum])
useEffect(() => {
if (!tab && modules.length) setTab(modules[0].key)
}, [modules, tab])
// Impressum erst beim Öffnen des Tabs analysieren (ImpressumAgent, ggf. LLM).
useEffect(() => {
if (tab !== 'impressum' || impressum || impLoading) return
setImpLoading(true)
fetch(`/api/sdk/v1/agent/snapshots/${snapshotId}/impressum-check`)
.then(r => r.json())
.then(setImpressum)
.catch(() => setImpressum({ error: 'Impressum-Analyse fehlgeschlagen', findings: [] }))
.finally(() => setImpLoading(false))
}, [tab, snapshotId, impressum, impLoading])
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 gap-1 border-b border-gray-200">
{modules.map(m => tabBtn(m.key, m.label))}
</div>
{tab === 'cookie' && hasCookies && (
<div className="space-y-4">
<CookieLibraryPanel snapshotId={snapshotId} data={check ?? undefined} />
<CookieResultView snapshot={snap} cookieCategories={check?.cookie_categories} />
</div>
)}
{tab === 'impressum' && (
impLoading ? (
<div className="text-sm text-gray-500">Impressum-Analyse läuft</div>
) : impressum?.error ? (
<div className="text-sm text-red-600">{impressum.error}</div>
) : impressum && (impressum.findings?.length || impressum.mc_coverage?.length) ? (
<AgentResultTab topicLabel="Impressum" output={impressum} />
) : impressum ? (
<div className="text-sm text-gray-500">
{impressum.notes || 'Keine Impressum-Auswertung verfügbar.'}
</div>
) : null
)}
</>
)}
</div>
)
}