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 { useEffect, useRef, useState } from 'react'
|
||||||
import { Alert, Box, Button, Chip, CircularProgress, LinearProgress, Typography } from '@mui/material'
|
import { Alert, Box, Button, Chip, CircularProgress, Dialog, DialogContent, DialogTitle, LinearProgress, Typography } from '@mui/material'
|
||||||
import { BarChart2, Building2, FileText, Lightbulb, TrendingUp } from 'lucide-react'
|
import { BarChart2, Building2, CheckCircle, Download, FileText, Lightbulb, TrendingUp } from 'lucide-react'
|
||||||
import { marketReportService } from '../../services/marketReportService'
|
import { marketReportService } from '../../services/marketReportService'
|
||||||
import { useToastStore } from '../../stores/toastStore'
|
|
||||||
import type { MarketReport, PropertyMarketSignal, SignalCategory } from '../../domain/marketReport'
|
import type { MarketReport, PropertyMarketSignal, SignalCategory } from '../../domain/marketReport'
|
||||||
|
|
||||||
interface Props {
|
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) {
|
export function PropertyMarketSignalsTab({ propertyId }: Props) {
|
||||||
const [report, setReport] = useState<MarketReport | null>(null)
|
const [report, setReport] = useState<MarketReport | null>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const showToast = useToastStore(s => s.showToast)
|
const [dialogOpen, setDialogOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|||||||
Reference in New Issue
Block a user