From f0988b7250b7b1e35b112faa572d44906b1e2186 Mon Sep 17 00:00:00 2001 From: Benjamin Sutter Date: Sun, 24 May 2026 01:05:57 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20extract=20constants=20+=20mapper=20?= =?UTF-8?q?from=20NewListing.tsx=20(526=20=E2=86=92=20442=20lines)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/pages/supply/NewListing.tsx | 134 +++++------------------- src/pages/supply/newListingConstants.ts | 53 ++++++++++ src/pages/supply/newListingMapper.ts | 78 ++++++++++++++ 3 files changed, 156 insertions(+), 109 deletions(-) create mode 100644 src/pages/supply/newListingConstants.ts create mode 100644 src/pages/supply/newListingMapper.ts diff --git a/src/pages/supply/NewListing.tsx b/src/pages/supply/NewListing.tsx index 224bacd..f129b5a 100644 --- a/src/pages/supply/NewListing.tsx +++ b/src/pages/supply/NewListing.tsx @@ -16,64 +16,16 @@ import { Typography, } from '@mui/material' import { ArrowLeft, CheckCircle, ImagePlus, Sparkles, X } from 'lucide-react' -import { AssetType, AvailabilityStatus, ResultType } from '../../domain/enums' import { propertyService } from '../../services/propertyService' import { parseListingText } from '../../services/aiService' -import type { CreatePropertyInput } from '../../domain/property' - -const ASSET_TYPE_LABELS: Record = { - OFFICE: 'Büro', - RETAIL: 'Einzelhandel', - LIGHT_INDUSTRIAL: 'Leichtindustrie', - LOGISTICS: 'Logistik', - PRODUCTION: 'Produktion', - 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 = { - 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 - } -} +import { + ASSET_TYPE_LABELS, + SOFT_FACTORS, + LEVEL_OPTIONS, + FIT_OUT_OPTIONS, + type LocationState, +} from './newListingConstants' +import { buildCreatePropertyInput } from './newListingMapper' export default function NewListing() { const navigate = useNavigate() @@ -165,59 +117,23 @@ export default function NewListing() { setError(null) setSubmitting(true) - const area = Number(areaSqm) - const rent = Number(rentPerSqm) - - 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, - } - - 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', - } + const input = buildCreatePropertyInput({ + assetType, + street, + houseNumber, + postalCode, + city, + areaSqm: Number(areaSqm), + rentPerSqm: Number(rentPerSqm), + availableFrom, + description, + softLevels, + floor, + fitOut, + parking, + ceilingHeight, + images, + }) try { await propertyService.create(input) diff --git a/src/pages/supply/newListingConstants.ts b/src/pages/supply/newListingConstants.ts new file mode 100644 index 0000000..738e359 --- /dev/null +++ b/src/pages/supply/newListingConstants.ts @@ -0,0 +1,53 @@ +export const ASSET_TYPE_LABELS: Record = { + 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 = { + 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 + } +} diff --git a/src/pages/supply/newListingMapper.ts b/src/pages/supply/newListingMapper.ts new file mode 100644 index 0000000..b9ae5bc --- /dev/null +++ b/src/pages/supply/newListingMapper.ts @@ -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 + 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', + } +}