first commit
This commit is contained in:
102
apps/frontend/components/addresses/address-map-picker.tsx
Normal file
102
apps/frontend/components/addresses/address-map-picker.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef } from 'react';
|
||||
import L from 'leaflet';
|
||||
|
||||
const DEFAULT_CENTER = { lat: 55.7558, lng: 37.6173 };
|
||||
|
||||
let iconsFixed = false;
|
||||
|
||||
function fixLeafletIcons() {
|
||||
if (iconsFixed || typeof window === 'undefined') return;
|
||||
iconsFixed = true;
|
||||
L.Icon.Default.mergeOptions({
|
||||
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
|
||||
iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png',
|
||||
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41]
|
||||
});
|
||||
}
|
||||
|
||||
export function AddressMapPicker({
|
||||
latitude,
|
||||
longitude,
|
||||
onPick
|
||||
}: {
|
||||
latitude?: number;
|
||||
longitude?: number;
|
||||
onPick: (lat: number, lng: number) => void;
|
||||
}) {
|
||||
const mapRef = useRef<HTMLDivElement>(null);
|
||||
const mapInstance = useRef<L.Map | null>(null);
|
||||
const markerRef = useRef<L.Marker | null>(null);
|
||||
const onPickRef = useRef(onPick);
|
||||
|
||||
useEffect(() => {
|
||||
onPickRef.current = onPick;
|
||||
}, [onPick]);
|
||||
|
||||
useEffect(() => {
|
||||
fixLeafletIcons();
|
||||
if (!mapRef.current || mapInstance.current) return;
|
||||
|
||||
const lat = latitude ?? DEFAULT_CENTER.lat;
|
||||
const lng = longitude ?? DEFAULT_CENTER.lng;
|
||||
const map = L.map(mapRef.current, { zoomControl: true }).setView([lat, lng], latitude != null ? 16 : 11);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}).addTo(map);
|
||||
|
||||
const marker = L.marker([lat, lng], { draggable: true }).addTo(map);
|
||||
|
||||
const emitPick = (nextLat: number, nextLng: number) => {
|
||||
onPickRef.current(nextLat, nextLng);
|
||||
};
|
||||
|
||||
marker.on('dragend', () => {
|
||||
const position = marker.getLatLng();
|
||||
emitPick(position.lat, position.lng);
|
||||
});
|
||||
|
||||
map.on('click', (event) => {
|
||||
marker.setLatLng(event.latlng);
|
||||
emitPick(event.latlng.lat, event.latlng.lng);
|
||||
});
|
||||
|
||||
mapInstance.current = map;
|
||||
markerRef.current = marker;
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
map.invalidateSize();
|
||||
});
|
||||
resizeObserver.observe(mapRef.current);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
map.remove();
|
||||
mapInstance.current = null;
|
||||
markerRef.current = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!markerRef.current || !mapInstance.current) return;
|
||||
if (latitude == null || longitude == null) return;
|
||||
const latLng = L.latLng(latitude, longitude);
|
||||
markerRef.current.setLatLng(latLng);
|
||||
mapInstance.current.setView(latLng, Math.max(mapInstance.current.getZoom(), 15));
|
||||
}, [latitude, longitude]);
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden rounded-2xl border border-[#eceef4]">
|
||||
<div ref={mapRef} className="h-[280px] w-full" />
|
||||
<p className="border-t border-[#eceef4] bg-[#fafbfd] px-4 py-2 text-xs text-[#667085]">
|
||||
Нажмите на карту или перетащите маркер, чтобы указать адрес
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user