From 362514681a55bc48b66da074a2a8c07000006af3 Mon Sep 17 00:00:00 2001 From: Benjamin Sutter Date: Wed, 24 Jun 2026 16:56:35 +0200 Subject: [PATCH] =?UTF-8?q?feat(demand):=20Ergebnis-Grid=20=E2=80=94=20Spa?= =?UTF-8?q?lten-Umschalter,=20sauberere=20Karten,=20mehr=20Abstand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 3/5/10-Spalten-Umschalter im Ergebnis-Header (nur Grid-Ansicht, in localStorage gemerkt) analog zu "Meine Objekte" - Karten-Bild auf aspectRatio 15/8 statt fester Höhe → keine Verzerrung bei beliebiger Spaltenzahl - Orts-Overlay vom Bild entfernt → Match-Score überdeckt das Ort-Label nicht mehr - Sidebar bleibt bis < lg (1200px) ausgeklappt → Navigation + Account auf Laptops sichtbar - Größere vertikale Abstände im Ergebnis-Feed Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/layout/AppShell.tsx | 4 +-- .../match-card/IntelligenceMatchCard.tsx | 6 ++-- src/components/results/ResultFeedHeader.tsx | 31 ++++++++++++++++--- src/components/results/UnifiedResultCard.tsx | 14 --------- src/components/results/UnifiedResultFeed.tsx | 7 +++-- src/components/shared/LocationPreview.tsx | 6 ++-- src/pages/demand/Results.tsx | 9 ++++-- 7 files changed, 46 insertions(+), 31 deletions(-) diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx index 9532d69..af30d91 100644 --- a/src/components/layout/AppShell.tsx +++ b/src/components/layout/AppShell.tsx @@ -27,13 +27,13 @@ export function AppShell() { const location = useLocation() const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('sm')) - const isCompact = useMediaQuery(theme.breakpoints.down('xl')) + const isCompact = useMediaQuery(theme.breakpoints.down('lg')) const [mobileOpen, setMobileOpen] = useState(false) // Close mobile menu on route change useEffect(() => { setMobileOpen(false) }, [location.pathname]) - // Auto-collapse sidebar on compact screens (laptops < 1536px) + // Auto-collapse sidebar only on smaller screens (< 1200px); laptops keep the full nav + account useEffect(() => { setSidebarCollapsed(isCompact) }, [isCompact, setSidebarCollapsed]) // Sync active workspace with URL diff --git a/src/components/match-card/IntelligenceMatchCard.tsx b/src/components/match-card/IntelligenceMatchCard.tsx index 7cb0935..3a7d2d8 100644 --- a/src/components/match-card/IntelligenceMatchCard.tsx +++ b/src/components/match-card/IntelligenceMatchCard.tsx @@ -16,11 +16,9 @@ interface Props { lat?: number lng?: number cityLabel?: string - overlayTypeLabel?: string - overlayLocationLabel?: string } -export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overlayTypeLabel, overlayLocationLabel }: Props) { +export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Props) { const tier = getScoreTier(vm.matchScore) const theme = SCORE_THEME[tier] const { currentUser } = useSessionStore() @@ -59,7 +57,7 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel, overl {/* ── Hero zone ── */} - + {/* Score badge — bottom-left editorial */} void + columns?: 3 | 5 | 10 + onColumnsChange?: (n: 3 | 5 | 10) => void } -export function ResultFeedHeader({ total, platformCount, maisonWorkCount, futureCount, view = 'list', onViewChange }: Props) { +export function ResultFeedHeader({ total, platformCount, maisonWorkCount, futureCount, view = 'list', onViewChange, columns = 3, onColumnsChange }: Props) { return ( @@ -21,9 +23,30 @@ export function ResultFeedHeader({ total, platformCount, maisonWorkCount, future {platformCount} Plattform · {maisonWorkCount} Maison Work · {futureCount} Future Availability - {onViewChange && ( - - )} + + {view === 'grid' && onColumnsChange && ( + + {([3, 5, 10] as const).map(n => ( + onColumnsChange(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} + + ))} + + )} + {onViewChange && ( + + )} + ) } diff --git a/src/components/results/UnifiedResultCard.tsx b/src/components/results/UnifiedResultCard.tsx index 8ae3934..453bd61 100644 --- a/src/components/results/UnifiedResultCard.tsx +++ b/src/components/results/UnifiedResultCard.tsx @@ -2,7 +2,6 @@ import { useNavigate } from 'react-router' import { MatchCardCompact } from '../match-card/MatchCardCompact' import { IntelligenceMatchCard } from '../match-card/IntelligenceMatchCard' import { buildMatchCardViewModel } from '../../features/matching/matchCardAdapter' -import { resolveImageLabel } from '../../lib/propertyImageResolver' import { useCompareStore } from '../../stores/compareStore' import { usePipelineStore } from '../../stores/pipelineStore' import type { UnifiedMatchResult } from '../../domain/unifiedResult' @@ -98,17 +97,6 @@ export function UnifiedResultCard({ result, view = 'list' }: Props) { ? result.property.location.city : result.signal.locationHint ?? undefined - const overlayLabels = result.resultType !== 'FUTURE_AVAILABILITY' - ? resolveImageLabel({ - assetType: result.property.assetType, - city: result.property.location.city, - district: result.property.location.district, - prestige: result.property.softFactors?.prestige, - floorLevel: result.property.floorLevel, - propertyId: result.property.id, - }) - : null - return ( ) } diff --git a/src/components/results/UnifiedResultFeed.tsx b/src/components/results/UnifiedResultFeed.tsx index 7be7de6..5047934 100644 --- a/src/components/results/UnifiedResultFeed.tsx +++ b/src/components/results/UnifiedResultFeed.tsx @@ -6,6 +6,7 @@ import { SCORE_THEME } from '../shared/scoreTheme' interface Props { results: UnifiedMatchResult[] view?: 'list' | 'grid' + columns?: 3 | 5 | 10 } const TIER_CONFIG = [ @@ -40,7 +41,7 @@ interface TierHeaderProps { function TierHeader({ label, sublabel, tierKey, count, isFirst }: TierHeaderProps) { const theme = SCORE_THEME[tierKey] return ( - + ({ ...t, items: results.filter(t.filter) })) @@ -80,7 +81,7 @@ export function UnifiedResultFeed({ results, view = 'list' }: Props) { function renderCards(items: UnifiedMatchResult[]) { return view === 'grid' ? ( - + {items.map(result => ( ))} diff --git a/src/components/shared/LocationPreview.tsx b/src/components/shared/LocationPreview.tsx index 1046ef9..b88b02e 100644 --- a/src/components/shared/LocationPreview.tsx +++ b/src/components/shared/LocationPreview.tsx @@ -9,6 +9,8 @@ interface Props { address?: string cityLabel?: string height?: number + /** When set, the hero scales proportionally with width (e.g. '15/8') instead of using a fixed pixel height. */ + aspectRatio?: string overlayTypeLabel?: string overlayLocationLabel?: string } @@ -23,13 +25,13 @@ function buildMapsUrl(lat?: number, lng?: number, address?: string): string { return 'https://www.google.com/maps' } -export function LocationPreview({ imageUrl, lat, lng, address, cityLabel, height = 180, overlayTypeLabel, overlayLocationLabel }: Props) { +export function LocationPreview({ imageUrl, lat, lng, address, cityLabel, height = 180, aspectRatio, overlayTypeLabel, overlayLocationLabel }: Props) { const [imgError, setImgError] = useState(false) const mapsUrl = buildMapsUrl(lat, lng, address) const showImage = !!imageUrl && !imgError return ( - + {showImage ? ( (() => (localStorage.getItem('view-results') as 'list' | 'grid') ?? 'list' ) + const [columns, setColumns] = useState<3 | 5 | 10>(() => + (Number(localStorage.getItem('view-results-cols')) as 3 | 5 | 10) || 3 + ) // When coming from NeedBuilder, invalidate so the freshly created need is included const activeNeedIdFromNav = (location.state as { activeNeedId?: string } | null)?.activeNeedId @@ -107,9 +110,11 @@ export default function Results() { futureCount={futureCount} view={view} onViewChange={v => { setView(v); localStorage.setItem('view-results', v) }} + columns={columns} + onColumnsChange={n => { setColumns(n); localStorage.setItem('view-results-cols', String(n)) }} /> - + {/* Aktive Suche */} {activeNeed && ( @@ -169,7 +174,7 @@ export default function Results() { )} - + )}