From 3ae8d5c7111191957a8035887f79bf49f485c805 Mon Sep 17 00:00:00 2001 From: Ariel Costas Guerrero Date: Thu, 16 Apr 2026 22:08:45 +0200 Subject: Fix sorting shenanigans, improve stop viewing --- src/frontend/app/components/PlannerOverlay.tsx | 6 +- src/frontend/app/data/PlannerApi.ts | 14 ++- src/frontend/app/routes/map.tsx | 125 +++++++++++++++++++------ 3 files changed, 113 insertions(+), 32 deletions(-) (limited to 'src/frontend') diff --git a/src/frontend/app/components/PlannerOverlay.tsx b/src/frontend/app/components/PlannerOverlay.tsx index d42bd94..39f6848 100644 --- a/src/frontend/app/components/PlannerOverlay.tsx +++ b/src/frontend/app/components/PlannerOverlay.tsx @@ -247,7 +247,11 @@ export const PlannerOverlay: React.FC = ({ setRemoteLoading(true); const t = setTimeout(async () => { try { - const results = await searchPlaces(q); + const results = await searchPlaces( + q, + userLocation?.latitude, + userLocation?.longitude + ); if (!cancelled) setRemoteResults(results); } finally { if (!cancelled) setRemoteLoading(false); diff --git a/src/frontend/app/data/PlannerApi.ts b/src/frontend/app/data/PlannerApi.ts index 6f39f50..09f62a6 100644 --- a/src/frontend/app/data/PlannerApi.ts +++ b/src/frontend/app/data/PlannerApi.ts @@ -6,6 +6,8 @@ export interface PlannerSearchResult { layer?: string; stopId?: string; stopCode?: string; + color?: string; + textColor?: string; } export interface RoutePlan { @@ -74,11 +76,15 @@ export interface Step { } export async function searchPlaces( - query: string + query: string, + lat?: number, + lon?: number ): Promise { - const response = await fetch( - `/api/planner/autocomplete?query=${encodeURIComponent(query)}` - ); + let url = `/api/planner/autocomplete?query=${encodeURIComponent(query)}`; + if (lat !== undefined && lon !== undefined) { + url += `&lat=${lat}&lon=${lon}`; + } + const response = await fetch(url); if (!response.ok) return []; return response.json(); } diff --git a/src/frontend/app/routes/map.tsx b/src/frontend/app/routes/map.tsx index 9774a0f..dae92f3 100644 --- a/src/frontend/app/routes/map.tsx +++ b/src/frontend/app/routes/map.tsx @@ -32,6 +32,16 @@ const mapSearchState: { query: string; results: PlannerSearchResult[] } = { results: [], }; +const FEED_LABELS: Record = { + vitrasa: "Vitrasa", + tussa: "Tussa", + tranvias: "Tranvías", + ourense: "TUORTE", + lugo: "AUCORSA", + xunta: "Xunta", + renfe: "Renfe", +}; + interface MapSearchBarProps { mapRef: React.RefObject; } @@ -82,7 +92,8 @@ function MapSearchBar({ mapRef }: MapSearchBarProps) { debounceRef.current = setTimeout(async () => { setLoading(true); try { - const res = await searchPlaces(q.trim()); + const center = mapRef.current?.getCenter(); + const res = await searchPlaces(q.trim(), center?.lat, center?.lng); setResults(res); mapSearchState.results = res; setShowResults(true); @@ -97,9 +108,11 @@ function MapSearchBar({ mapRef }: MapSearchBarProps) { const handleSelect = (place: PlannerSearchResult) => { const map = mapRef.current; if (map) { - map.flyTo({ center: [place.lon, place.lat], zoom: 15, duration: 800 }); + const zoom = place.layer === "stop" ? 17 : 16; + map.flyTo({ center: [place.lon, place.lat], zoom, duration: 800 }); } - // Keep results visible so user can pick another without retyping + setShowResults(false); + mapSearchState.results = []; }; const handleClear = () => { @@ -129,6 +142,10 @@ function MapSearchBar({ mapRef }: MapSearchBarProps) { onChange={(e) => handleQueryChange(e.target.value)} onFocus={() => { if (results.length > 0) setShowResults(true); + // Re-trigger search if we have a query but results were cleared + if (results.length === 0 && query.trim().length >= 2) { + handleQueryChange(query); + } }} /> {loading ? ( @@ -152,25 +169,50 @@ function MapSearchBar({ mapRef }: MapSearchBarProps) { {showResults && results.length > 0 && (
- {results.map((place, i) => ( -
- - ))} +
+
+ {place.name} +
+ {subtitle && ( +
+ {subtitle} +
+ )} +
+ + ); + })}
)} @@ -194,7 +236,7 @@ export default function StopMap() { >(null); const [isSheetOpen, setIsSheetOpen] = useState(false); const [disambiguationStops, setDisambiguationStops] = useState< - Array + Array >([]); const mapRef = useRef(null); @@ -323,6 +365,9 @@ export default function StopMap() { // Handle click events on clusters and individual stops const onMapClick = (e: MapLayerMouseEvent) => { + // Clicking anywhere on the map closes the disambiguation panel + setDisambiguationStops([]); + const features = e.features; if (!features || features.length === 0) { console.debug( @@ -349,7 +394,7 @@ export default function StopMap() { // Multiple overlapping stops – deduplicate by stop id and ask the user const seen = new Set(); - const candidates: Array = []; + const candidates: Array = []; for (const f of stopFeatures) { const id: string = f.properties!.id; if (!seen.has(id)) { @@ -358,16 +403,29 @@ export default function StopMap() { stopId: id, stopCode: f.properties!.code, name: f.properties!.name || "Unknown Stop", + color: f.properties!.color as string | undefined, }); } } - if (candidates.length === 1) { + // For xunta stops, further deduplicate by base code (strip first 2 chars) + // e.g. "xunta:1007958" and "xunta:2007958" → keep only the first seen + const xuntaBaseSeen = new Set(); + const deduped = candidates.filter((stop) => { + if (!stop.stopId?.startsWith("xunta:")) return true; + const code = stop.stopCode ?? ""; + const base = code.startsWith("xunta:") ? code.slice("xunta:".length + 2) : code.slice(2); + if (xuntaBaseSeen.has(base)) return false; + xuntaBaseSeen.add(base); + return true; + }); + + if (deduped.length === 1) { // After deduplication only one stop remains - setSelectedStop(candidates[0]); + setSelectedStop(deduped[0]); setIsSheetOpen(true); } else { - setDisambiguationStops(candidates); + setDisambiguationStops(deduped); } }; @@ -473,6 +531,7 @@ export default function StopMap() { onMapClick(e); }} onContextMenu={handleContextMenu} + onDragStart={() => setDisambiguationStops([])} attributionControl={{ compact: false }} > - + {stop.color ? ( + + ) : ( + + )}
{stop.name} -- cgit v1.3