refactor: split large page components + taxonomy/HeatBadge/FutureAvailability improvements

- Taxonomy: merge VERIFIED_PORTFOLIO + EXTERNAL_MARKET display → 'Plattform' (dark blue) across all surfaces
- HeatBadge: new flame indicator for hot properties (grid, list, pipeline views)
- FutureAvailabilityContextPanel: richer detail page with AI summary, strategic assessment, sources
- Refactor Pipeline.tsx (630→152 lines) → pipeline/PipelineCard, PipelineColumn, PipelineDetailPanel, pipelineConstants, pipelineUtils
- Refactor IntelligenceMatchCard.tsx (483→179 lines) → FutureAvailabilityCard extracted
- Refactor MatchDetail.tsx (559→464 lines) → useMatchDetailData hook, MatchDetailPropertyDetails
- Refactor Compare.tsx (638→485 lines) → compareUtils, CompareCriteriaCard extracted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-23 23:36:03 +02:00
parent 72e4f08900
commit 22c195b4a5
28 changed files with 1615 additions and 1282 deletions
+12 -11
View File
@@ -14,10 +14,9 @@ import {
} from '../../components/results'
import { AddToPipelineDialog } from '../../components/shortlist'
import { useSessionStore } from '../../stores/sessionStore'
import type { ResultType } from '../../domain/enums'
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
type FilterSource = Exclude<ResultType, 'FUTURE_AVAILABILITY' | 'VERIFIED_PORTFOLIO'> | 'ALL'
type FilterSource = 'ALL' | 'PLATFORM' | 'MAISON_WORK'
type SortBy = 'score' | 'rent' | 'area'
function sortResults(results: UnifiedMatchResult[], sortBy: SortBy): UnifiedMatchResult[] {
@@ -71,18 +70,21 @@ export default function Results() {
const isStaff = currentUser?.role === 'PROPERTY_MANAGER' || currentUser?.role === 'ORGANIZATION_ADMIN'
const filtered = results.filter(r => {
if (r.resultType === 'VERIFIED_PORTFOLIO') return showOwnProperties
// Future Availability toggle is independent of the source filter
if (r.resultType === 'FUTURE_AVAILABILITY') return showFutureAvailability
return filterSource === 'ALL' || r.resultType === filterSource
if (r.resultType === 'VERIFIED_PORTFOLIO') {
if (!showOwnProperties) return false
return filterSource === 'ALL' || filterSource === 'PLATFORM'
}
if (filterSource === 'ALL') return true
if (filterSource === 'PLATFORM') return r.resultType === 'EXTERNAL_MARKET'
return r.resultType === filterSource // MAISON_WORK
})
const sorted = sortResults(filtered, sortBy)
const verifiedCount = results.filter(r => r.resultType === 'VERIFIED_PORTFOLIO').length
const externalCount = results.filter(r => r.resultType === 'EXTERNAL_MARKET').length
const platformCount = results.filter(r => r.resultType === 'VERIFIED_PORTFOLIO' || r.resultType === 'EXTERNAL_MARKET').length
const maisonWorkCount = results.filter(r => r.resultType === 'MAISON_WORK').length
const futureCount = results.filter(r => r.resultType === 'FUTURE_AVAILABILITY').length
const futureCount = results.filter(r => r.resultType === 'FUTURE_AVAILABILITY').length
const strongCount = filtered.filter(r => r.matchScore >= 80).length
const missingDataCount = results.filter(r =>
'match' in r && Array.isArray((r as { match?: { missingData?: unknown[] } }).match?.missingData) &&
@@ -94,8 +96,8 @@ export default function Results() {
<AddToPipelineDialog />
<ResultFeedHeader
total={sorted.length}
verifiedCount={verifiedCount}
externalCount={externalCount}
platformCount={platformCount}
maisonWorkCount={maisonWorkCount}
futureCount={futureCount}
view={view}
onViewChange={v => { setView(v); localStorage.setItem('view-results', v) }}
@@ -107,7 +109,6 @@ export default function Results() {
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 }] : []),
...(externalCount > 0 ? [{ label: 'Direktinserate', value: externalCount, severity: 'neutral' as const }] : []),
...(maisonWorkCount > 0 ? [{ label: 'Maison Work', value: maisonWorkCount, 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 }] : []),