search in maps
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-23 12:38:23 +03:30
parent 7130c304c5
commit 67dfc03c7b
@@ -8,6 +8,13 @@ interface LocationPickerProps {
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(() => {
@@ -20,6 +27,10 @@ const LocationPicker: FC<LocationPickerProps> = ({ initialPosition, onLocationSe
const [position, setPosition] = useState<LatLngTuple>(
initialPosition || [35.6892, 51.3890] // تهران به عنوان پیش‌فرض
)
const [searchQuery, setSearchQuery] = useState('')
const [searchResults, setSearchResults] = useState<NominatimSearchResult[]>([])
const [isSearching, setIsSearching] = useState(false)
const [searchError, setSearchError] = useState('')
useEffect(() => {
if (initialPosition) {
@@ -27,6 +38,75 @@ const LocationPicker: FC<LocationPickerProps> = ({ initialPosition, onLocationSe
}
}, [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) {
@@ -50,7 +130,36 @@ const LocationPicker: FC<LocationPickerProps> = ({ initialPosition, onLocationSe
}
return (
<div className="w-full h-[400px] rounded-xl overflow-hidden">
<div className='w-full'>
<div className='mb-3'>
<input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder='جستجوی آدرس یا مکان'
className='w-full h-10 border border-border rounded-xl px-3 text-sm outline-none focus:border-primary'
/>
</div>
{isSearching && <div className='text-xs text-description mb-2'>در حال جستجو...</div>}
{searchError && <div className='text-xs text-red-500 mb-2'>{searchError}</div>}
{searchResults.length > 0 && (
<div className='mb-3 border border-border rounded-xl max-h-32 overflow-auto'>
{searchResults.map((result) => (
<button
key={result.place_id}
type='button'
onClick={() => handleSelectSearchResult(result)}
className='w-full text-right px-3 py-2 text-xs hover:bg-gray-50 border-b border-border last:border-b-0'
>
{result.display_name}
</button>
))}
</div>
)}
<div className='w-full h-[400px] rounded-xl overflow-hidden'>
<MapContainer
center={position}
zoom={13}
@@ -64,6 +173,7 @@ const LocationPicker: FC<LocationPickerProps> = ({ initialPosition, onLocationSe
/>
<LocationMarker />
</MapContainer>
</div>
</div>
)
}