MapPicker.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. 'use client'
  2. import { useEffect, useRef, useState } from 'react'
  3. import L from 'leaflet'
  4. import 'leaflet/dist/leaflet.css'
  5. interface MapPickerProps {
  6. onSelect: (lat: number, lng: number, address?: string) => void
  7. onClose: () => void
  8. }
  9. export default function MapPicker({ onSelect, onClose }: MapPickerProps) {
  10. const mapRef = useRef<HTMLDivElement>(null)
  11. const markerRef = useRef<L.Marker | null>(null)
  12. const mapInstanceRef = useRef<L.Map | null>(null)
  13. const [lat, setLat] = useState(55.7558)
  14. const [lng, setLng] = useState(37.6173)
  15. const [loading, setLoading] = useState(false)
  16. useEffect(() => {
  17. if (!mapRef.current || mapInstanceRef.current) return
  18. const map = L.map(mapRef.current, {
  19. zoomControl: false,
  20. attributionControl: false,
  21. }).setView([lat, lng], 12)
  22. L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', {
  23. maxZoom: 19,
  24. }).addTo(map)
  25. map.on('click', (e) => {
  26. const newLat = e.latlng.lat
  27. const newLng = e.latlng.lng
  28. setLat(newLat)
  29. setLng(newLng)
  30. if (markerRef.current) markerRef.current.remove()
  31. markerRef.current = L.marker([newLat, newLng]).addTo(map)
  32. })
  33. mapInstanceRef.current = map
  34. return () => {
  35. map.remove()
  36. mapInstanceRef.current = null
  37. }
  38. }, [])
  39. const handleConfirm = async () => {
  40. setLoading(true)
  41. try {
  42. const res = await fetch(
  43. `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json&accept-language=ru`
  44. )
  45. const data = await res.json()
  46. onSelect(lat, lng, data.display_name || undefined)
  47. } catch {
  48. onSelect(lat, lng)
  49. }
  50. }
  51. return (
  52. <div className="fixed inset-0 z-[60] flex flex-col bg-black/80 backdrop-blur-sm">
  53. <div ref={mapRef} className="flex-1" />
  54. <div className="flex items-center justify-between bg-[#1e293b] px-4 py-3">
  55. <div className="text-sm text-white/60">
  56. {lat.toFixed(6)}, {lng.toFixed(6)}
  57. </div>
  58. <div className="flex gap-3">
  59. <button
  60. onClick={onClose}
  61. className="rounded-lg bg-white/10 px-4 py-2 text-sm text-white hover:bg-white/20 transition"
  62. >
  63. Отмена
  64. </button>
  65. <button
  66. onClick={handleConfirm}
  67. disabled={loading}
  68. className="rounded-lg bg-green-600 px-4 py-2 text-sm text-white hover:bg-green-500 transition disabled:opacity-50"
  69. >
  70. {loading ? 'Загрузка...' : 'Подтвердить'}
  71. </button>
  72. </div>
  73. </div>
  74. </div>
  75. )
  76. }