refactor: split PropertyDetailView (998→192 lines) and MatchDetail (464→236 lines)

PropertyDetailView.tsx extracted into 5 focused components:
- PropertyDetailHelpers: Field, FieldGrid, SectionTitle, floorLabel
- UnitStructurePanel: floor structure with unit matching
- PreMarketPanel: schattenmarkt release controls
- PropertyDetailOverview: overview tab content
- MatchabilityTabPanel: need matches tab

MatchDetail.tsx extracted into 2 focused components:
- MatchDetailHero: image/map, header, key facts strip
- MatchDetailPropertySections: Preis/Hauptangaben/Eigenschaften/etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 00:48:13 +02:00
parent 6515acb7f0
commit 4d4ea6d2ff
9 changed files with 1130 additions and 1058 deletions
@@ -0,0 +1,56 @@
import { useEffect, useState } from 'react'
import { Alert, Box, CircularProgress, Typography } from '@mui/material'
import { useNavigate } from 'react-router'
import type { PropertyNeedMatch } from '../../domain/match'
import { matchService } from '../../services/matchService'
import { useOfferWizardStore } from '../../stores/offerWizardStore'
import { NeedMatchCard } from './NeedMatchCard'
export function MatchabilityTabPanel({ propertyId }: { propertyId: string }) {
const [matches, setMatches] = useState<PropertyNeedMatch[]>([])
const [loading, setLoading] = useState(true)
const navigate = useNavigate()
const setSelectedProperties = useOfferWizardStore(s => s.setSelectedProperties)
useEffect(() => {
let cancelled = false
setLoading(true)
matchService.getNeedMatchesForProperty(propertyId, { minScore: 80 }).then(result => {
if (!cancelled) { setMatches(result); setLoading(false) }
})
return () => { cancelled = true }
}, [propertyId])
function handleNeedCardClick() {
setSelectedProperties([propertyId])
navigate('/supply/anfragen', { state: { tab: 1 } })
}
if (loading) {
return (
<Box sx={{ p: 3, display: 'flex', justifyContent: 'center' }}>
<CircularProgress size={24} />
</Box>
)
}
return (
<Box sx={{ p: 2.5 }}>
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: '#0f172a' }}>
Matchability
</Typography>
<Typography variant="caption" color="text.secondary">
Bedarfsprofile mit 80% Match-Score Karte klicken um Angebot zu erstellen
</Typography>
</Box>
{matches.length === 0 ? (
<Alert severity="info" sx={{ fontSize: '0.8125rem' }}>
Keine Bedarfsprofile mit 80% Match-Score für dieses Objekt gefunden.
</Alert>
) : (
matches.map(m => <NeedMatchCard key={m.matchId} match={m} onClick={handleNeedCardClick} />)
)}
</Box>
)
}