feat: F027 screen-level decision design — DecisionContextPanel + Properties + Results
- Add DecisionContextPanel (src/components/ui): compact decision-framing strip with question, metric chips, expandable risks/missing data, actions. Answers: Welche Entscheidung? Welche Daten helfen/fehlen? Welche Risiken? Welche nächste Aktion? - Properties.tsx: replace generic listing header with decision frame: "Welche Objekte sind matchbereit und wo blockieren Datenlücken Matches?" Surfaces: matchbereit count, critical gaps, low-confidence count, stale data, missing field names, risk statements. Primary CTA → Datenpflege. - Results.tsx: add decision frame above result feed: "Welche Treffer lohnen sich für die Shortlist — welche tragen Risiken?" Surfaces: strong-match count, result-type breakdown, data-gap count, future-signal probabilistic risk warning. CTAs → Compare, refine search. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { useNavigate, useLocation } from 'react-router'
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useUnifiedResults } from '../../hooks/useUnifiedResults'
|
||||
import { needService } from '../../services/needService'
|
||||
import { DecisionContextPanel } from '../../components/ui'
|
||||
import {
|
||||
FeedEmptyState,
|
||||
FeedSkeleton,
|
||||
@@ -71,9 +72,14 @@ export default function Results() {
|
||||
const verifiedCount = results.filter(r => r.resultType === 'VERIFIED_PORTFOLIO').length
|
||||
const externalCount = results.filter(r => r.resultType === 'EXTERNAL_MARKET').length
|
||||
const futureCount = results.filter(r => r.resultType === 'FUTURE_AVAILABILITY').length
|
||||
const strongCount = results.filter(r => r.matchScore >= 80).length
|
||||
const missingDataCount = results.filter(r =>
|
||||
'match' in r && Array.isArray((r as { match?: { missingData?: unknown[] } }).match?.missingData) &&
|
||||
((r as { match?: { missingData?: unknown[] } }).match?.missingData?.length ?? 0) > 0
|
||||
).length
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
<AddToShortlistDialog />
|
||||
<ResultFeedHeader
|
||||
total={sorted.length}
|
||||
@@ -82,7 +88,29 @@ export default function Results() {
|
||||
futureCount={futureCount}
|
||||
/>
|
||||
|
||||
<Box sx={{ px: 3, py: 3 }}>
|
||||
{!isLoading && results.length > 0 && (
|
||||
<DecisionContextPanel
|
||||
decision="Welche Treffer lohnen sich für die Shortlist — und welche tragen Risiken, die zuerst geprüft werden müssen?"
|
||||
context={activeNeed ? `Suche: ${activeNeed.assetType} · ${activeNeed.requiredArea.min}–${activeNeed.requiredArea.max} m² · ${activeNeed.preferredLocations.join(', ')}` : undefined}
|
||||
metrics={[
|
||||
...(strongCount > 0 ? [{ label: 'starke Treffer (≥80)', value: strongCount, severity: 'positive' as const }] : []),
|
||||
...(verifiedCount > 0 ? [{ label: 'verifiziertes Portfolio', value: verifiedCount, severity: 'neutral' as const }] : []),
|
||||
...(externalCount > 0 ? [{ label: 'externer Markt', value: externalCount, severity: 'neutral' as const }] : []),
|
||||
...(futureCount > 0 ? [{ label: 'Zukunftssignale', value: futureCount, severity: 'warning' as const }] : []),
|
||||
...(missingDataCount > 0 ? [{ label: 'mit Datenlücken', value: missingDataCount, severity: 'warning' as const }] : []),
|
||||
]}
|
||||
risks={[
|
||||
...(futureCount > 0 ? [`${futureCount} Zukunftssignal${futureCount > 1 ? 'e' : ''} sind probabilistisch — keine bestätigte Verfügbarkeit`] : []),
|
||||
...(missingDataCount > 0 ? [`${missingDataCount} Treffer mit fehlenden Daten — Einschätzung eingeschränkt`] : []),
|
||||
]}
|
||||
actions={[
|
||||
{ label: 'Vergleich öffnen', onClick: () => navigate('/demand/compare') },
|
||||
{ label: 'Suche anpassen', onClick: () => navigate('/demand/ai-search') },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Box sx={{ flex: 1, overflowY: 'auto', px: 3, py: 3 }}>
|
||||
{activeNeed && (
|
||||
<Card sx={{ bgcolor: '#eff6ff', p: 2, mb: 2, border: '1px solid #bfdbfe' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 2 }}>
|
||||
|
||||
Reference in New Issue
Block a user