// desktop-screen-results.jsx — Search Results desktop screen function ResultsDesktop({ active = 'kauf', onNav = () => {}, onOpenDetail = () => {}, onOpenAI = () => {}, onRefresh = () => {}, onEvaluate = () => {}, onExport = () => {}, searchId = null, ctx = {}, searchQuery = '', topBarProps = {}, routeView = null, onSetResultsView = null, } = {}) { const listings = desktopListings(ctx); const searches = desktopSearches(ctx); const search = searches.find(s => String(s.id) === String(searchId)) || searches.find(s => s.category === 'kauf') || searches[0]; const defaultFilters = React.useCallback(() => ({ platforms: { immoscout24: true, kleinanzeigen: true, wohnungsboerse: true }, onlyScored: false, minScore: 0, provFreeOnly: false, balcony: false, elevator: false, denkmal: false, priceMin: search?.priceMin || '', priceMax: search?.priceMax || '', roomsMin: search?.roomsMin || '', roomsMax: search?.roomsMax || '', areaMin: search?.areaMin || '', areaMax: search?.areaMax || '', }), [search?.id]); const [filters, setFilters] = React.useState(defaultFilters); const [sort, setSort] = React.useState('score'); const [view, setView] = React.useState(['grid', 'list', 'map'].includes(routeView) ? routeView : 'grid'); const [page, setPage] = React.useState(1); const setResultView = (nextView) => { setView(nextView); onSetResultsView?.(nextView, search); }; React.useEffect(() => { setFilters(defaultFilters()); setPage(1); }, [defaultFilters]); React.useEffect(() => { if (['grid', 'list', 'map'].includes(routeView) && routeView !== view) setView(routeView); }, [routeView]); React.useEffect(() => setPage(1), [searchQuery, sort, view, JSON.stringify(filters)]); if (!search) { return (

Keine Suche gefunden

Legen Sie zuerst eine Suche an, damit ImmoBot Treffer sammelt.

); } const base = listings.filter(l => String(l.searchId || l.task_id) === String(search.id) || (!l.searchId && l.type === search.category)); const inRange = (value, min, max) => { const n = Number(value || 0); const low = Number(min || 0); const high = Number(max || 0); return (!low || n >= low) && (!high || n <= high); }; const hasFeature = (listing, token) => desktopFeatureText(listing).includes(token); const filtered = desktopFilterByQuery(base, searchQuery) .filter(l => filters.platforms[l.platform || 'kleinanzeigen'] !== false) .filter(l => !filters.onlyScored || Number(l.aiScore || 0) > 0) .filter(l => !filters.minScore || Number(l.aiScore || 0) >= Number(filters.minScore)) .filter(l => !filters.provFreeOnly || l.provisionsfrei) .filter(l => !filters.balcony || hasFeature(l, 'balkon')) .filter(l => !filters.elevator || hasFeature(l, 'aufzug')) .filter(l => !filters.denkmal || hasFeature(l, 'denkmal')) .filter(l => inRange(l.price, filters.priceMin, filters.priceMax)) .filter(l => inRange(l.rooms, filters.roomsMin, filters.roomsMax)) .filter(l => inRange(l.area, filters.areaMin, filters.areaMax)) .sort((a, b) => { if (sort === 'price') return Number(a.price || 0) - Number(b.price || 0); if (sort === 'newest') return new Date(b.posted || 0) - new Date(a.posted || 0); if (sort === 'sqm') return (Number(a.price || 0) / Math.max(1, Number(a.area || 0))) - (Number(b.price || 0) / Math.max(1, Number(b.area || 0))); return Number(b.aiScore || 0) - Number(a.aiScore || 0); }); const pageSize = view === 'grid' ? 6 : 10; const pageCount = Math.max(1, Math.ceil(filtered.length / pageSize)); const currentPage = Math.min(page, pageCount); const shown = filtered.slice((currentPage - 1) * pageSize, currentPage * pageSize); const updateFilter = (key, value) => setFilters(f => ({ ...f, [key]: value })); const updatePlatform = (key) => setFilters(f => ({ ...f, platforms: { ...f.platforms, [key]: !f.platforms[key] } })); const scoreCount = (score) => base.filter(l => Number(l.aiScore || 0) >= score).length; const featureCount = (token) => base.filter(l => hasFeature(l, token)).length; return (
{filtered.length} Treffer · zuletzt aktualisiert {fmtRelative(search.lastRun)} ·  ● aktiv } actions={<> } onClick={() => onRefresh(search)}>Aktualisieren {search.category === 'kauf' && } onClick={() => onEvaluate(search)}>KI starten} } onClick={() => onExport(search)}>CSV } {...topBarProps} />
{/* Filter rail */} {/* Results pane */}
{/* Toolbar */}
setSort('score')}>KI-Score ↓ setSort('price')}>Preis ↑ setSort('newest')}>Neueste setSort('sqm')}>€ / m²
Zeige {filtered.length} von {base.length || search.matches}
setResultView('grid')}> setResultView('list')}> setResultView('map')}>
{/* Insight banner */}
KI-Insight · 3 neue Top-Bewertungen seit gestern
Der Quadratmeterpreis in Prenzlauer Berg liegt aktuell 4% unter dem 30-Tage-Schnitt. Gute Kaufgelegenheit.
filtered[0] && onOpenAI(filtered[0].id)}>Details →
{/* Listings */} {view === 'grid' && (
{shown.map(l => ( ))}
)} {view === 'list' && ( {shown.map((l, i) => ( ))} )} {view === 'map' && (
{shown.map((l, i) => ( ))}
)} {shown.length === 0 && (

Keine Treffer

Passen Sie Suche oder Filter an.

)} {/* Pagination */} {pageCount > 1 && (
setPage(Math.max(1, currentPage - 1))}>‹ {Array.from({ length: Math.min(pageCount, 5) }, (_, i) => i + 1).map(num => ( setPage(num)}>{num} ))} = pageCount} onClick={() => setPage(Math.min(pageCount, currentPage + 1))}>›
)}
); } function FilterLabel({ children }) { return
{children}
; } function FilterGroup({ label, children }) { return (
{label} {children}
); } function RangeInputs({ leftVal, rightVal, unit = '', onLeft, onRight }) { const inputStyle = { width: '100%', border: 0, background: 'transparent', outline: 'none', fontFamily: 'var(--font-sans)', fontSize: 12.5, color: 'var(--ink)', fontVariantNumeric: 'tabular-nums', }; return (
onLeft?.(e.target.value)} style={inputStyle} aria-label="Minimum" />
onRight?.(e.target.value)} style={inputStyle} aria-label="Maximum" />
{unit && {unit.trim()}}
); } function MiniHistogram() { // Stylized price-distribution histogram const bars = [3, 6, 9, 14, 18, 22, 19, 13, 9, 6, 4, 2]; const max = Math.max(...bars); return (
{bars.map((b, i) => { const inRange = i >= 2 && i <= 7; return (
); })}
); } function CheckRow({ label, count, checked, muted, onClick }) { return ( ); } function SortChip({ children, active, onClick }) { return ( ); } function ViewToggle({ children, active, onClick, title }) { return ( ); } function PageBtn({ children, active, onClick, disabled = false }) { return ( ); } function DesktopResultsMap({ listings = [], onOpen }) { const streets = ['Kastanienallee', 'Pappelallee', 'Schönhauser Allee', 'Danziger Straße', 'Greifswalder Straße', 'Winsstraße']; return (
{streets.map((street, i) => (
{street}
))}
Kartenansicht · {listings.length} Objekte
{listings.slice(0, 12).map((l, i) => ( ))} ); } // ─── DesktopListingCard ──────────────────────────────────────────────── function DesktopListingCard({ listing, onTap, isFav = false, onFav }) { const isKauf = listing.type === 'kauf'; const photos = desktopPhotos(listing); const normalizedPricePerSqm = Number(listing.pricePerSqm || 0); const sqm = isKauf && normalizedPricePerSqm ? normalizedPricePerSqm.toLocaleString('de-DE') : (isKauf && listing.area ? Math.round(Number(listing.price || 0) / Math.max(1, Number(listing.area || 0))).toLocaleString('de-DE') : null); return (
onTap && onTap(listing.id)} style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 14, overflow: 'hidden', cursor: 'pointer', transition: 'transform .12s ease, box-shadow .12s ease', }}>
{listing.provisionsfrei && ( PROVISIONSFREI )}
{listing.aiScore !== null && listing.aiScore !== undefined && (
KI-Score
)}
{fmtPriceShort(listing.price)}
{sqm && (
{sqm} €/m²
)}
{listing.title}
{listing.district}, {listing.city}
{fmtArea(listing.area)} {listing.rooms} Zi. {listing.baujahr}
); } function DesktopListingListRow({ listing, last, onTap, isFav = false, onFav, compact = false }) { const photos = desktopPhotos(listing); const normalizedPricePerSqm = Number(listing.pricePerSqm || 0); const sqm = listing.type === 'kauf' && normalizedPricePerSqm ? `${normalizedPricePerSqm.toLocaleString('de-DE')} €/m²` : (listing.type === 'kauf' && listing.area ? `${Math.round(Number(listing.price || 0) / Math.max(1, Number(listing.area || 0))).toLocaleString('de-DE')} €/m²` : null); return (
onTap?.(listing.id)} onKeyDown={event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); onTap?.(listing.id); } }} style={{ width: '100%', display: 'grid', gridTemplateColumns: compact ? '58px 1fr 90px 34px' : '72px 1.5fr .65fr .65fr .65fr 38px', gap: 12, alignItems: 'center', padding: compact ? '11px 14px' : '14px 18px', border: 0, borderBottom: last ? 'none' : '1px solid var(--border)', background: 'var(--surface)', cursor: 'pointer', textAlign: 'left', fontFamily: 'var(--font-sans)', color: 'var(--ink)', }}>
{listing.title}
{listing.district}, {listing.city} · {fmtArea(listing.area)} · {listing.rooms || '—'} Zi.
{fmtPriceShort(listing.price)} {!compact && {sqm || fmtRelative(listing.posted)}} {!compact && }
); } Object.assign(window, { ResultsDesktop, DesktopListingCard });