From 279b55d70e4234a60a96b4cab36dcb8b4038a96e Mon Sep 17 00:00:00 2001 From: Benjamin Sutter Date: Tue, 19 May 2026 16:17:00 +0200 Subject: [PATCH] feat: Bericht erstellen shows full PDF preview before download Dialog now opens at md size (88vh), shows a progress bar while generating, then reveals the full report document: header, all 4 signal sections with colour-coded separators, confidence scores, sources, dates. Document is blurred during generation and sharpens when ready. Herunterladen button stays disabled until preview is ready. Co-Authored-By: Claude Sonnet 4.6 --- .../supply/PropertyMarketSignalsTab.tsx | 241 +++++++++++++----- 1 file changed, 180 insertions(+), 61 deletions(-) diff --git a/src/components/supply/PropertyMarketSignalsTab.tsx b/src/components/supply/PropertyMarketSignalsTab.tsx index dbc2970..f5903aa 100644 --- a/src/components/supply/PropertyMarketSignalsTab.tsx +++ b/src/components/supply/PropertyMarketSignalsTab.tsx @@ -93,9 +93,20 @@ function SignalCard({ signal }: { signal: PropertyMarketSignal }) { ) } -// ── PDF generation dialog ───────────────────────────────────────────────────── +// ── PDF preview dialog ──────────────────────────────────────────────────────── -type PdfState = 'idle' | 'generating' | 'ready' +const SECTION_COLORS: Record = { + development: '#1e3a5f', + demand: '#1a7a4a', + supply: '#b45309', + negotiation: '#7c3aed', +} +const SECTION_LABELS: Record = { + development: 'Entwicklungsnews', + demand: 'Nachfrage 5 km', + supply: 'Angebot 5 km', + negotiation: 'Verhandlungshinweise', +} interface BerichtDialogProps { onClose: () => void @@ -103,8 +114,8 @@ interface BerichtDialogProps { propertyId: string } -function BerichtDialog({ onClose, report, propertyId }: Omit) { - const [state, setState] = useState('generating') +function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProps) { + const [ready, setReady] = useState(false) const [progress, setProgress] = useState(0) const timerRef = useRef | null>(null) @@ -113,11 +124,12 @@ function BerichtDialog({ onClose, report, propertyId }: Omit { p += Math.random() * 18 + 8 if (p >= 100) { - p = 100 clearInterval(timerRef.current!) - setState('ready') + setProgress(100) + setReady(true) + } else { + setProgress(Math.min(100, p)) } - setProgress(Math.min(100, p)) }, 200) return () => { if (timerRef.current) clearInterval(timerRef.current) } }, []) @@ -132,7 +144,8 @@ function BerichtDialog({ onClose, report, propertyId }: Omit - - Bericht erstellen - - - {state === 'generating' && ( - - - - - PDF-Bericht wird generiert… + + {/* Header */} + + + + Bericht erstellen + + + Marktsignal-Bericht · {propertyId} + + + {ready && ( + + + Bereit + + )} + + + {/* Generating state */} + {!ready && ( + + + + + PDF-Bericht wird generiert… + + {Math.round(progress)}% + + + + )} + + {/* PDF Preview */} + + + {/* Document header */} + + + + + Marktsignal-Bericht - - - {Math.round(progress)}% — Marktsignale werden aufbereitet + + Wincasa AG · Zürich + {new Date().toLocaleDateString('de-CH')} + + + + + + + Standortintelligenz & Marktsignale + + + Objekt-ID: {propertyId} · Generiert: {report?.generatedAt ? new Date(report.generatedAt).toLocaleDateString('de-CH') : new Date().toLocaleDateString('de-CH')} · {signals.length} Signale + + + {/* Sections */} + {categories.map(cat => { + const catSignals = signals.filter(s => s.category === cat) + if (catSignals.length === 0) return null + const color = SECTION_COLORS[cat] + const label = SECTION_LABELS[cat] + return ( + + + + + {label} + + + ({catSignals.length}) + + + {catSignals.map((s, i) => ( + + + + {s.title} + + {s.confidence != null && ( + + KI-Konfidenz: {Math.round(s.confidence * 100)}% + + )} + + + {s.body} + + + {s.date && ( + + {new Date(s.date).toLocaleDateString('de-CH')} + + )} + {s.source && ( + + Quelle: {s.source} + + )} + + + ))} + + ) + })} + + {signals.length === 0 && ( + + Keine Marktsignale für dieses Objekt verfügbar. + + )} + + {/* Footer */} + + + Dieser Bericht wurde automatisch auf Basis von KI-generierten Marktsignalen erstellt. · Wincasa AG - )} + + - {state === 'ready' && ( - - - - - Bericht ist bereit - - - - Dateiname - - Marktsignal-Bericht_{propertyId}.pdf - - - {report?.signals.length ?? 0} Signale · {new Date().toLocaleDateString('de-CH')} - - - - - - - - )} - + {/* Footer actions */} + + + + ) }