feat(design-system): F003 – complete design system foundation

Design tokens:
- lib/ds.ts: DS_COLORS (resultType/confidence/risk/availability/freshness/dataQuality), scoreToConfidenceLevel(), scoreToDataQualityLevel()
- lib/constants.ts: added CONFIDENCE_LABELS, DATA_QUALITY_LABELS

Badge system (src/components/badges/):
- ResultTypeBadge, ConfidenceBadge, RiskBadge, AvailabilityBadge, FreshnessBadge, DataQualityBadge
- All use DS_COLORS, MUI Chip size=small, lucide icons, German labels from constants

Score components (src/components/scores/):
- ConfidenceScore: numeric % with color from utils.confidenceHex
- DataQualityScore: LinearProgress + qualitative label
- ScoreBar: horizontal bar with label/value/weight
- ScoreBreakdownMini: ScoreFactor[] mapped to ScoreBars with Tooltip explanations

Card system (src/components/cards/):
- DecisionCard: header+badges+score+body+actions, selected/loading/restricted states
- CompactCard: single-row 48px card with leading/trailing slots
- MetricCard: KPI card with label/value/delta/icon (extracted pattern from SupplyDashboard)

Panel system (src/components/panels/):
- InfoPanel: outlined Paper with icon+title header
- DetailPanel: Accordion sections in bordered Paper
- WarningPanel: MUI Alert with warning/critical severity

Table components (src/components/tables/):
- DataTableShell: Paper+TableContainer+Table with loading/empty/toolbar slots
- TableToolbar: flex row with title/count/filters/actions
- FilterBar: deletable Chips with clear-all
- EmptyTableState: centered empty row spanning all cols
- LoadingRows: Skeleton cell rows

Form components (src/components/forms/):
- TextInput: MUI TextField wrapper with onChange(string) signature
- SelectField: generic SelectField<T extends string> with MUI Select
- PriorityChipGroup: togglable Chip set (single/multi select)
- FieldWithConfidence: label+value+ConfidenceBadge for AI-parsed fields

UI additions (src/components/ui/):
- CardSkeleton, PanelLoadingState, UnauthorizedState, RestrictedState

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-15 11:34:25 +02:00
parent 5ef8b9d69d
commit 70c0d79d8c
38 changed files with 1385 additions and 0 deletions
@@ -0,0 +1,39 @@
import { Chip } from '@mui/material'
import { CheckCircle2, Clock, Sparkles, XCircle, HelpCircle } from 'lucide-react'
import type { AvailabilityStatus } from '../../domain/enums'
import { DS_COLORS } from '../../lib/ds'
import { AVAILABILITY_LABELS } from '../../lib/constants'
interface AvailabilityBadgeProps {
status: AvailabilityStatus
size?: 'small' | 'medium'
}
const ICONS: Record<AvailabilityStatus, React.ElementType> = {
AVAILABLE_NOW: CheckCircle2,
AVAILABLE_SOON: Clock,
FUTURE_SIGNAL: Sparkles,
OCCUPIED: XCircle,
UNKNOWN: HelpCircle,
}
export function AvailabilityBadge({ status, size = 'small' }: AvailabilityBadgeProps) {
const { bg, fg } = DS_COLORS.availability[status]
const Icon = ICONS[status]
return (
<Chip
size={size}
label={AVAILABILITY_LABELS[status] ?? status}
icon={<Icon size={11} color={fg} />}
aria-label={`Verfügbarkeit: ${AVAILABILITY_LABELS[status] ?? status}`}
sx={{
bgcolor: bg,
color: fg,
border: 'none',
fontWeight: 600,
fontSize: '0.7rem',
'& .MuiChip-icon': { ml: 0.5 },
}}
/>
)
}
+35
View File
@@ -0,0 +1,35 @@
import { Chip } from '@mui/material'
import { ShieldCheck } from 'lucide-react'
import type { ConfidenceLevel } from '../../domain/enums'
import { DS_COLORS, scoreToConfidenceLevel } from '../../lib/ds'
import { CONFIDENCE_LABELS } from '../../lib/constants'
interface ConfidenceBadgeProps {
level?: ConfidenceLevel
score?: number
size?: 'small' | 'medium'
}
export function ConfidenceBadge({ level, score, size = 'small' }: ConfidenceBadgeProps) {
const resolved: ConfidenceLevel =
level ?? (score !== undefined ? scoreToConfidenceLevel(score) : 'MEDIUM')
const { bg, fg } = DS_COLORS.confidence[resolved]
const scoreLabel = score !== undefined ? ` (${Math.round(score * 100)}%)` : ''
const label = `${CONFIDENCE_LABELS[resolved] ?? resolved}${scoreLabel}`
return (
<Chip
size={size}
label={label}
icon={<ShieldCheck size={11} color={fg} />}
aria-label={`Konfidenz: ${label}`}
sx={{
bgcolor: bg,
color: fg,
border: 'none',
fontWeight: 600,
fontSize: '0.7rem',
'& .MuiChip-icon': { ml: 0.5 },
}}
/>
)
}
@@ -0,0 +1,44 @@
import { Chip } from '@mui/material'
import { CheckCircle2, AlertTriangle, XCircle } from 'lucide-react'
import type { DataQualityLevel } from '../../domain/enums'
import { DS_COLORS, scoreToDataQualityLevel } from '../../lib/ds'
import { DATA_QUALITY_LABELS } from '../../lib/constants'
interface DataQualityBadgeProps {
level?: DataQualityLevel
score?: number
showScore?: boolean
size?: 'small' | 'medium'
}
const ICONS: Record<DataQualityLevel, React.ElementType> = {
HIGH: CheckCircle2,
MEDIUM: AlertTriangle,
LOW: XCircle,
INCOMPLETE: XCircle,
}
export function DataQualityBadge({ level, score, showScore = false, size = 'small' }: DataQualityBadgeProps) {
const resolved: DataQualityLevel =
level ?? (score !== undefined ? scoreToDataQualityLevel(score) : 'LOW')
const { bg, fg } = DS_COLORS.dataQuality[resolved]
const Icon = ICONS[resolved]
const scoreLabel = showScore && score !== undefined ? ` ${Math.round(score * 100)}%` : ''
const label = `${DATA_QUALITY_LABELS[resolved] ?? resolved}${scoreLabel}`
return (
<Chip
size={size}
label={label}
icon={<Icon size={11} color={fg} />}
aria-label={`Datenqualität: ${label}`}
sx={{
bgcolor: bg,
color: fg,
border: 'none',
fontWeight: 600,
fontSize: '0.7rem',
'& .MuiChip-icon': { ml: 0.5 },
}}
/>
)
}
+37
View File
@@ -0,0 +1,37 @@
import { Chip } from '@mui/material'
import { Zap, Clock, AlertCircle } from 'lucide-react'
import type { FreshnessStatus } from '../../domain/enums'
import { DS_COLORS } from '../../lib/ds'
import { FRESHNESS_LABELS } from '../../lib/constants'
interface FreshnessBadgeProps {
status: FreshnessStatus
size?: 'small' | 'medium'
}
const ICONS: Record<FreshnessStatus, React.ElementType> = {
FRESH: Zap,
STALE: Clock,
OUTDATED: AlertCircle,
}
export function FreshnessBadge({ status, size = 'small' }: FreshnessBadgeProps) {
const { bg, fg } = DS_COLORS.freshness[status]
const Icon = ICONS[status]
return (
<Chip
size={size}
label={FRESHNESS_LABELS[status] ?? status}
icon={<Icon size={11} color={fg} />}
aria-label={`Datenaktualität: ${FRESHNESS_LABELS[status] ?? status}`}
sx={{
bgcolor: bg,
color: fg,
border: 'none',
fontWeight: 600,
fontSize: '0.7rem',
'& .MuiChip-icon': { ml: 0.5 },
}}
/>
)
}
+37
View File
@@ -0,0 +1,37 @@
import { Chip } from '@mui/material'
import { ShieldCheck, Globe, Sparkles } from 'lucide-react'
import type { ResultType } from '../../domain/enums'
import { DS_COLORS } from '../../lib/ds'
import { RESULT_TYPE_LABELS } from '../../lib/constants'
interface ResultTypeBadgeProps {
type: ResultType
size?: 'small' | 'medium'
}
const ICONS: Record<ResultType, React.ElementType> = {
VERIFIED_PORTFOLIO: ShieldCheck,
EXTERNAL_MARKET: Globe,
FUTURE_AVAILABILITY: Sparkles,
}
export function ResultTypeBadge({ type, size = 'small' }: ResultTypeBadgeProps) {
const { bg, fg } = DS_COLORS.resultType[type]
const Icon = ICONS[type]
return (
<Chip
size={size}
label={RESULT_TYPE_LABELS[type] ?? type}
icon={<Icon size={11} color={fg} />}
aria-label={RESULT_TYPE_LABELS[type] ?? type}
sx={{
bgcolor: bg,
color: fg,
border: 'none',
fontWeight: 600,
fontSize: '0.7rem',
'& .MuiChip-icon': { ml: 0.5 },
}}
/>
)
}
+31
View File
@@ -0,0 +1,31 @@
import { Chip } from '@mui/material'
import { AlertTriangle, Shield } from 'lucide-react'
import type { RiskLevel } from '../../domain/enums'
import { DS_COLORS } from '../../lib/ds'
import { RISK_LABELS } from '../../lib/constants'
interface RiskBadgeProps {
level: RiskLevel
size?: 'small' | 'medium'
}
export function RiskBadge({ level, size = 'small' }: RiskBadgeProps) {
const { bg, fg } = DS_COLORS.risk[level]
const Icon = level === 'LOW' || level === 'MEDIUM' ? Shield : AlertTriangle
return (
<Chip
size={size}
label={RISK_LABELS[level] ?? level}
icon={<Icon size={11} color={fg} />}
aria-label={`Risiko: ${RISK_LABELS[level] ?? level}`}
sx={{
bgcolor: bg,
color: fg,
border: 'none',
fontWeight: 600,
fontSize: '0.7rem',
'& .MuiChip-icon': { ml: 0.5 },
}}
/>
)
}
+6
View File
@@ -0,0 +1,6 @@
export { ResultTypeBadge } from './ResultTypeBadge'
export { ConfidenceBadge } from './ConfidenceBadge'
export { RiskBadge } from './RiskBadge'
export { AvailabilityBadge } from './AvailabilityBadge'
export { FreshnessBadge } from './FreshnessBadge'
export { DataQualityBadge } from './DataQualityBadge'