feat: design token system — DS_TEXT/DS_SURFACE/DS_BORDER + 237 hex migrations
Token infrastructure (src/lib/ds.ts): - DS_TEXT: 16 semantic text color tokens (primary/secondary/muted/success/ warning/error/info/brand/signal + dark variants successDark/warningDark/ signalDark/infoDark for text on tinted surfaces) - DS_BG: page/surface/subtle/muted background tokens - DS_BORDER: default/muted/strong border tokens - DS_SURFACE: 10 bg+border surface pairs (success/warning/error/info/indigo/ purple/orange/blue/neutral/slate) - DS_MATCH_TIER: score-tier surface aliases keyed by strong/moderate/weak - DS_PRE_MARKET / DS_MARKET_SIGNAL: named aliases for futureCard tokens - DS_SHADOW: card/panel/dialog elevation tokens - BADGE_COLORS: 14 semantic presets for GenericBadge (confidenceHigh, riskMedium, preMarket, marketSignal, verified, gold, silver, bronze…) GenericBadge (src/components/shared/GenericBadge.tsx): - New semanticVariant prop (keyof BADGE_COLORS) — preferred over raw hex - color prop becomes optional (fallback), type-documented as escape hatch Priority file migrations — 237 hex literals replaced across 10 files: ScoreBreakdownPanel.tsx −22 PipelineDetailPanel.tsx −22 FutureAvailabilityCard.tsx −26 AICompareSummary.tsx −27 LocationIntelligencePanel −39 AssistantPromptSuggestions −18 UnitStructurePanel.tsx −25 BerichtDialog.tsx −24 PreMarketPanel.tsx −24 PropertyActivityLogPanel −10 ESLint (eslint.config.js): - Updated rule message to reference new tokens (DS_TEXT, DS_SURFACE, etc.) - Added 'stroke' to monitored property names - Remains 'warn' for gradual migration; use check:tokens for CI gate CI script (scripts/check-tokens.js + npm run check:tokens): - Counts hex patterns in components/ + pages/ - Fails if count > THRESHOLD (ratchet: 1958 baseline, lower per sprint) - Reports top 15 offenders for prioritizing next migration batch Results: ESLint targeted sx-prop violations: 1752 → 1030 (−41%) 0 TypeScript errors, 154 tests green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { useNavigate } from 'react-router'
|
||||
import { useProperties } from '../../hooks/useProperties'
|
||||
import { getCityIntelligence } from '../../lib/locationIntelligence'
|
||||
import type { Property } from '../../domain/property'
|
||||
import { DS_TEXT, DS_SURFACE, DS_BORDER } from '../../lib/ds'
|
||||
import { SoftFactorBar } from './SoftFactorBar'
|
||||
import { KpiTile } from './KpiTile'
|
||||
import { NEW_PROJECTS } from './locationIntelligenceConstants'
|
||||
@@ -63,31 +64,31 @@ export function LocationIntelligencePanel({ property }: Props) {
|
||||
label="Leerstandsquote"
|
||||
value={`${intel.vacancyRatePct}%`}
|
||||
sub={intel.vacancyRatePct < 3 ? 'Angespannter Markt' : intel.vacancyRatePct < 5 ? 'Ausgeglichen' : 'Käufermarkt'}
|
||||
color={intel.vacancyRatePct < 3 ? '#c0392b' : intel.vacancyRatePct < 5 ? '#d97706' : '#1a7a4a'}
|
||||
color={intel.vacancyRatePct < 3 ? DS_TEXT.error : intel.vacancyRatePct < 5 ? DS_TEXT.warning : DS_TEXT.success}
|
||||
/>
|
||||
<KpiTile
|
||||
label="Mietpreis-Trend"
|
||||
value={`${rentTrendPositive ? '+' : ''}${intel.rentTrend12m}%`}
|
||||
sub="letzte 12 Monate"
|
||||
color={rentTrendPositive ? '#c0392b' : '#1a7a4a'}
|
||||
color={rentTrendPositive ? DS_TEXT.error : DS_TEXT.success}
|
||||
/>
|
||||
<KpiTile
|
||||
label="Kaufkraft-Index"
|
||||
value={`${intel.purchasingPowerIndex}`}
|
||||
sub="CH-Mittel = 100"
|
||||
color={intel.purchasingPowerIndex >= 115 ? '#1a7a4a' : intel.purchasingPowerIndex >= 95 ? '#d97706' : '#c0392b'}
|
||||
color={intel.purchasingPowerIndex >= 115 ? DS_TEXT.success : intel.purchasingPowerIndex >= 95 ? DS_TEXT.warning : DS_TEXT.error}
|
||||
/>
|
||||
<KpiTile
|
||||
label="Ø Vermietungsdauer"
|
||||
value={`${intel.avgDaysOnMarket}T`}
|
||||
sub="Tage auf dem Markt"
|
||||
color={intel.avgDaysOnMarket < 40 ? '#c0392b' : intel.avgDaysOnMarket < 65 ? '#d97706' : '#1a7a4a'}
|
||||
color={intel.avgDaysOnMarket < 40 ? DS_TEXT.error : intel.avgDaysOnMarket < 65 ? DS_TEXT.warning : DS_TEXT.success}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Rent trend interpretation */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, p: 1.5, bgcolor: rentTrendPositive ? '#fff8f0' : '#f0fdf4', borderRadius: 1.5, mb: 2 }}>
|
||||
<Box sx={{ color: rentTrendPositive ? '#d97706' : '#1a7a4a', mt: 0.1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, p: 1.5, bgcolor: rentTrendPositive ? '#fff8f0' : DS_SURFACE.success.bg, borderRadius: 1.5, mb: 2 }}>
|
||||
<Box sx={{ color: rentTrendPositive ? DS_TEXT.warning : DS_TEXT.success, mt: 0.1 }}>
|
||||
{rentTrendPositive ? <TrendingUp size={16} /> : <TrendingDown size={16} />}
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ color: rentTrendPositive ? '#92400e' : '#14532d' }}>
|
||||
@@ -99,29 +100,29 @@ export function LocationIntelligencePanel({ property }: Props) {
|
||||
|
||||
{/* Tax + demand */}
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mb: 2 }}>
|
||||
<Box sx={{ flex: 1, p: 1.5, bgcolor: '#f8fafc', borderRadius: 1.5, border: '1px solid #e2e8f0', minWidth: 140 }}>
|
||||
<Box sx={{ flex: 1, p: 1.5, bgcolor: DS_SURFACE.neutral.bg, borderRadius: 1.5, border: `1px solid ${DS_BORDER.default}`, minWidth: 140 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.5 }}>
|
||||
<Percent size={13} color="#64748b" />
|
||||
<Percent size={13} color={DS_TEXT.muted} />
|
||||
<Typography variant="caption" color="text.secondary">Steuerindex Kanton</Typography>
|
||||
</Box>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: intel.taxIndexCanton <= 80 ? '#1a7a4a' : intel.taxIndexCanton <= 105 ? '#d97706' : '#c0392b' }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: intel.taxIndexCanton <= 80 ? DS_TEXT.success : intel.taxIndexCanton <= 105 ? DS_TEXT.warning : DS_TEXT.error }}>
|
||||
{intel.taxIndexCanton} <Typography component="span" variant="caption" color="text.secondary">(CH = 100)</Typography>
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.25 }}>
|
||||
{intel.taxIndexCanton <= 75 ? 'Sehr steuerattraktiv' : intel.taxIndexCanton <= 95 ? 'Günstige Steuerlast' : intel.taxIndexCanton <= 110 ? 'Durchschnittlich' : 'Hohe Steuerlast'}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ flex: 1, p: 1.5, bgcolor: '#f8fafc', borderRadius: 1.5, border: '1px solid #e2e8f0', minWidth: 140 }}>
|
||||
<Box sx={{ flex: 1, p: 1.5, bgcolor: DS_SURFACE.neutral.bg, borderRadius: 1.5, border: `1px solid ${DS_BORDER.default}`, minWidth: 140 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.5 }}>
|
||||
<Activity size={13} color="#64748b" />
|
||||
<Activity size={13} color={DS_TEXT.muted} />
|
||||
<Typography variant="caption" color="text.secondary">Nachfragestärke</Typography>
|
||||
</Box>
|
||||
<Chip
|
||||
label={{ LOW: 'Gering', MEDIUM: 'Mittel', HIGH: 'Hoch', VERY_HIGH: 'Sehr hoch' }[intel.demandStrength]}
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: { LOW: '#f1f5f9', MEDIUM: '#fef3c7', HIGH: '#dcfce7', VERY_HIGH: '#f0fdf4' }[intel.demandStrength],
|
||||
color: { LOW: '#64748b', MEDIUM: '#d97706', HIGH: '#16a34a', VERY_HIGH: '#1a7a4a' }[intel.demandStrength],
|
||||
bgcolor: { LOW: '#f1f5f9', MEDIUM: '#fef3c7', HIGH: '#dcfce7', VERY_HIGH: DS_SURFACE.success.bg }[intel.demandStrength],
|
||||
color: { LOW: DS_TEXT.muted, MEDIUM: DS_TEXT.warning, HIGH: '#16a34a', VERY_HIGH: DS_TEXT.success }[intel.demandStrength],
|
||||
fontWeight: 600, fontSize: 11,
|
||||
}}
|
||||
/>
|
||||
@@ -133,12 +134,12 @@ export function LocationIntelligencePanel({ property }: Props) {
|
||||
|
||||
{/* Industry clusters */}
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#475569', display: 'block', mb: 0.75 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.secondary, display: 'block', mb: 0.75 }}>
|
||||
Dominante Branchen-Cluster
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
{intel.dominantIndustryClusters.map(c => (
|
||||
<Chip key={c} label={c} size="small" sx={{ bgcolor: '#eff6ff', color: '#1e3a5f', fontSize: 11, height: 22 }} />
|
||||
<Chip key={c} label={c} size="small" sx={{ bgcolor: '#eff6ff', color: DS_TEXT.brand, fontSize: 11, height: 22 }} />
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -147,8 +148,8 @@ export function LocationIntelligencePanel({ property }: Props) {
|
||||
{intel.plannedInfrastructure.length > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 0.75 }}>
|
||||
<Train size={13} color="#64748b" />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#475569' }}>
|
||||
<Train size={13} color={DS_TEXT.muted} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.secondary }}>
|
||||
Geplante Infrastruktur-Projekte
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -173,7 +174,7 @@ export function LocationIntelligencePanel({ property }: Props) {
|
||||
{/* ── Soft Factors ── */}
|
||||
{hasSoftFactors && (
|
||||
<Box sx={{ mb: 1.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#475569', display: 'block', mb: 1 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.secondary, display: 'block', mb: 1 }}>
|
||||
KI-berechnete Standortqualität
|
||||
</Typography>
|
||||
<SoftFactorBar
|
||||
@@ -221,7 +222,7 @@ export function LocationIntelligencePanel({ property }: Props) {
|
||||
{comparables.length > 0 && (
|
||||
<>
|
||||
<Divider sx={{ my: 1.5 }} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#475569', display: 'block', mb: 0.75 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.secondary, display: 'block', mb: 0.75 }}>
|
||||
Vergleichbare Angebote in {city}
|
||||
</Typography>
|
||||
{comparables.map(p => (
|
||||
@@ -231,7 +232,7 @@ export function LocationIntelligencePanel({ property }: Props) {
|
||||
sx={{
|
||||
display: 'flex', alignItems: 'center', gap: 1.5, py: 0.75,
|
||||
borderBottom: '1px solid #f1f5f9', cursor: 'pointer',
|
||||
'&:hover': { bgcolor: '#f8fafc', borderRadius: 0.5 },
|
||||
'&:hover': { bgcolor: DS_SURFACE.neutral.bg, borderRadius: 0.5 },
|
||||
}}
|
||||
>
|
||||
<Building2 size={13} color="#3b82f6" />
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { FutureSignal } from '../../domain/futureSignal'
|
||||
import { CREDIBILITY_LABELS, HARD_KEYS, factorLabel } from './scoreBreakdownConstants'
|
||||
import { CriterionRow } from './CriterionRow'
|
||||
import { criterionScoreColor, criterionScoreTextColor } from '../../lib/utils'
|
||||
import { DS_COLORS } from '../../lib/ds'
|
||||
import { DS_COLORS, DS_TEXT, DS_SURFACE } from '../../lib/ds'
|
||||
import { SCORE_STRONG, SCORE_MODERATE } from '../../lib/constants'
|
||||
|
||||
// ── Standard breakdown (VERIFIED_PORTFOLIO / EXTERNAL / MAISON) ──────────────
|
||||
@@ -46,7 +46,7 @@ function StandardBreakdown({ match, taxCalculatorUrl }: StandardBreakdownProps)
|
||||
|
||||
{hardFactors.map((f, i) => <CriterionRow key={i} factor={f} maxWeight={maxHardWeight} />)}
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: '#eef2ff', px: 1.25, py: 0.75, borderRadius: 1, mt: 0.25, mb: hasAllFactors && softFactors.length > 0 ? 2 : 1.5 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: DS_SURFACE.indigo.bg, px: 1.25, py: 0.75, borderRadius: 1, mt: 0.25, mb: hasAllFactors && softFactors.length > 0 ? 2 : 1.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: 'primary.main' }}>
|
||||
Hart-Kriterien {sb.hardMatchScore}/100 × 60%
|
||||
</Typography>
|
||||
@@ -57,11 +57,11 @@ function StandardBreakdown({ match, taxCalculatorUrl }: StandardBreakdownProps)
|
||||
|
||||
{/* Soft factors: full list when allFactors present, otherwise summary row */}
|
||||
{!hasAllFactors && (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: '#f8fafc', px: 1.25, py: 0.75, borderRadius: 1, mb: 1.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#475569' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: DS_SURFACE.neutral.bg, px: 1.25, py: 0.75, borderRadius: 1, mb: 1.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.secondary }}>
|
||||
Soft-Faktoren {sb.softFactorScore}/100 × 40%
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: '#475569' }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: DS_TEXT.secondary }}>
|
||||
{softContrib} Pkt
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -70,15 +70,15 @@ function StandardBreakdown({ match, taxCalculatorUrl }: StandardBreakdownProps)
|
||||
{hasAllFactors && softFactors.length > 0 && (
|
||||
<>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: '#475569', textTransform: 'uppercase', letterSpacing: 0.6, display: 'block', mb: 1.25 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: DS_TEXT.secondary, textTransform: 'uppercase', letterSpacing: 0.6, display: 'block', mb: 1.25 }}>
|
||||
Soft-Faktoren
|
||||
</Typography>
|
||||
{softFactors.map((f, i) => <CriterionRow key={i} factor={f} maxWeight={maxSoftWeight} />)}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: '#f8fafc', px: 1.25, py: 0.75, borderRadius: 1, mt: 0.25, mb: 1.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#475569' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: DS_SURFACE.neutral.bg, px: 1.25, py: 0.75, borderRadius: 1, mt: 0.25, mb: 1.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.secondary }}>
|
||||
Soft-Score {sb.softFactorScore}/100 × 40%
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: '#475569' }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: DS_TEXT.secondary }}>
|
||||
{softContrib} Pkt
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -87,10 +87,10 @@ function StandardBreakdown({ match, taxCalculatorUrl }: StandardBreakdownProps)
|
||||
|
||||
{/* Tax link */}
|
||||
{taxCalculatorUrl && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5, p: 1.25, bgcolor: '#f0f9ff', borderRadius: 1, border: '1px solid #bae6fd' }}>
|
||||
<ExternalLink size={13} color="#0369a1" style={{ flexShrink: 0 }} />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1.5, p: 1.25, bgcolor: DS_SURFACE.info.bg, borderRadius: 1, border: `1px solid ${DS_SURFACE.info.border}` }}>
|
||||
<ExternalLink size={13} color={DS_TEXT.info} style={{ flexShrink: 0 }} />
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ color: '#0369a1', fontWeight: 600, display: 'block', lineHeight: 1.2 }}>
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.info, fontWeight: 600, display: 'block', lineHeight: 1.2 }}>
|
||||
Steuerlast in Bewertung eingeflossen
|
||||
</Typography>
|
||||
<Link
|
||||
@@ -98,7 +98,7 @@ function StandardBreakdown({ match, taxCalculatorUrl }: StandardBreakdownProps)
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
underline="hover"
|
||||
sx={{ fontSize: '0.72rem', color: '#0369a1' }}
|
||||
sx={{ fontSize: '0.72rem', color: DS_TEXT.info }}
|
||||
>
|
||||
Steuerrechner Gemeinde öffnen →
|
||||
</Link>
|
||||
@@ -109,11 +109,11 @@ function StandardBreakdown({ match, taxCalculatorUrl }: StandardBreakdownProps)
|
||||
<Divider sx={{ mb: 1.5 }} />
|
||||
|
||||
{/* Formula row — always visible */}
|
||||
<Box sx={{ bgcolor: '#f8fafc', px: 1.25, py: 0.75, borderRadius: 1, mb: 1.25 }}>
|
||||
<Typography variant="caption" sx={{ color: '#64748b', display: 'block', mb: 0.4, fontWeight: 500 }}>
|
||||
<Box sx={{ bgcolor: DS_SURFACE.neutral.bg, px: 1.25, py: 0.75, borderRadius: 1, mb: 1.25 }}>
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.muted, display: 'block', mb: 0.4, fontWeight: 500 }}>
|
||||
Berechnung
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ fontFamily: 'monospace', color: '#1e293b', display: 'block', lineHeight: 1.6 }}>
|
||||
<Typography variant="caption" sx={{ fontFamily: 'monospace', color: DS_TEXT.primary, display: 'block', lineHeight: 1.6 }}>
|
||||
Hart {sb.hardMatchScore} × 60% + Soft {sb.softFactorScore} × 40%
|
||||
{' = '}{hardContrib} + {softContrib} = {baseSum}
|
||||
</Typography>
|
||||
@@ -122,7 +122,7 @@ function StandardBreakdown({ match, taxCalculatorUrl }: StandardBreakdownProps)
|
||||
{/* Total */}
|
||||
<Box sx={{
|
||||
display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
|
||||
bgcolor: baseSum >= SCORE_STRONG ? '#f0fdf4' : baseSum >= SCORE_MODERATE ? '#fffbeb' : '#fef2f2',
|
||||
bgcolor: baseSum >= SCORE_STRONG ? DS_SURFACE.success.bg : baseSum >= SCORE_MODERATE ? DS_SURFACE.warning.bg : DS_SURFACE.error.bg,
|
||||
p: 1.5, borderRadius: 1,
|
||||
}}>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>Gesamt-Score</Typography>
|
||||
@@ -176,11 +176,11 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) {
|
||||
<Box key={i} sx={{ mb: 1.25 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.6, mb: 0.4 }}>
|
||||
{isPositive
|
||||
? <CheckCircle2 size={12} color="#1a7a4a" style={{ flexShrink: 0 }} />
|
||||
: <X size={12} color="#c0392b" style={{ flexShrink: 0 }} />
|
||||
? <CheckCircle2 size={12} color={DS_TEXT.success} style={{ flexShrink: 0 }} />
|
||||
: <X size={12} color={DS_TEXT.error} style={{ flexShrink: 0 }} />
|
||||
}
|
||||
<Box sx={{ flex: 1, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#1e293b' }}>{factorLabel(f.criterion)}</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.primary }}>{factorLabel(f.criterion)}</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: criterionScoreTextColor(f.score), ml: 1 }}>{f.score}/100</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -197,9 +197,9 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) {
|
||||
)
|
||||
})}
|
||||
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: '#f8fafc', px: 1.25, py: 0.75, borderRadius: 1, mt: 0.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#475569' }}>Basis-Score</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: '#1e293b' }}>{sb.hardMatchScore}/100</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: DS_SURFACE.neutral.bg, px: 1.25, py: 0.75, borderRadius: 1, mt: 0.5 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.secondary }}>Basis-Score</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: DS_TEXT.primary }}>{sb.hardMatchScore}/100</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -214,20 +214,20 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) {
|
||||
{isVerifiedContract ? (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 0.75 }}>
|
||||
<ShieldCheck size={13} color="#1a7a4a" style={{ flexShrink: 0 }} />
|
||||
<Typography variant="caption" sx={{ color: '#1a7a4a', lineHeight: 1.3 }}>
|
||||
<ShieldCheck size={13} color={DS_TEXT.success} style={{ flexShrink: 0 }} />
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.success, lineHeight: 1.3 }}>
|
||||
Vertragsende aus ERP verifiziert — keine Schätzung
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 1 }}>
|
||||
<ShieldCheck size={13} color="#1a7a4a" style={{ flexShrink: 0 }} />
|
||||
<Typography variant="caption" sx={{ color: '#1a7a4a', lineHeight: 1.3 }}>
|
||||
<ShieldCheck size={13} color={DS_TEXT.success} style={{ flexShrink: 0 }} />
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.success, lineHeight: 1.3 }}>
|
||||
Verwaltung hat Freigabe erteilt
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: '#f0fdf4', px: 1.25, py: 0.75, borderRadius: 1, border: '1px solid #bbf7d0' }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#1a7a4a' }}>Signal-Abschlag</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: '#1a7a4a' }}>keiner</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: DS_SURFACE.success.bg, px: 1.25, py: 0.75, borderRadius: 1, border: `1px solid ${DS_SURFACE.success.border}` }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.success }}>Signal-Abschlag</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: DS_TEXT.success }}>keiner</Typography>
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
@@ -235,18 +235,18 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) {
|
||||
{probPct !== null && (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 0.5 }}>
|
||||
<Typography variant="caption" color="text.secondary">Eintretenswahrscheinlichkeit: {probPct}%</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#c0392b' }}>–{Math.round(signalDeduction * 0.7)} Pkt.</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.error }}>–{Math.round(signalDeduction * 0.7)} Pkt.</Typography>
|
||||
</Box>
|
||||
)}
|
||||
{credLabel && (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', mb: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary">Quellenqualität: {credLabel}</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#d97706' }}>–{Math.round(signalDeduction * 0.3)} Pkt.</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.warning }}>–{Math.round(signalDeduction * 0.3)} Pkt.</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: '#fff7ed', px: 1.25, py: 0.75, borderRadius: 1, border: '1px solid #fed7aa' }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#d97706' }}>Signal-Abschlag</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: '#d97706' }}>–{signalDeduction} Punkte</Typography>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', bgcolor: DS_SURFACE.orange.bg, px: 1.25, py: 0.75, borderRadius: 1, border: `1px solid ${DS_SURFACE.orange.border}` }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_TEXT.warning }}>Signal-Abschlag</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 800, color: DS_TEXT.warning }}>–{signalDeduction} Punkte</Typography>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
@@ -255,7 +255,7 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) {
|
||||
<Divider sx={{ mb: 1.5 }} />
|
||||
|
||||
{/* Gesamt */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', bgcolor: '#f8fafc', p: 1.5, borderRadius: 1 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', bgcolor: DS_SURFACE.neutral.bg, p: 1.5, borderRadius: 1 }}>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>Gesamt-Score</Typography>
|
||||
<Typography
|
||||
variant="h4"
|
||||
|
||||
Reference in New Issue
Block a user