feat(supply): per-unit price/availability editor + mandatory fit-out cost-bearer for shell/edelrohbau
- unit.availableFrom: explicit per-unit availability date
- UnitStructurePanel: inline expandable editor (pencil) for per-unit price + availability via useUpdateUnit
- fitOutByLandlord now boolean|undefined ("not chosen"); toggle has no default for SHELL/BASIC
- NewListing: validate() blocks save until cost-bearer chosen for Rohbau/Edelrohbau
- PropertyDetailView.saveEdit: same guard for existing listings; required asterisk + hint in both forms
- newListingMapper persists explicit tenant(false) choice (was collapsed to undefined)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ interface Props {
|
|||||||
onFloorChange: (v: string) => void
|
onFloorChange: (v: string) => void
|
||||||
fitOut: string
|
fitOut: string
|
||||||
onFitOutChange: (v: string) => void
|
onFitOutChange: (v: string) => void
|
||||||
fitOutByLandlord: boolean
|
fitOutByLandlord: boolean | undefined
|
||||||
onFitOutByLandlordChange: (v: boolean) => void
|
onFitOutByLandlordChange: (v: boolean) => void
|
||||||
parking: string
|
parking: string
|
||||||
onParkingChange: (v: string) => void
|
onParkingChange: (v: string) => void
|
||||||
@@ -66,19 +66,25 @@ export function TechnicalDetailsSection({
|
|||||||
{/* Ausbau-Träger — nur relevant, wenn die Fläche noch Ausbau benötigt (Rohbau/Edelrohbau) */}
|
{/* Ausbau-Träger — nur relevant, wenn die Fläche noch Ausbau benötigt (Rohbau/Edelrohbau) */}
|
||||||
{showFitOutResponsibility && (
|
{showFitOutResponsibility && (
|
||||||
<Box sx={{ mt: 2.5, pt: 2, borderTop: '1px solid #f1f5f9' }}>
|
<Box sx={{ mt: 2.5, pt: 2, borderTop: '1px solid #f1f5f9' }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>Wer baut aus?</Typography>
|
<Typography variant="body2" sx={{ fontWeight: 600, mb: 1 }}>
|
||||||
|
Wer baut aus? <Box component="span" sx={{ color: '#dc2626' }}>*</Box>
|
||||||
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2, flexWrap: 'wrap' }}>
|
||||||
<ToggleButtonGroup
|
<ToggleButtonGroup
|
||||||
exclusive
|
exclusive
|
||||||
size="small"
|
size="small"
|
||||||
value={fitOutByLandlord ? 'landlord' : 'tenant'}
|
value={fitOutByLandlord === undefined ? null : fitOutByLandlord ? 'landlord' : 'tenant'}
|
||||||
onChange={(_, v) => { if (v) onFitOutByLandlordChange(v === 'landlord') }}
|
onChange={(_, v) => { if (v) onFitOutByLandlordChange(v === 'landlord') }}
|
||||||
>
|
>
|
||||||
<ToggleButton value="landlord" sx={{ textTransform: 'none' }}>Vermieter übernimmt</ToggleButton>
|
<ToggleButton value="landlord" sx={{ textTransform: 'none' }}>Vermieter übernimmt</ToggleButton>
|
||||||
<ToggleButton value="tenant" sx={{ textTransform: 'none' }}>Mieter baut aus</ToggleButton>
|
<ToggleButton value="tenant" sx={{ textTransform: 'none' }}>Mieter baut aus</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
|
|
||||||
{fitOutByLandlord ? (
|
{fitOutByLandlord === undefined ? (
|
||||||
|
<Typography variant="caption" sx={{ flex: 1, minWidth: 220, mt: 0.5, color: '#b45309' }}>
|
||||||
|
Pflichtangabe bei Rohbau/Edelrohbau — bitte wählen, wer den Ausbau trägt.
|
||||||
|
</Typography>
|
||||||
|
) : fitOutByLandlord ? (
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ flex: 1, minWidth: 220, mt: 0.5 }}>
|
<Typography variant="caption" color="text.secondary" sx={{ flex: 1, minWidth: 220, mt: 0.5 }}>
|
||||||
Ausbau im Mietzins enthalten — bitte die <strong>bezugsfertige</strong> Miete eintragen.
|
Ausbau im Mietzins enthalten — bitte die <strong>bezugsfertige</strong> Miete eintragen.
|
||||||
Es wird kein Aufschlag berechnet.
|
Es wird kein Aufschlag berechnet.
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function PropertyDetailOverview({ p, editing, draft, onDraftChange }: Pro
|
|||||||
// Effektive (Draft-überlagerte) hardFacts für den Bearbeiten-Modus
|
// Effektive (Draft-überlagerte) hardFacts für den Bearbeiten-Modus
|
||||||
const hf = draft.hardFacts ?? p.hardFacts ?? {}
|
const hf = draft.hardFacts ?? p.hardFacts ?? {}
|
||||||
const editFitOut = hf.fitOut ?? ''
|
const editFitOut = hf.fitOut ?? ''
|
||||||
const editByLandlord = hf.fitOutByLandlord ?? false
|
const editByLandlord = hf.fitOutByLandlord
|
||||||
const showBuildResponsibility = FIT_OUT_NEEDS_BUILD.has(editFitOut)
|
const showBuildResponsibility = FIT_OUT_NEEDS_BUILD.has(editFitOut)
|
||||||
const patchHF = (patch: Partial<NonNullable<Property['hardFacts']>>) =>
|
const patchHF = (patch: Partial<NonNullable<Property['hardFacts']>>) =>
|
||||||
onDraftChange({ ...draft, hardFacts: { ...hf, ...patch } })
|
onDraftChange({ ...draft, hardFacts: { ...hf, ...patch } })
|
||||||
@@ -187,17 +187,24 @@ export function PropertyDetailOverview({ p, editing, draft, onDraftChange }: Pro
|
|||||||
|
|
||||||
{showBuildResponsibility && (
|
{showBuildResponsibility && (
|
||||||
<Box sx={{ mt: 1.5 }}>
|
<Box sx={{ mt: 1.5 }}>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 0.5 }}>Wer baut aus?</Typography>
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 0.5 }}>
|
||||||
|
Wer baut aus? <Box component="span" sx={{ color: '#dc2626' }}>*</Box>
|
||||||
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center', flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 1.5, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
<ToggleButtonGroup
|
<ToggleButtonGroup
|
||||||
exclusive size="small"
|
exclusive size="small"
|
||||||
value={editByLandlord ? 'landlord' : 'tenant'}
|
value={editByLandlord === undefined ? null : editByLandlord ? 'landlord' : 'tenant'}
|
||||||
onChange={(_, v) => { if (v) patchHF({ fitOutByLandlord: v === 'landlord', ...(v === 'landlord' ? { mieterausbaubeitragPerSqm: undefined } : {}) }) }}
|
onChange={(_, v) => { if (v) patchHF({ fitOutByLandlord: v === 'landlord', ...(v === 'landlord' ? { mieterausbaubeitragPerSqm: undefined } : {}) }) }}
|
||||||
>
|
>
|
||||||
<ToggleButton value="landlord" sx={{ textTransform: 'none' }}>Vermieter übernimmt</ToggleButton>
|
<ToggleButton value="landlord" sx={{ textTransform: 'none' }}>Vermieter übernimmt</ToggleButton>
|
||||||
<ToggleButton value="tenant" sx={{ textTransform: 'none' }}>Mieter baut aus</ToggleButton>
|
<ToggleButton value="tenant" sx={{ textTransform: 'none' }}>Mieter baut aus</ToggleButton>
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
{!editByLandlord && (
|
{editByLandlord === undefined && (
|
||||||
|
<Typography variant="caption" sx={{ color: '#b45309' }}>
|
||||||
|
Pflichtangabe bei Rohbau/Edelrohbau
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{editByLandlord === false && (
|
||||||
<TextField
|
<TextField
|
||||||
label="Mieterausbaubeitrag (CHF/m²)"
|
label="Mieterausbaubeitrag (CHF/m²)"
|
||||||
size="small" type="number" sx={{ width: 240 }}
|
size="small" type="number" sx={{ width: 240 }}
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ export function PropertyDetailView({ propertyId, onClose, hideTabs = [] }: Prope
|
|||||||
|
|
||||||
function saveEdit() {
|
function saveEdit() {
|
||||||
if (!property) return
|
if (!property) return
|
||||||
|
// Träger-Pflicht bei Rohbau/Edelrohbau (SHELL/BASIC)
|
||||||
|
const effFitOut = draft.hardFacts?.fitOut ?? property.hardFacts?.fitOut
|
||||||
|
const effByLandlord = draft.hardFacts?.fitOutByLandlord ?? property.hardFacts?.fitOutByLandlord
|
||||||
|
if ((effFitOut === 'SHELL' || effFitOut === 'BASIC') && effByLandlord === undefined) {
|
||||||
|
showToast('Bitte wählen, wer den Ausbau trägt (Vermieter oder Mieter).', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
updateProperty.mutate(
|
updateProperty.mutate(
|
||||||
{ id: property.id, input: draft },
|
{ id: property.id, input: draft },
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { Box, Button, Checkbox, Chip, Collapse, Divider, IconButton, Switch, TextField, Tooltip, Typography } from '@mui/material'
|
import { Box, Button, Checkbox, Chip, Collapse, Divider, IconButton, Switch, TextField, Tooltip, Typography } from '@mui/material'
|
||||||
import { ChevronDown, ChevronUp, ExternalLink, FileText, Layers, Users } from 'lucide-react'
|
import { ChevronDown, ChevronUp, ExternalLink, FileText, Layers, Pencil, Users } from 'lucide-react'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
import type { Property, PropertyUnit, UnitNeedMatch } from '../../domain/property'
|
import type { Property, PropertyUnit, UnitNeedMatch } from '../../domain/property'
|
||||||
import type { Lease } from '../../domain/lease'
|
import type { Lease } from '../../domain/lease'
|
||||||
@@ -50,8 +50,19 @@ export function UnitStructurePanel({ p }: { p: Property }) {
|
|||||||
const [expandedUnit, setExpandedUnit] = useState<string | null>(null)
|
const [expandedUnit, setExpandedUnit] = useState<string | null>(null)
|
||||||
const [editingFlexUnit, setEditingFlexUnit] = useState<string | null>(null)
|
const [editingFlexUnit, setEditingFlexUnit] = useState<string | null>(null)
|
||||||
const [flexDraft, setFlexDraft] = useState<Record<string, number | undefined>>({})
|
const [flexDraft, setFlexDraft] = useState<Record<string, number | undefined>>({})
|
||||||
|
const [editingUnit, setEditingUnit] = useState<string | null>(null)
|
||||||
|
const [unitDraft, setUnitDraft] = useState<{ rentPricePerSqm?: number; availableFrom?: string }>({})
|
||||||
const updateUnit = useUpdateUnit(p.id)
|
const updateUnit = useUpdateUnit(p.id)
|
||||||
|
|
||||||
|
function openUnitEditor(u: PropertyUnit) {
|
||||||
|
setUnitDraft({ rentPricePerSqm: u.rentPricePerSqm ?? p.rentPricePerSqm, availableFrom: u.availableFrom })
|
||||||
|
setEditingUnit(u.id)
|
||||||
|
}
|
||||||
|
function saveUnitEditor(unitId: string) {
|
||||||
|
updateUnit.mutate({ unitId, data: { rentPricePerSqm: unitDraft.rentPricePerSqm, availableFrom: unitDraft.availableFrom || undefined } })
|
||||||
|
setEditingUnit(null)
|
||||||
|
}
|
||||||
|
|
||||||
const freeUnits = useMemo(() => (p.units ?? []).filter(u => u.available), [p.units])
|
const freeUnits = useMemo(() => (p.units ?? []).filter(u => u.available), [p.units])
|
||||||
|
|
||||||
const unitMatches = useUnitMatchesMap(freeUnits, p)
|
const unitMatches = useUnitMatchesMap(freeUnits, p)
|
||||||
@@ -286,6 +297,16 @@ export function UnitStructurePanel({ p }: { p: Property }) {
|
|||||||
{isExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
{isExpanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
|
{/* Edit price / availability per unit */}
|
||||||
|
<Tooltip title="Preis & Verfügbarkeit bearbeiten">
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => (editingUnit === u.id ? setEditingUnit(null) : openUnitEditor(u))}
|
||||||
|
sx={{ p: 0.25, color: editingUnit === u.id ? '#2563eb' : DS_TEXT.disabled }}
|
||||||
|
>
|
||||||
|
<Pencil size={11} />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Area + Preis + Verfügbarkeit pro Einheit */}
|
{/* Area + Preis + Verfügbarkeit pro Einheit */}
|
||||||
@@ -301,16 +322,49 @@ export function UnitStructurePanel({ p }: { p: Property }) {
|
|||||||
<Typography variant="caption" sx={{ color: DS_TEXT.muted, fontSize: '0.6rem', display: 'block' }}>
|
<Typography variant="caption" sx={{ color: DS_TEXT.muted, fontSize: '0.6rem', display: 'block' }}>
|
||||||
CHF {(u.rentPricePerSqm ?? p.rentPricePerSqm).toLocaleString('de-CH')}/m²
|
CHF {(u.rentPricePerSqm ?? p.rentPricePerSqm).toLocaleString('de-CH')}/m²
|
||||||
</Typography>
|
</Typography>
|
||||||
{u.available && (
|
{u.available && (() => {
|
||||||
<Typography variant="caption" sx={{ color: DS_TEXT.disabled, fontSize: '0.6rem', display: 'block' }}>
|
const av = u.availableFrom ?? u.schattenmarktRelease?.availableFrom
|
||||||
{u.schattenmarktRelease?.availableFrom
|
return (
|
||||||
? `ab ${new Date(u.schattenmarktRelease.availableFrom).toLocaleDateString('de-CH', { month: 'short', year: '2-digit' })}`
|
<Typography variant="caption" sx={{ color: DS_TEXT.disabled, fontSize: '0.6rem', display: 'block' }}>
|
||||||
: 'sofort'}
|
{av ? `ab ${new Date(av).toLocaleDateString('de-CH', { month: 'short', year: '2-digit' })}` : 'sofort'}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)
|
||||||
|
})()}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Inline editor: price + availability per unit */}
|
||||||
|
<Collapse in={editingUnit === u.id}>
|
||||||
|
<Box sx={{ px: 2, py: 1.25, bgcolor: '#f8fafc', borderBottom: !isLastUnit ? `1px solid ${DS_BORDER.default}` : 'none' }}>
|
||||||
|
<Box sx={{ display: 'grid', gridTemplateColumns: u.available ? '1fr 1fr' : '1fr', gap: 1.25 }}>
|
||||||
|
<TextField
|
||||||
|
label="Preis (CHF/m²/Jahr)" type="number" size="small"
|
||||||
|
value={unitDraft.rentPricePerSqm ?? ''}
|
||||||
|
onChange={e => setUnitDraft(d => ({ ...d, rentPricePerSqm: parseInt(e.target.value) || undefined }))}
|
||||||
|
slotProps={{ htmlInput: { min: 0, step: 10 } }}
|
||||||
|
helperText="Leer = Objekt-Preis"
|
||||||
|
/>
|
||||||
|
{u.available && (
|
||||||
|
<TextField
|
||||||
|
label="Verfügbar ab" type="date" size="small"
|
||||||
|
slotProps={{ inputLabel: { shrink: true } }}
|
||||||
|
value={unitDraft.availableFrom ?? ''}
|
||||||
|
onChange={e => setUnitDraft(d => ({ ...d, availableFrom: e.target.value }))}
|
||||||
|
helperText="Leer = sofort"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1, mt: 1, justifyContent: 'flex-end' }}>
|
||||||
|
<Button size="small" onClick={() => setEditingUnit(null)} sx={{ textTransform: 'none', color: DS_TEXT.muted }}>
|
||||||
|
Abbrechen
|
||||||
|
</Button>
|
||||||
|
<Button size="small" variant="contained" onClick={() => saveUnitEditor(u.id)} sx={{ textTransform: 'none', bgcolor: '#152642', '&:hover': { bgcolor: '#16304d' } }}>
|
||||||
|
Speichern
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
{/* Expanded: matches for free units */}
|
{/* Expanded: matches for free units */}
|
||||||
<Collapse in={isExpanded && u.available}>
|
<Collapse in={isExpanded && u.available}>
|
||||||
<Box sx={{ px: 2, py: 1, bgcolor: DS_SURFACE.neutral.bg, borderBottom: !isLastUnit ? `1px solid ${DS_BORDER.default}` : 'none' }}>
|
<Box sx={{ px: 2, py: 1, bgcolor: DS_SURFACE.neutral.bg, borderBottom: !isLastUnit ? `1px solid ${DS_BORDER.default}` : 'none' }}>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export interface PropertyUnit {
|
|||||||
areaSqm: number
|
areaSqm: number
|
||||||
available: boolean
|
available: boolean
|
||||||
rentPricePerSqm?: number // annual CHF/m²; falls back to property.rentPricePerSqm
|
rentPricePerSqm?: number // annual CHF/m²; falls back to property.rentPricePerSqm
|
||||||
|
availableFrom?: string // explicit availability date for a free unit (ISO); else derived from leases
|
||||||
// Unit-level fit-out override — falls back to property.hardFacts.fitOut when unset.
|
// Unit-level fit-out override — falls back to property.hardFacts.fitOut when unset.
|
||||||
// Relevant for pre-market, where units of one property are matched individually.
|
// Relevant for pre-market, where units of one property are matched individually.
|
||||||
fitOut?: 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM'
|
fitOut?: 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM'
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ function validate(fields: {
|
|||||||
city: string
|
city: string
|
||||||
areaSqm: string
|
areaSqm: string
|
||||||
rentPerSqm: string
|
rentPerSqm: string
|
||||||
|
fitOut: string
|
||||||
|
fitOutByLandlord: boolean | undefined
|
||||||
}): string | null {
|
}): string | null {
|
||||||
if (!fields.street.trim()) return 'Strasse erforderlich'
|
if (!fields.street.trim()) return 'Strasse erforderlich'
|
||||||
if (!fields.postalCode.trim()) return 'PLZ erforderlich'
|
if (!fields.postalCode.trim()) return 'PLZ erforderlich'
|
||||||
@@ -23,6 +25,9 @@ function validate(fields: {
|
|||||||
|| Number(fields.areaSqm) <= 0) return 'Gültige Fläche eingeben'
|
|| Number(fields.areaSqm) <= 0) return 'Gültige Fläche eingeben'
|
||||||
if (!fields.rentPerSqm || isNaN(Number(fields.rentPerSqm))
|
if (!fields.rentPerSqm || isNaN(Number(fields.rentPerSqm))
|
||||||
|| Number(fields.rentPerSqm) <= 0) return 'Gültigen Mietpreis eingeben'
|
|| Number(fields.rentPerSqm) <= 0) return 'Gültigen Mietpreis eingeben'
|
||||||
|
// Bei Rohbau/Edelrohbau muss der Ausbau-Träger aktiv gewählt werden
|
||||||
|
if ((fields.fitOut === 'SHELL' || fields.fitOut === 'BASIC')
|
||||||
|
&& fields.fitOutByLandlord === undefined) return 'Bitte wählen, wer den Ausbau trägt (Vermieter oder Mieter)'
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +48,7 @@ export interface NewListingFormState {
|
|||||||
// Technical
|
// Technical
|
||||||
floor: string
|
floor: string
|
||||||
fitOut: string
|
fitOut: string
|
||||||
fitOutByLandlord: boolean
|
fitOutByLandlord: boolean | undefined
|
||||||
parking: string
|
parking: string
|
||||||
ceilingHeight: string
|
ceilingHeight: string
|
||||||
mieterausbaubeitrag: string
|
mieterausbaubeitrag: string
|
||||||
@@ -81,7 +86,7 @@ export interface NewListingFormHandlers {
|
|||||||
setSoftLevel: (key: string, value: string) => void
|
setSoftLevel: (key: string, value: string) => void
|
||||||
setFloor: (v: string) => void
|
setFloor: (v: string) => void
|
||||||
setFitOut: (v: string) => void
|
setFitOut: (v: string) => void
|
||||||
setFitOutByLandlord: (v: boolean) => void
|
setFitOutByLandlord: (v: boolean | undefined) => void
|
||||||
setParking: (v: string) => void
|
setParking: (v: string) => void
|
||||||
setCeilingHeight: (v: string) => void
|
setCeilingHeight: (v: string) => void
|
||||||
setMieterausbaubeitrag: (v: string) => void
|
setMieterausbaubeitrag: (v: string) => void
|
||||||
@@ -119,7 +124,7 @@ export function useNewListingForm(pre: Prefill): NewListingFormState & NewListin
|
|||||||
const [softLevels, setSoftLevels] = useState<Record<string, string>>(() => ({ ...emptySoftLevels(), ...pre.softLevels }))
|
const [softLevels, setSoftLevels] = useState<Record<string, string>>(() => ({ ...emptySoftLevels(), ...pre.softLevels }))
|
||||||
const [floor, setFloor] = useState(pre.floor ?? '')
|
const [floor, setFloor] = useState(pre.floor ?? '')
|
||||||
const [fitOut, setFitOut] = useState(pre.fitOut ?? '')
|
const [fitOut, setFitOut] = useState(pre.fitOut ?? '')
|
||||||
const [fitOutByLandlord, setFitOutByLandlord] = useState(pre.fitOutByLandlord ?? false)
|
const [fitOutByLandlord, setFitOutByLandlord] = useState<boolean | undefined>(pre.fitOutByLandlord)
|
||||||
const [parking, setParking] = useState(pre.parking != null ? String(pre.parking) : '')
|
const [parking, setParking] = useState(pre.parking != null ? String(pre.parking) : '')
|
||||||
const [ceilingHeight, setCeilingHeight]= useState(pre.ceilingHeight != null ? String(pre.ceilingHeight) : '')
|
const [ceilingHeight, setCeilingHeight]= useState(pre.ceilingHeight != null ? String(pre.ceilingHeight) : '')
|
||||||
const [mieterausbaubeitrag, setMieterausbaubeitrag] = useState(pre.mieterausbaubeitragPerSqm != null ? String(pre.mieterausbaubeitragPerSqm) : '')
|
const [mieterausbaubeitrag, setMieterausbaubeitrag] = useState(pre.mieterausbaubeitragPerSqm != null ? String(pre.mieterausbaubeitragPerSqm) : '')
|
||||||
@@ -164,7 +169,7 @@ export function useNewListingForm(pre: Prefill): NewListingFormState & NewListin
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
const err = validate({ street, postalCode, city, areaSqm, rentPerSqm })
|
const err = validate({ street, postalCode, city, areaSqm, rentPerSqm, fitOut, fitOutByLandlord })
|
||||||
if (err) { setError(err); return }
|
if (err) { setError(err); return }
|
||||||
setError(null)
|
setError(null)
|
||||||
createProperty.mutate(
|
createProperty.mutate(
|
||||||
@@ -188,7 +193,7 @@ export function useNewListingForm(pre: Prefill): NewListingFormState & NewListin
|
|||||||
setAreaSqm(''); setRentPerSqm(''); setAvailableFrom(''); setDescription('')
|
setAreaSqm(''); setRentPerSqm(''); setAvailableFrom(''); setDescription('')
|
||||||
setContactName(''); setContactEmail(''); setContactPhone('')
|
setContactName(''); setContactEmail(''); setContactPhone('')
|
||||||
setSoftLevels(emptySoftLevels())
|
setSoftLevels(emptySoftLevels())
|
||||||
setFloor(''); setFitOut(''); setFitOutByLandlord(false); setParking(''); setCeilingHeight('')
|
setFloor(''); setFitOut(''); setFitOutByLandlord(undefined); setParking(''); setCeilingHeight('')
|
||||||
setMieterausbaubeitrag(''); setIsFlexible(false); setMinLettableSqm('')
|
setMieterausbaubeitrag(''); setIsFlexible(false); setMinLettableSqm('')
|
||||||
setImages([]); setImageInput(''); setFloorPlanUrl('')
|
setImages([]); setImageInput(''); setFloorPlanUrl('')
|
||||||
setAiText(''); setAiApplied(false)
|
setAiText(''); setAiApplied(false)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function buildCreatePropertyInput(fields: {
|
|||||||
const hf = {
|
const hf = {
|
||||||
floor: floor ? parseInt(floor) : undefined,
|
floor: floor ? parseInt(floor) : undefined,
|
||||||
fitOut: (fitOut || undefined) as 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM' | undefined,
|
fitOut: (fitOut || undefined) as 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM' | undefined,
|
||||||
fitOutByLandlord: fitOutByLandlord || undefined,
|
fitOutByLandlord,
|
||||||
// MAB nur relevant, wenn der Mieter ausbaut — sonst nicht speichern
|
// MAB nur relevant, wenn der Mieter ausbaut — sonst nicht speichern
|
||||||
mieterausbaubeitragPerSqm: !fitOutByLandlord && mieterausbaubeitrag ? parseInt(mieterausbaubeitrag) : undefined,
|
mieterausbaubeitragPerSqm: !fitOutByLandlord && mieterausbaubeitrag ? parseInt(mieterausbaubeitrag) : undefined,
|
||||||
parking: parking ? parseInt(parking) : undefined,
|
parking: parking ? parseInt(parking) : undefined,
|
||||||
|
|||||||
Reference in New Issue
Block a user