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,97 @@
|
||||
import { Alert, Box, Chip, Paper, Typography } from '@mui/material'
|
||||
import type { Match } from '../../domain/match'
|
||||
import type { FutureSignal } from '../../domain/futureSignal'
|
||||
|
||||
const SIGNAL_TYPE_LABELS: Record<string, string> = {
|
||||
EXPANSION: 'Expansion',
|
||||
POSSIBLE_MOVE_OUT: 'Möglicher Auszug',
|
||||
CONSTRUCTION_PROJECT: 'Bauvorhaben',
|
||||
RESTRUCTURING: 'Restrukturierung',
|
||||
PROJECT_DEVELOPMENT: 'Projektentwicklung',
|
||||
SPACE_CONSOLIDATION: 'Flächenkonsolidierung',
|
||||
}
|
||||
|
||||
const SENSITIVITY_META: Record<string, { label: string; color: 'error' | 'warning' | 'default' }> = {
|
||||
CONFIDENTIAL: { label: 'Vertraulich', color: 'error' },
|
||||
INTERNAL: { label: 'Intern', color: 'warning' },
|
||||
PUBLIC: { label: 'Öffentlich', color: 'default' },
|
||||
}
|
||||
|
||||
const REVIEW_STATUS_META: Record<string, { label: string; color: 'warning' | 'success' | 'default' | 'error' }> = {
|
||||
UNREVIEWED: { label: 'Ungeprüft', color: 'warning' },
|
||||
IN_REVIEW: { label: 'In Prüfung', color: 'warning' },
|
||||
APPROVED: { label: 'Genehmigt', color: 'success' },
|
||||
REJECTED: { label: 'Abgelehnt', color: 'error' },
|
||||
FLAGGED: { label: 'Markiert', color: 'warning' },
|
||||
}
|
||||
|
||||
interface Props {
|
||||
match: Match
|
||||
signal: FutureSignal | null
|
||||
}
|
||||
|
||||
export function FutureAvailabilityContextPanel({ match: _match, signal }: Props) {
|
||||
if (!signal) return null
|
||||
|
||||
const sensitiveMeta = SENSITIVITY_META[signal.sensitivityLevel] ?? SENSITIVITY_META.PUBLIC
|
||||
const reviewMeta = signal.reviewStatus ? (REVIEW_STATUS_META[signal.reviewStatus] ?? null) : null
|
||||
|
||||
return (
|
||||
<Paper sx={{ p: 2.5, mb: 2 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1.5 }}>Zukunftssignal – Kontext</Typography>
|
||||
|
||||
{/* Mandatory disclaimer */}
|
||||
<Alert severity="warning" sx={{ mb: 2 }}>
|
||||
{signal.disclaimer}
|
||||
</Alert>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{signal.signalType && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 160 }}>Signaltyp</Typography>
|
||||
<Typography variant="body2">{SIGNAL_TYPE_LABELS[signal.signalType] ?? signal.signalType}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 160 }}>Konfidenz</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
{Math.round(signal.confidenceScore * 100)}%
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 160 }}>Wahrscheinlichkeit</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
{Math.round(signal.probability * 100)}%
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 160 }}>Zeithorizont</Typography>
|
||||
<Typography variant="body2">~{signal.timeHorizonMonths} Monate</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 160 }}>Vertraulichkeit</Typography>
|
||||
<Chip label={sensitiveMeta.label} size="small" color={sensitiveMeta.color} variant="outlined" />
|
||||
</Box>
|
||||
{reviewMeta && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 160 }}>Prüfstatus</Typography>
|
||||
<Chip label={reviewMeta.label} size="small" color={reviewMeta.color} />
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ minWidth: 160 }}>Quellglaubwürdigkeit</Typography>
|
||||
<Typography variant="body2">{signal.source.credibility}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{signal.evidence?.summary && (
|
||||
<Box sx={{ mt: 1.5, p: 1.5, bgcolor: '#f8fafc', borderRadius: 1 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, display: 'block', mb: 0.5 }}>
|
||||
Evidenz
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">{signal.evidence.summary}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user