import { useState, useMemo } from 'react' import { Box, Button, Chip, Collapse, Drawer, Typography, useMediaQuery, useTheme } from '@mui/material' import { useNavigate } from 'react-router' import { ChevronDown, ChevronUp } from 'lucide-react' import { PageHeader } from '../../components/layout' import { ViewToggle } from '../../components/shared' import { useProperties } from '../../hooks/useProperties' import { useActiveInquiries } from '../../hooks/useInquiries' import { PropertyFilterBar, PropertyTable, PropertyDetailView, PropertyIntelligenceCard } from '../../components/supply' import type { PropertyTableFilters } from '../../components/supply' import type { Property } from '../../domain/property' function applyFilters(properties: Property[], filters: PropertyTableFilters): Property[] { let result = [...properties] if (filters.search) { const q = filters.search.toLowerCase() result = result.filter( p => p.title.toLowerCase().includes(q) || p.location.city.toLowerCase().includes(q) || p.address.street.toLowerCase().includes(q), ) } if (filters.assetTypes && filters.assetTypes.length > 0) { result = result.filter(p => filters.assetTypes!.includes(p.assetType)) } if (filters.availabilityStatus) { result = result.filter(p => p.availabilityStatus === filters.availabilityStatus) } if (filters.sortBy) { const dir = filters.sortDir === 'asc' ? 1 : -1 result.sort((a, b) => { switch (filters.sortBy) { case 'dataQuality': return dir * (a.dataQuality.score - b.dataQuality.score) case 'area': return dir * (a.areaSqm - b.areaSqm) case 'rent': return dir * (a.rentPricePerSqm - b.rentPricePerSqm) case 'confidence': return dir * (a.confidenceScore - b.confidenceScore) case 'availability': return dir * a.availabilityStatus.localeCompare(b.availabilityStatus) default: return 0 } }) } return result } export default function Properties() { const navigate = useNavigate() const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('md')) const [selectedId, setSelectedId] = useState(null) const [filters, setFilters] = useState({}) const [view, setView] = useState<'list' | 'grid'>(() => (localStorage.getItem('view-properties') as 'list' | 'grid') ?? 'list' ) const [columns, setColumns] = useState<3 | 5 | 10>(() => (Number(localStorage.getItem('view-properties-cols')) as 3 | 5 | 10) || 3 ) const [headerOpen, setHeaderOpen] = useState(() => localStorage.getItem('props-header-open') !== 'false' ) const { data: properties = [], isLoading, isError } = useProperties() const { data: inquiries = [] } = useActiveInquiries() const inquiryCountByProperty = useMemo(() => { const map = new Map() for (const inq of inquiries) { if (inq.propertyId) map.set(inq.propertyId, (map.get(inq.propertyId) ?? 0) + 1) } return map }, [inquiries]) const filtered = useMemo(() => applyFilters(properties, filters), [properties, filters]) // Decision-relevant aggregates — single pass over the full list const { matchReady, criticalGaps, staleOrOutdated } = useMemo(() => { const matchReady: typeof properties = [] const criticalGaps: typeof properties = [] const lowConfidence: typeof properties = [] const staleOrOutdated: typeof properties = [] const missingSet = new Set() for (const p of properties) { if ((p.availabilityStatus === 'AVAILABLE_NOW' || p.availabilityStatus === 'AVAILABLE_SOON') && p.confidenceScore >= 0.7 && p.dataQuality.missingCriticalFields.length === 0) matchReady.push(p) if (p.dataQuality.missingCriticalFields.length > 0) { criticalGaps.push(p) p.dataQuality.missingCriticalFields.forEach(f => missingSet.add(f)) } if (p.confidenceScore < 0.55) lowConfidence.push(p) if (p.dataQuality.freshness === 'STALE' || p.dataQuality.freshness === 'OUTDATED') staleOrOutdated.push(p) } return { matchReady, criticalGaps, lowConfidence, staleOrOutdated, allMissingFields: [...missingSet].slice(0, 4), } }, [properties]) return ( {view === 'grid' && ( {([3, 5, 10] as const).map(n => ( { setColumns(n); localStorage.setItem('view-properties-cols', String(n)) }} sx={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', fontSize: '0.72rem', fontWeight: 600, bgcolor: columns === n ? '#152642' : 'transparent', color: columns === n ? 'white' : '#64748b', '&:hover': { bgcolor: columns === n ? '#162d4a' : '#f1f5f9' }, }} > {n} ))} )} { setView(v); localStorage.setItem('view-properties', v) }} /> } /> {/* Collapse toggle strip */} { const next = !headerOpen setHeaderOpen(next) localStorage.setItem('props-header-open', String(next)) }} sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', px: 3, py: 0.5, bgcolor: '#f8fafc', borderBottom: '1px solid #e2e8f0', cursor: 'pointer', flexShrink: 0, userSelect: 'none', '&:hover': { bgcolor: '#f1f5f9' }, }} > Filter & Übersicht {headerOpen ? : } {!isLoading && properties.length > 0 && ( 0 ? '#f0fdf4' : '#fef9c3', color: matchReady.length > 0 ? '#1a7a4a' : '#92400e' }} /> {criticalGaps.length > 0 && ( )} {staleOrOutdated.length > 0 && ( )} )} {view === 'grid' ? ( {filtered.map(p => ( ))} ) : ( )} setSelectedId(null)} slotProps={{ paper: { sx: { width: isMobile ? '100vw' : { md: 520, lg: 580, xl: 640 }, boxShadow: '-4px 0 24px rgba(0,0,0,0.10)' } } }} > {selectedId && ( setSelectedId(null)} /> )} ) }