diff --git a/src/components/demand/NeedInput.tsx b/src/components/demand/NeedInput.tsx
index de28635..840fdad 100644
--- a/src/components/demand/NeedInput.tsx
+++ b/src/components/demand/NeedInput.tsx
@@ -1,5 +1,5 @@
import { useState } from 'react'
-import { Box, Card, Chip, FormControlLabel, Slider, Stack, Switch, TextField, Typography } from '@mui/material'
+import { Box, Card, Chip, Slider, Stack, TextField, Typography } from '@mui/material'
import type { ParsedNeedCriteria } from '../../domain/needBuilder'
import { AssetType } from '../../domain/enums'
import { NeedExtendedRequirements } from './NeedExtendedRequirements'
@@ -242,31 +242,7 @@ export function NeedInput({ criteria: c, onCriteriaChange: set }: Props) {
)}
- {/* Anonymous search */}
-
-
-
- Anonyme Suche
-
-
- Firmenname wird nicht an Vermieter übermittelt
-
-
- set({ ...c, isAnonymous: e.target.checked })}
- sx={{ '& .MuiSwitch-thumb': { bgcolor: c.isAnonymous ? '#7c3aed' : undefined }, '& .Mui-checked + .MuiSwitch-track': { bgcolor: '#7c3aed' } }}
- />
- }
- label=""
- sx={{ m: 0 }}
- />
-
-
-
+
)
}
diff --git a/src/components/match-card/MatchCardViewModel.ts b/src/components/match-card/MatchCardViewModel.ts
index 40fa3a4..c01f434 100644
--- a/src/components/match-card/MatchCardViewModel.ts
+++ b/src/components/match-card/MatchCardViewModel.ts
@@ -86,6 +86,10 @@ export interface MatchCardViewModel {
preMarketUnit?: PropertyUnit // specific unit being released pre-market
preMarketAllUnits?: PropertyUnit[] // all units of the backing property
+ // Key metrics for compact display (Ginesta-style metric line)
+ areaSqm?: number
+ rentPerSqm?: number
+
// Score transparency
scoreBreakdown?: {
hardMatchScore: number
diff --git a/src/components/results/UnifiedResultCard.tsx b/src/components/results/UnifiedResultCard.tsx
index 84e634a..8ae3934 100644
--- a/src/components/results/UnifiedResultCard.tsx
+++ b/src/components/results/UnifiedResultCard.tsx
@@ -125,16 +125,6 @@ export function UnifiedResultCard({ result, view = 'list' }: Props) {
const listImageUrl = result.resultType !== 'FUTURE_AVAILABILITY'
? result.property.images?.[0]
: undefined
- const listOverlay = result.resultType !== 'FUTURE_AVAILABILITY'
- ? resolveImageLabel({
- assetType: result.property.assetType,
- city: result.property.location.city,
- district: result.property.location.district,
- prestige: result.property.softFactors?.prestige,
- floorLevel: result.property.floorLevel,
- propertyId: result.property.id,
- })
- : null
- return
+ return
}
diff --git a/src/components/results/UnifiedResultFeed.tsx b/src/components/results/UnifiedResultFeed.tsx
index dbb1689..7be7de6 100644
--- a/src/components/results/UnifiedResultFeed.tsx
+++ b/src/components/results/UnifiedResultFeed.tsx
@@ -86,7 +86,7 @@ export function UnifiedResultFeed({ results, view = 'list' }: Props) {
))}
) : (
-
+
{items.map(result => (
))}
diff --git a/src/components/supply/PropertyIntelligenceCard.tsx b/src/components/supply/PropertyIntelligenceCard.tsx
index ca14334..489923d 100644
--- a/src/components/supply/PropertyIntelligenceCard.tsx
+++ b/src/components/supply/PropertyIntelligenceCard.tsx
@@ -43,19 +43,26 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
onSelect(p.id)}
sx={{
- borderRadius: 1,
+ borderRadius: 2,
overflow: 'hidden',
border: `1px solid ${DS_BORDER.default}`,
bgcolor: '#ffffff',
display: 'flex',
flexDirection: 'column',
cursor: 'pointer',
- transition: 'border-color 0.15s',
- '&:hover': { borderColor: '#c8c7c4' },
+ position: 'relative',
+ zIndex: 0,
+ transition: 'transform 0.18s ease, box-shadow 0.18s ease, border-color 0.15s',
+ '&:hover': {
+ borderColor: '#b0aead',
+ transform: 'scale(1.025)',
+ boxShadow: '0 10px 32px rgba(0,0,0,0.13)',
+ zIndex: 1,
+ },
}}
>
{/* ── Image — clean, no text overlay ── */}
-
+
{hasImage ? (
@@ -78,7 +85,7 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
{/* ── Body ── */}
-
+
{/* Title + location + status inline */}
@@ -130,36 +137,32 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
- {/* Property spec chips */}
-
- {p.hardFacts?.fitOut && (() => {
- const meta = FIT_OUT_META[p.hardFacts!.fitOut!] ?? { label: p.hardFacts!.fitOut!, tip: '' }
- return (
-
-
-
- )
- })()}
- {p.contractDurationMonths && (
-
- )}
-
-
- {/* Data completeness */}
-
-
-
- Datenvollständigkeit
-
- = 0.75 ? '#b8975a' : DS_TEXT.muted }}>
+ {/* Chips + Datenvollständigkeit in einer Zeile */}
+
+
+ {p.hardFacts?.fitOut && (() => {
+ const meta = FIT_OUT_META[p.hardFacts!.fitOut!] ?? { label: p.hardFacts!.fitOut!, tip: '' }
+ return (
+
+
+
+ )
+ })()}
+ {p.contractDurationMonths && (
+
+ )}
+
+
+ Daten
+
+ = 0.75 ? '#b8975a' : DS_TEXT.muted }}>
{confPct}%
-
diff --git a/src/pages/demand/Results.tsx b/src/pages/demand/Results.tsx
index 15c2617..6ff1eca 100644
--- a/src/pages/demand/Results.tsx
+++ b/src/pages/demand/Results.tsx
@@ -1,11 +1,10 @@
import { useState, useMemo, useEffect } from 'react'
-import { Box, Button, Card, Chip, Typography } from '@mui/material'
+import { Box, Button, Typography } from '@mui/material'
import { Zap } from 'lucide-react'
import { useNavigate, useLocation } from 'react-router'
import { useQueryClient } from '@tanstack/react-query'
import { useUnifiedResults } from '../../hooks/useUnifiedResults'
import { useNeeds } from '../../hooks/useNeeds'
-import { DecisionContextPanel } from '../../components/ui'
import {
FeedEmptyState,
FeedSkeleton,
@@ -72,16 +71,14 @@ export default function Results() {
const isStaff = currentUser?.role === 'PROPERTY_MANAGER' || currentUser?.role === 'ORGANIZATION_ADMIN'
// Counts over the full result set — single pass, stable across filter changes
- const { platformCount, maisonWorkCount, futureCount, missingDataCount } = useMemo(() => {
- let platform = 0, maison = 0, future = 0, missing = 0
+ const { platformCount, maisonWorkCount, futureCount } = useMemo(() => {
+ let platform = 0, maison = 0, future = 0
for (const r of results) {
if (r.resultType === 'VERIFIED_PORTFOLIO') platform++
else if (r.resultType === 'MAISON_WORK') maison++
else if (r.resultType === 'FUTURE_AVAILABILITY') future++
- if ('match' in r && Array.isArray((r as { match?: { missingData?: unknown[] } }).match?.missingData) &&
- ((r as { match?: { missingData?: unknown[] } }).match?.missingData?.length ?? 0) > 0) missing++
}
- return { platformCount: platform, maisonWorkCount: maison, futureCount: future, missingDataCount: missing }
+ return { platformCount: platform, maisonWorkCount: maison, futureCount: future }
}, [results])
// Filter + sort in one memo — only reruns when inputs change
@@ -98,7 +95,6 @@ export default function Results() {
return sortResults(filtered, sortBy)
}, [results, filterSource, sortBy, showFutureAvailability, showOwnProperties])
- const strongCount = useMemo(() => sorted.filter(r => r.matchScore >= 80).length, [sorted])
return (
@@ -113,103 +109,27 @@ export default function Results() {
onViewChange={v => { setView(v); localStorage.setItem('view-results', v) }}
/>
- {!isLoading && results.length > 0 && (
- 0 ? [{ label: 'starke Treffer (≥80)', value: strongCount, severity: 'positive' as const }] : []),
- ...(maisonWorkCount > 0 ? [{ label: 'Maison Work', value: maisonWorkCount, severity: 'neutral' as const }] : []),
- ...(futureCount > 0 ? [{ label: 'Zukunftssignale', value: futureCount, severity: 'neutral' as const }] : []),
- ...(missingDataCount > 0 ? [{ label: 'mit Datenlücken', value: missingDataCount, severity: 'warning' as const }] : []),
- ]}
- risks={[
- ...(missingDataCount > 0 ? [`${missingDataCount} Treffer mit fehlenden Daten — Einschätzung eingeschränkt`] : []),
- ]}
- actions={[
- { label: 'Vergleich öffnen', onClick: () => navigate('/demand/compare') },
- { label: 'Suche anpassen', onClick: () => navigate('/demand/ai-search') },
- ]}
- />
- )}
-
+
+ {/* Aktive Suche */}
{activeNeed && (
-
-
-
-
- Aktive Suche: {activeNeed.companyName}
-
-
-
- Typ: {activeNeed.assetType}
-
-
- Fläche: {activeNeed.requiredArea.min}–{activeNeed.requiredArea.max} m²
-
-
- Standort: {activeNeed.preferredLocations.join(', ')}
-
- {activeNeed.budgetRange && activeNeed.budgetRange.maxPerSqm > 0 && (
-
- Budget: max. CHF {activeNeed.budgetRange.maxPerSqm}/m²/Jahr
-
- )}
- {activeNeed.timing?.earliestMoveIn && !isNaN(new Date(activeNeed.timing.earliestMoveIn).getTime()) && (
-
- Bezug ab: {new Date(activeNeed.timing.earliestMoveIn).toLocaleDateString('de-CH', { month: 'long', year: 'numeric' })}
-
- )}
- {activeNeed.searchRadius && (
-
- Radius: {activeNeed.searchRadius} km
-
- )}
- {activeNeed.requiresDivisibility && activeNeed.minDivisibleUnit && (
-
- Teilbar ab: {activeNeed.minDivisibleUnit} m²
-
- )}
- {activeNeed.requiredFitOut && (
-
- Ausbaustandard: min. {
- activeNeed.requiredFitOut === 'BASIC' ? 'Basisausbau' :
- activeNeed.requiredFitOut === 'FULL' ? 'Vollausbau' : 'Premiumausbau'
- }
-
- )}
- {activeNeed.fitOutBudgetMaxPerSqm && (
-
- Ausbaubudget: max. CHF {activeNeed.fitOutBudgetMaxPerSqm}/m²
-
- )}
- {activeNeed.mustCriteriaText && activeNeed.mustCriteriaText.length > 0 && (
-
- Must-haves: {activeNeed.mustCriteriaText.slice(0, 3).join(' · ')}
-
- )}
-
-
-
- {activeNeed.isAnonymous && (
-
- )}
-
-
+
+
+
+ {activeNeed.companyName}
+
+ {activeNeed.assetType}
+ {activeNeed.requiredArea.min}–{activeNeed.requiredArea.max} m²
+ {activeNeed.preferredLocations.join(', ')}
+ {activeNeed.budgetRange?.maxPerSqm > 0 && (
+ max. CHF {activeNeed.budgetRange.maxPerSqm}/m²/J
+ )}
-
+
+
)}
(() =>
(localStorage.getItem('view-properties') as 'list' | 'grid') ?? 'list'
)
+ const [columns, setColumns] = useState<3 | 5 | 10>(() =>
+ (Number(localStorage.getItem('view-properties-cols')) as 3 | 5 | 10) || 3
+ )
const [headerOpen, setHeaderOpen] = useState(() =>
localStorage.getItem('props-header-open') !== 'false'
)
@@ -109,10 +112,31 @@ export default function Properties() {
title="Objektverwaltung"
subtitle={`${filtered.length} von ${properties.length} Objekten`}
secondaryActions={
- { setView(v); localStorage.setItem('view-properties', v) }}
- />
+
+ {view === 'grid' && (
+
+ {([3, 5, 10] as const).map(n => (
+ { setColumns(n); localStorage.setItem('view-properties-cols', String(n)) }}
+ sx={{
+ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center',
+ cursor: 'pointer', fontSize: '0.72rem', fontWeight: 600,
+ bgcolor: columns === n ? '#152642' : 'transparent',
+ color: columns === n ? 'white' : '#64748b',
+ '&:hover': { bgcolor: columns === n ? '#162d4a' : '#f1f5f9' },
+ }}
+ >
+ {n}
+
+ ))}
+
+ )}
+ { setView(v); localStorage.setItem('view-properties', v) }}
+ />
+
}
/>
@@ -201,7 +225,7 @@ export default function Properties() {
{view === 'grid' ? (
-
+
{filtered.map(p => (
))}