feat: Mietvertrag hinterlegen pro Objekt (Link + Bezeichnung)
- Property.leaseContractUrl? + leaseContractName? im Domain - PropertyDetailOverview: Mietvertrag-Block in "Miet- & Mieterinformationen" — View-Modus: Öffnen-Button oder "Noch kein Vertrag"-Hinweis — Edit-Modus: URL-Feld + Bezeichnungsfeld (gespeichert via updateProperty) - ReminderDetailDrawer: Mietvertrag-Link im "Objekt"-Abschnitt (lädt Property über usePropertyById) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Box, Divider, LinearProgress, TextField, Typography } from '@mui/material'
|
import { Box, Button, Divider, LinearProgress, TextField, Typography } from '@mui/material'
|
||||||
|
import { ExternalLink, FileText, Plus } from 'lucide-react'
|
||||||
import type { Property, UpdatePropertyInput } from '../../domain/property'
|
import type { Property, UpdatePropertyInput } from '../../domain/property'
|
||||||
import { PropertyMap } from '../shared'
|
import { PropertyMap } from '../shared'
|
||||||
import { getAssetTypeLabel, qualityColor } from './propertyHelpers'
|
import { getAssetTypeLabel, qualityColor } from './propertyHelpers'
|
||||||
@@ -97,8 +98,32 @@ export function PropertyDetailOverview({ p, editing, draft, onDraftChange }: Pro
|
|||||||
onChange={e => onDraftChange({ ...draft, leaseEndDate: e.target.value })}
|
onChange={e => onDraftChange({ ...draft, leaseEndDate: e.target.value })}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</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>
|
</Box>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<FieldGrid>
|
<FieldGrid>
|
||||||
<Field label="Aktueller Mieter" value={p.currentTenant} />
|
<Field label="Aktueller Mieter" value={p.currentTenant} />
|
||||||
<Field label="Mietlaufzeit" value={p.leaseTerm} />
|
<Field label="Mietlaufzeit" value={p.leaseTerm} />
|
||||||
@@ -109,6 +134,39 @@ export function PropertyDetailOverview({ p, editing, draft, onDraftChange }: Pro
|
|||||||
<Field label="Importiert aus" value={p.importedFrom} />
|
<Field label="Importiert aus" value={p.importedFrom} />
|
||||||
<Field label="Zuletzt aktualisiert" value={p.lastUpdatedAt ? new Date(p.lastUpdatedAt).toLocaleDateString('de-CH') : undefined} />
|
<Field label="Zuletzt aktualisiert" value={p.lastUpdatedAt ? new Date(p.lastUpdatedAt).toLocaleDateString('de-CH') : undefined} />
|
||||||
</FieldGrid>
|
</FieldGrid>
|
||||||
|
|
||||||
|
{/* 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 ? '#1e3a5f' : '#94a3b8'} />
|
||||||
|
<Typography variant="caption" sx={{ fontWeight: 700, color: p.leaseContractUrl ? '#1e3a5f' : '#94a3b8', textTransform: 'uppercase', letterSpacing: 0.4 }}>
|
||||||
|
Mietvertrag
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
{p.leaseContractUrl ? (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1 }}>
|
||||||
|
<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: '#1e3a5f', whiteSpace: 'nowrap', flexShrink: 0 }}
|
||||||
|
>
|
||||||
|
Öffnen
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Typography variant="caption" sx={{ color: '#94a3b8' }}>
|
||||||
|
Noch kein Mietvertrag hinterlegt — im Bearbeitungsmodus hinzufügen.
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Floor / unit structure */}
|
{/* Floor / unit structure */}
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import {
|
|||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mui/material'
|
} from '@mui/material'
|
||||||
import { X, MapPin, Calendar, DollarSign, Eye, Activity } from 'lucide-react'
|
import { X, MapPin, Calendar, DollarSign, Eye, Activity, FileText, ExternalLink } from 'lucide-react'
|
||||||
import { useReminderStore } from '../../stores/reminderStore'
|
import { useReminderStore } from '../../stores/reminderStore'
|
||||||
import { useReminder, useCompleteReminder, useDismissReminder, useSnoozeReminder } from '../../hooks/useReminders'
|
import { useReminder, useCompleteReminder, useDismissReminder, useSnoozeReminder } from '../../hooks/useReminders'
|
||||||
|
import { usePropertyById } from '../../hooks/useProperties'
|
||||||
import { ReminderPriorityBadge } from './ReminderPriorityBadge'
|
import { ReminderPriorityBadge } from './ReminderPriorityBadge'
|
||||||
import { ReminderTypeBadge } from './ReminderTypeBadge'
|
import { ReminderTypeBadge } from './ReminderTypeBadge'
|
||||||
import { ReminderDaysIndicator } from './ReminderDaysIndicator'
|
import { ReminderDaysIndicator } from './ReminderDaysIndicator'
|
||||||
@@ -24,6 +25,7 @@ import { SHADOW_RISK_COLOR, SHADOW_RISK_LABEL, STATUS_CHIP_COLOR, STATUS_LABEL,
|
|||||||
export function ReminderDetailDrawer() {
|
export function ReminderDetailDrawer() {
|
||||||
const { selectedId, drawerOpen, setDrawerOpen, setSelectedId } = useReminderStore()
|
const { selectedId, drawerOpen, setDrawerOpen, setSelectedId } = useReminderStore()
|
||||||
const { data: reminder } = useReminder(selectedId ?? '')
|
const { data: reminder } = useReminder(selectedId ?? '')
|
||||||
|
const { data: reminderProperty } = usePropertyById(reminder?.propertyId ?? '')
|
||||||
const complete = useCompleteReminder()
|
const complete = useCompleteReminder()
|
||||||
const dismiss = useDismissReminder()
|
const dismiss = useDismissReminder()
|
||||||
const snooze = useSnoozeReminder()
|
const snooze = useSnoozeReminder()
|
||||||
@@ -101,6 +103,27 @@ export function ReminderDetailDrawer() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">Mieter: {reminder.tenantName}</Typography>
|
<Typography variant="caption" color="text.secondary">Mieter: {reminder.tenantName}</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">Fläche: {reminder.areaSqm.toLocaleString('de-CH')} m²</Typography>
|
<Typography variant="caption" color="text.secondary">Fläche: {reminder.areaSqm.toLocaleString('de-CH')} m²</Typography>
|
||||||
|
{reminderProperty?.leaseContractUrl ? (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mt: 0.5 }}>
|
||||||
|
<FileText size={12} color="#1e3a5f" />
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
endIcon={<ExternalLink size={10} />}
|
||||||
|
href={reminderProperty.leaseContractUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
sx={{ textTransform: 'none', fontSize: '0.68rem', py: 0.125, px: 0.75, borderColor: '#cbd5e1', color: '#1e3a5f' }}
|
||||||
|
>
|
||||||
|
{reminderProperty.leaseContractName ?? 'Mietvertrag'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 0.5 }}>
|
||||||
|
<FileText size={12} color="#cbd5e1" />
|
||||||
|
<Typography variant="caption" sx={{ color: '#cbd5e1' }}>Kein Mietvertrag hinterlegt</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ export interface Property {
|
|||||||
description?: string
|
description?: string
|
||||||
images?: string[]
|
images?: string[]
|
||||||
floorPlanUrl?: string
|
floorPlanUrl?: string
|
||||||
|
leaseContractUrl?: string // link to the signed lease document
|
||||||
|
leaseContractName?: string // display label, e.g. "Mietvertrag 2021–2026"
|
||||||
|
|
||||||
propertyNumber?: string
|
propertyNumber?: string
|
||||||
units?: PropertyUnit[]
|
units?: PropertyUnit[]
|
||||||
|
|||||||
Reference in New Issue
Block a user