feat: Persistent result history — click to reload old scan results

Both DocCheckTab and BannerCheckTab now:
- Store full scan results per history entry in localStorage
- History entries are clickable — loads the saved result immediately
- No need to re-scan to see old results
- Fallback to last result if specific entry not found
- Banner-Check sends HTML email report to mailpit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-10 07:59:02 +02:00
parent 1b8e9881bb
commit 33f0a64ff6
2 changed files with 36 additions and 7 deletions
@@ -55,7 +55,7 @@ export function BannerCheckTab() {
try { const s = localStorage.getItem('banner-check-result'); return s ? JSON.parse(s) : null } catch { return null }
})
const [categories, setCategories] = useState<string[]>(['all'])
const [history, setHistory] = useState<{ url: string; date: string; provider: string; violations: number; pct: number }[]>(() => {
const [history, setHistory] = useState<{ url: string; date: string; provider: string; violations: number; pct: number; resultKey: string }[]>(() => {
if (typeof window === 'undefined') return []
try { return JSON.parse(localStorage.getItem('banner-check-history') || '[]') } catch { return [] }
})
@@ -97,14 +97,17 @@ export function BannerCheckTab() {
setResult(data)
localStorage.setItem('banner-check-result', JSON.stringify(data))
// Add to history
// Add to history with persistent result
const violations = data.structured_checks?.filter((c: CheckItem) => !c.passed && !c.skipped).length || 0
const resultKey = `banner-check-result-${Date.now()}`
try { localStorage.setItem(resultKey, JSON.stringify(data)) } catch { /* quota */ }
const entry = {
url: url.trim(),
date: new Date().toISOString(),
provider: data.banner_provider || 'Unbekannt',
violations,
pct: data.completeness_pct ?? 0,
resultKey,
}
const updated = [entry, ...history].slice(0, 30)
setHistory(updated)
@@ -117,8 +120,19 @@ export function BannerCheckTab() {
}
}
const loadFromHistory = (entry: { url: string }) => {
const loadFromHistory = (entry: { url: string; resultKey?: string }) => {
setUrl(entry.url)
if (entry.resultKey) {
try {
const saved = localStorage.getItem(entry.resultKey)
if (saved) { setResult(JSON.parse(saved)); return }
} catch {}
}
// Fallback: load last result
try {
const last = localStorage.getItem('banner-check-result')
if (last) setResult(JSON.parse(last))
} catch {}
}
const structuredChecks = result?.structured_checks || []
@@ -38,7 +38,7 @@ export function DocCheckTab() {
try { const s = localStorage.getItem('doc-check-results'); return s ? JSON.parse(s) : null } catch { return null }
})
const [error, setError] = useState<string | null>(null)
const [history, setHistory] = useState<{ date: string; urls: number; findings: number }[]>(() => {
const [history, setHistory] = useState<{ date: string; urls: number; findings: number; resultKey: string }[]>(() => {
if (typeof window === 'undefined') return []
try { return JSON.parse(localStorage.getItem('doc-check-history') || '[]') } catch { return [] }
})
@@ -110,7 +110,9 @@ export function DocCheckTab() {
setResults(pollData.result)
setProgress('')
localStorage.setItem('doc-check-results', JSON.stringify(pollData.result))
const entry = { date: new Date().toISOString(), urls: validEntries.length, findings: pollData.result.total_findings || 0 }
const resultKey = `doc-check-result-${Date.now()}`
try { localStorage.setItem(resultKey, JSON.stringify(pollData.result)) } catch { /* quota */ }
const entry = { date: new Date().toISOString(), urls: validEntries.length, findings: pollData.result.total_findings || 0, resultKey }
const updated = [entry, ...history].slice(0, 30)
setHistory(updated)
localStorage.setItem('doc-check-history', JSON.stringify(updated))
@@ -270,7 +272,20 @@ export function DocCheckTab() {
<h4 className="text-sm font-medium text-gray-700 mb-2">Letzte Pruefungen</h4>
<div className="space-y-1">
{history.map((h, i) => (
<div key={i} className="flex items-center justify-between text-sm py-1.5 border-b border-gray-50 last:border-0">
<button key={i} onClick={() => {
if (h.resultKey) {
try {
const saved = localStorage.getItem(h.resultKey)
if (saved) { setResults(JSON.parse(saved)); return }
} catch {}
}
// Fallback: load last result
try {
const last = localStorage.getItem('doc-check-results')
if (last) setResults(JSON.parse(last))
} catch {}
}}
className="w-full flex items-center justify-between text-sm py-2 px-2 rounded-lg border border-gray-50 hover:border-purple-200 hover:bg-purple-50/30 transition-all text-left">
<span className="text-gray-600">
{new Date(h.date).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })}
</span>
@@ -280,7 +295,7 @@ export function DocCheckTab() {
{h.findings} Findings
</span>
</div>
</div>
</button>
))}
</div>
</div>