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 {