import { useState } from 'react' import { Box, Button, Card, Chip, Typography } from '@mui/material' 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, ResultFeedHeader, ResultFilterBar, UnifiedResultFeed, } from '../../components/results' import { AddToShortlistDialog } from '../../components/shortlist' import { useSessionStore } from '../../stores/sessionStore' import type { ResultType } from '../../domain/enums' import type { UnifiedMatchResult } from '../../domain/unifiedResult' type FilterSource = Exclude | 'ALL' type SortBy = 'score' | 'rent' | 'area' function sortResults(results: UnifiedMatchResult[], sortBy: SortBy): UnifiedMatchResult[] { return [...results].sort((a, b) => { if (sortBy === 'score') return b.matchScore - a.matchScore const propA = a.resultType !== 'FUTURE_AVAILABILITY' ? (a as { property: { rentPricePerSqm: number; areaSqm: number } }).property : null const propB = b.resultType !== 'FUTURE_AVAILABILITY' ? (b as { property: { rentPricePerSqm: number; areaSqm: number } }).property : null if (sortBy === 'rent') return (propA?.rentPricePerSqm ?? 0) - (propB?.rentPricePerSqm ?? 0) if (sortBy === 'area') return (propB?.areaSqm ?? 0) - (propA?.areaSqm ?? 0) return 0 }) } export default function Results() { const navigate = useNavigate() const location = useLocation() const queryClient = useQueryClient() const { currentUser } = useSessionStore() const [filterSource, setFilterSource] = useState('ALL') const [sortBy, setSortBy] = useState('score') const [showFutureAvailability, setShowFutureAvailability] = useState(true) const [showOwnProperties, setShowOwnProperties] = useState(false) const [view, setView] = useState<'list' | 'grid'>(() => (localStorage.getItem('view-results') as 'list' | 'grid') ?? 'list' ) // When coming from NeedBuilder, invalidate so the freshly created need is included const activeNeedIdFromNav = (location.state as { activeNeedId?: string } | null)?.activeNeedId const { data: needResp } = useQuery({ queryKey: ['needs'], queryFn: () => needService.getAll(), // Refetch on mount when navigating from NeedBuilder to pick up the new need refetchOnMount: activeNeedIdFromNav ? 'always' : true, gcTime: 0, }) // Prefer the ID passed from NeedBuilder; fall back to most-recently-created const activeNeed = needResp?.data ? activeNeedIdFromNav ? (needResp.data.find(n => n.id === activeNeedIdFromNav) ?? needResp.data[0]) : [...needResp.data].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() )[0] : undefined // Ensure query cache is invalidated when a new need was just created if (activeNeedIdFromNav) { queryClient.invalidateQueries({ queryKey: ['needs'] }) } const { data: results = [], isLoading } = useUnifiedResults(activeNeed?.id) const filtered = results.filter(r => { if (r.resultType === 'VERIFIED_PORTFOLIO') return showOwnProperties && currentUser?.role === 'PROPERTY_MANAGER' // Future Availability toggle is independent of the source filter if (r.resultType === 'FUTURE_AVAILABILITY') return showFutureAvailability return filterSource === 'ALL' || r.resultType === filterSource }) 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 maisonWorkCount = results.filter(r => r.resultType === 'MAISON_WORK').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 ( { setView(v); localStorage.setItem('view-results', v) }} /> {!isLoading && results.length > 0 && ( 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 }] : []), ]} 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') }, ]} /> )} {activeNeed && ( Aktive Suche: {activeNeed.companyName} Typ: {activeNeed.assetType} Fläche: {activeNeed.requiredArea.min}–{activeNeed.requiredArea.max} m² Standort: {activeNeed.preferredLocations.join(', ')} {activeNeed.budgetRange && ( Budget: max. CHF {activeNeed.budgetRange.maxPerSqm}/m² )} {activeNeed.timing && ( Bezug ab: {new Date(activeNeed.timing.earliestMoveIn).toLocaleDateString('de-CH', { month: 'long', year: 'numeric' })} )} {activeNeed.mustCriteriaText && activeNeed.mustCriteriaText.length > 0 && ( Must-haves: {activeNeed.mustCriteriaText.slice(0, 3).join(' · ')} )} )} {isLoading ? ( ) : sorted.length === 0 ? ( ) : ( )} ) }