diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index bf2e353..5c35cb6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,7 +17,7 @@ import { Sheet, SheetContent, SheetTrigger } from './components/ui/sheet'; import { Button } from './components/ui/button'; import { Filter, Heart } from 'lucide-react'; import type { GeoJSONFeatureCollection, PropertyProperties, PropertyFeature, POI, POITravelFilter } from '@/types'; -import { refreshListings, streamListingGeoJSON, fetchUserPOIs, type StreamingProgress } from '@/services'; +import { refreshListings, streamListingGeoJSON, fetchUserPOIs, fetchBulkPOIDistances, type StreamingProgress } from '@/services'; import { setOnUnauthorized } from '@/services/apiClient'; import { clearPasskeyUser } from './auth/passkeyService'; import { poiMetricPropertyName, injectPoiMetricProperty } from '@/utils/poiUtils'; @@ -167,7 +167,7 @@ function App() { try { for await (const batch of streamListingGeoJSON(user, parameters, (progress) => { setStreamingProgress(progress); - }, { includePoiDistances: userPOIs.length > 0, signal: controller.signal })) { + }, { signal: controller.signal })) { // Deduplicate features by URL const uniqueBatch = batch.filter((feature) => { const url = feature.properties?.url; @@ -200,7 +200,39 @@ function App() { setStreamingProgress(null); } } - }, [user, userPOIs]); + }, [user]); + + // Merge POI distances into listing data after both are available + useEffect(() => { + if (!user || !listingData || userPOIs.length === 0 || !queryParameters) return; + + let cancelled = false; + fetchBulkPOIDistances(user, (queryParameters.listing_type as 'RENT' | 'BUY') ?? 'RENT') + .then((distanceLookup) => { + if (cancelled) return; + const updatedFeatures = accumulatedFeaturesRef.current.map(feature => { + const id = feature.properties?.id; + if (id && distanceLookup[id]) { + return { + ...feature, + properties: { + ...feature.properties, + poi_distances: distanceLookup[id], + }, + }; + } + return feature; + }); + accumulatedFeaturesRef.current = updatedFeatures; + setListingData({ + type: 'FeatureCollection', + features: [...updatedFeatures], + }); + }) + .catch(() => {}); // POI distances are best-effort + + return () => { cancelled = true; }; + }, [user, listingData?.features.length, userPOIs.length, queryParameters]); // Compute processed listing data: inject synthetic POI metric property & apply max travel filter const processedListingData = useMemo(() => { diff --git a/frontend/src/services/streamingService.ts b/frontend/src/services/streamingService.ts index ba681a3..730ec78 100644 --- a/frontend/src/services/streamingService.ts +++ b/frontend/src/services/streamingService.ts @@ -69,12 +69,9 @@ export async function* streamListingGeoJSON( user: AuthUser, parameters: ParameterValues, onProgress?: (progress: StreamingProgress) => void, - options?: { includePoiDistances?: boolean; signal?: AbortSignal }, + options?: { signal?: AbortSignal }, ): AsyncGenerator { const params = buildListingParams(parameters); - if (options?.includePoiDistances) { - params.include_poi_distances = 'true'; - } const queryString = buildQueryString(params); const url = queryString ? `${API_ENDPOINTS.LISTING_GEOJSON_STREAM}?${queryString}`