From 161b2d609a7fbc91c8c6183772cc35f2410bff38 Mon Sep 17 00:00:00 2001 From: Benjamin Sutter Date: Tue, 9 Jun 2026 22:28:56 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20PropertyIntelligenceCard=20?= =?UTF-8?q?=E2=80=94=20E&V-style=20minimal=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove all image overlays, colored badges, metric boxes and confidence bar. New structure: image → location (gray) → title → price large (DM Serif) → single facts line. Single neutral dark tag for asset type. Matches Engel & Völkers card pattern. Co-Authored-By: Claude Sonnet 4.6 --- .../supply/PropertyIntelligenceCard.tsx | 201 ++++++------------ 1 file changed, 60 insertions(+), 141 deletions(-) diff --git a/src/components/supply/PropertyIntelligenceCard.tsx b/src/components/supply/PropertyIntelligenceCard.tsx index 978a19c..efcf738 100644 --- a/src/components/supply/PropertyIntelligenceCard.tsx +++ b/src/components/supply/PropertyIntelligenceCard.tsx @@ -1,7 +1,6 @@ import { memo } from 'react' -import { Box, Chip, LinearProgress, Tooltip, Typography } from '@mui/material' -import { MapPin, Maximize2, TrendingUp, Calendar } from 'lucide-react' -import { getAssetTypeColor, getAssetTypeLabel, getAvailabilityLabel } from './propertyHelpers' +import { Box, Chip, Tooltip, Typography } from '@mui/material' +import { getAssetTypeColor, getAssetTypeLabel } from './propertyHelpers' import { DS_TEXT, DS_BORDER } from '../../lib/ds' import type { Property } from '../../domain/property' @@ -10,19 +9,24 @@ interface Props { onSelect: (id: string) => void } -function availabilityColor(status: string) { - if (status === 'AVAILABLE_NOW') return { bg: '#dcfce7', text: '#1d6342' } - if (status === 'AVAILABLE_SOON') return { bg: '#fef9c3', text: '#a16207' } - return { bg: '#f4f3f0', text: '#9a9a9a' } +const FIT_OUT_LABELS: Record = { + SHELL: 'Rohbau', BASIC: 'Grundausbau', FULL: 'Vollausbau', PREMIUM: 'Premium-Ausbau', } export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({ property: p, onSelect }: Props) { - const confPct = Math.round(p.confidenceScore * 100) const annualRent = Math.round(p.areaSqm * p.rentPricePerSqm / 1000) - const avail = availabilityColor(p.availabilityStatus) const assetColor = getAssetTypeColor(p.assetType) const hasImage = !!p.images?.[0] + // single compact facts line below price + const facts: string[] = [] + facts.push(`${p.areaSqm.toLocaleString('de-CH')} m²`) + if (p.availabilityDate) { + facts.push(`ab ${new Date(p.availabilityDate).toLocaleDateString('de-CH', { month: 'short', year: '2-digit' })}`) + } + if (p.hardFacts?.fitOut) facts.push(FIT_OUT_LABELS[p.hardFacts.fitOut] ?? p.hardFacts.fitOut) + if (p.contractDurationMonths) facts.push(`${p.contractDurationMonths}M Vertrag`) + return ( onSelect(p.id)} @@ -30,8 +34,7 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({ borderRadius: 2, overflow: 'hidden', border: `1px solid ${DS_BORDER.default}`, - bgcolor: 'white', - boxShadow: '0 1px 3px rgba(0,0,0,0.06)', + bgcolor: '#ffffff', display: 'flex', flexDirection: 'column', cursor: 'pointer', @@ -39,154 +42,70 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({ '&:hover': { borderColor: '#c8c7c4' }, }} > - {/* ── Image / header ── */} - + {/* ── Image ── */} + {hasImage ? ( ) : ( - - 🏢 + + 🏢 )} - - - {/* Top badges */} - - - - - - {/* Bottom: annual rent on image */} - - - Jahresmiete ca. - - CHF {annualRent}k + {/* Single small neutral asset-type tag */} + + + + {getAssetTypeLabel(p.assetType)} - - {p.rentPricePerSqm} /m²/J - - + - {/* ── Card body ── */} - + {/* ── Content ── */} + - {/* Title + location */} - - - {p.title} + {/* Location */} + + {p.location.city}{p.location.district ? ` · ${p.location.district}` : ''} + + + {/* Title */} + + {p.title} + + + {/* Price — hero element */} + + + CHF {annualRent}k + + + {p.rentPricePerSqm} CHF/m²/Jahr - - - - {p.location.city}{p.location.district ? ` · ${p.location.district}` : ''} - - - {/* Key metrics row */} - - - - - Fläche - - - {p.areaSqm.toLocaleString('de-CH')} m² - - - {p.availabilityDate && ( - - - - Ab - - - {new Date(p.availabilityDate).toLocaleDateString('de-CH', { month: 'short', year: '2-digit' })} - - - )} - {p.softFactors?.prestige != null && ( - - - - Prestige - - - {p.softFactors.prestige} - - - )} - - - {/* Chips: fit-out + contract */} - {p.softFactors && ( - - {p.hardFacts?.fitOut && (() => { - const FIT_OUT_META: Record = { - SHELL: { label: 'Rohbau', tip: 'Keine Einbauten — volle Ausbauinvestition durch Mieter.' }, - BASIC: { label: 'Grundausbau', tip: 'Grundinfrastruktur vorhanden, Ausbau noch nötig.' }, - FULL: { label: 'Vollausbau', tip: 'Bezugsfertig ausgebaut.' }, - PREMIUM: { label: 'Premium-Ausbau', tip: 'Hochwertig ausgebaut, sofort bezugsfertig.' }, - } - const meta = FIT_OUT_META[p.hardFacts!.fitOut!] ?? { label: p.hardFacts!.fitOut!, tip: '' } - return ( - - - - ) - })()} - {p.contractDurationMonths && ( - - - - )} - - )} - - {/* Confidence bar */} - - - Datenkonfidenz - = 0.75 ? '#b8975a' : DS_TEXT.muted }}>{confPct}% - - - + {/* Key facts — single compact line */} + + {facts.join(' · ')} + )