refactor: consolidate duplicated UI score/color/badge logic into lib/
- Add matchScoreHex(), criterionScoreColor(), criterionScoreTextColor() to lib/utils.ts
- Add RESULT_TYPE_META (labels + colors) to lib/ds.ts as single source of truth
- Remove 5 local scoreColor() functions: StrongMatchMiniCard, MatchListCard,
pipelineUtils (re-exported as matchScoreHex), CriterionRow, ScoreBreakdownPanel,
ScoreInlineBreakdown
- Remove local RESULT_TYPE_META/TYPE_META from MatchCardHeader, ResultTypeBadge;
remove RESULT_TYPE_LABEL/COLOR from pipelineConstants — all now use lib/ds.ts
- Replace local confidenceColor() in MatchCardHeader, ConfidenceFieldBadge,
ReliabilityScorePanel with confidenceHex() from lib/utils.ts
- Replace local qualityColor() in propertyHelpers, DataQualityWidget with
dataQualityColor() from lib/utils.ts
- Fix FUTURE_AVAILABILITY label inconsistency ('Future'/'Zukunft' → 'Zukunftssignal')
- Fix EXTERNAL_MARKET label inconsistency ('Direktinserat' → 'Plattform')
- tsc --noEmit passes with zero errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 (
|
||||
<Chip
|
||||
label={`${Math.round(confidence * 100)}%`}
|
||||
size={size}
|
||||
sx={{ bgcolor: confidenceColor(confidence), color: 'white', fontWeight: 700, fontSize: 11 }}
|
||||
sx={{ bgcolor: confidenceHex(confidence), color: 'white', fontWeight: 700, fontSize: 11 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<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' },
|
||||
}
|
||||
|
||||
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) {
|
||||
<Chip
|
||||
label={`${confPct}% Konfidenz`}
|
||||
size="small"
|
||||
sx={{ bgcolor: confidenceColor(vm.confidenceScore), color: 'white', fontSize: 11 }}
|
||||
sx={{ bgcolor: confidenceHex(vm.confidenceScore), color: 'white', fontSize: 11 }}
|
||||
/>
|
||||
{vm.availabilityLabel && (
|
||||
<Chip label={vm.availabilityLabel} size="small" variant="outlined" sx={{ fontSize: 11 }} />
|
||||
|
||||
@@ -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<string, string> = {
|
||||
|
||||
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 (
|
||||
<Box sx={{ mb: 1.25 }}>
|
||||
@@ -54,7 +47,7 @@ function FactorRow({ factor, maxWeight }: { factor: ScoreFactor; maxWeight: numb
|
||||
<Typography variant="caption" sx={{ color: '#cbd5e1', fontSize: '0.65rem' }}>{pct}%</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 0.75 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: scoreTextColor(factor.score) }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: criterionScoreTextColor(factor.score) }}>
|
||||
{factor.score}/100
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: '#94a3b8', fontSize: '0.68rem', minWidth: 40, textAlign: 'right' }}>
|
||||
@@ -82,7 +75,7 @@ export function ScoreInlineBreakdown({ scoreBreakdown: sb, allFactors, compact =
|
||||
<Typography variant="caption" sx={{ fontFamily: 'monospace', color: '#1e293b', display: 'block', lineHeight: 1.5 }}>
|
||||
Hart {sb.hardMatchScore} × 60% + Soft {sb.softFactorScore} × 40%
|
||||
{' = '}{hardContrib} + {softContrib}{' = '}
|
||||
<span style={{ fontWeight: 800, color: scoreTextColor(sb.totalScore) }}>{sb.totalScore}</span>
|
||||
<span style={{ fontWeight: 800, color: criterionScoreTextColor(sb.totalScore) }}>{sb.totalScore}</span>
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@@ -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<string, { label: string; bg: string; color: string }> = {
|
||||
STRONG: { label: 'Stark', bg: '#f0fdf4', color: '#1a7a4a' },
|
||||
@@ -10,12 +11,6 @@ const STRENGTH_META: Record<string, { label: string; bg: string; color: string }
|
||||
WEAK: { label: 'Schwach', bg: '#fff1f2', color: '#c0392b' },
|
||||
}
|
||||
|
||||
function scoreColor(score: number) {
|
||||
if (score >= 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',
|
||||
|
||||
@@ -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
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 1 }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: scoreTextColor(factor.score) }}>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: criterionScoreTextColor(factor.score) }}>
|
||||
{factor.score}/100
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: '#64748b', fontSize: '0.68rem', minWidth: 44, textAlign: 'right' }}>
|
||||
|
||||
@@ -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)
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>Gesamt-Score</Typography>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{ fontWeight: 800, color: scoreTextColor(Math.round(baseSum)) }}
|
||||
sx={{ fontWeight: 800, color: criterionScoreTextColor(Math.round(baseSum)) }}
|
||||
>
|
||||
{baseSum}/100
|
||||
</Typography>
|
||||
@@ -188,13 +179,13 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) {
|
||||
}
|
||||
<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: 700, color: scoreTextColor(f.score), ml: 1 }}>{f.score}/100</Typography>
|
||||
<Typography variant="caption" sx={{ fontWeight: 700, color: criterionScoreTextColor(f.score), ml: 1 }}>{f.score}/100</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={f.score}
|
||||
color={scoreColor(f.score)}
|
||||
color={criterionScoreColor(f.score)}
|
||||
sx={{ height: 5, borderRadius: 3, mb: 0.35, ml: 2.25 }}
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', ml: 2.25, lineHeight: 1.3 }}>
|
||||
@@ -266,7 +257,7 @@ function SignalScoreDerivation({ match, signal }: SignalScoreDerivationProps) {
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 700 }}>Gesamt-Score</Typography>
|
||||
<Typography
|
||||
variant="h4"
|
||||
sx={{ fontWeight: 800, color: scoreTextColor(sb.totalScore) }}
|
||||
sx={{ fontWeight: 800, color: criterionScoreTextColor(sb.totalScore) }}
|
||||
>
|
||||
{sb.totalScore}/100
|
||||
</Typography>
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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({
|
||||
<HeatBadge propertyId={item.propertyId} size="sm" />
|
||||
<Chip
|
||||
size="small"
|
||||
label={RESULT_TYPE_LABEL[item.resultType] ?? item.resultType}
|
||||
sx={{ bgcolor: RESULT_TYPE_COLOR[item.resultType] ?? '#475569', color: 'white', fontSize: 10, height: 20, fontWeight: 600 }}
|
||||
label={RESULT_TYPE_META[item.resultType]?.label ?? item.resultType}
|
||||
sx={{ bgcolor: RESULT_TYPE_META[item.resultType]?.color ?? '#475569', color: 'white', fontSize: 10, height: 20, fontWeight: 600 }}
|
||||
/>
|
||||
<Chip
|
||||
size="small"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PipelineStage } from '../../domain/pipeline'
|
||||
export { RESULT_TYPE_META } from '../../lib/ds'
|
||||
|
||||
// ── Stage config ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -20,20 +21,6 @@ export const NEXT_STAGE: Partial<Record<PipelineStage, { key: PipelineStage; lab
|
||||
NEGOTIATION: { key: 'CLOSED_WON', label: 'Als gewonnen markieren' },
|
||||
}
|
||||
|
||||
export const RESULT_TYPE_LABEL: Record<string, string> = {
|
||||
VERIFIED_PORTFOLIO: 'Plattform',
|
||||
EXTERNAL_MARKET: 'Plattform',
|
||||
MAISON_WORK: 'Maison Work',
|
||||
FUTURE_AVAILABILITY: 'Future',
|
||||
}
|
||||
|
||||
export const RESULT_TYPE_COLOR: Record<string, string> = {
|
||||
VERIFIED_PORTFOLIO: '#1e3a5f',
|
||||
EXTERNAL_MARKET: '#1e3a5f',
|
||||
MAISON_WORK: '#0369a1',
|
||||
FUTURE_AVAILABILITY: '#7c3aed',
|
||||
}
|
||||
|
||||
export const MOCK_DOCS: Record<string, { name: string; date: string }[]> = {
|
||||
'pl-001': [
|
||||
{ name: 'Expose_Zollstrasse12.pdf', date: '05.05.2026' },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import type { ResultType } from '../../domain/enums'
|
||||
import { Chip } from '@mui/material'
|
||||
|
||||
const TYPE_META: Record<string, { label: string; color: string }> = {
|
||||
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 (
|
||||
<Chip
|
||||
label={meta.label}
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { Box, Button, Card, CardContent, Chip, LinearProgress, Typography } from '@mui/material'
|
||||
import type { DataQualitySummary } from '../../domain/dashboard'
|
||||
import { dataQualityColor } from '../../lib/utils'
|
||||
|
||||
interface DataQualityWidgetProps {
|
||||
summary: DataQualitySummary
|
||||
onNavigate: () => 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 (
|
||||
<Card>
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
|
||||
@@ -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<string, string> = {
|
||||
@@ -51,33 +53,11 @@ export function getAvailabilityChipColor(status: AvailabilityStatus): 'success'
|
||||
}
|
||||
|
||||
export function getResultTypeLabel(type: ResultType): string {
|
||||
const labels: Record<string, string> = {
|
||||
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<string, string> = {
|
||||
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
|
||||
|
||||
@@ -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<string, { label: string; color: string; bg: string }> = {
|
||||
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)' },
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user