feat: F011 match card system

- MatchCardViewModel: typed view model decoupling card from raw API data
- Sub-components: MatchCardHeader, MatchScoreDisplay, MatchReasonList, TradeoffList, MatchDataQualitySummary, MatchActionToolbar, MatchCardSkeleton, MatchCardRestrictedState
- 4 variants: MatchCardCompact, MatchCardExpanded, MatchCardReview, MatchCardCompareMini
- MatchCard: main entry point with variant prop
- matchCardAdapter: buildMatchCardViewModel() converts UnifiedMatchResult -> ViewModel
- UnifiedResultCard: now thin wrapper using MatchCardCompact via adapter
- FUTURE_AVAILABILITY always shows non-dismissable disclaimer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-16 13:39:35 +02:00
parent 307b450807
commit ea221def74
17 changed files with 972 additions and 147 deletions
@@ -0,0 +1,56 @@
import type { ResultType } from '../../domain/enums'
import type { TradeOff, Risk, MissingDataItem } from '../../domain/match'
export type MatchCardVariant = 'compact' | 'expanded' | 'review' | 'compare-mini'
export type MatchCardActionType =
| 'OPEN_DETAIL'
| 'ADD_COMPARE'
| 'SAVE_SHORTLIST'
| 'SEND_REVIEW'
| 'REJECT'
| 'REQUEST_DATA'
export interface MatchCardAction {
id: string
label: string
actionType: MatchCardActionType
variant?: 'primary' | 'secondary' | 'danger'
disabled?: boolean
onClick: () => void
}
export interface MatchCardReason {
type: 'HARD_FACT' | 'SOFT_FACTOR' | 'STRATEGIC'
label: string
explanation: string
score: number
}
export interface MatchCardViewModel {
id: string
title: string
resultType: ResultType
assetType?: string
matchScore: number
confidenceScore: number // 01
dataQualityScore: number // 01
locationLabel: string
availabilityLabel?: string
sourceLabel?: string
externalUrl?: string
reasons: MatchCardReason[] // max 3: HARD_FACT, SOFT_FACTOR, STRATEGIC
tradeoffs: TradeOff[]
risks: Risk[]
missingData: MissingDataItem[]
actions: MatchCardAction[]
disclaimer?: string // required for FUTURE_AVAILABILITY
explainabilitySummary?: string
// States
isSelected?: boolean
isCompareSelected?: boolean
isRestricted?: boolean
isReviewRequired?: boolean
isStaleData?: boolean
}