import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { API_URL, getImageUrl } from "../config/authConstants"; import { useGeolocation, GEO_STATUS } from "../hooks/useGeolocation"; const MARKER_COLORS = { LOST: "#ff3b30", LOSS: "#ff3b30", FOUND: "#34c759", }; function createMarkerLayout(color, imageUrl) { const bgStyle = imageUrl ? `background-image:url('${imageUrl}');background-size:cover;background-position:center;` : ""; return window.ymaps.templateLayoutFactory.createClass( `
`, { build() { this.constructor.superclass.build.call(this); }, } ); } const FOCUS_ZOOM = 16; const COORD_MATCH_TOLERANCE = 0.005; export default function MapView({ focusPostId, focusLat, focusLng, onPostClick, onBack }) { const { t } = useTranslation(); const mapRef = useRef(null); const ymapRef = useRef(null); const userMarkerRef = useRef(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const { coords: geoCoords, status: geoStatus } = useGeolocation({ onMount: true }); const onPostClickRef = useRef(onPostClick); onPostClickRef.current = onPostClick; useEffect(() => { let map; const destroyed = { current: false }; const init = async () => { if (!mapRef.current || destroyed.current) return; try { const res = await fetch(`${API_URL}/api/v1/posts/map`); if (!res.ok) throw new Error("Failed to fetch map markers"); const markers = await res.json(); if (destroyed.current) return; map = new window.ymaps.Map(mapRef.current, { center: [55.751574, 37.573856], zoom: 11, controls: ["zoomControl", "geolocationControl"], }); ymapRef.current = map; const placemarksById = new Map(); markers.forEach((m) => { if (m.latitude == null || m.longitude == null) return; const color = MARKER_COLORS[m.type] || MARKER_COLORS.LOST; const typeLabel = m.type === "FOUND" ? "Находка" : "Потеря"; const imgSrc = m.imageUrl ? getImageUrl(m.imageUrl) : null; const iconLayout = createMarkerLayout(color, imgSrc); const coords = [m.latitude, m.longitude]; const placemark = new window.ymaps.Placemark( coords, {}, { iconLayout, iconImageSize: [40, 40], iconImageOffset: [-20, -20], // Без явного iconShape карта не может корректно вычислить // кликабельную область для кастомного HTML-лейаута — // маркер рисуется, но клики по нему не срабатывают. iconShape: { type: "Circle", coordinates: [20, 20], radius: 20, }, hasBalloon: false, hasHint: true, hintContent: m.title || typeLabel, } ); map.geoObjects.add(placemark); if (m.id != null) { const postId = String(m.id); placemarksById.set(postId, { placemark, coords }); placemark.events.add("click", () => { if (onPostClickRef.current) { onPostClickRef.current(postId); } }); } }); let target = focusPostId != null ? placemarksById.get(String(focusPostId)) : null; if (!target && Number.isFinite(focusLat) && Number.isFinite(focusLng)) { let best = null; let bestDist = Infinity; placemarksById.forEach((entry) => { const d = Math.pow(entry.coords[0] - focusLat, 2) + Math.pow(entry.coords[1] - focusLng, 2); if (d < bestDist) { bestDist = d; best = entry; } }); if (best && bestDist < COORD_MATCH_TOLERANCE * COORD_MATCH_TOLERANCE) { target = best; } } if (target) { map.setCenter(target.coords, FOCUS_ZOOM); } else if (placemarksById.size > 0) { map.setBounds( map.geoObjects.getBounds(), { checkZoomRange: true, zoomMargin: 40 } ); } } catch (e) { if (!destroyed.current) setError(e.message); } finally { if (!destroyed.current) setLoading(false); } }; if (window.ymaps?.ready) { window.ymaps.ready(init); } return () => { destroyed.current = true; ymapRef.current?.destroy(); ymapRef.current = null; userMarkerRef.current = null; }; }, [focusPostId, focusLat, focusLng]); // Геолокация: если юзер разрешил и карта уже создана — центр на его месте. // Не трогаем, если карта была открыта с фокусом на конкретном объявлении. const hasFocus = focusPostId != null || (Number.isFinite(focusLat) && Number.isFinite(focusLng)); useEffect(() => { if (hasFocus) return; if (geoStatus !== GEO_STATUS.SUCCESS || !geoCoords) return; const map = ymapRef.current; if (!map) return; const [lat, lng] = [geoCoords.latitude, geoCoords.longitude]; map.setCenter([lat, lng], 14); if (userMarkerRef.current) { userMarkerRef.current.geometry.setCoordinates([lat, lng]); } else { const pm = new window.ymaps.Placemark( [lat, lng], {}, { preset: "islands#blueCircleIcon" } ); map.geoObjects.add(pm); userMarkerRef.current = pm; } }, [hasFocus, geoStatus, geoCoords, loading]); return (
{loading && (
)} {error && (
Ошибка загрузки карты
)}
{geoStatus === GEO_STATUS.LOADING && !hasFocus && (
Определение местоположения…
)} {geoStatus === GEO_STATUS.DENIED && (
Доступ к геолокации запрещён
)} {geoStatus === GEO_STATUS.TIMEOUT && (
Геолокация не определилась
)} {geoStatus === GEO_STATUS.UNSUPPORTED && (
Геолокация недоступна
)} {onBack && ( )}
); }