fix: replace toast with mock PDF generation dialog in Marktsignale tab
BerichtDialog now animates a progress bar (idle → generating → ready),
then triggers a blob download of Marktsignal-Bericht_{id}.pdf on confirm.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Alert, Box, Button, Chip, CircularProgress, LinearProgress, Typography } from '@mui/material'
|
||||
import { BarChart2, Building2, FileText, Lightbulb, TrendingUp } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Alert, Box, Button, Chip, CircularProgress, Dialog, DialogContent, DialogTitle, LinearProgress, Typography } from '@mui/material'
|
||||
import { BarChart2, Building2, CheckCircle, Download, FileText, Lightbulb, TrendingUp } from 'lucide-react'
|
||||
import { marketReportService } from '../../services/marketReportService'
|
||||
import { useToastStore } from '../../stores/toastStore'
|
||||
import type { MarketReport, PropertyMarketSignal, SignalCategory } from '../../domain/marketReport'
|
||||
|
||||
interface Props {
|
||||
@@ -94,10 +93,133 @@ function SignalCard({ signal }: { signal: PropertyMarketSignal }) {
|
||||
)
|
||||
}
|
||||
|
||||
// ── PDF generation dialog ─────────────────────────────────────────────────────
|
||||
|
||||
type PdfState = 'idle' | 'generating' | 'ready'
|
||||
|
||||
interface BerichtDialogProps {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
report: MarketReport | null
|
||||
propertyId: string
|
||||
}
|
||||
|
||||
function BerichtDialog({ open, onClose, report, propertyId }: BerichtDialogProps) {
|
||||
const [state, setState] = useState<PdfState>('idle')
|
||||
const [progress, setProgress] = useState(0)
|
||||
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) { setState('idle'); setProgress(0); return }
|
||||
setState('generating')
|
||||
setProgress(0)
|
||||
let p = 0
|
||||
timerRef.current = setInterval(() => {
|
||||
p += Math.random() * 18 + 8
|
||||
if (p >= 100) {
|
||||
p = 100
|
||||
clearInterval(timerRef.current!)
|
||||
setTimeout(() => setState('ready'), 300)
|
||||
}
|
||||
setProgress(Math.min(100, p))
|
||||
}, 200)
|
||||
return () => { if (timerRef.current) clearInterval(timerRef.current) }
|
||||
}, [open])
|
||||
|
||||
function handleDownload() {
|
||||
const signals = report?.signals ?? []
|
||||
const lines: string[] = [
|
||||
'MARKTSIGNAL-BERICHT',
|
||||
'====================',
|
||||
`Objekt-ID: ${propertyId}`,
|
||||
`Erstellt: ${new Date().toLocaleString('de-CH')}`,
|
||||
'',
|
||||
]
|
||||
for (const s of signals) {
|
||||
lines.push(`[${s.category.toUpperCase()}] ${s.title}`)
|
||||
lines.push(s.body)
|
||||
if (s.source) lines.push(`Quelle: ${s.source}`)
|
||||
lines.push('')
|
||||
}
|
||||
const blob = new Blob([lines.join('\n')], { type: 'application/pdf' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `Marktsignal-Bericht_${propertyId}.pdf`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={state === 'generating' ? undefined : onClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 700, fontSize: '1rem', pb: 1 }}>
|
||||
Bericht erstellen
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{state === 'generating' && (
|
||||
<Box sx={{ py: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 2 }}>
|
||||
<CircularProgress size={20} />
|
||||
<Typography variant="body2" sx={{ color: '#374151' }}>
|
||||
PDF-Bericht wird generiert…
|
||||
</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
sx={{ height: 6, borderRadius: 3, bgcolor: '#e2e8f0', '& .MuiLinearProgress-bar': { bgcolor: '#1e3a5f' } }}
|
||||
/>
|
||||
<Typography variant="caption" sx={{ color: '#94a3b8', mt: 0.75, display: 'block' }}>
|
||||
{Math.round(progress)}% — Marktsignale werden aufbereitet
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{state === 'ready' && (
|
||||
<Box sx={{ py: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 2 }}>
|
||||
<CheckCircle size={20} color="#1a7a4a" />
|
||||
<Typography variant="body2" sx={{ color: '#166534', fontWeight: 600 }}>
|
||||
Bericht ist bereit
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ p: 2, bgcolor: '#f8fafc', borderRadius: 1.5, border: '1px solid #e2e8f0', mb: 2 }}>
|
||||
<Typography variant="caption" sx={{ color: '#64748b', display: 'block' }}>Dateiname</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||
Marktsignal-Bericht_{propertyId}.pdf
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: '#94a3b8', display: 'block', mt: 0.5 }}>
|
||||
{report?.signals.length ?? 0} Signale · {new Date().toLocaleDateString('de-CH')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
startIcon={<Download size={15} />}
|
||||
onClick={handleDownload}
|
||||
sx={{ textTransform: 'none', bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#16304d' } }}
|
||||
>
|
||||
Herunterladen
|
||||
</Button>
|
||||
<Button fullWidth variant="outlined" onClick={onClose} sx={{ textTransform: 'none' }}>
|
||||
Schliessen
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
// ── Main tab component ────────────────────────────────────────────────────────
|
||||
|
||||
export function PropertyMarketSignalsTab({ propertyId }: Props) {
|
||||
const [report, setReport] = useState<MarketReport | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const showToast = useToastStore(s => s.showToast)
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
|
||||
Reference in New Issue
Block a user