Auto deploy: 2025-12-09 05:35:44

This commit is contained in:
Your Name 2025-12-09 05:35:48 +00:00
parent 057826feb0
commit 7c0509466c
1 changed files with 46 additions and 47 deletions

View File

@ -1,7 +1,8 @@
import React, { useEffect, useRef, useState } from "react";
import maplibregl from "maplibre-gl";
import "../../styles/maplibre.css"; // CSS locale copiée depuis maplibre-gl
import "../../styles/maplibre.css";
import "../../styles/mapMarkers.css";
import { fetchPosts, fetchIpLocation, createPost } from "../../api/client";
import {
@ -19,34 +20,30 @@ import {
} from "./mapFilter";
import { createMarkerForPost, clearAllMarkers } from "./markerManager";
/* =====================================================
COMPONENT
===================================================== */
export default function MapView() {
const containerRef = useRef(null);
const mapRef = useRef(null);
const markersRef = useRef([]); // [{ id, marker, el, ... }]
const expandedElRef = useRef(null);
// posts bruts accumulés (sans filtre temps)
// posts bruts accumulés
const allPostsRef = useRef([]);
// dernière zone fetchée (centre/rayon + filtres) pour éviter de spam le backend
// dernière zone fetchée
const lastFetchRef = useRef({
center: null, // [lng, lat]
center: null, // [lng, lat]
radiusKm: null,
filterKey: "", // `${mainFilter}|${subFilter}`
filterKey: "",
});
const [status, setStatus] = useState("Loading posts...");
const [mainFilter, setMainFilter] = useState("All"); // All/News/Friends/Events/Market
const [mainFilter, setMainFilter] = useState("All"); // All/News/Friends/Events/Market
const [timeFilter, setTimeFilter] = useState("RECENT"); // NOW/TODAY/RECENT/PAST
const [subFilter, setSubFilter] = useState("All"); // "All" ou sous-cat
const [subFilter, setSubFilter] = useState("All");
const [userPosition, setUserPosition] = useState(null); // [lng, lat]
// juste pour mémoriser centre/zoom et les sauver dans localStorage
const [viewParams, setViewParams] = useState({
center: null,
radiusKm: 250,
@ -66,7 +63,8 @@ export default function MapView() {
const [isSaving, setIsSaving] = useState(false);
const [saveError, setSaveError] = useState("");
/* ------------ INIT MAP (THEME BLEU NUIT + LAST VIEW) -------------- */
/* ================= INIT MAP ================= */
useEffect(() => {
if (!containerRef.current) return;
@ -87,6 +85,7 @@ export default function MapView() {
layers: [
{
id: "carto-dark-layer",
type: "raster",
source: "carto-dark",
},
],
@ -114,19 +113,19 @@ export default function MapView() {
hadLastView = true;
}
}
} catch (_) {}
} catch {
// ignore
}
setHasLastView(hadLastView);
map.addControl(new maplibregl.NavigationControl(), "top-right");
mapRef.current = map;
// première vue viewParams
map.on("load", () => {
const vp = getViewFromMap(map);
setViewParams(vp);
});
// update viewParams à chaque move (pour sauvegarder vue + servir de zone)
map.on("moveend", () => {
const vp = getViewFromMap(map);
setViewParams(vp);
@ -140,7 +139,9 @@ export default function MapView() {
zoom: map.getZoom(),
})
);
} catch (_) {}
} catch {
// ignore
}
});
return () => {
@ -150,7 +151,8 @@ export default function MapView() {
};
}, []);
/* ------------ POSITION UTILISATEUR (GPS/IP) -------------- */
/* ================= POSITION UTILISATEUR (GPS / IP) ================= */
useEffect(() => {
const map = mapRef.current;
if (!map) return;
@ -163,7 +165,9 @@ export default function MapView() {
setUserPosition(coords);
try {
localStorage.setItem(LAST_POS_KEY, JSON.stringify({ lat, lon }));
} catch (_) {}
} catch {
// ignore
}
if (!hasLastView) {
map.flyTo({
@ -185,7 +189,9 @@ export default function MapView() {
setUserPosition([p.lon, p.lat]);
}
}
} catch (_) {}
} catch {
// ignore
}
// 2) GPS 3) IP backend
function viaIpFallback() {
@ -222,7 +228,8 @@ export default function MapView() {
};
}, [hasLastView]);
/* ------------ SOUS-CATEGORIES (bas) -------------- */
/* ================= FILTRES (bas) ================= */
const bottomCategories = FILTER_CATEGORY_MAP[mainFilter] || ["All"];
useEffect(() => {
@ -232,7 +239,8 @@ export default function MapView() {
}
}, [mainFilter, bottomCategories, subFilter]);
/* ------------ RECONSTRUIRE LES MARKERS SELON LE TIME FILTER -------------- */
/* ================= MARKERS & TIME FILTER ================= */
const rebuildMarkersForTimeFilter = (tf) => {
const map = mapRef.current;
if (!map) return;
@ -252,7 +260,8 @@ export default function MapView() {
setStatus(visible.length ? "" : "No posts found.");
};
/* ------------ LOAD POSTS PAR ZONE + FILTRES (ACCUMULATIF) -------------- */
/* ================= FETCH PAR ZONE (ACCUMULATIF) ================= */
useEffect(() => {
const map = mapRef.current;
if (!map) return;
@ -267,13 +276,11 @@ export default function MapView() {
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
const minMoveKm = Math.max(100, radiusKm * 0.4);
if (distKm < minMoveKm) {
return;
}
@ -288,11 +295,10 @@ export default function MapView() {
? subFilter
: "";
// 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
time: "",
lat,
lon: lng,
radiusKm,
@ -303,14 +309,12 @@ export default function MapView() {
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)) {
@ -321,7 +325,6 @@ export default function MapView() {
allPostsRef.current = Array.from(byId.values());
// On se souvient de la dernière zone fetchée
lastFetchRef.current = {
center: [...viewParams.center],
radiusKm,
@ -340,20 +343,16 @@ export default function MapView() {
return () => {
cancelled = true;
};
// 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
}, [viewParams, mainFilter, subFilter]);
/* ------------ QUAND LE TIME FILTER CHANGE, ON RECONSTRUIT JUSTE LES MARKERS -------------- */
useEffect(() => {
rebuildMarkersForTimeFilter(timeFilter);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [timeFilter]);
/* ------------ WEBSOCKET : AJOUTE JUSTE LES POSTS QUI MATCH LES FILTRES -------------- */
/* ================= WEBSOCKET ================= */
useEffect(() => {
const proto = window.location.protocol === "https:" ? "wss" : "ws";
const host =
@ -377,7 +376,6 @@ export default function MapView() {
if (msg.type === "new_post" && msg.post) {
const p = msg.post;
// Filtrage live: cat + sous-cat + temps
const catCode = categoryCode(mainFilter);
if (
catCode &&
@ -392,7 +390,6 @@ export default function MapView() {
return;
}
// éviter doublon dans la liste
const id = typeof p.id === "number" ? p.id : null;
if (
id !== null &&
@ -403,10 +400,11 @@ export default function MapView() {
allPostsRef.current = [...allPostsRef.current, p];
// et on lajoute visuellement
createMarkerForPost(p, mapRef, markersRef, expandedElRef);
}
} catch (_) {}
} catch {
// ignore
}
};
ws.onerror = (e) => {
@ -420,7 +418,7 @@ export default function MapView() {
};
}, [mainFilter, subFilter, timeFilter]);
/* ------------ ACTIONS UI -------------- */
/* ================= ACTIONS UI ================= */
const handleFlyToMe = () => {
const map = mapRef.current;
@ -506,7 +504,7 @@ export default function MapView() {
}
};
/* ------------ RENDER -------------- */
/* ================= RENDER ================= */
return (
<div className="map-view">
@ -547,7 +545,7 @@ export default function MapView() {
))}
</div>
{/* RIGHT — catégories principales avec All */}
{/* RIGHT — main categories */}
<div className="map-overlay map-overlay-right">
{FILTER_MAIN_CATEGORIES.map((cat) => (
<button
@ -562,7 +560,7 @@ export default function MapView() {
))}
</div>
{/* BOTTOM — sous-cats + All */}
{/* BOTTOM — subcategories */}
<div className="map-overlay map-overlay-bottom">
{bottomCategories.map((c) => (
<button
@ -577,7 +575,7 @@ export default function MapView() {
))}
</div>
{/* BOUTON "MY SPOT" */}
{/* MY SPOT */}
<div className="map-overlay map-overlay-myloc">
<button
className="chip-pill"
@ -588,14 +586,14 @@ export default function MapView() {
</button>
</div>
{/* CROSSHAIR SUR LA MAP */}
{/* CROSSHAIR */}
{isCreating && (
<div className="map-overlay map-crosshair" onClick={handlePickCenter}>
<div className="crosshair-aim" />
</div>
)}
{/* PANEL CREATION DE POST */}
{/* CREATE POST PANEL */}
{isCreating && (
<div className="map-overlay create-post-panel">
<div className="create-post-header">
@ -672,6 +670,7 @@ export default function MapView() {
disabled={isSaving}
>
{isSaving ? "Posting..." : "Post as tommy"}
</button>
</div>
</div>
)}