178 lines
5.7 KiB
JavaScript
178 lines
5.7 KiB
JavaScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import { fetchPosts } from "../../api/client";
|
|
import { categoryCode, matchesSubFilter, matchesTimeFilter } from "./mapFilter";
|
|
import { haversineKm } from "./mapGeo";
|
|
import { createMarkerForPost, clearAllMarkers } from "./markerManager";
|
|
|
|
export function usePostsEngine({
|
|
mapRef,
|
|
viewParams,
|
|
mainFilter,
|
|
subFilter,
|
|
timeFilter,
|
|
markersRef,
|
|
expandedElRef,
|
|
}) {
|
|
const allPostsRef = useRef([]);
|
|
const lastFetchRef = useRef({ center: null, radiusKm: null, filterKey: "" });
|
|
const delayedFetchRef = useRef(null);
|
|
|
|
const [status, setStatus] = useState("Loading posts...");
|
|
const [visiblePosts, setVisiblePosts] = useState([]);
|
|
const [loadingPosts, setLoadingPosts] = useState(false);
|
|
const [loadError, setLoadError] = useState("");
|
|
|
|
const rebuildMarkersForFilters = useCallback(
|
|
(tf) => {
|
|
const map = mapRef.current;
|
|
if (!map) return;
|
|
|
|
// If expanded is open and we are inside ignore window, do not rebuild (it closes the popup)
|
|
try {
|
|
const until = map.__swIgnoreRebuildUntil || 0;
|
|
if (expandedElRef?.current && until && Date.now() < until) return;
|
|
} catch {}
|
|
|
|
clearAllMarkers(markersRef, expandedElRef);
|
|
|
|
const posts = allPostsRef.current || [];
|
|
const catCode = categoryCode(mainFilter);
|
|
|
|
const visible = posts.filter((p) => {
|
|
if (catCode) {
|
|
const pc = (p.category || p.Category || "").toString().toUpperCase();
|
|
if (pc !== catCode) return false;
|
|
}
|
|
if (!matchesSubFilter(p, subFilter)) return false;
|
|
if (!matchesTimeFilter(p.created_at || p.CreatedAt, tf)) return false;
|
|
return true;
|
|
});
|
|
|
|
visible.forEach((post) => createMarkerForPost(post, mapRef, markersRef, expandedElRef));
|
|
|
|
setVisiblePosts(visible);
|
|
setStatus(visible.length ? "" : "No posts found.");
|
|
},
|
|
[mapRef, markersRef, expandedElRef, mainFilter, subFilter]
|
|
);
|
|
|
|
useEffect(() => {
|
|
rebuildMarkersForFilters(timeFilter);
|
|
}, [timeFilter, mainFilter, subFilter, rebuildMarkersForFilters]);
|
|
|
|
useEffect(() => {
|
|
const map = mapRef.current;
|
|
if (!map) return;
|
|
if (!viewParams.center) return;
|
|
|
|
let cancelled = false;
|
|
|
|
async function load() {
|
|
const mapObj = mapRef.current;
|
|
|
|
// Delay fetch while popup is open (prevents marker clear/rebuild)
|
|
try {
|
|
const until = mapObj?.__swIgnoreFetchUntil || 0;
|
|
if (until && Date.now() < until) {
|
|
const wait = Math.min(4000, until - Date.now());
|
|
if (delayedFetchRef.current) clearTimeout(delayedFetchRef.current);
|
|
delayedFetchRef.current = setTimeout(load, wait + 25);
|
|
return;
|
|
}
|
|
} catch {}
|
|
|
|
const [lng, lat] = viewParams.center;
|
|
const radiusKm = viewParams.radiusKm || 750;
|
|
const filterKey = `${mainFilter}|${subFilter}`;
|
|
|
|
const last = lastFetchRef.current;
|
|
|
|
if (last.center && last.filterKey === filterKey) {
|
|
const [lastLng, lastLat] = last.center;
|
|
const distKm = haversineKm(lastLat, lastLng, viewParams.center[1], lng);
|
|
|
|
const lastR = last.radiusKm || 0;
|
|
const radiusChanged = !lastR || Math.abs(radiusKm - lastR) / Math.max(lastR, 1) >= 0.25;
|
|
|
|
const minMoveKm = Math.max(50, radiusKm * 0.25);
|
|
|
|
if (!radiusChanged && distKm < minMoveKm) return;
|
|
}
|
|
|
|
try {
|
|
setStatus("Loading posts...");
|
|
setLoadingPosts(true);
|
|
setLoadError("");
|
|
|
|
const catCode = categoryCode(mainFilter);
|
|
const subCatParam =
|
|
subFilter && subFilter.toUpperCase() !== "ALL" && subFilter !== "All" ? subFilter : "";
|
|
|
|
const newPosts = await fetchPosts({
|
|
category: catCode,
|
|
subCategory: subCatParam,
|
|
time: "",
|
|
lat,
|
|
lon: lng,
|
|
radiusKm,
|
|
});
|
|
|
|
if (cancelled) return;
|
|
|
|
const existing = allPostsRef.current || [];
|
|
const byId = new Map();
|
|
for (const p of existing) {
|
|
if (p && typeof p.id !== "undefined") byId.set(p.id, p);
|
|
}
|
|
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());
|
|
lastFetchRef.current = { center: [...viewParams.center], radiusKm, filterKey };
|
|
|
|
rebuildMarkersForFilters(timeFilter);
|
|
setLoadingPosts(false);
|
|
} catch (err) {
|
|
console.error("Erreur chargement posts:", err);
|
|
if (!cancelled) {
|
|
setStatus("Error loading posts.");
|
|
setLoadError("Error loading posts.");
|
|
setLoadingPosts(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
load();
|
|
return () => {
|
|
cancelled = true;
|
|
if (delayedFetchRef.current) {
|
|
try { clearTimeout(delayedFetchRef.current); } catch {}
|
|
delayedFetchRef.current = null;
|
|
}
|
|
};
|
|
}, [viewParams, mainFilter, subFilter, mapRef, rebuildMarkersForFilters, timeFilter]);
|
|
|
|
const handleIncomingPost = useCallback(
|
|
(p) => {
|
|
allPostsRef.current = [...allPostsRef.current, p];
|
|
|
|
const catCode = categoryCode(mainFilter);
|
|
if (catCode) {
|
|
const pc = (p.category || p.Category || "").toString().toUpperCase();
|
|
if (pc !== catCode) return;
|
|
}
|
|
if (!matchesSubFilter(p, subFilter)) return;
|
|
if (!matchesTimeFilter(p.created_at || p.CreatedAt, timeFilter)) return;
|
|
|
|
createMarkerForPost(p, mapRef, markersRef, expandedElRef);
|
|
setVisiblePosts((current) => [...current, p]);
|
|
},
|
|
[mainFilter, subFilter, timeFilter, mapRef, markersRef, expandedElRef]
|
|
);
|
|
|
|
return { status, visiblePosts, loadingPosts, loadError, handleIncomingPost };
|
|
}
|