feat: F006 supply dashboard — complete spec implementation

- dataQualityService with getPortfolioQualitySummary()
- Service methods: getDashboardPropertiesSummary, getStrongMatches,
  getSignalSummary, getDashboardTasks added to respective services
- dashboardService uses Promise.allSettled for partial error resilience
- KpiCard: onClick + Tooltip support
- KpiGrid: navigate actions and tooltips per card
- StrongMatchMiniCard: Match Center, Prüfung, Vergleichen action buttons
- FutureSignalWidget: time horizon distribution (0–6, 6–12, 12–24 Mo.)
- SupplyDashboard: DashboardHeader, empty state, error state,
  partial widget errors, permission-based rendering per UserRole

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-15 16:31:54 +02:00
parent 46acca9e62
commit 7b7be0b436
12 changed files with 454 additions and 183 deletions
+42 -21
View File
@@ -1,4 +1,4 @@
import { Card, CardContent, Chip, Typography } from '@mui/material'
import { Card, CardContent, CardActionArea, Chip, Tooltip, Typography } from '@mui/material'
import type { KpiCardData } from '../../domain/dashboard'
interface KpiCardProps {
@@ -9,32 +9,53 @@ export function KpiCard({ card }: KpiCardProps) {
const trendColor =
card.trend === 'up' ? 'success' : card.trend === 'down' ? 'error' : 'default'
return (
const content = (
<CardContent sx={{ p: 2.5 }}>
<Typography
variant="overline"
sx={{ color: 'text.secondary', lineHeight: 1.4, display: 'block' }}
>
{card.label}
</Typography>
<Typography variant="h4" sx={{ fontWeight: 700, mt: 0.5, mb: 0.5 }}>
{card.value}
</Typography>
{card.trendLabel && (
<Chip
label={card.trendLabel}
size="small"
color={trendColor as 'success' | 'error' | 'default'}
sx={{ mt: 0.5 }}
/>
)}
</CardContent>
)
const card_ = (
<Card
sx={{
borderTop: card.accent ? `4px solid ${card.accent}` : '4px solid transparent',
height: '100%',
cursor: card.onClick ? 'pointer' : 'default',
}}
>
<CardContent sx={{ p: 2.5 }}>
<Typography
variant="overline"
sx={{ color: 'text.secondary', lineHeight: 1.4, display: 'block' }}
>
{card.label}
</Typography>
<Typography variant="h4" sx={{ fontWeight: 700, mt: 0.5, mb: 0.5 }}>
{card.value}
</Typography>
{card.trendLabel && (
<Chip
label={card.trendLabel}
size="small"
color={trendColor as 'success' | 'error' | 'default'}
sx={{ mt: 0.5 }}
/>
)}
</CardContent>
{card.onClick ? (
<CardActionArea onClick={card.onClick} sx={{ height: '100%', alignItems: 'flex-start' }}>
{content}
</CardActionArea>
) : (
content
)}
</Card>
)
if (card.tooltip) {
return (
<Tooltip title={card.tooltip} placement="top" arrow>
<span style={{ display: 'contents' }}>{card_}</span>
</Tooltip>
)
}
return card_
}