feat: unit-first architecture — floor + street as primary card title
Every VERIFIED_PORTFOLIO property now has explicit units in mock data (added to prop-002, prop-008, prop-009, prop-010, prop-011, prop-014). PropertyUnit extracted to domain/unit.ts with formatFloorLabel, formatUnitTitle, formatMultiUnitFloors helpers; re-exported from property.ts so all existing imports are unaffected. Cards now show floor + street as the primary title: - Specific unit match: "1.OG Nord · Zollstrasse 12" - Multi-unit (whole building): "1.OG–3.OG · Zollstrasse 12" - EG retail: "EG Verkauf · Löwenplatz 3" Building name (e.g. "Bürofläche Zollstrasse 12") appears as subtitle. Both IntelligenceMatchCard (grid) and MatchCardCompact (list) updated. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -392,39 +392,28 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro
|
|||||||
|
|
||||||
{/* ── Card body ── */}
|
{/* ── Card body ── */}
|
||||||
<Box sx={{ p: 2, flex: 1, display: 'flex', flexDirection: 'column', gap: 0 }}>
|
<Box sx={{ p: 2, flex: 1, display: 'flex', flexDirection: 'column', gap: 0 }}>
|
||||||
|
{/* Unit-first title (floor + street) */}
|
||||||
<Typography variant="subtitle1" sx={{ fontWeight: 700, lineHeight: 1.3 }} noWrap>
|
<Typography variant="subtitle1" sx={{ fontWeight: 700, lineHeight: 1.3 }} noWrap>
|
||||||
{vm.title}
|
{vm.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{/* Building name as subtitle when a unit or floor range is in the title */}
|
||||||
|
{vm.propertySubtitle && (
|
||||||
|
<Typography variant="caption" sx={{ display: 'block', mt: 0.125, color: '#475569', fontWeight: 500 }} noWrap>
|
||||||
|
{vm.propertySubtitle}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.25 }}>
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.25 }}>
|
||||||
{[vm.locationLabel, vm.availabilityLabel].filter(Boolean).join(' · ')}
|
{[vm.locationLabel, vm.availabilityLabel].filter(Boolean).join(' · ')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* Specific named unit */}
|
{/* Unit area + rent when a specific unit is matched */}
|
||||||
{vm.preMarketUnit?.unitLabel && (
|
{vm.preMarketUnit && (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mt: 0.5 }}>
|
<Typography variant="caption" sx={{ color: '#64748b', fontSize: '0.68rem', mt: 0.25 }}>
|
||||||
<Chip
|
{vm.preMarketUnit.areaSqm.toLocaleString('de-CH')} m²
|
||||||
size="small"
|
{vm.preMarketUnit.rentPricePerSqm
|
||||||
label={`${floorLabel(vm.preMarketUnit.floorLevel)} · ${vm.preMarketUnit.unitLabel}`}
|
? ` · CHF ${Math.round(vm.preMarketUnit.rentPricePerSqm / 12).toLocaleString('de-CH')}/m²/Mt.`
|
||||||
sx={{ height: 18, fontSize: '0.62rem', bgcolor: '#f1f5f9', color: '#374151', border: '1px solid #e2e8f0' }}
|
: ''}
|
||||||
/>
|
</Typography>
|
||||||
<Typography variant="caption" sx={{ color: '#64748b', fontSize: '0.68rem' }}>
|
|
||||||
{vm.preMarketUnit.areaSqm.toLocaleString('de-CH')} m²
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Multi-unit property: no specific unit matched — show aggregate summary */}
|
|
||||||
{!vm.preMarketUnit && (vm.preMarketAllUnits?.length ?? 0) > 1 && (
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mt: 0.5 }}>
|
|
||||||
<Chip
|
|
||||||
size="small"
|
|
||||||
label={`${vm.preMarketAllUnits!.length} Einheiten`}
|
|
||||||
sx={{ height: 18, fontSize: '0.62rem', bgcolor: '#eff6ff', color: '#1e40af', border: '1px solid #bfdbfe' }}
|
|
||||||
/>
|
|
||||||
<Typography variant="caption" sx={{ color: '#64748b', fontSize: '0.68rem' }}>
|
|
||||||
total {vm.preMarketAllUnits!.reduce((s, u) => s + u.areaSqm, 0).toLocaleString('de-CH')} m²
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Regular explainability summary */}
|
{/* Regular explainability summary */}
|
||||||
|
|||||||
@@ -52,6 +52,11 @@ export function MatchCardCompact({ vm }: Props) {
|
|||||||
<Typography variant="subtitle1" sx={{ fontWeight: 600 }} noWrap>
|
<Typography variant="subtitle1" sx={{ fontWeight: 600 }} noWrap>
|
||||||
{vm.title}
|
{vm.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{vm.propertySubtitle && (
|
||||||
|
<Typography variant="caption" sx={{ display: 'block', color: '#64748b', fontWeight: 500, mt: 0.125 }} noWrap>
|
||||||
|
{vm.propertySubtitle}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 0.25 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 0.25 }}>
|
||||||
<MapPin size={13} color="#64748b" />
|
<MapPin size={13} color="#64748b" />
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ export interface MatchCardViewModel {
|
|||||||
signalIsControlled?: boolean
|
signalIsControlled?: boolean
|
||||||
signalAreaSqmEstimate?: number
|
signalAreaSqmEstimate?: number
|
||||||
|
|
||||||
|
// Unit-first display (primary)
|
||||||
|
propertySubtitle?: string // building name shown below the unit title
|
||||||
|
|
||||||
// Property / unit reference (for FUTURE_AVAILABILITY with a backing property)
|
// Property / unit reference (for FUTURE_AVAILABILITY with a backing property)
|
||||||
propertyId?: string
|
propertyId?: string
|
||||||
unitId?: string
|
unitId?: string
|
||||||
|
|||||||
+3
-43
@@ -4,6 +4,9 @@ import type {
|
|||||||
} from './enums'
|
} from './enums'
|
||||||
import { AvailabilityStatus as AS } from './enums'
|
import { AvailabilityStatus as AS } from './enums'
|
||||||
|
|
||||||
|
// Re-export unit types so all existing imports from 'property' continue to work
|
||||||
|
export type { PropertyUnit, UnitBundle, UnitNeedMatch } from './unit'
|
||||||
|
|
||||||
// ── Location / Address ────────────────────────────────────────────────────────
|
// ── Location / Address ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface Location {
|
export interface Location {
|
||||||
@@ -83,49 +86,6 @@ export interface SoftFactors {
|
|||||||
infrastructureNotes?: string
|
infrastructureNotes?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Floor / Unit structure ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export interface PropertyUnit {
|
|
||||||
id: string
|
|
||||||
propertyId?: string // FK to parent Property (set when stored independently)
|
|
||||||
floorLevel: number // 0=EG, 1=1.OG, -1=UG
|
|
||||||
unitLabel?: string // e.g. "Ost", "West", "Einheit A"
|
|
||||||
areaSqm: number
|
|
||||||
available: boolean
|
|
||||||
rentPricePerSqm?: number
|
|
||||||
currentTenant?: string
|
|
||||||
leaseTerm?: string
|
|
||||||
leaseEndDate?: string
|
|
||||||
// Flexible letting
|
|
||||||
isFlexible?: boolean // can be partially leased (Teilfläche)
|
|
||||||
minLettableSqm?: number // minimum area that can be leased standalone
|
|
||||||
offeredSqm?: number // currently offered area (≤ areaSqm); undefined = full unit
|
|
||||||
// Unit-level pre-market release
|
|
||||||
schattenmarktRelease?: {
|
|
||||||
enabled: boolean
|
|
||||||
availableFrom?: string // ISO date; overrides property-level leaseEndDate for this unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A bundle groups multiple free units for a combined offer
|
|
||||||
export interface UnitBundle {
|
|
||||||
unitIds: string[]
|
|
||||||
combinedSqm: number
|
|
||||||
label: string // e.g. "1.OG + 2.OG"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result of unit-level need matching
|
|
||||||
export interface UnitNeedMatch {
|
|
||||||
needId: string
|
|
||||||
tenantName: string
|
|
||||||
tenantCompany?: string
|
|
||||||
requiredSqmMin: number
|
|
||||||
requiredSqmMax: number
|
|
||||||
matchScore: number
|
|
||||||
matchType: 'exact' | 'partial' | 'bundle' // how the unit satisfies the need
|
|
||||||
suggestedSqm?: number // for partial: how much of the unit to offer
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Property ──────────────────────────────────────────────────────────────────
|
// ── Property ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface Property {
|
export interface Property {
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// ── Rentable Unit — primary entity for demand-side matching ──────────────────
|
||||||
|
//
|
||||||
|
// A Property is the building/address container.
|
||||||
|
// A PropertyUnit is what is actually rented: a specific floor, wing, or section.
|
||||||
|
// Floor level is business-critical: retail needs EG for walk-in traffic,
|
||||||
|
// offices can occupy upper floors, logistics needs ground-level loading access.
|
||||||
|
|
||||||
|
export interface PropertyUnit {
|
||||||
|
id: string
|
||||||
|
propertyId?: string // FK to parent Property
|
||||||
|
floorLevel: number // 0=EG, 1=1.OG, -1=UG1
|
||||||
|
unitLabel?: string // e.g. "Nord", "West", "Einheit A"
|
||||||
|
areaSqm: number
|
||||||
|
available: boolean
|
||||||
|
rentPricePerSqm?: number // annual CHF/m²; falls back to property.rentPricePerSqm
|
||||||
|
currentTenant?: string
|
||||||
|
leaseTerm?: string
|
||||||
|
leaseEndDate?: string
|
||||||
|
// Flexible letting (Teilfläche)
|
||||||
|
isFlexible?: boolean
|
||||||
|
minLettableSqm?: number
|
||||||
|
offeredSqm?: number // currently offered area (≤ areaSqm)
|
||||||
|
// Unit-level pre-market release
|
||||||
|
schattenmarktRelease?: {
|
||||||
|
enabled: boolean
|
||||||
|
availableFrom?: string // ISO date
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bundle groups multiple free units into a combined offer
|
||||||
|
export interface UnitBundle {
|
||||||
|
unitIds: string[]
|
||||||
|
combinedSqm: number
|
||||||
|
label: string // e.g. "1.OG + 2.OG"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result of unit-level need matching (used in supply-side detail views)
|
||||||
|
export interface UnitNeedMatch {
|
||||||
|
needId: string
|
||||||
|
tenantName: string
|
||||||
|
tenantCompany?: string
|
||||||
|
requiredSqmMin: number
|
||||||
|
requiredSqmMax: number
|
||||||
|
matchScore: number
|
||||||
|
matchType: 'exact' | 'partial' | 'bundle'
|
||||||
|
suggestedSqm?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Display helpers ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export function formatFloorLabel(level: number): string {
|
||||||
|
if (level === 0) return 'EG'
|
||||||
|
if (level < 0) return `UG${Math.abs(level)}`
|
||||||
|
return `${level}.OG`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the primary display title for a specific unit: "1.OG Nord · Zollstrasse 12" */
|
||||||
|
export function formatUnitTitle(
|
||||||
|
unit: PropertyUnit,
|
||||||
|
address: { street: string; houseNumber: string },
|
||||||
|
): string {
|
||||||
|
const floor = formatFloorLabel(unit.floorLevel)
|
||||||
|
const label = unit.unitLabel ? ` ${unit.unitLabel}` : ''
|
||||||
|
return `${floor}${label} · ${address.street} ${address.houseNumber}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a compact floor-range string for multi-unit properties: "1.OG–3.OG" or "EG · 1.OG" */
|
||||||
|
export function formatMultiUnitFloors(units: PropertyUnit[]): string {
|
||||||
|
const floors = [...new Set(units.map(u => u.floorLevel))].sort((a, b) => a - b)
|
||||||
|
if (floors.length === 0) return ''
|
||||||
|
if (floors.length === 1) return formatFloorLabel(floors[0])
|
||||||
|
if (floors.length === 2) return `${formatFloorLabel(floors[0])} · ${formatFloorLabel(floors[1])}`
|
||||||
|
return `${formatFloorLabel(floors[0])}–${formatFloorLabel(floors[floors.length - 1])}`
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
MatchCardReason,
|
MatchCardReason,
|
||||||
} from '../../components/match-card/MatchCardViewModel'
|
} from '../../components/match-card/MatchCardViewModel'
|
||||||
import { getCityIntelligence } from '../../lib/locationIntelligence'
|
import { getCityIntelligence } from '../../lib/locationIntelligence'
|
||||||
|
import { formatUnitTitle, formatMultiUnitFloors } from '../../domain/unit'
|
||||||
|
|
||||||
const HARD_CRITERIA = new Set(['area', 'location', 'budget', 'timing'])
|
const HARD_CRITERIA = new Set(['area', 'location', 'budget', 'timing'])
|
||||||
|
|
||||||
@@ -50,8 +51,22 @@ export function buildMatchCardViewModel(
|
|||||||
? `${city}${district ? `, ${district}` : ''}`
|
? `${city}${district ? `, ${district}` : ''}`
|
||||||
: signal?.locationHint ?? '–'
|
: signal?.locationHint ?? '–'
|
||||||
|
|
||||||
// For PRE-MARKET: title comes from unit if available, then signal, then property
|
// Unit-first title: specific unit → floor range of all units → signal → property
|
||||||
|
let cardTitle: string
|
||||||
|
let propertySubtitle: string | undefined
|
||||||
|
|
||||||
|
if (unit && property?.address) {
|
||||||
|
cardTitle = formatUnitTitle(unit, property.address)
|
||||||
|
propertySubtitle = property.title
|
||||||
|
} else if (signal?.title) {
|
||||||
|
cardTitle = signal.title
|
||||||
|
} else if (property?.units && property.units.length > 0 && property.address) {
|
||||||
|
const floorRange = formatMultiUnitFloors(property.units)
|
||||||
|
cardTitle = `${floorRange} · ${property.address.street} ${property.address.houseNumber}`
|
||||||
|
propertySubtitle = property.title
|
||||||
|
} else {
|
||||||
|
cardTitle = property?.title ?? signal?.companyName ?? signal?.locationHint ?? '–'
|
||||||
|
}
|
||||||
|
|
||||||
const availabilityLabel =
|
const availabilityLabel =
|
||||||
property?.availabilityDate ??
|
property?.availabilityDate ??
|
||||||
@@ -74,12 +89,8 @@ export function buildMatchCardViewModel(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: result.matchId,
|
id: result.matchId,
|
||||||
title:
|
title: cardTitle,
|
||||||
signal?.title ??
|
propertySubtitle,
|
||||||
property?.title ??
|
|
||||||
signal?.companyName ??
|
|
||||||
signal?.locationHint ??
|
|
||||||
'–',
|
|
||||||
resultType,
|
resultType,
|
||||||
assetType: property?.assetType,
|
assetType: property?.assetType,
|
||||||
matchScore,
|
matchScore,
|
||||||
|
|||||||
@@ -99,6 +99,10 @@ export const mockProperties: Property[] = [
|
|||||||
riskLevel: RiskLevel.LOW,
|
riskLevel: RiskLevel.LOW,
|
||||||
images: ['https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=800&h=400&fit=crop'],
|
images: ['https://images.unsplash.com/photo-1586528116311-ad8dd3c8310d?w=800&h=400&fit=crop'],
|
||||||
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
||||||
|
units: [
|
||||||
|
{ id: 'unit-002-1', propertyId: 'prop-002', floorLevel: 0, unitLabel: 'Halle A', areaSqm: 1400, available: false, rentPricePerSqm: 168, currentTenant: 'Spedition Rhein GmbH', leaseTerm: '3 Jahre', leaseEndDate: '2026-09-30', schattenmarktRelease: { enabled: true, availableFrom: '2026-09-30' } },
|
||||||
|
{ id: 'unit-002-2', propertyId: 'prop-002', floorLevel: 0, unitLabel: 'Halle B', areaSqm: 1000, available: false, rentPricePerSqm: 168, currentTenant: 'Spedition Rhein GmbH', leaseTerm: '3 Jahre', leaseEndDate: '2026-09-30', schattenmarktRelease: { enabled: true, availableFrom: '2026-09-30' } },
|
||||||
|
],
|
||||||
importedFrom: 'SAP RE-FX',
|
importedFrom: 'SAP RE-FX',
|
||||||
importedAt: '2025-01-15T08:00:00Z',
|
importedAt: '2025-01-15T08:00:00Z',
|
||||||
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
||||||
@@ -207,6 +211,12 @@ export const mockProperties: Property[] = [
|
|||||||
riskLevel: RiskLevel.LOW,
|
riskLevel: RiskLevel.LOW,
|
||||||
images: ['https://images.unsplash.com/photo-1568992687947-868a62a9f521?w=800&h=400&fit=crop'],
|
images: ['https://images.unsplash.com/photo-1568992687947-868a62a9f521?w=800&h=400&fit=crop'],
|
||||||
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
||||||
|
units: [
|
||||||
|
{ id: 'unit-008-1', propertyId: 'prop-008', floorLevel: 1, unitLabel: '1.OG', areaSqm: 220, available: false, rentPricePerSqm: 384, currentTenant: 'Pharma Research GmbH', leaseTerm: '6 Jahre', leaseEndDate: '2027-08-31', schattenmarktRelease: { enabled: true, availableFrom: '2027-08-31' } },
|
||||||
|
{ id: 'unit-008-2', propertyId: 'prop-008', floorLevel: 2, unitLabel: '2.OG West', areaSqm: 250, available: false, rentPricePerSqm: 384, currentTenant: 'Pharma Research GmbH', leaseTerm: '6 Jahre', leaseEndDate: '2027-08-31', schattenmarktRelease: { enabled: true, availableFrom: '2027-08-31' } },
|
||||||
|
{ id: 'unit-008-3', propertyId: 'prop-008', floorLevel: 3, unitLabel: '3.OG Ost', areaSqm: 230, available: false, rentPricePerSqm: 384, currentTenant: 'Pharma Research GmbH', leaseTerm: '6 Jahre', leaseEndDate: '2027-08-31', schattenmarktRelease: { enabled: true, availableFrom: '2027-08-31' } },
|
||||||
|
{ id: 'unit-008-4', propertyId: 'prop-008', floorLevel: 4, unitLabel: '4.OG', areaSqm: 200, available: false, rentPricePerSqm: 408, currentTenant: 'Pharma Research GmbH', leaseTerm: '6 Jahre', leaseEndDate: '2027-08-31', schattenmarktRelease: { enabled: false } },
|
||||||
|
],
|
||||||
importedFrom: 'SAP RE-FX',
|
importedFrom: 'SAP RE-FX',
|
||||||
importedAt: '2025-01-15T08:00:00Z',
|
importedAt: '2025-01-15T08:00:00Z',
|
||||||
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
||||||
@@ -256,6 +266,10 @@ export const mockProperties: Property[] = [
|
|||||||
riskLevel: RiskLevel.LOW,
|
riskLevel: RiskLevel.LOW,
|
||||||
images: ['https://images.unsplash.com/photo-1553413077-190dd305871c?w=800&h=400&fit=crop'],
|
images: ['https://images.unsplash.com/photo-1553413077-190dd305871c?w=800&h=400&fit=crop'],
|
||||||
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
||||||
|
units: [
|
||||||
|
{ id: 'unit-009-1', propertyId: 'prop-009', floorLevel: 0, unitLabel: 'Lager Nord', areaSqm: 900, available: false, rentPricePerSqm: 156, currentTenant: 'Sperrgut Logistik AG', leaseTerm: '6 Jahre', leaseEndDate: '2026-10-31', schattenmarktRelease: { enabled: true, availableFrom: '2026-10-31' } },
|
||||||
|
{ id: 'unit-009-2', propertyId: 'prop-009', floorLevel: 0, unitLabel: 'Lager Süd', areaSqm: 900, available: false, rentPricePerSqm: 156, currentTenant: 'Sperrgut Logistik AG', leaseTerm: '6 Jahre', leaseEndDate: '2026-10-31', schattenmarktRelease: { enabled: true, availableFrom: '2026-10-31' } },
|
||||||
|
],
|
||||||
importedFrom: 'SAP RE-FX',
|
importedFrom: 'SAP RE-FX',
|
||||||
importedAt: '2025-01-15T08:00:00Z',
|
importedAt: '2025-01-15T08:00:00Z',
|
||||||
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
||||||
@@ -305,6 +319,10 @@ export const mockProperties: Property[] = [
|
|||||||
riskLevel: RiskLevel.LOW,
|
riskLevel: RiskLevel.LOW,
|
||||||
images: ['https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=800&h=400&fit=crop'],
|
images: ['https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=800&h=400&fit=crop'],
|
||||||
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
||||||
|
units: [
|
||||||
|
{ id: 'unit-010-1', propertyId: 'prop-010', floorLevel: 0, unitLabel: 'EG Verkauf', areaSqm: 205, available: false, rentPricePerSqm: 1056, currentTenant: 'Fashion Concept GmbH', leaseTerm: '6 Jahre', leaseEndDate: '2026-09-30', schattenmarktRelease: { enabled: true, availableFrom: '2026-09-30' } },
|
||||||
|
{ id: 'unit-010-2', propertyId: 'prop-010', floorLevel: -1, unitLabel: 'UG Lager', areaSqm: 80, available: false, rentPricePerSqm: 432, currentTenant: 'Fashion Concept GmbH', leaseTerm: '6 Jahre', leaseEndDate: '2026-09-30', schattenmarktRelease: { enabled: false } },
|
||||||
|
],
|
||||||
importedFrom: 'SAP RE-FX',
|
importedFrom: 'SAP RE-FX',
|
||||||
importedAt: '2025-01-15T08:00:00Z',
|
importedAt: '2025-01-15T08:00:00Z',
|
||||||
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
||||||
@@ -354,6 +372,10 @@ export const mockProperties: Property[] = [
|
|||||||
riskLevel: RiskLevel.LOW,
|
riskLevel: RiskLevel.LOW,
|
||||||
images: ['https://images.unsplash.com/photo-1565043589221-1a6fd9ae45c7?w=800&h=400&fit=crop'],
|
images: ['https://images.unsplash.com/photo-1565043589221-1a6fd9ae45c7?w=800&h=400&fit=crop'],
|
||||||
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
mapImageUrl: 'https://images.unsplash.com/photo-1524661135-423995f22d0b?w=600&h=300&fit=crop',
|
||||||
|
units: [
|
||||||
|
{ id: 'unit-011-1', propertyId: 'prop-011', floorLevel: 0, unitLabel: 'Halle West', areaSqm: 1600, available: false, rentPricePerSqm: 144, currentTenant: 'Metallbau Bern AG', leaseTerm: '11 Jahre', leaseEndDate: '2026-11-30', schattenmarktRelease: { enabled: true, availableFrom: '2026-11-30' } },
|
||||||
|
{ id: 'unit-011-2', propertyId: 'prop-011', floorLevel: 0, unitLabel: 'Halle Ost', areaSqm: 1200, available: false, rentPricePerSqm: 144, currentTenant: 'Metallbau Bern AG', leaseTerm: '11 Jahre', leaseEndDate: '2026-11-30', schattenmarktRelease: { enabled: true, availableFrom: '2026-11-30' } },
|
||||||
|
],
|
||||||
importedFrom: 'SAP RE-FX',
|
importedFrom: 'SAP RE-FX',
|
||||||
importedAt: '2025-01-15T08:00:00Z',
|
importedAt: '2025-01-15T08:00:00Z',
|
||||||
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
||||||
@@ -520,6 +542,11 @@ export const mockProperties: Property[] = [
|
|||||||
importedFrom: 'SAP RE-FX',
|
importedFrom: 'SAP RE-FX',
|
||||||
importedAt: '2025-01-15T08:00:00Z',
|
importedAt: '2025-01-15T08:00:00Z',
|
||||||
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
lastUpdatedAt: '2025-05-10T10:00:00Z',
|
||||||
|
units: [
|
||||||
|
{ id: 'unit-014-1', propertyId: 'prop-014', floorLevel: 0, unitLabel: 'Lager A', areaSqm: 1600, available: false, rentPricePerSqm: 180, currentTenant: 'Handels- und Lagerbetrieb AG', leaseTerm: '6 Jahre', leaseEndDate: '2026-09-30', schattenmarktRelease: { enabled: true, availableFrom: '2026-09-30' } },
|
||||||
|
{ id: 'unit-014-2', propertyId: 'prop-014', floorLevel: 0, unitLabel: 'Lager B', areaSqm: 1000, available: false, rentPricePerSqm: 180, currentTenant: 'Handels- und Lagerbetrieb AG', leaseTerm: '6 Jahre', leaseEndDate: '2026-09-30', schattenmarktRelease: { enabled: true, availableFrom: '2026-09-30' } },
|
||||||
|
{ id: 'unit-014-3', propertyId: 'prop-014', floorLevel: 0, unitLabel: 'Büro EG', areaSqm: 500, available: true, rentPricePerSqm: 240, isFlexible: true, minLettableSqm: 200 },
|
||||||
|
],
|
||||||
currentTenant: 'Handels- und Lagerbetrieb AG',
|
currentTenant: 'Handels- und Lagerbetrieb AG',
|
||||||
leaseTerm: '6 Jahre',
|
leaseTerm: '6 Jahre',
|
||||||
leaseStartDate: '2020-09-01',
|
leaseStartDate: '2020-09-01',
|
||||||
|
|||||||
Reference in New Issue
Block a user