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,80 @@
|
||||
import { Box, Button, Paper, Typography } from '@mui/material'
|
||||
import type { Match, NextBestAction } from '../../domain/match'
|
||||
|
||||
const PRIORITY_ORDER: Record<string, number> = { HIGH: 0, MEDIUM: 1, LOW: 2 }
|
||||
|
||||
const PRIORITY_COLOR: Record<string, 'contained' | 'outlined'> = {
|
||||
HIGH: 'contained',
|
||||
MEDIUM: 'outlined',
|
||||
LOW: 'outlined',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
match: Match
|
||||
onCompare?: () => void
|
||||
onShortlist?: () => void
|
||||
onReject?: () => void
|
||||
onReview?: () => void
|
||||
}
|
||||
|
||||
export function NextActionsPanel({ match, onCompare, onShortlist, onReject, onReview }: Props) {
|
||||
const engineActions: NextBestAction[] = [...(match.nextBestActions ?? [])].sort(
|
||||
(a, b) => (PRIORITY_ORDER[a.priority] ?? 2) - (PRIORITY_ORDER[b.priority] ?? 2)
|
||||
)
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 2.5, mb: 2 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1.5 }}>Empfohlene Aktionen</Typography>
|
||||
|
||||
{engineActions.length > 0 && (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.75, mb: 1.5 }}>
|
||||
{engineActions.map((action, i) => (
|
||||
<Box key={i}>
|
||||
<Button
|
||||
fullWidth
|
||||
size="small"
|
||||
variant={PRIORITY_COLOR[action.priority]}
|
||||
sx={
|
||||
action.priority === 'HIGH'
|
||||
? { bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' }, justifyContent: 'flex-start' }
|
||||
: { justifyContent: 'flex-start' }
|
||||
}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
{action.description && (
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.25, px: 1 }}>
|
||||
{action.description}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Standard actions */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.75 }}>
|
||||
{onShortlist && (
|
||||
<Button fullWidth size="small" variant="outlined" onClick={onShortlist} sx={{ justifyContent: 'flex-start' }}>
|
||||
Zur Shortlist hinzufügen
|
||||
</Button>
|
||||
)}
|
||||
{onCompare && (
|
||||
<Button fullWidth size="small" variant="outlined" onClick={onCompare} sx={{ justifyContent: 'flex-start' }}>
|
||||
Zum Vergleich hinzufügen
|
||||
</Button>
|
||||
)}
|
||||
{onReview && (
|
||||
<Button fullWidth size="small" variant="outlined" onClick={onReview} sx={{ justifyContent: 'flex-start', color: '#d97706', borderColor: '#d97706' }}>
|
||||
Zur Überprüfung senden
|
||||
</Button>
|
||||
)}
|
||||
{onReject && (
|
||||
<Button fullWidth size="small" variant="outlined" onClick={onReject} sx={{ justifyContent: 'flex-start', color: '#c0392b', borderColor: '#c0392b' }}>
|
||||
Match ablehnen
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user