diff --git a/src/components/Map/usePostsEngine.js b/src/components/Map/usePostsEngine.js index db77ead..1608883 100644 --- a/src/components/Map/usePostsEngine.js +++ b/src/components/Map/usePostsEngine.js @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { fetchPosts, fetchSmartFeed } from "../../api/client"; +import { fetchPosts, fetchSmartFeed, fetchUnifiedSearch } from "../../api/client"; import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter"; import { getCoords, getViewFromMap, haversineKm } from "./mapGeo"; import { createMarkerForPost, createSimpleMarkerForPost } from "./markerManager"; @@ -105,6 +105,32 @@ function filterByBounds(posts, map) { }); } +function getPostTimeMs(p) { + const raw = p?.created_at || p?.CreatedAt || p?.createdAt || p?.published_at; + if (!raw) return 0; + const d = raw instanceof Date ? raw : new Date(String(raw)); + if (Number.isNaN(d.getTime())) return 0; + return d.getTime(); +} + +function prunePosts(posts, center, maxCount) { + if (!Array.isArray(posts) || posts.length <= maxCount) return posts || []; + const [centerLon, centerLat] = center || []; + const now = Date.now(); + const scored = posts.map((p) => { + const coords = getCoords(p); + let dist = 99999; + if (coords && Number.isFinite(centerLat) && Number.isFinite(centerLon)) { + dist = haversineKm(centerLat, centerLon, coords[1], coords[0]); + } + const ageH = Math.max(0, (now - getPostTimeMs(p)) / 3600000); + const score = dist + ageH * 0.4; + return { p, score }; + }); + scored.sort((a, b) => a.score - b.score); + return scored.slice(0, maxCount).map((x) => x.p); +} + export function usePostsEngine({ mapRef, viewParams, @@ -541,6 +567,7 @@ export function usePostsEngine({ const resolvedView = viewParams && viewParams.center ? viewParams : getViewFromMap(mapObj); if (!resolvedView?.center) return; + if ((searchQuery || "").trim()) return; // If expanded overlay is open: delay fetch (do NOT disturb UI) if (expandedElRef?.current) { @@ -621,7 +648,7 @@ export function usePostsEngine({ lat, lon: lng, radius_km: radiusKm, - limit: 80, + limit: 50, bounds, username: username || undefined, }); @@ -662,7 +689,7 @@ export function usePostsEngine({ } } - allPostsRef.current = Array.from(byId.values()); + allPostsRef.current = prunePosts(Array.from(byId.values()), [lng, lat], 180); lastFetchRef.current = { center: [...resolvedView.center], radiusKm, filterKey, timeFilter, bounds }; // Smooth update: sync markers/clusters + wall without clearing everything @@ -698,6 +725,41 @@ export function usePostsEngine({ }; }, [viewParams, mapReady, mainFilter, subFilter, mapRef, timeFilter, expandedElRef, refreshVisibleAndMarkers, computeVisible, onAutoWidenTimeFilter, syncMarkers, username]); + useEffect(() => { + const q = (searchQuery || "").trim(); + if (!q) return; + let cancelled = false; + + async function runSearch() { + try { + const map = mapRef.current; + let lat; + let lon; + try { + const center = map?.getCenter?.(); + lat = center?.lat; + lon = center?.lng; + } catch {} + const data = await fetchUnifiedSearch(q, { + type: "posts", + lat, + lon, + radius_km: viewParams?.radiusKm, + limit: 80, + }); + if (cancelled) return; + const posts = Array.isArray(data?.posts) ? data.posts : []; + allPostsRef.current = prunePosts(posts, [lon, lat], 120); + refreshVisibleAndMarkers(timeFilter); + } catch {} + } + + runSearch(); + return () => { + cancelled = true; + }; + }, [searchQuery, viewParams, mapRef, refreshVisibleAndMarkers, timeFilter]); + useEffect(() => { const map = mapRef.current; if (!map) return; @@ -720,7 +782,9 @@ export function usePostsEngine({ const exists = (allPostsRef.current || []).some((x) => getPostKey(x) === key); if (exists) return; } - allPostsRef.current = [p, ...allPostsRef.current]; + const map = mapRef.current; + const center = map?.getCenter?.(); + allPostsRef.current = prunePosts([p, ...allPostsRef.current], [center?.lng, center?.lat], 200); const catCode = categoryCode(mainFilter); if (catCode) {