Files
property-match/src/components/match-detail/MatchDetailHeader.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

114 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Alert, Box, Button, Chip, Paper, Typography } from '@mui/material'
import { ArrowLeft, Bookmark, Columns2 } from 'lucide-react'
import { MatchScoreDisplay } from '../match-card/MatchScoreDisplay'
import type { Match } from '../../domain/match'
import type { Property } from '../../domain/property'
import type { FutureSignal } from '../../domain/futureSignal'
const RESULT_TYPE_META: Record<string, { label: string; color: string }> = {
VERIFIED_PORTFOLIO: { label: 'Verified Portfolio', color: '#152642' },
MAISON_WORK: { label: 'Maison Work', color: '#0369a1' },
FUTURE_AVAILABILITY: { label: 'Zukunftssignal', color: '#7c3aed' },
}
function confColor(c: number): string {
return c >= 0.75 ? '#1a7a4a' : c >= 0.55 ? '#d97706' : '#c0392b'
}
function dqColor(q: number): string {
return q >= 0.80 ? '#1a7a4a' : q >= 0.60 ? '#d97706' : '#c0392b'
}
interface Props {
match: Match
property: Property | null
signal: FutureSignal | null
onBack: () => void
onCompare: () => void
onShortlist: () => void
}
export function MatchDetailHeader({ match, property, signal, onBack, onCompare, onShortlist }: Props) {
const rt = RESULT_TYPE_META[match.resultType ?? 'VERIFIED_PORTFOLIO'] ?? { label: '', color: '#64748b' }
const title = property?.title ?? signal?.companyName ?? signal?.locationHint ?? ''
const isFuture = match.resultType === 'FUTURE_AVAILABILITY'
const dqScore = property?.dataQuality?.score ?? signal?.confidenceScore ?? 0.5
const confPct = Math.round(match.confidenceLevel * 100)
const dqPct = Math.round(dqScore * 100)
const location = property?.location?.city
? `${property.location.city}${property.location.district ? `, ${property.location.district}` : ''}`
: signal?.locationHint ?? ''
const source = property?.sourceLabel ?? property?.sourceMeta?.sourceLabel ?? ''
const availability = property?.availabilityDate ?? (signal?.timeHorizonMonths ? `~${signal.timeHorizonMonths} Monate` : undefined)
return (
<Paper sx={{ p: 3, mb: 2 }}>
{isFuture && (
<Alert severity="warning" sx={{ mb: 2 }}>
Probabilistisches Signal keine bestätigte Fläche. Alle Angaben sind Schätzungen.
</Alert>
)}
<Button
startIcon={<ArrowLeft size={16} />}
onClick={onBack}
size="small"
sx={{ mb: 1.5, color: '#64748b' }}
>
Zurück zu Resultaten
</Button>
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 2, flexWrap: 'wrap' }}>
<Box sx={{ flex: 1, minWidth: 0 }}>
<Typography variant="h5" sx={{ fontWeight: 700, mb: 0.5 }}>{title}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>{location}</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75, alignItems: 'center' }}>
<Chip label={rt.label} size="small" sx={{ bgcolor: rt.color, color: 'white', fontWeight: 600 }} />
{property?.assetType && (
<Chip label={property.assetType} size="small" variant="outlined" />
)}
<Chip
label={`${confPct}% Konfidenz`}
size="small"
sx={{ bgcolor: confColor(match.confidenceLevel), color: 'white' }}
/>
<Chip
label={`DQ ${dqPct}%`}
size="small"
sx={{ bgcolor: dqColor(dqScore), color: 'white' }}
/>
{availability && <Chip label={availability} size="small" variant="outlined" />}
{source !== '' && (
<Typography variant="caption" color="text.secondary">Quelle: {source}</Typography>
)}
</Box>
</Box>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 1.5 }}>
<MatchScoreDisplay score={match.matchScore} size="lg" />
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
variant="outlined"
size="small"
startIcon={<Bookmark size={14} />}
onClick={onShortlist}
>
Shortlist
</Button>
<Button
variant="contained"
size="small"
startIcon={<Columns2 size={14} />}
onClick={onCompare}
sx={{ bgcolor: '#152642', '&:hover': { bgcolor: '#162d4a' } }}
>
Vergleichen
</Button>
</Box>
</Box>
</Box>
</Paper>
)
}