refactor: premium design polish — cards, results, grid view

- PropertyIntelligenceCard: aspect-ratio image, hover animation, compact
  data-completeness row, tighter body padding
- Results: replace verbose Card with compact active-search strip, remove
  DecisionContextPanel
- Properties: add 3/5/10 column toggle in grid view, persist to localStorage
- NeedInput, UnifiedResultCard/Feed, MatchCardViewModel: cleanup from
  earlier session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-06-11 00:06:23 +02:00
parent 6e67c698ce
commit beecf754d5
7 changed files with 94 additions and 177 deletions
@@ -43,19 +43,26 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
<Box
onClick={() => 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 ── */}
<Box sx={{ position: 'relative', height: 150, flexShrink: 0, bgcolor: '#f4f3f0', overflow: 'hidden' }}>
<Box sx={{ position: 'relative', aspectRatio: '3/2', flexShrink: 0, bgcolor: '#f4f3f0', overflow: 'hidden' }}>
{hasImage ? (
<Box component="img" src={p.images![0]} alt={p.title}
sx={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
@@ -78,7 +85,7 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
</Box>
{/* ── Body ── */}
<Box sx={{ px: 1.75, pt: 1.5, pb: 1.75, display: 'flex', flexDirection: 'column', gap: 1.125, flex: 1 }}>
<Box sx={{ px: 2.25, pt: 1.75, pb: 2, display: 'flex', flexDirection: 'column', gap: 1.5, flex: 1 }}>
{/* Title + location + status inline */}
<Box>
@@ -130,36 +137,32 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
</Tooltip>
</Box>
{/* Property spec chips */}
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
{p.hardFacts?.fitOut && (() => {
const meta = FIT_OUT_META[p.hardFacts!.fitOut!] ?? { label: p.hardFacts!.fitOut!, tip: '' }
return (
<Tooltip title={meta.tip} placement="top" arrow>
<Chip label={meta.label} size="small"
sx={{ height: 20, fontSize: '0.68rem', bgcolor: '#f4f3f0', color: DS_TEXT.secondary, border: `1px solid ${DS_BORDER.default}`, cursor: 'help' }} />
</Tooltip>
)
})()}
{p.contractDurationMonths && (
<Chip label={`${p.contractDurationMonths}M Vertrag`} size="small"
sx={{ height: 20, fontSize: '0.68rem', bgcolor: '#f4f3f0', color: DS_TEXT.secondary, border: `1px solid ${DS_BORDER.default}` }} />
)}
</Box>
{/* Data completeness */}
<Box sx={{ mt: 'auto' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5 }}>
<Typography sx={{ fontSize: '0.62rem', color: DS_TEXT.disabled, letterSpacing: '0.03em' }}>
Datenvollständigkeit
</Typography>
<Typography sx={{ fontSize: '0.75rem', fontWeight: 700, color: p.confidenceScore >= 0.75 ? '#b8975a' : DS_TEXT.muted }}>
{/* Chips + Datenvollständigkeit in einer Zeile */}
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, mt: 'auto' }}>
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', alignItems: 'center' }}>
{p.hardFacts?.fitOut && (() => {
const meta = FIT_OUT_META[p.hardFacts!.fitOut!] ?? { label: p.hardFacts!.fitOut!, tip: '' }
return (
<Tooltip title={meta.tip} placement="top" arrow>
<Chip label={meta.label} size="small"
sx={{ height: 20, fontSize: '0.68rem', bgcolor: '#f4f3f0', color: DS_TEXT.secondary, border: `1px solid ${DS_BORDER.default}`, cursor: 'help' }} />
</Tooltip>
)
})()}
{p.contractDurationMonths && (
<Chip label={`${p.contractDurationMonths}M Vertrag`} size="small"
sx={{ height: 20, fontSize: '0.68rem', bgcolor: '#f4f3f0', color: DS_TEXT.secondary, border: `1px solid ${DS_BORDER.default}` }} />
)}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, flexShrink: 0 }}>
<Typography sx={{ fontSize: '0.62rem', color: DS_TEXT.disabled }}>Daten</Typography>
<LinearProgress variant="determinate" value={confPct}
sx={{ width: 40, height: 3, borderRadius: 1, bgcolor: '#f4f3f0',
'& .MuiLinearProgress-bar': { bgcolor: '#152642', borderRadius: 1 } }} />
<Typography sx={{ fontSize: '0.72rem', fontWeight: 700, color: p.confidenceScore >= 0.75 ? '#b8975a' : DS_TEXT.muted }}>
{confPct}%
</Typography>
</Box>
<LinearProgress variant="determinate" value={confPct}
sx={{ height: 3, borderRadius: 1, bgcolor: '#f4f3f0',
'& .MuiLinearProgress-bar': { bgcolor: '#152642', borderRadius: 1 } }} />
</Box>
</Box>
</Box>