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:
+25
-109
@@ -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<string, string> = {
|
||||
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<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
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user