import { type FC, useState, useEffect } from 'react' import { MapContainer, TileLayer, Marker, useMapEvents, useMap } from 'react-leaflet' import { Icon } from 'leaflet' import type { LatLngTuple } from 'leaflet' interface LocationPickerProps { initialPosition?: [number, number] onLocationSelect: (lat: number, lng: number) => void } interface NominatimSearchResult { place_id: number display_name: string lat: string lon: string } const ChangeView = ({ center, zoom }: { center: LatLngTuple; zoom: number }) => { const map = useMap() useEffect(() => { map.setView(center, zoom) }, [map, center, zoom]) return null } const LocationPicker: FC = ({ initialPosition, onLocationSelect }) => { const [position, setPosition] = useState( initialPosition || [35.6892, 51.3890] // تهران به عنوان پیش‌فرض ) const [searchQuery, setSearchQuery] = useState('') const [searchResults, setSearchResults] = useState([]) const [isSearching, setIsSearching] = useState(false) const [searchError, setSearchError] = useState('') useEffect(() => { if (initialPosition) { setPosition(initialPosition) } }, [initialPosition]) useEffect(() => { const query = searchQuery.trim() if (!query) { setSearchResults([]) setSearchError('') setIsSearching(false) return } const controller = new AbortController() const timer = window.setTimeout(async () => { setIsSearching(true) setSearchError('') try { const params = new URLSearchParams({ q: query, format: 'json', addressdetails: '1', limit: '5', }) const response = await fetch( `https://nominatim.openstreetmap.org/search?${params.toString()}`, { headers: { Accept: 'application/json', }, signal: controller.signal, } ) if (!response.ok) { throw new Error('search request failed') } const data = (await response.json()) as NominatimSearchResult[] if (!data.length) { setSearchResults([]) setSearchError('نتیجه‌ای پیدا نشد') return } setSearchResults(data) } catch (error) { if ((error as DOMException).name === 'AbortError') { return } setSearchResults([]) setSearchError('خطا در جستجوی موقعیت') } finally { setIsSearching(false) } }, 500) return () => { controller.abort() window.clearTimeout(timer) } }, [searchQuery]) const handleSelectSearchResult = (result: NominatimSearchResult) => { const lat = Number(result.lat) const lng = Number(result.lon) const newPosition: LatLngTuple = [lat, lng] setPosition(newPosition) } const LocationMarker = () => { useMapEvents({ click(e) { const newPosition: LatLngTuple = [e.latlng.lat, e.latlng.lng] setPosition(newPosition) onLocationSelect(newPosition[0], newPosition[1]) }, }) const customIcon = new Icon({ 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], }) return position ? : null } return (
setSearchQuery(e.target.value)} placeholder='جستجوی آدرس یا مکان' className='w-full h-10 border border-border rounded-xl px-3 text-sm outline-none focus:border-primary' />
{isSearching &&
در حال جستجو...
} {searchError &&
{searchError}
} {searchResults.length > 0 && (
{searchResults.map((result) => ( ))}
)}
) } export default LocationPicker