feat: F012 match detail view
Full decision analysis page at /demand/results/:matchId: - MatchDetailHeader: score, badges, CTA (compare/shortlist), back button - ExecutiveSummaryPanel: 4-row summary (fit, top reason, tradeoff, next step) - PropertyOverviewPanel: key facts grid; signal-aware for FUTURE_AVAILABILITY - NeedAlignmentPanel: comparison table with fit indicators (MATCH/PARTIAL/NO_MATCH) - ScoreBreakdownPanel: hard/soft scores + modifiers + total (sidebar) - TradeoffPanel: severity-coded list with mitigation hints - RiskPanel: risks sorted CRITICAL→LOW with category chips - MissingInformationPanel: priority-grouped with per-item CTAs - SourceProvenancePanel: source type, label, URL, freshness - FutureAvailabilityContextPanel: mandatory disclaimer + signal metadata - NextActionsPanel: engine-ranked actions + standard actions (sidebar) - Details button on result cards now navigates to match detail Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { Box, Paper, Typography } from '@mui/material'
|
||||
import { CheckCircle2, AlertTriangle, ArrowRight, Target } from 'lucide-react'
|
||||
import type { Match } from '../../domain/match'
|
||||
|
||||
interface Props {
|
||||
match: Match
|
||||
}
|
||||
|
||||
export function ExecutiveSummaryPanel({ match }: Props) {
|
||||
const topReason = match.positiveFactors[0]
|
||||
const topTradeoff = match.tradeoffs?.[0]
|
||||
const nextStep = match.nextBestActions?.[0]
|
||||
|
||||
const rows: { icon: React.ReactNode; label: string; text: string }[] = [
|
||||
{
|
||||
icon: <Target size={15} color="#1e3a5f" />,
|
||||
label: 'Gesamteignung',
|
||||
text: match.explainabilitySummary || `${match.matchStrength}-Match mit ${match.matchScore} Punkten`,
|
||||
},
|
||||
...(topReason ? [{
|
||||
icon: <CheckCircle2 size={15} color="#1a7a4a" />,
|
||||
label: 'Stärkster Grund',
|
||||
text: topReason.explanation,
|
||||
}] : []),
|
||||
...(topTradeoff ? [{
|
||||
icon: <AlertTriangle size={15} color="#d97706" />,
|
||||
label: 'Hauptabwägung',
|
||||
text: topTradeoff.concern,
|
||||
}] : []),
|
||||
...(nextStep ? [{
|
||||
icon: <ArrowRight size={15} color="#7c3aed" />,
|
||||
label: 'Empfohlener nächster Schritt',
|
||||
text: nextStep.description ?? nextStep.label,
|
||||
}] : []),
|
||||
]
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 2.5, mb: 2, bgcolor: '#f8fafc', border: '1px solid #e2e8f0' }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1.5 }}>Executive Summary</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.25 }}>
|
||||
{rows.map((row, i) => (
|
||||
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 1 }}>
|
||||
<Box sx={{ mt: 0.25, flexShrink: 0 }}>{row.icon}</Box>
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#64748b', display: 'block' }}>
|
||||
{row.label}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
{row.text}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user