From 421b7ab20b1bb93582957345eae1fbe35ca1674b Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 9 Dec 2025 04:54:19 +0000 Subject: [PATCH] Auto deploy: 2025-12-09 04:54:16 --- src/components/Map/MapView.jsx | 85 +++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 12 deletions(-) diff --git a/src/components/Map/MapView.jsx b/src/components/Map/MapView.jsx index c8c0e08..e6c5e5e 100644 --- a/src/components/Map/MapView.jsx +++ b/src/components/Map/MapView.jsx @@ -11,7 +11,7 @@ import { FILTER_CATEGORY_MAP, CREATE_CATEGORY_MAP, } from "./mapConfig"; -import { getViewFromMap } from "./mapGeo"; +import { getViewFromMap, haversineKm } from "./mapGeo"; import { categoryCode, matchesSubFilter, @@ -28,9 +28,16 @@ export default function MapView() { const markersRef = useRef([]); // [{ id, marker, el, ... }] const expandedElRef = useRef(null); - // posts bruts (sans filtre temps) – maintenant indépendants du zoom + // posts bruts accumulés (sans filtre temps) const allPostsRef = useRef([]); + // dernière zone fetchée (centre/rayon + filtres) pour éviter de spam le backend + const lastFetchRef = useRef({ + center: null, // [lng, lat] + radiusKm: null, + filterKey: "", // `${mainFilter}|${subFilter}` + }); + const [status, setStatus] = useState("Loading posts..."); const [mainFilter, setMainFilter] = useState("All"); // All/News/Friends/Events/Market @@ -39,7 +46,7 @@ export default function MapView() { const [userPosition, setUserPosition] = useState(null); // [lng, lat] - // on garde ça juste pour mémoriser la vue & lastView + // juste pour mémoriser centre/zoom et les sauver dans localStorage const [viewParams, setViewParams] = useState({ center: null, radiusKm: 250, @@ -120,7 +127,7 @@ export default function MapView() { setViewParams(vp); }); - // update viewParams à chaque move (juste pour sauvegarder centre/zoom) + // update viewParams à chaque move (pour sauvegarder vue + servir de zone) map.on("moveend", () => { const vp = getViewFromMap(map); setViewParams(vp); @@ -246,11 +253,33 @@ export default function MapView() { setStatus(visible.length ? "" : "No posts found."); }; - /* ------------ LOAD POSTS (cat + sous-cat) – PLUS LIÉ AU ZOOM -------------- */ + /* ------------ LOAD POSTS PAR ZONE + FILTRES (ACCUMULATIF) -------------- */ useEffect(() => { + const map = mapRef.current; + if (!map) return; + if (!viewParams.center) return; + let cancelled = false; async function load() { + const [lng, lat] = viewParams.center; + const radiusKm = viewParams.radiusKm || 250; + const filterKey = `${mainFilter}|${subFilter}`; + + const last = lastFetchRef.current; + + // Si les filtres n'ont pas changé, on regarde la distance + if (last.center && last.filterKey === filterKey) { + const [lastLng, lastLat] = last.center; + const distKm = haversineKm(lastLat, lastLng, viewParams.center[1], lng); + + // Distance trop petite → même zone -> pas de refetch + const minMoveKm = Math.max(100, radiusKm * 0.4); // ex: 100 km ou 40% du rayon + if (distKm < minMoveKm) { + return; + } + } + try { setStatus("Loading posts..."); @@ -260,16 +289,45 @@ export default function MapView() { ? subFilter : ""; - // ⚠️ PAS de lat/lon/radius → backend renvoie tout (limité à 500) - const posts = await fetchPosts({ + // ici on utilise lat/lon/radius pour que le backend renvoie les posts de la zone + const newPosts = await fetchPosts({ category: catCode, subCategory: subCatParam, time: "", // on gère le temps en front + lat, + lon: lng, + radiusKm, }); if (cancelled) return; - allPostsRef.current = Array.isArray(posts) ? posts : []; + const existing = allPostsRef.current || []; + const byId = new Map(); + + // garde tout ce qu'on avait déjà + for (const p of existing) { + if (p && typeof p.id !== "undefined") { + byId.set(p.id, p); + } + } + + // ajoute les nouveaux posts (sans écraser ceux déjà présents) + if (Array.isArray(newPosts)) { + for (const p of newPosts) { + if (p && typeof p.id !== "undefined" && !byId.has(p.id)) { + byId.set(p.id, p); + } + } + } + + allPostsRef.current = Array.from(byId.values()); + + // On se souvient de la dernière zone fetchée + lastFetchRef.current = { + center: [...viewParams.center], + radiusKm, + filterKey, + }; rebuildMarkersForTimeFilter(timeFilter); } catch (err) { @@ -283,9 +341,12 @@ export default function MapView() { return () => { cancelled = true; }; - // déclenche seulement quand la catégorie ou sous-cat change + // on refetch SI: + // - la vue change assez (seuil calculé) + // - OU filtres changent + // (PAS quand timeFilter change, lui ne fait que rebuild côté front) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [mainFilter, subFilter]); + }, [viewParams, mainFilter, subFilter]); /* ------------ QUAND LE TIME FILTER CHANGE, ON RECONSTRUIT JUSTE LES MARKERS -------------- */ useEffect(() => { @@ -317,7 +378,7 @@ export default function MapView() { if (msg.type === "new_post" && msg.post) { const p = msg.post; - // Filtrage live: cat + sous-cat + temps (plus de rayon) + // Filtrage live: cat + sous-cat + temps const catCode = categoryCode(mainFilter); if ( catCode && @@ -332,7 +393,7 @@ export default function MapView() { return; } - // évite doublon dans la liste + // éviter doublon dans la liste const id = typeof p.id === "number" ? p.id : null; if ( id !== null &&