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
+34
View File
@@ -0,0 +1,34 @@
import { MockupPropertyProvider } from '../provider/MockupPropertyProvider'
import type { DataQualitySummary } from '../domain/dashboard'
export const dataQualityService = {
async getPortfolioQualitySummary(): Promise<DataQualitySummary> {
const properties = await MockupPropertyProvider.getAll()
const avgScoreRaw =
properties.length > 0
? properties.reduce((sum, p) => sum + (p.dataQuality?.score ?? 0), 0) / properties.length
: 0
const fieldCounts = properties
.flatMap(p => p.dataQuality?.missingCriticalFields ?? [])
.reduce<Record<string, number>>((acc, f) => {
acc[f] = (acc[f] ?? 0) + 1
return acc
}, {})
const topMissingFields = Object.entries(fieldCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([f]) => f)
return {
avgScore: Math.round(avgScoreRaw * 100),
critical: properties.filter(p => (p.dataQuality?.score ?? 0) < 0.5).length,
propertiesWithMissingCritical: properties.filter(
p => (p.dataQuality?.missingCriticalFields?.length ?? 0) > 0,
).length,
topMissingFields,
}
},
}