From 67dfc03c7beddf514553e26620aabe476c8a3962 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 23 Jun 2026 12:38:23 +0330 Subject: [PATCH] search in maps --- .../settings/components/LocationPicker.tsx | 112 +++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/components/LocationPicker.tsx b/src/pages/settings/components/LocationPicker.tsx index c6e2dd2..bbb1e8f 100644 --- a/src/pages/settings/components/LocationPicker.tsx +++ b/src/pages/settings/components/LocationPicker.tsx @@ -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 = ({ initialPosition, onLocationSe 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) { @@ -27,6 +38,75 @@ const LocationPicker: FC = ({ 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 = ({ initialPosition, onLocationSe } 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) => ( + + ))} +
+ )} + +
= ({ initialPosition, onLocationSe /> +
) }