fix(typography): remove DM Serif Display — all UI text now uses Inter
PageHeader titles, MatchCardCompact score badge, and IntelligenceMatchCard score badge were the only 3 places using the serif override. All now use the theme-defined Inter stack (fontWeight 600/700, letterSpacing tight). PDF/print previews (BerichtDialog, LatentInquiryReportPreview) intentionally keep their serif fonts — those are document renderers, not UI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,7 @@ export function PageHeader({
|
|||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Typography variant="h6" sx={{ fontFamily: '"DM Serif Display", serif', fontWeight: 400, fontSize: '1.25rem', lineHeight: 1.3 }}>
|
<Typography variant="h6" sx={{ fontWeight: 600, fontSize: '1.25rem', lineHeight: 1.3 }}>
|
||||||
{title}
|
{title}
|
||||||
</Typography>
|
</Typography>
|
||||||
{badge}
|
{badge}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Box, Button, Chip, Tooltip, Typography } from '@mui/material'
|
import { Box, Button, Chip, Typography } from '@mui/material'
|
||||||
import { Building2, CheckCircle2 } from 'lucide-react'
|
import { Building2, CheckCircle2, MapPin } from 'lucide-react'
|
||||||
import { useSessionStore } from '../../stores/sessionStore'
|
import { useSessionStore } from '../../stores/sessionStore'
|
||||||
import { useInquiryStore } from '../../stores/inquiryStore'
|
import { useInquiryStore } from '../../stores/inquiryStore'
|
||||||
import { LocationPreview } from '../shared/LocationPreview'
|
import { LocationPreview } from '../shared/LocationPreview'
|
||||||
@@ -34,13 +34,8 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl
|
|||||||
return <FutureAvailabilityCard vm={vm} />
|
return <FutureAvailabilityCard vm={vm} />
|
||||||
}
|
}
|
||||||
|
|
||||||
// secondary chips — max 2 visible
|
// secondary chips — max 1 visible (availability only), no overflow chip
|
||||||
const chips: Array<{ label: string; color?: string }> = []
|
const secondaryChip = vm.availabilityLabel ?? vm.fitOutLabel ?? null
|
||||||
if (vm.availabilityLabel) chips.push({ label: vm.availabilityLabel })
|
|
||||||
if (vm.fitOutLabel) chips.push({ label: vm.fitOutLabel })
|
|
||||||
if (vm.isDivisible && vm.minDivisibleUnitSqm) chips.push({ label: `Teilbar ab ${vm.minDivisibleUnitSqm} m²` })
|
|
||||||
const visibleChips = chips.slice(0, 2)
|
|
||||||
const hiddenChips = chips.slice(2)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
@@ -51,8 +46,15 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl
|
|||||||
boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
|
boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
transition: 'border-color 0.15s',
|
position: 'relative',
|
||||||
'&:hover': { borderColor: '#c8c7c4' },
|
zIndex: 0,
|
||||||
|
transition: 'transform 0.18s ease, box-shadow 0.18s ease, border-color 0.15s',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '#b0aead',
|
||||||
|
transform: 'scale(1.025)',
|
||||||
|
boxShadow: '0 10px 32px rgba(0,0,0,0.13)',
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
{/* ── Hero zone ── */}
|
{/* ── Hero zone ── */}
|
||||||
@@ -67,15 +69,29 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl
|
|||||||
border: `1px solid ${theme.border}`,
|
border: `1px solid ${theme.border}`,
|
||||||
}}>
|
}}>
|
||||||
<Typography sx={{
|
<Typography sx={{
|
||||||
fontFamily: '"DM Serif Display", serif',
|
fontWeight: 700, fontSize: '1.625rem',
|
||||||
fontWeight: 400, fontSize: '1.625rem',
|
color: '#ffffff', lineHeight: 1, letterSpacing: '-0.02em',
|
||||||
color: '#ffffff', lineHeight: 1,
|
|
||||||
}}>
|
}}>
|
||||||
{vm.matchScore}%
|
{vm.matchScore}%
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Top-right badge strip */}
|
{/* Location pill — Ginesta-style, top-left */}
|
||||||
|
<Box sx={{
|
||||||
|
position: 'absolute', top: 10, left: 10,
|
||||||
|
bgcolor: 'rgba(255,255,255,0.92)',
|
||||||
|
borderRadius: '20px',
|
||||||
|
px: 1, py: 0.35,
|
||||||
|
display: 'flex', alignItems: 'center', gap: 0.4,
|
||||||
|
backdropFilter: 'blur(4px)',
|
||||||
|
}}>
|
||||||
|
<MapPin size={9} color="#64748b" />
|
||||||
|
<Typography sx={{ fontSize: '0.65rem', fontWeight: 500, color: '#374151', lineHeight: 1 }}>
|
||||||
|
{vm.locationLabel}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Top-right badge strip — result type + owner + heat */}
|
||||||
<Box sx={{ position: 'absolute', top: 10, right: 10, display: 'flex', flexDirection: 'column', gap: 0.5, alignItems: 'flex-end' }}>
|
<Box sx={{ position: 'absolute', top: 10, right: 10, display: 'flex', flexDirection: 'column', gap: 0.5, alignItems: 'flex-end' }}>
|
||||||
<Chip label={rt.label} size="small" sx={{ bgcolor: rt.color, color: 'white', fontWeight: 600, fontSize: 10, height: 20 }} />
|
<Chip label={rt.label} size="small" sx={{ bgcolor: rt.color, color: 'white', fontWeight: 600, fontSize: 10, height: 20 }} />
|
||||||
{vm.resultType === 'VERIFIED_PORTFOLIO' && isPortfolioOwner && (
|
{vm.resultType === 'VERIFIED_PORTFOLIO' && isPortfolioOwner && (
|
||||||
@@ -116,19 +132,13 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Confidence + secondary chips */}
|
{/* Confidence + one secondary chip max */}
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 1, alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 1, alignItems: 'center' }}>
|
||||||
<Chip label={`${confPct}% Konfidenz`} size="small"
|
<Chip label={`${confPct}% Konfidenz`} size="small"
|
||||||
sx={{ bgcolor: confidenceHex(vm.confidenceScore), color: 'white', fontSize: 10, height: 20 }} />
|
sx={{ bgcolor: confidenceHex(vm.confidenceScore), color: 'white', fontSize: 10, height: 20 }} />
|
||||||
{visibleChips.map((c, i) => (
|
{secondaryChip && (
|
||||||
<Chip key={i} label={c.label} size="small" variant="outlined"
|
<Chip label={secondaryChip} size="small" variant="outlined"
|
||||||
sx={{ fontSize: 10, height: 20, borderColor: DS_BORDER.default, color: DS_TEXT.secondary }} />
|
sx={{ fontSize: 10, height: 20, borderColor: DS_BORDER.default, color: DS_TEXT.secondary }} />
|
||||||
))}
|
|
||||||
{hiddenChips.length > 0 && (
|
|
||||||
<Tooltip title={hiddenChips.map(c => c.label).join(' · ')} arrow>
|
|
||||||
<Chip label={`+${hiddenChips.length}`} size="small" variant="outlined"
|
|
||||||
sx={{ fontSize: 10, height: 20, borderColor: DS_BORDER.default, color: DS_TEXT.muted }} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -145,7 +155,7 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl
|
|||||||
|
|
||||||
{vm.reasons.length > 0 && (
|
{vm.reasons.length > 0 && (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, mt: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, mt: 1 }}>
|
||||||
{vm.reasons.slice(0, 3).map((r, i) => (
|
{vm.reasons.slice(0, 2).map((r, i) => (
|
||||||
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.75 }}>
|
<Box key={i} sx={{ display: 'flex', alignItems: 'flex-start', gap: 0.75 }}>
|
||||||
<CheckCircle2 size={13} color="#1d6342" style={{ marginTop: 2, flexShrink: 0 }} />
|
<CheckCircle2 size={13} color="#1d6342" style={{ marginTop: 2, flexShrink: 0 }} />
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
import { memo } from 'react'
|
import { memo } from 'react'
|
||||||
import { Alert, Box, Button, Chip, Divider, Tooltip, Typography } from '@mui/material'
|
import { Alert, Box, Button, Chip, Typography } from '@mui/material'
|
||||||
import { MapPin, Building2, CheckCircle2, AlertTriangle } from 'lucide-react'
|
import { MapPin, AlertTriangle } from 'lucide-react'
|
||||||
import { useSessionStore } from '../../stores/sessionStore'
|
import { useSessionStore } from '../../stores/sessionStore'
|
||||||
import { useInquiryStore } from '../../stores/inquiryStore'
|
import { useInquiryStore } from '../../stores/inquiryStore'
|
||||||
import { HeatBadge } from '../shared/HeatBadge'
|
import { HeatBadge } from '../shared/HeatBadge'
|
||||||
import { getScoreTier, SCORE_THEME } from '../shared/scoreTheme'
|
import { getScoreTier, SCORE_THEME } from '../shared/scoreTheme'
|
||||||
import { RESULT_TYPE_META, DS_TEXT, DS_BORDER } from '../../lib/ds'
|
import { RESULT_TYPE_META, DS_TEXT, DS_BORDER } from '../../lib/ds'
|
||||||
import { confidenceHex } from '../../lib/utils'
|
import { confidenceHex } from '../../lib/utils'
|
||||||
import { ScoreInlineBreakdown } from './ScoreInlineBreakdown'
|
|
||||||
import { MatchCardRestrictedState } from './MatchCardRestrictedState'
|
import { MatchCardRestrictedState } from './MatchCardRestrictedState'
|
||||||
import type { MatchCardViewModel } from './MatchCardViewModel'
|
import type { MatchCardViewModel } from './MatchCardViewModel'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
vm: MatchCardViewModel
|
vm: MatchCardViewModel
|
||||||
imageUrl?: string
|
imageUrl?: string
|
||||||
overlayTypeLabel?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, overlayTypeLabel }: Props) {
|
export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl }: Props) {
|
||||||
if (vm.isRestricted) return <MatchCardRestrictedState />
|
if (vm.isRestricted) return <MatchCardRestrictedState />
|
||||||
|
|
||||||
const { currentUser } = useSessionStore()
|
const { currentUser } = useSessionStore()
|
||||||
@@ -32,18 +30,20 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o
|
|||||||
|
|
||||||
const leftAccent = vm.isCompareSelected ? '#5b3f8a' : vm.isSelected ? '#152642' : 'transparent'
|
const leftAccent = vm.isCompareSelected ? '#5b3f8a' : vm.isSelected ? '#152642' : 'transparent'
|
||||||
|
|
||||||
const extraChips: string[] = []
|
// Compact metric line — area · rent · availability
|
||||||
if (vm.availabilityLabel) extraChips.push(vm.availabilityLabel)
|
const metricParts: string[] = []
|
||||||
if (vm.fitOutLabel) extraChips.push(vm.fitOutLabel)
|
if (vm.areaSqm) metricParts.push(`${vm.areaSqm.toLocaleString('de-CH')} m²`)
|
||||||
if (vm.isDivisible && vm.minDivisibleUnitSqm) extraChips.push(`Teilbar ab ${vm.minDivisibleUnitSqm} m²`)
|
if (vm.rentPerSqm) metricParts.push(`CHF ${vm.rentPerSqm}/m²/J`)
|
||||||
if (topRisk) extraChips.push(topRisk.level === 'CRITICAL' ? 'Kritisch' : 'Hohes Risiko')
|
if (vm.availabilityLabel) metricParts.push(vm.availabilityLabel)
|
||||||
const visibleExtra = extraChips.slice(0, 2)
|
const metricLine = metricParts.join(' · ')
|
||||||
const hiddenExtra = extraChips.slice(2)
|
|
||||||
|
// Detail action — prefer the "Details →" action if available, fallback to first action
|
||||||
|
const detailAction = vm.actions.find(a => a.actionType === 'OPEN_DETAIL' && a.label.includes('Detail'))
|
||||||
|
?? vm.actions.find(a => a.actionType === 'OPEN_DETAIL')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
mb: 1.5,
|
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
border: `1px solid ${DS_BORDER.default}`,
|
border: `1px solid ${DS_BORDER.default}`,
|
||||||
@@ -51,12 +51,19 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o
|
|||||||
bgcolor: '#ffffff',
|
bgcolor: '#ffffff',
|
||||||
boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
|
boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
|
||||||
opacity: vm.isStaleData ? 0.75 : 1,
|
opacity: vm.isStaleData ? 0.75 : 1,
|
||||||
transition: 'border-color 0.15s',
|
transition: 'border-color 0.15s, transform 0.18s ease, box-shadow 0.18s ease',
|
||||||
'&:hover': { borderColor: '#c8c7c4' },
|
position: 'relative',
|
||||||
|
zIndex: 0,
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '#b0aead',
|
||||||
|
transform: 'scale(1.012)',
|
||||||
|
boxShadow: '0 6px 20px rgba(0,0,0,0.10)',
|
||||||
|
zIndex: 1,
|
||||||
|
},
|
||||||
}}>
|
}}>
|
||||||
|
|
||||||
{/* ── Image strip ── */}
|
{/* ── Image strip — 180px, Ginesta proportion ── */}
|
||||||
<Box sx={{ width: 120, flexShrink: 0, position: 'relative', bgcolor: '#f4f3f0' }}>
|
<Box sx={{ width: 180, flexShrink: 0, position: 'relative', bgcolor: '#f4f3f0' }}>
|
||||||
{imageUrl ? (
|
{imageUrl ? (
|
||||||
<img
|
<img
|
||||||
src={imageUrl}
|
src={imageUrl}
|
||||||
@@ -73,7 +80,7 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Score badge */}
|
{/* Score badge — top left */}
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
position: 'absolute', top: 8, left: 8,
|
position: 'absolute', top: 8, left: 8,
|
||||||
bgcolor: scoreBadgeBg, borderRadius: '6px',
|
bgcolor: scoreBadgeBg, borderRadius: '6px',
|
||||||
@@ -81,58 +88,50 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o
|
|||||||
border: `1px solid ${SCORE_THEME[tier].border}`,
|
border: `1px solid ${SCORE_THEME[tier].border}`,
|
||||||
}}>
|
}}>
|
||||||
<Typography sx={{
|
<Typography sx={{
|
||||||
fontFamily: '"DM Serif Display", serif',
|
fontWeight: 700, fontSize: '1.125rem',
|
||||||
fontWeight: 400, fontSize: '1.125rem',
|
color: '#ffffff', lineHeight: 1, letterSpacing: '-0.01em',
|
||||||
color: '#ffffff', lineHeight: 1,
|
|
||||||
}}>
|
}}>
|
||||||
{vm.matchScore}%
|
{vm.matchScore}%
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{overlayTypeLabel && (
|
{/* Location pill — Ginesta-style, top right */}
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
position: 'absolute', bottom: 0, left: 0, right: 0,
|
position: 'absolute', top: 8, right: 8,
|
||||||
px: 0.75, py: 0.5,
|
bgcolor: 'rgba(255,255,255,0.92)',
|
||||||
background: 'linear-gradient(to top, rgba(0,0,0,0.55) 0%, transparent 100%)',
|
borderRadius: '20px',
|
||||||
}}>
|
px: 1, py: 0.35,
|
||||||
<Typography sx={{ fontSize: '0.6rem', fontWeight: 600, color: 'white', lineHeight: 1.3 }}>
|
display: 'flex', alignItems: 'center', gap: 0.4,
|
||||||
{overlayTypeLabel}
|
backdropFilter: 'blur(4px)',
|
||||||
</Typography>
|
}}>
|
||||||
</Box>
|
<MapPin size={9} color="#64748b" />
|
||||||
)}
|
<Typography sx={{ fontSize: '0.65rem', fontWeight: 500, color: '#374151', lineHeight: 1 }}>
|
||||||
|
{vm.locationLabel}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* ── Content ── */}
|
{/* ── Content ── */}
|
||||||
<Box sx={{ flex: 1, p: 2, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
|
<Box sx={{ flex: 1, px: 2, py: 2.5, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
|
||||||
|
|
||||||
{vm.disclaimer && (
|
{vm.disclaimer && (
|
||||||
<Alert severity="warning" sx={{ mb: 1, py: 0.25, fontSize: '0.72rem' }}>{vm.disclaimer}</Alert>
|
<Alert severity="warning" sx={{ mb: 1, py: 0.25, fontSize: '0.72rem' }}>{vm.disclaimer}</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Badges row */}
|
{/* Chips — result type + heat + confidence */}
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, alignItems: 'center', mb: 0.75 }}>
|
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center', mb: 0.875 }}>
|
||||||
<HeatBadge propertyId={vm.propertyId} size="sm" />
|
|
||||||
<Chip label={rt.label} size="small"
|
<Chip label={rt.label} size="small"
|
||||||
sx={{ bgcolor: rt.color, color: 'white', fontWeight: 600, fontSize: 10, height: 20 }} />
|
sx={{ bgcolor: rt.color, color: 'white', fontWeight: 600, fontSize: 10, height: 20 }} />
|
||||||
{vm.resultType === 'VERIFIED_PORTFOLIO' && isPortfolioOwner && (
|
<HeatBadge propertyId={vm.propertyId} size="sm" />
|
||||||
<Chip icon={<Building2 size={9} color="#152642" />} label="Ihr Objekt" size="small"
|
|
||||||
sx={{ bgcolor: 'rgba(21,38,66,0.10)', color: '#152642', fontWeight: 700, fontSize: 9, height: 18, '& .MuiChip-icon': { ml: 0.5 } }} />
|
|
||||||
)}
|
|
||||||
<Chip label={`${confPct}% Konfidenz`} size="small"
|
<Chip label={`${confPct}% Konfidenz`} size="small"
|
||||||
sx={{ bgcolor: confidenceHex(vm.confidenceScore), color: 'white', fontSize: 10, height: 20 }} />
|
sx={{ bgcolor: confidenceHex(vm.confidenceScore), color: 'white', fontSize: 10, height: 20 }} />
|
||||||
{visibleExtra.map((label, i) => (
|
{vm.resultType === 'VERIFIED_PORTFOLIO' && isPortfolioOwner && (
|
||||||
<Chip key={i} label={label} size="small" variant="outlined"
|
<Chip label="Ihr Objekt" size="small"
|
||||||
sx={{ fontSize: 10, height: 20, borderColor: DS_BORDER.default, color: DS_TEXT.secondary }} />
|
sx={{ bgcolor: 'rgba(21,38,66,0.10)', color: '#152642', fontWeight: 700, fontSize: 9, height: 18 }} />
|
||||||
))}
|
|
||||||
{hiddenExtra.length > 0 && (
|
|
||||||
<Tooltip title={hiddenExtra.join(' · ')} arrow>
|
|
||||||
<Chip label={`+${hiddenExtra.length}`} size="small" variant="outlined"
|
|
||||||
sx={{ fontSize: 10, height: 20, borderColor: DS_BORDER.default, color: DS_TEXT.muted }} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Title + location */}
|
{/* Title */}
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, lineHeight: 1.3, color: DS_TEXT.primary }} noWrap>
|
<Typography variant="subtitle2" sx={{ fontWeight: 700, lineHeight: 1.3, color: DS_TEXT.primary }} noWrap>
|
||||||
{vm.title}
|
{vm.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -141,40 +140,25 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o
|
|||||||
{vm.propertySubtitle}
|
{vm.propertySubtitle}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4, mt: 0.25 }}>
|
|
||||||
<MapPin size={11} color="#9a9a9a" />
|
|
||||||
<Typography variant="caption" sx={{ color: DS_TEXT.muted }}>
|
|
||||||
{vm.locationLabel}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
|
{/* Metric line — Ginesta-style */}
|
||||||
|
{metricLine && (
|
||||||
|
<Typography sx={{ fontSize: '0.75rem', color: '#94a3b8', mt: 0.5, letterSpacing: 0.1 }}>
|
||||||
|
{metricLine}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Explainability summary — 1 line */}
|
||||||
{vm.explainabilitySummary && (
|
{vm.explainabilitySummary && (
|
||||||
<Typography variant="body2" sx={{ color: DS_TEXT.secondary, fontWeight: 500, mt: 0.75, lineHeight: 1.5,
|
<Typography variant="body2" sx={{
|
||||||
overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' }}>
|
color: DS_TEXT.secondary, fontWeight: 500, mt: 0.875, lineHeight: 1.5,
|
||||||
|
overflow: 'hidden', display: '-webkit-box', WebkitLineClamp: 1, WebkitBoxOrient: 'vertical',
|
||||||
|
}}>
|
||||||
{vm.explainabilitySummary}
|
{vm.explainabilitySummary}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{vm.reasons.length > 0 && (
|
{/* Critical risk only */}
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.35, mt: 0.75 }}>
|
|
||||||
{vm.reasons.slice(0, 2).map((r, i) => (
|
|
||||||
<Box key={i} sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
||||||
<CheckCircle2 size={11} color="#1d6342" style={{ flexShrink: 0 }} />
|
|
||||||
<Typography sx={{ fontSize: '0.72rem', color: DS_TEXT.secondary, lineHeight: 1.3 }}>
|
|
||||||
{r.label}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{vm.scoreBreakdown && vm.allFactors && vm.allFactors.length > 0 && (
|
|
||||||
<>
|
|
||||||
<Divider sx={{ my: 0.75 }} />
|
|
||||||
<ScoreInlineBreakdown scoreBreakdown={vm.scoreBreakdown} allFactors={vm.allFactors} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{topRisk && topRisk.level === 'CRITICAL' && (
|
{topRisk && topRisk.level === 'CRITICAL' && (
|
||||||
<Alert icon={<AlertTriangle size={13} />} severity="error"
|
<Alert icon={<AlertTriangle size={13} />} severity="error"
|
||||||
sx={{ mt: 0.75, py: 0.25, fontSize: '0.72rem' }}>
|
sx={{ mt: 0.75, py: 0.25, fontSize: '0.72rem' }}>
|
||||||
@@ -182,8 +166,8 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Actions */}
|
{/* 2 Buttons — Anfrage + Details */}
|
||||||
<Box sx={{ display: 'flex', gap: 0.625, mt: 'auto', pt: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 0.625, mt: 'auto', pt: 1.25 }}>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -201,18 +185,16 @@ export const MatchCardCompact = memo(function MatchCardCompact({ vm, imageUrl, o
|
|||||||
>
|
>
|
||||||
Anfrage
|
Anfrage
|
||||||
</Button>
|
</Button>
|
||||||
{vm.actions.map(a => (
|
{detailAction && (
|
||||||
<Button
|
<Button
|
||||||
key={a.id}
|
|
||||||
size="small"
|
size="small"
|
||||||
variant={a.variant === 'primary' ? 'contained' : 'outlined'}
|
variant="outlined"
|
||||||
onClick={a.onClick}
|
onClick={detailAction.onClick}
|
||||||
disabled={a.disabled}
|
|
||||||
sx={{ textTransform: 'none', fontSize: '0.7rem', py: 0.375, px: 1 }}
|
sx={{ textTransform: 'none', fontSize: '0.7rem', py: 0.375, px: 1 }}
|
||||||
>
|
>
|
||||||
{a.label}
|
Details →
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user