fix: UX polish — score formula, role badges, compare view, page titles

- Score: remove hidden dataQuality/confidence modifiers from finalScore;
  formula now correctly shows hard×60% + soft×40% = displayed total
- "Ihr Objekt" badge: only shown for PROPERTY_MANAGER/ORGANIZATION_ADMIN
  in both IntelligenceMatchCard and MatchCardHeader (DEMAND_USER sees
  Verified Portfolio as a normal listing without ownership indicator)
- Compare: fix factor lookup to use allFactors (includes mid-range 45–70
  scores) so Prestige, Erreichbarkeit etc. no longer show "Nicht verfügbar"
- Compare: richer AI summary with overallAssessment, perPropertyAssessment
  (strengths/weaknesses/bestFor/keyRisk per property), and recommendation
- Compare/Results: pb:10 so CompareTray never overlaps last row of content
- AppShell: dynamic route names for /demand/results/:id → "Match Detail"
  and /demand/property/:id → "Objekt Detail" (no more UUID in header)
- MatchDetail: remove "Match EF9" debug chip from sticky nav
- LocationIntelligencePanel: comparable listings are now clickable links
  navigating to /demand/property/:id
- Comparable links: blue text + hover highlight

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-22 21:05:36 +02:00
parent 9f7062d137
commit 76f9927c7c
11 changed files with 180 additions and 43 deletions
+70 -4
View File
@@ -1,6 +1,6 @@
import { useState } from 'react'
import { Alert, Box, Chip, CircularProgress, Collapse, Typography } from '@mui/material'
import { ChevronDown, ChevronUp, Trophy, TrendingDown, ShieldCheck, AlertTriangle, Info, ArrowRight } from 'lucide-react'
import { ChevronDown, ChevronUp, Trophy, TrendingDown, ShieldCheck, AlertTriangle, Info, ArrowRight, CheckCircle2, XCircle, Star } from 'lucide-react'
import type { ComparisonSummary } from '../../services/aiService'
interface Props {
@@ -44,6 +44,11 @@ export function AICompareSummary({ summary, isLoading }: Props) {
{!isLoading && summary && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{/* Overall assessment */}
<Box sx={{ bgcolor: '#f0f9ff', border: '1px solid #bae6fd', borderRadius: 1, p: 1.5 }}>
<Typography variant="body2" sx={{ color: '#0c4a6e', lineHeight: 1.5 }}>{summary.overallAssessment}</Typography>
</Box>
{/* Strongest option */}
<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-start' }}>
<Trophy size={16} color="#1a7a4a" style={{ flexShrink: 0, marginTop: 2 }} />
@@ -106,12 +111,73 @@ export function AICompareSummary({ summary, isLoading }: Props) {
</Box>
)}
{/* Next step */}
{/* Per-property assessment */}
{summary.perPropertyAssessment.length > 0 && (
<Box>
<Typography variant="caption" sx={{ fontWeight: 600, color: '#64748b', display: 'block', mb: 0.75 }}>Objektbewertung</Typography>
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
{summary.perPropertyAssessment.map(prop => (
<Box
key={prop.matchId}
sx={{
flex: '1 1 200px',
border: '1px solid #e2e8f0',
borderRadius: 1,
p: 1.25,
display: 'flex',
flexDirection: 'column',
gap: 0.75,
}}
>
<Typography variant="body2" sx={{ fontWeight: 700, color: '#1e3a5f' }}>{prop.label}</Typography>
{/* Strengths */}
<Box>
{prop.strengths.map((s, i) => (
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.5, mb: 0.25 }}>
<CheckCircle2 size={12} color="#1a7a4a" style={{ flexShrink: 0, marginTop: 2 }} />
<Typography variant="caption" color="text.secondary" sx={{ lineHeight: 1.4 }}>{s}</Typography>
</Box>
))}
</Box>
{/* Weaknesses */}
<Box>
{prop.weaknesses.map((w, i) => (
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.5, mb: 0.25 }}>
<XCircle size={12} color="#dc2626" style={{ flexShrink: 0, marginTop: 2 }} />
<Typography variant="caption" color="text.secondary" sx={{ lineHeight: 1.4 }}>{w}</Typography>
</Box>
))}
</Box>
{/* Best for */}
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.5 }}>
<Star size={12} color="#d97706" style={{ flexShrink: 0, marginTop: 2 }} />
<Typography variant="caption" sx={{ color: '#92400e', lineHeight: 1.4 }}>
<strong>Am besten für:</strong> {prop.bestFor}
</Typography>
</Box>
{/* Key risk */}
{prop.keyRisk && (
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.5 }}>
<AlertTriangle size={12} color="#d97706" style={{ flexShrink: 0, marginTop: 2 }} />
<Typography variant="caption" color="text.secondary" sx={{ lineHeight: 1.4 }}>{prop.keyRisk}</Typography>
</Box>
)}
</Box>
))}
</Box>
</Box>
)}
{/* Recommendation */}
<Box sx={{ display: 'flex', gap: 1, alignItems: 'flex-start', pt: 0.5, borderTop: '1px solid #f1f5f9', mt: 0.5 }}>
<ArrowRight size={16} color="#1e3a5f" style={{ flexShrink: 0, marginTop: 2 }} />
<Box>
<Typography variant="caption" sx={{ fontWeight: 600, color: '#64748b', display: 'block' }}>Empfohlener nächster Schritt</Typography>
<Typography variant="body2">{summary.recommendedNextStep}</Typography>
<Typography variant="caption" sx={{ fontWeight: 600, color: '#64748b', display: 'block' }}>Empfehlung</Typography>
<Typography variant="body2">{summary.recommendation}</Typography>
</Box>
</Box>
</Box>