diff --git a/src/features/online-board/components/OnlineBoardFilter.tsx b/src/features/online-board/components/OnlineBoardFilter.tsx index e1ff609d..b3ead5b7 100644 --- a/src/features/online-board/components/OnlineBoardFilter.tsx +++ b/src/features/online-board/components/OnlineBoardFilter.tsx @@ -8,7 +8,7 @@ * @module */ -import { type FC, useState, useCallback, type FormEvent } from "react"; +import { type FC, useState, useCallback, useEffect, useRef, type FormEvent } from "react"; import { useNavigate, useParams } from "@modern-js/runtime/router"; import { Calendar } from "primereact/calendar"; import { Slider, type SliderChangeEvent } from "primereact/slider"; @@ -91,6 +91,46 @@ export const OnlineBoardFilter: FC = ({ ); const [timeRange, setTimeRange] = useState<[number, number]>([0, 1440]); + // When the parent feeds new initial* props (e.g. a popular-request click + // pushes ?departure=SVO&arrival=LED into the URL), keep the fields in + // sync. useState only reads initial values once, so without this effect + // clicking a popular route left the sidebar untouched. + const lastInitialRef = useRef({ + departure: initialDeparture, + arrival: initialArrival, + date: initialDate, + tab: initialTab, + flightNumber: initialFlightNumber, + }); + useEffect(() => { + const prev = lastInitialRef.current; + if (prev.departure !== initialDeparture) { + setRouteDepartureCode(initialDeparture ?? ""); + } + if (prev.arrival !== initialArrival) { + setRouteArrivalCode(initialArrival ?? ""); + } + if (prev.date !== initialDate && initialDate) { + setRouteDate(yyyymmddToDate(initialDate)); + if (initialTab === "flight") { + setFlightDate(yyyymmddToDate(initialDate)); + } + } + if (prev.tab !== initialTab && initialTab) { + setActiveTab(initialTab); + } + if (prev.flightNumber !== initialFlightNumber) { + setFlightNumber(initialFlightNumber ?? ""); + } + lastInitialRef.current = { + departure: initialDeparture, + arrival: initialArrival, + date: initialDate, + tab: initialTab, + flightNumber: initialFlightNumber, + }; + }, [initialDeparture, initialArrival, initialDate, initialTab, initialFlightNumber]); + const handleTabClick = useCallback((tab: AccordionTab) => { setActiveTab((prev) => (prev === tab ? prev : tab)); }, []); diff --git a/src/ui/city-autocomplete/CityAutocomplete.tsx b/src/ui/city-autocomplete/CityAutocomplete.tsx index 37991685..3d001a3f 100644 --- a/src/ui/city-autocomplete/CityAutocomplete.tsx +++ b/src/ui/city-autocomplete/CityAutocomplete.tsx @@ -107,7 +107,21 @@ export const CityAutocomplete: FC = ({ ); }, []); - const selectedCity = dictionaries?.cityByCode.get(value.toUpperCase()) ?? null; + // Resolve the code to the owning city. The API (popular-requests, + // deep links, etc.) hands us either a city code like "MOW" or an + // airport code like "SVO"; in the latter case we look up the airport + // and follow its city_code so the label reads "MOW" instead of blank. + const selectedCity = (() => { + if (!value || !dictionaries) return null; + const upper = value.toUpperCase(); + const direct = dictionaries.cityByCode.get(upper); + if (direct) return direct; + const airport = dictionaries.airportByCode.get(upper); + if (airport) { + return dictionaries.cityByCode.get(airport.city_code.toUpperCase()) ?? null; + } + return null; + })(); const hasValue = Boolean(selectedCity); return (