Files
property-match/src/components/compare/CompareCriteriaCard.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

101 lines
5.1 KiB
TypeScript

import { Box, Card, Chip, Table, TableBody, TableCell, TableHead, TableRow, Typography } from '@mui/material'
import { Trophy, CheckCircle2 } from 'lucide-react'
import { LABEL_SX, DATA_SX, CRITERION_ALIASES, getTitle, scoreBar } from './compareUtils'
import type { WeightingKey } from '../../domain/needBuilder'
import { MissingDataCell } from './CompareCell'
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
interface Props {
compareItems: UnifiedMatchResult[]
relevantCriteria: Array<{ key: string; label: string; weight: number }>
overallWinnerIdx: number
activeNeed: { companyName: string }
}
export function CompareCriteriaCard({ compareItems, relevantCriteria, overallWinnerIdx, activeNeed }: Props) {
return (
<Card sx={{ mb: 3, overflow: 'hidden' }}>
<Box sx={{ p: 2.5, borderBottom: '1px solid #e2e8f0', bgcolor: '#f8fafc', display: 'flex', alignItems: 'center', gap: 2 }}>
<Trophy size={18} color="#d4920e" />
<Box>
<Typography variant="h6" sx={{ fontWeight: 700, lineHeight: 1.2 }}>Vergleich nach Suchkriterien</Typography>
<Typography variant="caption" color="text.secondary">Basierend auf der Suche: {activeNeed.companyName}</Typography>
</Box>
{overallWinnerIdx !== -1 && (
<Box sx={{ ml: 'auto', bgcolor: '#fffbeb', border: '1px solid #fbbf24', borderRadius: 2, px: 2, py: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
<Trophy size={16} color="#d4920e" />
<Box>
<Typography variant="caption" sx={{ color: '#92400e', fontWeight: 700, display: 'block', lineHeight: 1 }}>Gesamtsieger</Typography>
<Typography variant="body2" sx={{ fontWeight: 800, color: '#7a4f00' }}>{getTitle(compareItems[overallWinnerIdx])}</Typography>
</Box>
</Box>
)}
</Box>
<Box sx={{ overflowX: 'auto' }}>
<Table sx={{ tableLayout: 'auto' }}>
<TableHead>
<TableRow sx={{ bgcolor: '#f8fafc' }}>
<TableCell sx={{ ...LABEL_SX, bgcolor: '#f8fafc', fontSize: 11 }}>Kriterium</TableCell>
{compareItems.map(item => (
<TableCell key={item.matchId} sx={{ ...DATA_SX, bgcolor: '#f8fafc' }}>
<Typography variant="caption" sx={{ fontWeight: 700, color: '#152642' }} noWrap>
{getTitle(item)}
</Typography>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{relevantCriteria.map(({ key, label }) => {
const allCriteria = (item: typeof compareItems[0]) =>
item.match.allFactors ?? [...item.match.positiveFactors, ...item.match.negativeFactors]
const factors = compareItems.map(item =>
allCriteria(item).find(f => CRITERION_ALIASES[key as WeightingKey].some(a => a.toLowerCase() === f.criterion.toLowerCase()))
)
const numericScores = factors.map(f => f?.score ?? null)
const presentScores = numericScores.filter((s): s is number => s !== null)
const maxScore = presentScores.length > 0 ? Math.max(...presentScores) : 0
const winnerIdx = compareItems.length > 1 && presentScores.length > 1 && numericScores.filter(s => s === maxScore).length === 1
? numericScores.indexOf(maxScore)
: -1
return (
<TableRow key={key} hover>
<TableCell sx={LABEL_SX}>
<Typography variant="body2" sx={{ fontWeight: 600 }}>{label}</Typography>
</TableCell>
{factors.map((factor, idx) => (
<TableCell key={idx} sx={{ ...DATA_SX, bgcolor: idx === winnerIdx ? 'rgba(26,122,74,0.05)' : undefined }}>
{factor ? (
<Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
{scoreBar(factor.score / 100)}
{idx === winnerIdx && (
<Chip label="Besser" size="small"
icon={<CheckCircle2 size={10} />}
sx={{ height: 18, fontSize: 9, bgcolor: '#f0fdf4', color: '#166534',
'& .MuiChip-icon': { color: '#1a7a4a', ml: 0.5 },
'& .MuiChip-label': { px: 0.75 } }} />
)}
</Box>
<Typography variant="caption" color="text.secondary"
sx={{ display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden', lineHeight: 1.4 }}>
{factor.explanation}
</Typography>
</Box>
) : (
<MissingDataCell reason="Kein Score für dieses Kriterium" />
)}
</TableCell>
))}
</TableRow>
)
})}
</TableBody>
</Table>
</Box>
</Card>
)
}