// 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 (
);
}
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 });