70c0d79d8c
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>
96 lines
2.6 KiB
TypeScript
96 lines
2.6 KiB
TypeScript
import { Box, Card, CardActions, CardContent, Divider, Typography } from '@mui/material'
|
|
import type { SxProps, Theme } from '@mui/material'
|
|
import type { ReactNode } from 'react'
|
|
import { CardSkeleton } from '../ui/CardSkeleton'
|
|
import { RestrictedState } from '../ui/RestrictedState'
|
|
|
|
interface DecisionCardProps {
|
|
title: string
|
|
subtitle?: string
|
|
badges?: ReactNode
|
|
score?: ReactNode
|
|
body?: ReactNode
|
|
actions?: ReactNode
|
|
selected?: boolean
|
|
isLoading?: boolean
|
|
isRestricted?: boolean
|
|
onClick?: () => void
|
|
sx?: SxProps<Theme>
|
|
}
|
|
|
|
export function DecisionCard({
|
|
title,
|
|
subtitle,
|
|
badges,
|
|
score,
|
|
body,
|
|
actions,
|
|
selected = false,
|
|
isLoading = false,
|
|
isRestricted = false,
|
|
onClick,
|
|
sx,
|
|
}: DecisionCardProps) {
|
|
if (isLoading) return <CardSkeleton hasActions={!!actions} />
|
|
|
|
return (
|
|
<Card
|
|
onClick={onClick}
|
|
sx={{
|
|
outline: selected ? '2px solid #1e3a5f' : 'none',
|
|
outlineOffset: -1,
|
|
bgcolor: selected ? 'rgba(30,58,95,0.03)' : 'background.paper',
|
|
cursor: onClick ? 'pointer' : 'default',
|
|
transition: 'outline 0.1s, background-color 0.1s',
|
|
'&:hover': onClick ? { bgcolor: 'rgba(0,0,0,0.01)' } : {},
|
|
position: 'relative',
|
|
...sx,
|
|
}}
|
|
>
|
|
{isRestricted ? (
|
|
<RestrictedState sx={{ py: 5 }} />
|
|
) : (
|
|
<>
|
|
<CardContent sx={{ pb: body || actions ? 1 : 2 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 1 }}>
|
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
<Typography
|
|
sx={{ fontWeight: 600, fontSize: '0.9375rem', lineHeight: 1.3, mb: subtitle ? 0.25 : 0 }}
|
|
noWrap
|
|
>
|
|
{title}
|
|
</Typography>
|
|
{subtitle && (
|
|
<Typography sx={{ fontSize: '0.8125rem', color: 'text.secondary', lineHeight: 1.4 }}>
|
|
{subtitle}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
{score && <Box sx={{ flexShrink: 0 }}>{score}</Box>}
|
|
</Box>
|
|
{badges && (
|
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 1 }}>
|
|
{badges}
|
|
</Box>
|
|
)}
|
|
</CardContent>
|
|
|
|
{body && (
|
|
<>
|
|
<Divider />
|
|
<CardContent sx={{ py: 1.5 }}>{body}</CardContent>
|
|
</>
|
|
)}
|
|
|
|
{actions && (
|
|
<>
|
|
<Divider />
|
|
<CardActions sx={{ px: 2, py: 1 }}>{actions}</CardActions>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</Card>
|
|
)
|
|
}
|