Files
property-match/src/components/match-center/MatchListCard.tsx
T
Benjamin Sutter da50f3b5ea feat: premium redesign — DM Serif, refined palette, flat score badges
- Design tokens (ds.ts, theme.ts, scoreTheme.ts): new warm palette
  (#152642 navy, #f9f8f6 warm white, #e8e7e4 borders, #b8975a gold accent),
  flat score tier badges replacing CSS gradients, Inter + DM Serif Display typography
- Card components: white-background cards, DM Serif score numbers, max-2 badge
  chips with +N overflow tooltip, editorial score badge positioning
- Layout shell: gold left-accent nav active state, 64px top bar, outlined
  workspace chip, DM Serif page titles
- Shared atoms: GenericBadge (outlined/solid variants), ResultFilterBar
  (simplified chip styles), DecisionContextPanel (dot metrics, no left accent)
- Global replacement (118 files): #1e3a5f→#152642, #e2e8f0→#e8e7e4,
  #f4f6f9→#f9f8f6 — all handled via Node.js for proper UTF-8 safety

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 22:26:30 +02:00

130 lines
4.2 KiB
TypeScript

import { Box, Button, Chip, Typography } from '@mui/material'
import { MatchStatusBadge } from './MatchStatusBadge'
import type { Match } from '../../domain/match'
import type { Property } from '../../domain/property'
import type { Need } from '../../domain/need'
import { matchScoreHex } from '../../lib/utils'
const STRENGTH_META: Record<string, { label: string; bg: string; color: string }> = {
STRONG: { label: 'Stark', bg: '#f0fdf4', color: '#1a7a4a' },
MODERATE: { label: 'Mittel', bg: '#fefce8', color: '#d97706' },
WEAK: { label: 'Schwach', bg: '#fff1f2', color: '#c0392b' },
}
interface Props {
match: Match
property: Property | undefined
need: Need | undefined
onSelect: () => void
onApprove: () => void
}
export function MatchListCard({ match, property, need, onSelect, onApprove }: Props) {
const strength = STRENGTH_META[match.matchStrength] ?? { label: match.matchStrength, bg: '#f1f5f9', color: '#64748b' }
const summary = match.explainabilitySummary ?? ''
return (
<Box
onClick={onSelect}
sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
px: 3,
py: 2,
borderBottom: '1px solid #e2e8f0',
bgcolor: 'white',
cursor: 'pointer',
'&:hover': { bgcolor: '#f8fafc' },
}}
>
{/* Score bubble */}
<Box
sx={{
width: 52,
height: 52,
borderRadius: 2,
bgcolor: matchScoreHex(match.matchScore),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
}}
>
<Typography variant="h6" sx={{ color: 'white', fontWeight: 700, lineHeight: 1 }}>
{match.matchScore}
</Typography>
</Box>
{/* Property + need */}
<Box sx={{ flex: 1, minWidth: 0 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.25, flexWrap: 'wrap' }}>
<Typography variant="body2" sx={{ fontWeight: 600, whiteSpace: 'nowrap' }}>
{property?.title ?? match.propertyId}
</Typography>
<MatchStatusBadge status={match.status} />
</Box>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
{[property?.location.city, property?.areaSqm ? `${property.areaSqm}` : null].filter(Boolean).join(' · ')}
</Typography>
<Box sx={{ display: 'flex', gap: 0.75, mt: 0.5, flexWrap: 'wrap' }}>
{need && (
<Chip
label={need.isAnonymous ? 'Anonyme Anfrage' : need.companyName}
size="small"
sx={need.isAnonymous
? { bgcolor: '#f5f3ff', color: '#6d28d9', fontSize: 11, height: 20, fontWeight: 600, border: '1px solid #ddd6fe' }
: { bgcolor: '#eff6ff', color: '#152642', fontSize: 11, height: 20, fontWeight: 500 }
}
/>
)}
<Chip
label={strength.label}
size="small"
sx={{ bgcolor: strength.bg, color: strength.color, fontSize: 11, height: 20 }}
/>
</Box>
</Box>
{/* Summary excerpt */}
{summary && (
<Typography
variant="caption"
color="text.secondary"
sx={{
maxWidth: 220,
display: { xs: 'none', lg: 'block' },
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{summary.length > 90 ? summary.slice(0, 90) + '…' : summary}
</Typography>
)}
{/* Actions */}
<Box sx={{ display: 'flex', gap: 0.75, flexShrink: 0 }} onClick={e => e.stopPropagation()}>
{match.status !== 'APPROVED' && (
<Button
size="small"
variant="contained"
onClick={onApprove}
sx={{ textTransform: 'none', bgcolor: '#1a7a4a', '&:hover': { bgcolor: '#15643c' } }}
>
Genehmigen
</Button>
)}
<Button
size="small"
variant="outlined"
onClick={onSelect}
sx={{ textTransform: 'none' }}
>
Details
</Button>
</Box>
</Box>
)
}