feat: Verwaltungszugang refactor — lease data, 3-tab detail, market signals

- Dashboard: remove QuickActionPanel, StrongMatchOverview, FutureSignalWidget,
  DataQualityWidget, header buttons, Marktchancen nav; KpiGrid → 4 cards
- Property domain: 9 new fields (leaseTerm, leaseStartDate/End, breakoutOption,
  currentTenant, importedFrom, importedAt, lastUpdatedAt)
- Mock data: annual CHF/m²/Jahr prices (×12), Swiss images, tenant/lease data
  for all 10 VERIFIED_PORTFOLIO properties; need budgets updated to annual
- PropertyTable: swap columns — add Aktueller Mieter, Mietlaufzeit,
  Breakoutoption, Breakoutoption Zeitpunkt; remove Verfügbarkeit, Konfidenz, Quelle
- PropertyDetailView: 3 tabs (Übersicht, Matchability, Marktsignale) with
  inline edit mode, NeedMatchCard list, PropertyMarketSignalsTab
- New: marketReport domain + mock data + service, NeedMatchCard,
  PropertyMarketSignalsTab with 4 sections + PDF button
- matchService: getNeedMatchesForProperty(propertyId, {minScore})

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-19 15:43:52 +02:00
parent 52a2693cad
commit 920ce8eb09
17 changed files with 1036 additions and 476 deletions
+43 -37
View File
@@ -19,10 +19,6 @@ import type { PropertyTableFilters } from './PropertyFilterBar'
import {
getAssetTypeColor,
getAssetTypeLabel,
getAvailabilityChipColor,
getAvailabilityLabel,
getResultTypeColor,
getResultTypeLabel,
qualityColor,
} from './propertyHelpers'
@@ -38,8 +34,9 @@ interface PropertyTableProps {
}
const COL_HEADERS = [
'Objekt', 'Typ', 'Standort', 'Fläche', 'Miete/m²',
'Verfügbarkeit', 'Konfidenz', 'Datenqualität', 'Quelle', 'Aktionen',
'Objekt', 'Typ', 'Standort', 'Fläche', 'Miete CHF/m²/Jahr',
'Aktueller Mieter', 'Mietlaufzeit', 'Breakoutoption', 'Breakoutoption Zeitpunkt',
'Datenqualität', 'Aktionen',
]
function LoadingRows() {
@@ -108,11 +105,12 @@ export function PropertyTable({
<TableCell><Typography variant="caption" sx={{ fontWeight: 600 }}>Typ</Typography></TableCell>
<TableCell><Typography variant="caption" sx={{ fontWeight: 600 }}>Standort</Typography></TableCell>
<TableCell><SortableHeader field="area" label="Fläche (m²)" /></TableCell>
<TableCell><SortableHeader field="rent" label="Miete/m²" /></TableCell>
<TableCell><SortableHeader field="availability" label="Verfügbarkeit" /></TableCell>
<TableCell><SortableHeader field="confidence" label="Konfidenz" /></TableCell>
<TableCell><SortableHeader field="rent" label="Miete CHF/m²/Jahr" /></TableCell>
<TableCell><Typography variant="caption" sx={{ fontWeight: 600 }}>Aktueller Mieter</Typography></TableCell>
<TableCell><Typography variant="caption" sx={{ fontWeight: 600 }}>Mietlaufzeit</Typography></TableCell>
<TableCell><Typography variant="caption" sx={{ fontWeight: 600 }}>Breakoutoption</Typography></TableCell>
<TableCell><Typography variant="caption" sx={{ fontWeight: 600 }}>Breakoutoption Zeitpunkt</Typography></TableCell>
<TableCell><SortableHeader field="dataQuality" label="Datenqualität" /></TableCell>
<TableCell><Typography variant="caption" sx={{ fontWeight: 600 }}>Quelle</Typography></TableCell>
<TableCell><Typography variant="caption" sx={{ fontWeight: 600 }}>Aktionen</Typography></TableCell>
</TableRow>
</TableHead>
@@ -121,7 +119,7 @@ export function PropertyTable({
<LoadingRows />
) : properties.length === 0 ? (
<TableRow>
<TableCell colSpan={10}>
<TableCell colSpan={11}>
<Typography variant="body2" sx={{ color: 'text.secondary', textAlign: 'center', py: 4 }}>
Keine Objekte gefunden.
</Typography>
@@ -184,30 +182,47 @@ export function PropertyTable({
{/* Miete */}
<TableCell>
<Typography variant="body2">
{p.rentPricePerSqm > 0 ? `CHF ${p.rentPricePerSqm}` : <span style={{ color: '#94a3b8' }}>k.A.</span>}
{p.rentPricePerSqm > 0 ? `CHF ${p.rentPricePerSqm.toLocaleString('de-CH')}` : <span style={{ color: '#94a3b8' }}>k.A.</span>}
</Typography>
</TableCell>
{/* Verfügbarkeit */}
{/* Aktueller Mieter */}
<TableCell>
<Chip
label={getAvailabilityLabel(p.availabilityStatus)}
size="small"
color={getAvailabilityChipColor(p.availabilityStatus)}
variant="outlined"
/>
<Typography variant="body2" sx={{ whiteSpace: 'nowrap' }}>
{p.currentTenant ?? <span style={{ color: '#94a3b8' }}></span>}
</Typography>
</TableCell>
{/* Konfidenz */}
{/* Mietlaufzeit */}
<TableCell>
<Typography
variant="body2"
sx={{
fontWeight: 600,
color: p.confidenceScore >= 0.85 ? '#1a7a4a' : p.confidenceScore >= 0.65 ? '#1e3a5f' : '#d97706',
}}
>
{Math.round(p.confidenceScore * 100)}%
<Typography variant="body2">
{p.leaseTerm ?? <span style={{ color: '#94a3b8' }}></span>}
</Typography>
</TableCell>
{/* Breakoutoption */}
<TableCell>
{p.breakoutOption == null
? <Typography variant="body2" sx={{ color: '#94a3b8' }}></Typography>
: <Chip
label={p.breakoutOption ? 'Ja' : 'Nein'}
size="small"
sx={{
bgcolor: p.breakoutOption ? '#dcfce7' : '#f1f5f9',
color: p.breakoutOption ? '#166534' : '#64748b',
fontSize: '0.68rem',
}}
/>
}
</TableCell>
{/* Breakoutoption Zeitpunkt */}
<TableCell>
<Typography variant="body2">
{p.breakoutOptionDate
? new Date(p.breakoutOptionDate).toLocaleDateString('de-CH')
: <span style={{ color: '#94a3b8' }}></span>
}
</Typography>
</TableCell>
@@ -231,15 +246,6 @@ export function PropertyTable({
</Tooltip>
</TableCell>
{/* Quelle */}
<TableCell>
<Chip
label={getResultTypeLabel(p.resultType)}
size="small"
sx={{ bgcolor: getResultTypeColor(p.resultType), color: 'white', fontSize: '0.68rem' }}
/>
</TableCell>
{/* Aktionen */}
<TableCell onClick={e => e.stopPropagation()}>
<Box sx={{ display: 'flex', gap: 0.25 }}>