"use client"; /* Выбор адреса на карте ЕКБ (Leaflet + OSM). Клик по дому → reverse-геокодинг. */ /* eslint-disable @typescript-eslint/no-explicit-any -- интероп с CDN-библиотекой Leaflet */ import { useEffect, useRef, useState } from "react"; import { API_BASE_URL } from "@/lib/api"; const LEAFLET_VER = "1.9.4"; const LEAFLET_CSS = `https://unpkg.com/leaflet@${LEAFLET_VER}/dist/leaflet.css`; const LEAFLET_JS = `https://unpkg.com/leaflet@${LEAFLET_VER}/dist/leaflet.js`; const EKB_CENTER: [number, number] = [56.8389, 60.6057]; /** Подгружает Leaflet с CDN один раз, резолвит window.L. */ function loadLeaflet(): Promise { return new Promise((resolve, reject) => { const w = window as any; if (w.L) { resolve(w.L); return; } if (!document.querySelector(`link[data-leaflet]`)) { const link = document.createElement("link"); link.rel = "stylesheet"; link.href = LEAFLET_CSS; link.setAttribute("data-leaflet", "1"); document.head.appendChild(link); } const existing = document.querySelector(`script[data-leaflet]`); if (existing) { existing.addEventListener("load", () => resolve(w.L)); existing.addEventListener("error", () => reject(new Error("leaflet load failed"))); return; } const script = document.createElement("script"); script.src = LEAFLET_JS; script.setAttribute("data-leaflet", "1"); script.onload = () => resolve(w.L); script.onerror = () => reject(new Error("leaflet load failed")); document.body.appendChild(script); }); } interface Props { onPick: (address: string) => void; onClose: () => void; } export function MapPicker({ onPick, onClose }: Props) { const mapRef = useRef(null); const [address, setAddress] = useState(null); const [loading, setLoading] = useState(false); const [mapError, setMapError] = useState(false); useEffect(() => { let map: any = null; let marker: any = null; let cancelled = false; loadLeaflet() .then((L) => { if (cancelled || !mapRef.current) return; map = L.map(mapRef.current).setView(EKB_CENTER, 12); L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: "© OpenStreetMap", maxZoom: 19, }).addTo(map); setTimeout(() => map && map.invalidateSize(), 120); map.on("click", (e: any) => { const lat = e.latlng.lat as number; const lon = e.latlng.lng as number; if (marker) marker.setLatLng([lat, lon]); else marker = L.circleMarker([lat, lon], { radius: 8, color: "#1d4ed8", weight: 2, fillColor: "#1d4ed8", fillOpacity: 0.45, }).addTo(map); setAddress(null); setLoading(true); fetch(`${API_BASE_URL}/api/v1/geocode/reverse?lat=${lat}&lon=${lon}`) .then((r) => (r.ok ? r.json() : null)) .then((d) => setAddress((d?.address as string) ?? null)) .catch(() => setAddress(null)) .finally(() => setLoading(false)); }); }) .catch(() => setMapError(true)); return () => { cancelled = true; if (map) map.remove(); }; }, []); return (
e.stopPropagation()} style={{ background: "#fff", borderRadius: 12, width: "min(900px, 100%)", maxHeight: "92vh", display: "flex", flexDirection: "column", overflow: "hidden", }} >
Выбор адреса на карте ЕКБ
{mapError ? (
Не удалось загрузить карту. Проверьте интернет-соединение.
) : (
)}
{loading ? "Определяем адрес…" : address ? address : "Кликните по дому на карте"}
); }