refactor: extract constants + mapper from NewListing.tsx (526 → 442 lines)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 01:05:57 +02:00
parent 0bacd188d6
commit f0988b7250
3 changed files with 156 additions and 109 deletions
+25 -109
View File
@@ -16,64 +16,16 @@ import {
Typography, Typography,
} from '@mui/material' } from '@mui/material'
import { ArrowLeft, CheckCircle, ImagePlus, Sparkles, X } from 'lucide-react' import { ArrowLeft, CheckCircle, ImagePlus, Sparkles, X } from 'lucide-react'
import { AssetType, AvailabilityStatus, ResultType } from '../../domain/enums'
import { propertyService } from '../../services/propertyService' import { propertyService } from '../../services/propertyService'
import { parseListingText } from '../../services/aiService' import { parseListingText } from '../../services/aiService'
import type { CreatePropertyInput } from '../../domain/property' import {
ASSET_TYPE_LABELS,
const ASSET_TYPE_LABELS: Record<string, string> = { SOFT_FACTORS,
OFFICE: 'Büro', LEVEL_OPTIONS,
RETAIL: 'Einzelhandel', FIT_OUT_OPTIONS,
LIGHT_INDUSTRIAL: 'Leichtindustrie', type LocationState,
LOGISTICS: 'Logistik', } from './newListingConstants'
PRODUCTION: 'Produktion', import { buildCreatePropertyInput } from './newListingMapper'
MIXED: 'Gemischt',
}
const SOFT_FACTORS = [
{ key: 'prestige', label: 'Prestige / Adressqualität' },
{ key: 'accessibility', label: 'ÖV-Anbindung' },
{ key: 'visibility', label: 'Sichtbarkeit' },
{ key: 'footfall', label: 'Passantenfrequenz' },
{ key: 'talentAccess', label: 'Fachkräfte / Talent' },
{ key: 'esg', label: 'ESG / Nachhaltigkeit' },
{ key: 'flexibility', label: 'Flexibilität Grundriss' },
{ key: 'expansionPotential', label: 'Expansionspotenzial' },
{ key: 'taxEnvironment', label: 'Steuerumgebung' },
]
const LEVEL_OPTIONS = [
{ value: '', label: 'Keine Angabe' },
{ value: 'LOW', label: 'Tief' },
{ value: 'MEDIUM', label: 'Mittel' },
{ value: 'HIGH', label: 'Hoch' },
]
const LEVEL_TO_SCORE: Record<string, number | undefined> = {
LOW: 0.30, MEDIUM: 0.55, HIGH: 0.85, '': undefined,
}
const FIT_OUT_OPTIONS = [
{ value: '', label: 'Keine Angabe' },
{ value: 'SHELL', label: 'Rohbau' },
{ value: 'BASIC', label: 'Basis-Ausbau' },
{ value: 'FULL', label: 'Vollausbau' },
{ value: 'PREMIUM', label: 'Premiumausbau' },
]
interface LocationState {
prefill?: {
assetType?: string
street?: string
houseNumber?: string
postalCode?: string
city?: string
areaSqm?: number
rentPricePerSqm?: number
unitLabel?: string
propertyId?: string
}
}
export default function NewListing() { export default function NewListing() {
const navigate = useNavigate() const navigate = useNavigate()
@@ -165,59 +117,23 @@ export default function NewListing() {
setError(null) setError(null)
setSubmitting(true) setSubmitting(true)
const area = Number(areaSqm) const input = buildCreatePropertyInput({
const rent = Number(rentPerSqm) assetType,
street,
const sf = { houseNumber,
prestigeScore: LEVEL_TO_SCORE[softLevels.prestige ?? ''], postalCode,
commuterAccessScore: LEVEL_TO_SCORE[softLevels.accessibility ?? ''], city,
visibilityScore: LEVEL_TO_SCORE[softLevels.visibility ?? ''], areaSqm: Number(areaSqm),
footfallScore: LEVEL_TO_SCORE[softLevels.footfall ?? ''], rentPerSqm: Number(rentPerSqm),
talentAccessScore: LEVEL_TO_SCORE[softLevels.talentAccess ?? ''], availableFrom,
esgScore: LEVEL_TO_SCORE[softLevels.esg ?? ''], description,
flexibilityScore: LEVEL_TO_SCORE[softLevels.flexibility ?? ''], softLevels,
expansionPotentialScore: LEVEL_TO_SCORE[softLevels.expansionPotential ?? ''], floor,
taxEnvironmentScore: LEVEL_TO_SCORE[softLevels.taxEnvironment ?? ''], fitOut,
} parking,
ceilingHeight,
const hf = { images,
floor: floor ? parseInt(floor) : undefined, })
fitOut: (fitOut || undefined) as 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM' | undefined,
parking: parking ? parseInt(parking) : undefined,
ceilingHeightM: ceilingHeight ? parseFloat(ceilingHeight) : undefined,
}
const input: CreatePropertyInput = {
title: `${ASSET_TYPE_LABELS[assetType] ?? assetType} · ${city}`,
assetType: assetType as typeof AssetType[keyof typeof AssetType],
resultType: ResultType.EXTERNAL_MARKET,
sourceType: 'DIRECT',
location: { city, country: 'CH' },
address: {
street: street.trim(),
houseNumber: houseNumber.trim(),
postalCode: postalCode.trim(),
city: city.trim(),
country: 'CH',
},
areaSqm: area,
rentPricePerSqm: rent,
availabilityDate: availableFrom || new Date().toISOString().slice(0, 10),
availabilityStatus: availableFrom ? AvailabilityStatus.AVAILABLE_SOON : AvailabilityStatus.AVAILABLE_NOW,
confidenceScore: 1.0,
description: description.trim() || undefined,
softFactors: sf,
hardFacts: hf,
images: images.length > 0 ? images : undefined,
dataQuality: {
score: 1.0,
missingCriticalFields: [],
missingOptionalFields: [],
freshness: 'FRESH',
warnings: [],
},
status: 'ACTIVE',
}
try { try {
await propertyService.create(input) await propertyService.create(input)
+53
View File
@@ -0,0 +1,53 @@
export const ASSET_TYPE_LABELS: Record<string, string> = {
OFFICE: 'Büro',
RETAIL: 'Einzelhandel',
LIGHT_INDUSTRIAL: 'Leichtindustrie',
LOGISTICS: 'Logistik',
PRODUCTION: 'Produktion',
MIXED: 'Gemischt',
}
export const SOFT_FACTORS = [
{ key: 'prestige', label: 'Prestige / Adressqualität' },
{ key: 'accessibility', label: 'ÖV-Anbindung' },
{ key: 'visibility', label: 'Sichtbarkeit' },
{ key: 'footfall', label: 'Passantenfrequenz' },
{ key: 'talentAccess', label: 'Fachkräfte / Talent' },
{ key: 'esg', label: 'ESG / Nachhaltigkeit' },
{ key: 'flexibility', label: 'Flexibilität Grundriss' },
{ key: 'expansionPotential', label: 'Expansionspotenzial' },
{ key: 'taxEnvironment', label: 'Steuerumgebung' },
]
export const LEVEL_OPTIONS = [
{ value: '', label: 'Keine Angabe' },
{ value: 'LOW', label: 'Tief' },
{ value: 'MEDIUM', label: 'Mittel' },
{ value: 'HIGH', label: 'Hoch' },
]
export const LEVEL_TO_SCORE: Record<string, number | undefined> = {
LOW: 0.30, MEDIUM: 0.55, HIGH: 0.85, '': undefined,
}
export const FIT_OUT_OPTIONS = [
{ value: '', label: 'Keine Angabe' },
{ value: 'SHELL', label: 'Rohbau' },
{ value: 'BASIC', label: 'Basis-Ausbau' },
{ value: 'FULL', label: 'Vollausbau' },
{ value: 'PREMIUM', label: 'Premiumausbau' },
]
export interface LocationState {
prefill?: {
assetType?: string
street?: string
houseNumber?: string
postalCode?: string
city?: string
areaSqm?: number
rentPricePerSqm?: number
unitLabel?: string
propertyId?: string
}
}
+78
View File
@@ -0,0 +1,78 @@
import { AssetType, AvailabilityStatus, ResultType } from '../../domain/enums'
import type { CreatePropertyInput } from '../../domain/property'
import { ASSET_TYPE_LABELS, LEVEL_TO_SCORE } from './newListingConstants'
export function buildCreatePropertyInput(fields: {
assetType: string
street: string
houseNumber: string
postalCode: string
city: string
areaSqm: number
rentPerSqm: number
availableFrom: string
description: string
softLevels: Record<string, string>
floor: string
fitOut: string
parking: string
ceilingHeight: string
images: string[]
}): CreatePropertyInput {
const {
assetType, street, houseNumber, postalCode, city,
areaSqm, rentPerSqm, availableFrom, description,
softLevels, floor, fitOut, parking, ceilingHeight, images,
} = fields
const sf = {
prestigeScore: LEVEL_TO_SCORE[softLevels.prestige ?? ''],
commuterAccessScore: LEVEL_TO_SCORE[softLevels.accessibility ?? ''],
visibilityScore: LEVEL_TO_SCORE[softLevels.visibility ?? ''],
footfallScore: LEVEL_TO_SCORE[softLevels.footfall ?? ''],
talentAccessScore: LEVEL_TO_SCORE[softLevels.talentAccess ?? ''],
esgScore: LEVEL_TO_SCORE[softLevels.esg ?? ''],
flexibilityScore: LEVEL_TO_SCORE[softLevels.flexibility ?? ''],
expansionPotentialScore: LEVEL_TO_SCORE[softLevels.expansionPotential ?? ''],
taxEnvironmentScore: LEVEL_TO_SCORE[softLevels.taxEnvironment ?? ''],
}
const hf = {
floor: floor ? parseInt(floor) : undefined,
fitOut: (fitOut || undefined) as 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM' | undefined,
parking: parking ? parseInt(parking) : undefined,
ceilingHeightM: ceilingHeight ? parseFloat(ceilingHeight) : undefined,
}
return {
title: `${ASSET_TYPE_LABELS[assetType] ?? assetType} · ${city}`,
assetType: assetType as typeof AssetType[keyof typeof AssetType],
resultType: ResultType.EXTERNAL_MARKET,
sourceType: 'DIRECT',
location: { city, country: 'CH' },
address: {
street: street.trim(),
houseNumber: houseNumber.trim(),
postalCode: postalCode.trim(),
city: city.trim(),
country: 'CH',
},
areaSqm,
rentPricePerSqm: rentPerSqm,
availabilityDate: availableFrom || new Date().toISOString().slice(0, 10),
availabilityStatus: availableFrom ? AvailabilityStatus.AVAILABLE_SOON : AvailabilityStatus.AVAILABLE_NOW,
confidenceScore: 1.0,
description: description.trim() || undefined,
softFactors: sf,
hardFacts: hf,
images: images.length > 0 ? images : undefined,
dataQuality: {
score: 1.0,
missingCriticalFields: [],
missingOptionalFields: [],
freshness: 'FRESH',
warnings: [],
},
status: 'ACTIVE',
}
}