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,
|
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)
|
||||||
|
|||||||
@@ -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