refactor: unit-centric lease view — Einheiten & Mietverträge
Replace building-level Miet- & Mieterinformationen section with unit-level lease display. Each unit row now shows tenant name and lease end date directly; expand reveals full contract details (Mietbeginn, Mietende, Laufzeit, Breakout, source system, doc link). Floor grouping groups consecutive same-floor units under one label. Domain model extended with Lease and Tenant types; deprecated property-level tenant fields preserved via @deprecated for compat. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -63,114 +63,66 @@ export function PropertyDetailOverview({ p, editing, draft, onDraftChange }: Pro
|
||||
</Box>
|
||||
) : null}
|
||||
|
||||
{/* Lease & Tenant */}
|
||||
<SectionTitle title="Miet- & Mieterinformationen" />
|
||||
{editing ? (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5, mb: 1 }}>
|
||||
<TextField
|
||||
label="Aktueller Mieter"
|
||||
size="small"
|
||||
value={draft.currentTenant ?? p.currentTenant ?? ''}
|
||||
onChange={e => onDraftChange({ ...draft, currentTenant: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
label="Mietlaufzeit"
|
||||
size="small"
|
||||
value={draft.leaseTerm ?? p.leaseTerm ?? ''}
|
||||
onChange={e => onDraftChange({ ...draft, leaseTerm: e.target.value })}
|
||||
placeholder="z.B. 5 Jahre"
|
||||
/>
|
||||
<TextField
|
||||
label="Mietbeginn"
|
||||
size="small"
|
||||
type="date"
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
value={draft.leaseStartDate ?? p.leaseStartDate ?? ''}
|
||||
onChange={e => onDraftChange({ ...draft, leaseStartDate: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
label="Mietende"
|
||||
size="small"
|
||||
type="date"
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
value={draft.leaseEndDate ?? p.leaseEndDate ?? ''}
|
||||
onChange={e => onDraftChange({ ...draft, leaseEndDate: e.target.value })}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mt: 0.5, mb: 0.5 }}>
|
||||
<FileText size={13} color="#64748b" />
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 0.4 }}>
|
||||
Mietvertrag
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5 }}>
|
||||
<TextField
|
||||
label="Vertrag URL"
|
||||
size="small"
|
||||
value={draft.leaseContractUrl ?? p.leaseContractUrl ?? ''}
|
||||
onChange={e => onDraftChange({ ...draft, leaseContractUrl: e.target.value })}
|
||||
placeholder="https://…"
|
||||
slotProps={{ input: { startAdornment: <ExternalLink size={12} style={{ marginRight: 6, color: '#94a3b8', flexShrink: 0 }} /> } }}
|
||||
/>
|
||||
<TextField
|
||||
label="Bezeichnung (optional)"
|
||||
size="small"
|
||||
value={draft.leaseContractName ?? p.leaseContractName ?? ''}
|
||||
onChange={e => onDraftChange({ ...draft, leaseContractName: e.target.value })}
|
||||
placeholder="z.B. Mietvertrag 2021–2026"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<>
|
||||
<FieldGrid>
|
||||
<Field label="Aktueller Mieter" value={p.currentTenant} />
|
||||
<Field label="Mietlaufzeit" value={p.leaseTerm} />
|
||||
<Field label="Mietbeginn" value={p.leaseStartDate ? new Date(p.leaseStartDate).toLocaleDateString('de-CH') : undefined} />
|
||||
<Field label="Mietende" value={p.leaseEndDate ? new Date(p.leaseEndDate).toLocaleDateString('de-CH') : undefined} />
|
||||
<Field label="Breakoutoption" value={p.breakoutOption} />
|
||||
<Field label="Breakoutoption Zeitpunkt" value={p.breakoutOptionDate ? new Date(p.breakoutOptionDate).toLocaleDateString('de-CH') : undefined} />
|
||||
<Field label="Importiert aus" value={p.importedFrom} />
|
||||
<Field label="Zuletzt aktualisiert" value={p.lastUpdatedAt ? new Date(p.lastUpdatedAt).toLocaleDateString('de-CH') : undefined} />
|
||||
</FieldGrid>
|
||||
{/* Floor / unit structure — primary tenant/lease view */}
|
||||
<UnitStructurePanel p={p} />
|
||||
|
||||
{/* Mietvertrag */}
|
||||
<Box sx={{ mt: 1.5, p: 1.5, border: '1px solid #e2e8f0', borderRadius: 1.5, bgcolor: p.leaseContractUrl ? '#f8fafc' : '#fafafa' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: p.leaseContractUrl ? 0.75 : 0 }}>
|
||||
<FileText size={13} color={p.leaseContractUrl ? '#152642' : '#94a3b8'} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: p.leaseContractUrl ? '#152642' : '#94a3b8', textTransform: 'uppercase', letterSpacing: 0.4 }}>
|
||||
Mietvertrag
|
||||
</Typography>
|
||||
{/* Fallback: property-level contract doc only when no unit-level contracts exist */}
|
||||
{(() => {
|
||||
const hasUnitContracts = p.units?.some(u => u.leases?.some(l => l.contractDocumentUrl))
|
||||
if (editing) {
|
||||
return (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 1 }}>
|
||||
<FileText size={13} color="#64748b" />
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: '#64748b', textTransform: 'uppercase', letterSpacing: 0.4 }}>
|
||||
Gebäude-Mietvertrag (Fallback)
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5 }}>
|
||||
<TextField
|
||||
label="Vertrag URL"
|
||||
size="small"
|
||||
value={draft.leaseContractUrl ?? p.leaseContractUrl ?? ''}
|
||||
onChange={e => onDraftChange({ ...draft, leaseContractUrl: e.target.value })}
|
||||
placeholder="https://…"
|
||||
slotProps={{ input: { startAdornment: <ExternalLink size={12} style={{ marginRight: 6, color: '#94a3b8', flexShrink: 0 }} /> } }}
|
||||
/>
|
||||
<TextField
|
||||
label="Bezeichnung (optional)"
|
||||
size="small"
|
||||
value={draft.leaseContractName ?? p.leaseContractName ?? ''}
|
||||
onChange={e => onDraftChange({ ...draft, leaseContractName: e.target.value })}
|
||||
placeholder="z.B. Mietvertrag 2021–2026"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{p.leaseContractUrl ? (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||
)
|
||||
}
|
||||
if (!p.leaseContractUrl || hasUnitContracts) return null
|
||||
return (
|
||||
<Box sx={{ mt: 0.5, mb: 1.5, p: 1.5, border: '1px solid #e2e8f0', borderRadius: 1.5, bgcolor: '#f8fafc' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
|
||||
<FileText size={13} color="#152642" />
|
||||
<Typography variant="caption" sx={{ color: '#475569', fontWeight: 500 }} noWrap>
|
||||
{p.leaseContractName ?? 'Mietvertrag'}
|
||||
</Typography>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
endIcon={<ExternalLink size={11} />}
|
||||
href={p.leaseContractUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
sx={{ textTransform: 'none', fontSize: '0.7rem', py: 0.25, px: 1, borderColor: '#cbd5e1', color: '#152642', whiteSpace: 'nowrap', flexShrink: 0 }}
|
||||
>
|
||||
Öffnen
|
||||
</Button>
|
||||
</Box>
|
||||
) : (
|
||||
<Typography variant="caption" sx={{ color: '#94a3b8' }}>
|
||||
Noch kein Mietvertrag hinterlegt — im Bearbeitungsmodus hinzufügen.
|
||||
</Typography>
|
||||
)}
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
endIcon={<ExternalLink size={11} />}
|
||||
href={p.leaseContractUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
sx={{ textTransform: 'none', fontSize: '0.7rem', py: 0.25, px: 1, borderColor: '#cbd5e1', color: '#152642', whiteSpace: 'nowrap', flexShrink: 0 }}
|
||||
>
|
||||
Öffnen
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Floor / unit structure */}
|
||||
<UnitStructurePanel p={p} />
|
||||
)
|
||||
})()}
|
||||
|
||||
{/* Pre-Market Matching */}
|
||||
<PreMarketPanel p={p} />
|
||||
@@ -230,6 +182,8 @@ export function PropertyDetailOverview({ p, editing, draft, onDraftChange }: Pro
|
||||
<Field label="Parkplätze" value={p.hardFacts?.parking ?? p.softFactors?.parkingSpots} />
|
||||
<Field label="ÖV (Min.)" value={p.softFactors?.publicTransportMinutes} />
|
||||
<Field label="Nebenkosten/m²" value={p.ancillaryCosts ? `CHF ${p.ancillaryCosts}` : undefined} />
|
||||
<Field label="Importiert aus" value={p.importedFrom} />
|
||||
<Field label="Zuletzt aktualisiert" value={p.lastUpdatedAt ? new Date(p.lastUpdatedAt).toLocaleDateString('de-CH') : undefined} />
|
||||
</FieldGrid>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user