da50f3b5ea
- 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>
114 lines
4.5 KiB
TypeScript
114 lines
4.5 KiB
TypeScript
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>
|
||
)
|
||
}
|