import { useEffect, useRef, useState } from 'react'; import { loadNearby } from '../lib/api'; import { groupStops } from '../lib/stopGroups'; import { MapPin, RefreshCw } from './Icons'; import { StopList, StopListItem } from './StopList'; import type { NearbyStop, Stop } from '../types'; export function NearbyStops({ cityId, mode, online, onSelect, selectedGroupKey, favoriteStopIds, autoLocate = false }: { cityId: number; mode: 'live' | 'demo'; online: boolean; onSelect: (stop: Stop) => void; selectedGroupKey?: string; favoriteStopIds: number[]; autoLocate?: boolean }) { const [results, setResults] = useState(null); const [busy, setBusy] = useState(false); const [message, setMessage] = useState(''); const [approximate, setApproximate] = useState(false); const request = useRef(0); const controller = useRef(null); useEffect(() => () => { request.current++; controller.current?.abort(); }, []); useEffect(() => { if (!autoLocate) return; const timer = setTimeout(() => void searchNearby(), 0); return () => clearTimeout(timer); }, [autoLocate]); async function searchNearby() { if (!online || busy) return; if (!navigator.geolocation || !window.isSecureContext) { setMessage('Поиск по местоположению недоступен в этом браузере. Найдите остановку по названию.'); return; } const current = ++request.current; controller.current?.abort(); controller.current = new AbortController(); const signal = controller.current.signal; setBusy(true); setMessage(''); try { const position = await new Promise((resolve, reject) => navigator.geolocation.getCurrentPosition(resolve, reject, { enableHighAccuracy: false, timeout: 10000, maximumAge: 60000 })); if (current !== request.current) return; const stops = await loadNearby(mode, cityId, position.coords.latitude, position.coords.longitude, signal); if (current !== request.current) return; setResults(stops); setApproximate(position.coords.accuracy > 300); } catch (error) { if (current !== request.current) return; const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined; setMessage(code === 1 ? 'Доступ к местоположению закрыт. Можно выбрать остановку по названию.' : code === 3 ? 'Не удалось определить местоположение вовремя. Попробуйте ещё раз или найдите остановку по названию.' : 'Не удалось найти остановки рядом. Попробуйте ещё раз или воспользуйтесь поиском.'); } finally { if (current === request.current) setBusy(false); } } const groups = groupStops(results ?? []).map(group => ({ group, nearest: (results ?? []).filter(stop => group.stops.some(item => item.id === stop.id)).sort((a, b) => a.distance - b.distance)[0] })).sort((a, b) => a.nearest.distance - b.nearest.distance); return
{message &&

{message}

} {results &&

{mode === 'demo' ? 'Примеры остановок рядом' : 'В радиусе 1 км'}{approximate && ' · местоположение приблизительное'}

{!groups.length ?
Рядом нет остановок выбранного города. Попробуйте выбрать другой город.
: groups.map(({ group, nearest }) => favoriteStopIds.includes(stop.id))} distance={nearest.distance} onSelect={() => onSelect(nearest)} />)}
}
; }