diff --git a/src/components/demand/ConfidenceFieldBadge.tsx b/src/components/demand/ConfidenceFieldBadge.tsx index a89f6ab..dfd75d4 100644 --- a/src/components/demand/ConfidenceFieldBadge.tsx +++ b/src/components/demand/ConfidenceFieldBadge.tsx @@ -1,22 +1,17 @@ import { Chip } from '@mui/material' +import { confidenceHex } from '../../lib/utils' interface Props { confidence: number size?: 'small' | 'medium' } -function confidenceColor(c: number): string { - if (c >= 0.8) return '#1a7a4a' - if (c >= 0.6) return '#d97706' - return '#c0392b' -} - export function ConfidenceFieldBadge({ confidence, size = 'small' }: Props) { return ( ) } diff --git a/src/components/match-card/MatchCardHeader.tsx b/src/components/match-card/MatchCardHeader.tsx index dc57457..5d4629d 100644 --- a/src/components/match-card/MatchCardHeader.tsx +++ b/src/components/match-card/MatchCardHeader.tsx @@ -3,21 +3,10 @@ import { Building2 } from 'lucide-react' import { MatchScoreDisplay } from './MatchScoreDisplay' import { HeatBadge } from '../shared' import { useSessionStore } from '../../stores/sessionStore' +import { RESULT_TYPE_META } from '../../lib/ds' +import { confidenceHex } from '../../lib/utils' import type { MatchCardViewModel } from './MatchCardViewModel' -const RESULT_TYPE_META: Record = { - 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' }, -} - -function confidenceColor(score: number): string { - if (score >= 0.75) return '#1a7a4a' - if (score >= 0.55) return '#d97706' - return '#c0392b' -} - interface Props { vm: MatchCardViewModel compact?: boolean @@ -61,7 +50,7 @@ export function MatchCardHeader({ vm, compact }: Props) { {vm.availabilityLabel && ( diff --git a/src/components/match-card/ScoreInlineBreakdown.tsx b/src/components/match-card/ScoreInlineBreakdown.tsx index 6f9fa7e..f0e1878 100644 --- a/src/components/match-card/ScoreInlineBreakdown.tsx +++ b/src/components/match-card/ScoreInlineBreakdown.tsx @@ -1,5 +1,6 @@ import { Box, Divider, LinearProgress, Typography } from '@mui/material' import type { ScoreFactor } from '../../domain/match' +import { criterionScoreColor, criterionScoreTextColor } from '../../lib/utils' interface ScoreBreakdownData { hardMatchScore: number @@ -22,14 +23,6 @@ const CRITERION_LABEL: Record = { const HARD_KEYS = new Set(['area', 'location', 'budget', 'timing']) -function scoreColor(v: number): 'success' | 'warning' | 'error' { - return v >= 70 ? 'success' : v >= 50 ? 'warning' : 'error' -} - -function scoreTextColor(v: number): string { - return v >= 70 ? '#1a7a4a' : v >= 50 ? '#d97706' : '#c0392b' -} - function importanceLabel(weight: number, maxWeight: number): { label: string; color: string } { const ratio = maxWeight > 0 ? weight / maxWeight : 0 if (ratio >= 0.85) return { label: 'Entscheidend', color: '#1e3a5f' } @@ -43,7 +36,7 @@ function FactorRow({ factor, maxWeight }: { factor: ScoreFactor; maxWeight: numb const label = CRITERION_LABEL[factor.criterion] ?? factor.criterion const pct = Math.round(factor.weight * 100) const imp = importanceLabel(factor.weight, maxWeight) - const color = scoreColor(factor.score) + const color = criterionScoreColor(factor.score) return ( @@ -54,7 +47,7 @@ function FactorRow({ factor, maxWeight }: { factor: ScoreFactor; maxWeight: numb {pct}% - + {factor.score}/100 @@ -82,7 +75,7 @@ export function ScoreInlineBreakdown({ scoreBreakdown: sb, allFactors, compact = Hart {sb.hardMatchScore} × 60% + Soft {sb.softFactorScore} × 40% {' = '}{hardContrib} + {softContrib}{' = '} - {sb.totalScore} + {sb.totalScore} ) diff --git a/src/components/match-center/MatchListCard.tsx b/src/components/match-center/MatchListCard.tsx index 163326e..e425a2a 100644 --- a/src/components/match-center/MatchListCard.tsx +++ b/src/components/match-center/MatchListCard.tsx @@ -3,6 +3,7 @@ import { MatchStatusBadge } from './MatchStatusBadge' import type { Match } from '../../domain/match' import type { Property } from '../../domain/property' import type { Need } from '../../domain/need' +import { matchScoreHex } from '../../lib/utils' const STRENGTH_META: Record = { STRONG: { label: 'Stark', bg: '#f0fdf4', color: '#1a7a4a' }, @@ -10,12 +11,6 @@ const STRENGTH_META: Record= 80) return '#1a7a4a' - if (score >= 60) return '#d97706' - return '#c0392b' -} - interface Props { match: Match property: Property | undefined @@ -49,7 +44,7 @@ export function MatchListCard({ match, property, need, onSelect, onApprove }: Pr width: 52, height: 52, borderRadius: 2, - bgcolor: scoreColor(match.matchScore), + bgcolor: matchScoreHex(match.matchScore), display: 'flex', alignItems: 'center', justifyContent: 'center', diff --git a/src/components/match-detail/CriterionRow.tsx b/src/components/match-detail/CriterionRow.tsx index 516c7b3..eecd4a2 100644 --- a/src/components/match-detail/CriterionRow.tsx +++ b/src/components/match-detail/CriterionRow.tsx @@ -1,19 +1,12 @@ import { Box, LinearProgress, Typography } from '@mui/material' import type { ScoreFactor } from '../../domain/match' import { factorLabel, importanceLabel } from './scoreBreakdownConstants' - -function scoreColor(v: number): 'success' | 'warning' | 'error' { - return v >= 70 ? 'success' : v >= 50 ? 'warning' : 'error' -} - -function scoreTextColor(v: number): string { - return v >= 70 ? '#1a7a4a' : v >= 50 ? '#d97706' : '#c0392b' -} +import { criterionScoreColor, criterionScoreTextColor } from '../../lib/utils' export function CriterionRow({ factor, maxWeight }: { factor: ScoreFactor; maxWeight: number }) { const label = factorLabel(factor.criterion) const pct = Math.round(factor.weight * 100) - const color = scoreColor(factor.score) + const color = criterionScoreColor(factor.score) const imp = importanceLabel(factor.weight, maxWeight) return ( @@ -34,7 +27,7 @@ export function CriterionRow({ factor, maxWeight }: { factor: ScoreFactor; maxWe - + {factor.score}/100 diff --git a/src/components/match-detail/ScoreBreakdownPanel.tsx b/src/components/match-detail/ScoreBreakdownPanel.tsx index ea235cc..7625e10 100644 --- a/src/components/match-detail/ScoreBreakdownPanel.tsx +++ b/src/components/match-detail/ScoreBreakdownPanel.tsx @@ -5,16 +5,7 @@ import type { Match } from '../../domain/match' import type { FutureSignal } from '../../domain/futureSignal' import { CREDIBILITY_LABELS, HARD_KEYS, factorLabel } from './scoreBreakdownConstants' import { CriterionRow } from './CriterionRow' - -// ── Shared helpers ───────────────────────────────────────────────────────────── - -function scoreColor(v: number): 'success' | 'warning' | 'error' { - return v >= 70 ? 'success' : v >= 50 ? 'warning' : 'error' -} - -function scoreTextColor(v: number): string { - return v >= 70 ? '#1a7a4a' : v >= 50 ? '#d97706' : '#c0392b' -} +import { criterionScoreColor, criterionScoreTextColor } from '../../lib/utils' // ── Standard breakdown (VERIFIED_PORTFOLIO / EXTERNAL / MAISON) ────────────── @@ -135,7 +126,7 @@ function StandardBreakdown({ match, taxCalculatorUrl }: StandardBreakdownProps) Gesamt-Score {baseSum}/100 @@ -188,13 +179,13 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) { } {factorLabel(f.criterion)} - {f.score}/100 + {f.score}/100 @@ -266,7 +257,7 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) { Gesamt-Score {sb.totalScore}/100 diff --git a/src/components/ops/ReliabilityScorePanel.tsx b/src/components/ops/ReliabilityScorePanel.tsx index efade0f..57c1f9a 100644 --- a/src/components/ops/ReliabilityScorePanel.tsx +++ b/src/components/ops/ReliabilityScorePanel.tsx @@ -1,10 +1,5 @@ import { Box, LinearProgress, Tooltip, Typography } from '@mui/material' - -function scoreColor(score: number): string { - if (score >= 0.85) return '#15803d' - if (score >= 0.65) return '#a16207' - return '#dc2626' -} +import { confidenceHex } from '../../lib/utils' interface ReliabilityScorePanelProps { score: number @@ -13,7 +8,7 @@ interface ReliabilityScorePanelProps { export function ReliabilityScorePanel({ score, compact = false }: ReliabilityScorePanelProps) { const pct = Math.round(score * 100) - const color = scoreColor(score) + const color = confidenceHex(score) if (compact) { return ( diff --git a/src/components/pipeline/PipelineCard.tsx b/src/components/pipeline/PipelineCard.tsx index f47339c..186815c 100644 --- a/src/components/pipeline/PipelineCard.tsx +++ b/src/components/pipeline/PipelineCard.tsx @@ -6,7 +6,7 @@ import { ExternalLink, MapPin, MessageSquare } from 'lucide-react' import { MatchScoreDisplay } from '../match-card/MatchScoreDisplay' import { HeatBadge } from '../shared' import type { PipelineItem } from '../../domain/pipeline' -import { STAGES, RESULT_TYPE_LABEL, RESULT_TYPE_COLOR } from './pipelineConstants' +import { STAGES, RESULT_TYPE_META } from './pipelineConstants' import { detailPath } from './pipelineUtils' // ── DraggableCard ───────────────────────────────────────────────────────────── @@ -68,8 +68,8 @@ export function DraggableCard({ = { - VERIFIED_PORTFOLIO: 'Plattform', - EXTERNAL_MARKET: 'Plattform', - MAISON_WORK: 'Maison Work', - FUTURE_AVAILABILITY: 'Future', -} - -export const RESULT_TYPE_COLOR: Record = { - VERIFIED_PORTFOLIO: '#1e3a5f', - EXTERNAL_MARKET: '#1e3a5f', - MAISON_WORK: '#0369a1', - FUTURE_AVAILABILITY: '#7c3aed', -} - export const MOCK_DOCS: Record = { 'pl-001': [ { name: 'Expose_Zollstrasse12.pdf', date: '05.05.2026' }, diff --git a/src/components/pipeline/pipelineUtils.ts b/src/components/pipeline/pipelineUtils.ts index 39efff3..4fc8b43 100644 --- a/src/components/pipeline/pipelineUtils.ts +++ b/src/components/pipeline/pipelineUtils.ts @@ -1,8 +1,5 @@ import type { PipelineItem } from '../../domain/pipeline' - -export function scoreColor(score: number) { - return score >= 80 ? '#1a7a4a' : score >= 65 ? '#d97706' : '#c0392b' -} +export { matchScoreHex as scoreColor } from '../../lib/utils' export function detailPath(item: PipelineItem): string | null { // propertyId is always stable across sessions — prefer it diff --git a/src/components/results/ResultTypeBadge.tsx b/src/components/results/ResultTypeBadge.tsx index 426d5ed..e616410 100644 --- a/src/components/results/ResultTypeBadge.tsx +++ b/src/components/results/ResultTypeBadge.tsx @@ -1,15 +1,9 @@ import type { ResultType } from '../../domain/enums' import { Chip } from '@mui/material' - -const TYPE_META: Record = { - VERIFIED_PORTFOLIO: { label: 'Verified Portfolio', color: '#1e3a5f' }, - EXTERNAL_MARKET: { label: 'Direktinserat', color: '#d97706' }, - MAISON_WORK: { label: 'Maison Work', color: '#0369a1' }, - FUTURE_AVAILABILITY: { label: 'Zukunftssignal', color: '#7c3aed' }, -} +import { RESULT_TYPE_META } from '../../lib/ds' export function ResultTypeBadge({ resultType }: { resultType: ResultType }) { - const meta = TYPE_META[resultType] ?? { label: resultType, color: '#64748b' } + const meta = RESULT_TYPE_META[resultType] ?? { label: resultType, color: '#64748b' } return ( void } -function qualityColor(score: number): 'success' | 'warning' | 'error' { - if (score >= 80) return 'success' - if (score >= 60) return 'warning' - return 'error' -} - export function DataQualityWidget({ summary, onNavigate }: DataQualityWidgetProps) { - const color = qualityColor(summary.avgScore) + const color = dataQualityColor(summary.avgScore / 100) return ( diff --git a/src/components/supply/StrongMatchMiniCard.tsx b/src/components/supply/StrongMatchMiniCard.tsx index c02bf23..a2e699f 100644 --- a/src/components/supply/StrongMatchMiniCard.tsx +++ b/src/components/supply/StrongMatchMiniCard.tsx @@ -1,17 +1,12 @@ import { Box, Button, Card, CardContent, Chip, Typography } from '@mui/material' import { useNavigate } from 'react-router' import type { StrongMatchItem } from '../../domain/dashboard' +import { matchScoreHex } from '../../lib/utils' interface StrongMatchMiniCardProps { match: StrongMatchItem } -function scoreColor(score: number): string { - if (score >= 80) return '#1a7a4a' - if (score >= 60) return '#d97706' - return '#c0392b' -} - export function StrongMatchMiniCard({ match }: StrongMatchMiniCardProps) { const navigate = useNavigate() @@ -28,7 +23,7 @@ export function StrongMatchMiniCard({ match }: StrongMatchMiniCardProps) { sx={{ ml: 1, fontWeight: 700, - bgcolor: scoreColor(match.matchScore), + bgcolor: matchScoreHex(match.matchScore), color: 'white', flexShrink: 0, }} diff --git a/src/components/supply/propertyHelpers.ts b/src/components/supply/propertyHelpers.ts index d556b83..6e6b85a 100644 --- a/src/components/supply/propertyHelpers.ts +++ b/src/components/supply/propertyHelpers.ts @@ -1,4 +1,6 @@ import type { AssetType, AvailabilityStatus, ResultType } from '../../domain/enums' +import { RESULT_TYPE_META } from '../../lib/ds' +import { dataQualityColor } from '../../lib/utils' export function getAssetTypeLabel(type: AssetType): string { const labels: Record = { @@ -51,33 +53,11 @@ export function getAvailabilityChipColor(status: AvailabilityStatus): 'success' } export function getResultTypeLabel(type: ResultType): string { - const labels: Record = { - VERIFIED_PORTFOLIO: 'Portfolio', - EXTERNAL_MARKET: 'Direktinserat', - MAISON_WORK: 'Maison Work', - FUTURE_AVAILABILITY: 'Zukunft', - } - return labels[type] ?? type + return RESULT_TYPE_META[type]?.label ?? type } export function getResultTypeColor(type: ResultType): string { - const colors: Record = { - VERIFIED_PORTFOLIO: '#1e3a5f', - EXTERNAL_MARKET: '#1a7a4a', - MAISON_WORK: '#0369a1', - FUTURE_AVAILABILITY: '#7c3aed', - } - return colors[type] ?? '#6b7280' + return RESULT_TYPE_META[type]?.color ?? '#6b7280' } -export function qualityColor(score: number): 'success' | 'warning' | 'error' { - if (score >= 0.8) return 'success' - if (score >= 0.6) return 'warning' - return 'error' -} - -export function confidenceColor(score: number): string { - if (score >= 0.85) return '#1a7a4a' - if (score >= 0.65) return '#1e3a5f' - return '#d97706' -} +export const qualityColor = dataQualityColor diff --git a/src/lib/ds.ts b/src/lib/ds.ts index 23f2573..6bc9ace 100644 --- a/src/lib/ds.ts +++ b/src/lib/ds.ts @@ -60,3 +60,12 @@ export function scoreToDataQualityLevel(score: number): DataQualityLevel { if (score > 0) return 'LOW' return 'INCOMPLETE' } + +// ── Result type meta — single source for labels + colors ───────────────────── + +export const RESULT_TYPE_META: Record = { + VERIFIED_PORTFOLIO: { label: 'Plattform', color: '#1e3a5f', bg: 'rgba(30,58,95,0.10)' }, + EXTERNAL_MARKET: { label: 'Plattform', color: '#1e3a5f', bg: 'rgba(30,58,95,0.10)' }, + MAISON_WORK: { label: 'Maison Work', color: '#0369a1', bg: 'rgba(3,105,161,0.10)' }, + FUTURE_AVAILABILITY: { label: 'Zukunftssignal', color: '#7c3aed', bg: 'rgba(109,40,217,0.10)' }, +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 95fc43d..315138d 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -83,6 +83,20 @@ export function probabilityHex(prob: number): string { return '#c0392b' } +export function matchScoreHex(score: number): string { + if (score >= SCORE_STRONG) return '#1a7a4a' + if (score >= SCORE_MODERATE) return '#d97706' + return '#c0392b' +} + +export function criterionScoreColor(score: number): 'success' | 'warning' | 'error' { + return score >= 70 ? 'success' : score >= 50 ? 'warning' : 'error' +} + +export function criterionScoreTextColor(score: number): string { + return score >= 70 ? '#1a7a4a' : score >= 50 ? '#d97706' : '#c0392b' +} + // ── Misc ────────────────────────────────────────────────────────────────────── export function clamp(value: number, min: number, max: number): number {