refactor: replace hardcoded hex colors with design tokens across priority components
- Add DS_COLORS.heat, DS_COLORS.futureCard, INQUIRY_STATUS_META tokens to ds.ts - Eliminate 4 duplicate RESULT_TYPE_META / TYPE_META constants (now all use ds.ts) - Eliminate 3 duplicate scoreColor / SCORE_COLOR functions (now all use matchScoreHex) - HeatBadge: use DS_COLORS.heat.VERY_HOT / HOT tokens - FutureAvailabilityCard: use DS_COLORS.futureCard.controlled / signal tokens - IntelligenceMatchCard: import RESULT_TYPE_META from ds.ts, remove primary override - MatchScoreDisplay: replace local scoreColor() with matchScoreHex() - CompareColumnHeader: remove local TYPE_META + SCORE_COLOR, use ds.ts + utils.ts - CompareTableBody: import RESULT_TYPE_META + matchScoreHex, fix #7c3aed → token - compareUtils: remove TYPE_META + SCORE_COLOR, use dataQualityHex in scoreBar - Anfragen + AnfragenInquiryItem: deduplicate STATUS_CONFIG → INQUIRY_STATUS_META - Anfragen: #1e3a5f → 'primary.main', KI alert → futureCard.controlled tokens - AnfragenMessageBubble: own-message bubble → 'primary.main', AI bubble → tokens - ScoreBreakdownPanel: #1e3a5f → 'primary.main', #6d28d9 → futureCard.controlled - ESLint: add warn rule against new hex colors in component sx props Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,17 +3,10 @@ import { X, AlertTriangle, BookmarkCheck, ExternalLink, Kanban } from 'lucide-re
|
||||
import { useNavigate } from 'react-router'
|
||||
import { usePipelineItems } from '../../hooks/usePipeline'
|
||||
import { usePipelineStore } from '../../stores/pipelineStore'
|
||||
import { RESULT_TYPE_META } from '../../lib/ds'
|
||||
import { matchScoreHex } from '../../lib/utils'
|
||||
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
|
||||
|
||||
const TYPE_META: Record<string, { label: string; color: string }> = {
|
||||
VERIFIED_PORTFOLIO: { label: 'Plattform', color: '#1e3a5f' },
|
||||
EXTERNAL_MARKET: { label: 'Plattform', color: '#1e3a5f' },
|
||||
MAISON_WORK: { label: 'Maison Work', color: '#0369a1' },
|
||||
FUTURE_AVAILABILITY: { label: 'Zukunftssignal', color: '#7c3aed' },
|
||||
}
|
||||
|
||||
const SCORE_COLOR = (s: number) => s >= 78 ? '#1a7a4a' : s >= 52 ? '#d97706' : '#c0392b'
|
||||
|
||||
interface Props {
|
||||
item: UnifiedMatchResult
|
||||
onRemove: () => void
|
||||
@@ -24,7 +17,7 @@ export function CompareColumnHeader({ item, onRemove }: Props) {
|
||||
const { data: pipelineItems = [] } = usePipelineItems()
|
||||
const { openSavedDialog } = usePipelineStore()
|
||||
|
||||
const meta = TYPE_META[item.resultType] ?? { label: item.resultType, color: '#64748b' }
|
||||
const meta = RESULT_TYPE_META[item.resultType] ?? { label: item.resultType, color: '#64748b' }
|
||||
const prop = item.resultType !== 'FUTURE_AVAILABILITY' ? (item as any).property : null
|
||||
const sig = item.resultType === 'FUTURE_AVAILABILITY' ? (item as any).signal : null
|
||||
|
||||
@@ -86,7 +79,7 @@ export function CompareColumnHeader({ item, onRemove }: Props) {
|
||||
|
||||
{/* Row 3: score */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 0.5, mb: 1 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 800, color: SCORE_COLOR(item.matchScore), lineHeight: 1 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 800, color: matchScoreHex(item.matchScore), lineHeight: 1 }}>
|
||||
{item.matchScore}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">/100</Typography>
|
||||
@@ -152,7 +145,7 @@ export function CompareColumnHeader({ item, onRemove }: Props) {
|
||||
variant="contained"
|
||||
startIcon={<Kanban size={12} />}
|
||||
onClick={handleSave}
|
||||
sx={{ flex: 1, textTransform: 'none', fontSize: '0.72rem', py: 0.5, bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' } }}
|
||||
sx={{ flex: 1, textTransform: 'none', fontSize: '0.72rem', py: 0.5 }}
|
||||
>
|
||||
Merken
|
||||
</Button>
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
import { AlertOctagon, AlertTriangle, CheckCircle2, Trophy, XCircle, Zap } from 'lucide-react'
|
||||
import {
|
||||
HARD_CRITERIA,
|
||||
SCORE_COLOR,
|
||||
TYPE_META,
|
||||
RISK_LEVEL_ORDER,
|
||||
getProp,
|
||||
getSig,
|
||||
@@ -20,6 +18,8 @@ import {
|
||||
scoreBar,
|
||||
} from './compareUtils'
|
||||
import { CompareCell, MissingDataCell } from './index'
|
||||
import { RESULT_TYPE_META, DS_COLORS } from '../../lib/ds'
|
||||
import { matchScoreHex } from '../../lib/utils'
|
||||
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
|
||||
|
||||
// ── Local helper ──────────────────────────────────────────────────────────────
|
||||
@@ -61,7 +61,7 @@ export function CompareTableBody({
|
||||
|
||||
{/* 1. Result Type */}
|
||||
{row('1. Result-Typ', compareItems.map(item => {
|
||||
const m = TYPE_META[item.resultType] ?? { label: item.resultType, color: '#64748b' }
|
||||
const m = RESULT_TYPE_META[item.resultType] ?? { label: item.resultType, color: '#64748b' }
|
||||
return <Chip label={m.label} size="small" sx={{ bgcolor: m.color, color: 'white', fontWeight: 600, fontSize: 11 }} />
|
||||
}))}
|
||||
|
||||
@@ -83,7 +83,7 @@ export function CompareTableBody({
|
||||
iconTooltip="Höchster Match Score"
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 0.5 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 800, color: SCORE_COLOR(item.matchScore), lineHeight: 1 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 800, color: matchScoreHex(item.matchScore), lineHeight: 1 }}>
|
||||
{item.matchScore}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">/100</Typography>
|
||||
@@ -172,9 +172,9 @@ export function CompareTableBody({
|
||||
const sig = getSig(item)
|
||||
if (prop) return <Typography variant="body2">{prop.availabilityDate}</Typography>
|
||||
if (sig) return (
|
||||
<CompareCell highlight="future" icon={<Zap size={14} color="#7c3aed" />} iconTooltip="Probabilistisches Signal — keine bestätigte Verfügbarkeit">
|
||||
<CompareCell highlight="future" icon={<Zap size={14} color={DS_COLORS.futureCard.controlled.accent} />} iconTooltip="Probabilistisches Signal — keine bestätigte Verfügbarkeit">
|
||||
<Typography variant="body2">~{sig.timeHorizonMonths} Monate</Typography>
|
||||
<Typography variant="caption" sx={{ color: '#7c3aed' }}>
|
||||
<Typography variant="caption" sx={{ color: DS_COLORS.futureCard.controlled.accent }}>
|
||||
{Math.round(sig.probability * 100)}% Wahrscheinlichkeit
|
||||
</Typography>
|
||||
</CompareCell>
|
||||
@@ -305,9 +305,9 @@ export function CompareTableBody({
|
||||
<Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>Nicht anwendbar</Typography>
|
||||
)
|
||||
return (
|
||||
<CompareCell highlight="future" icon={<Zap size={14} color="#7c3aed" />} iconTooltip="Probabilistisches Zukunftssignal">
|
||||
<CompareCell highlight="future" icon={<Zap size={14} color={DS_COLORS.futureCard.controlled.accent} />} iconTooltip="Probabilistisches Zukunftssignal">
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.25 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: '#7c3aed' }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: DS_COLORS.futureCard.controlled.accent }}>
|
||||
{Math.round(sig.probability * 100)}% Wahrscheinlichkeit
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
|
||||
@@ -2,11 +2,11 @@ import type { ReactNode } from 'react'
|
||||
import { Box, LinearProgress, Typography } from '@mui/material'
|
||||
import type { WeightingKey } from '../../domain/needBuilder'
|
||||
import type { UnifiedMatchResult, VerifiedPortfolioResult, ExternalMarketResult, FutureAvailabilityResult } from '../../domain/unifiedResult'
|
||||
import { dataQualityHex } from '../../lib/utils'
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export const HARD_CRITERIA = new Set(['area', 'location', 'budget', 'timing'])
|
||||
export const SCORE_COLOR = (s: number) => s >= 78 ? '#1a7a4a' : s >= 52 ? '#d97706' : '#c0392b'
|
||||
|
||||
export function getProp(item: UnifiedMatchResult) {
|
||||
return item.resultType !== 'FUTURE_AVAILABILITY'
|
||||
@@ -20,13 +20,6 @@ export function getSig(item: UnifiedMatchResult) {
|
||||
: null
|
||||
}
|
||||
|
||||
export const TYPE_META: Record<string, { label: string; color: string }> = {
|
||||
VERIFIED_PORTFOLIO: { label: 'Plattform', color: '#1e3a5f' },
|
||||
EXTERNAL_MARKET: { label: 'Plattform', color: '#1e3a5f' },
|
||||
MAISON_WORK: { label: 'Maison Work', color: '#0369a1' },
|
||||
FUTURE_AVAILABILITY: { label: 'Zukunftssignal', color: '#7c3aed' },
|
||||
}
|
||||
|
||||
export const RISK_LEVEL_ORDER: Record<string, number> = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 }
|
||||
|
||||
export const CRITERION_ALIASES: Record<WeightingKey, string[]> = {
|
||||
@@ -78,7 +71,7 @@ export const DATA_SX = {
|
||||
// ── Score bar helper ──────────────────────────────────────────────────────────
|
||||
|
||||
export function scoreBar(value: number, label?: string): ReactNode {
|
||||
const color = value >= 0.8 ? '#1a7a4a' : value >= 0.6 ? '#d97706' : '#c0392b'
|
||||
const color = dataQualityHex(value)
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Box sx={{ width: 70 }}>
|
||||
|
||||
Reference in New Issue
Block a user