From b01fc2f0c98e8ac5c1c93aeb96b497d8ef5767d4 Mon Sep 17 00:00:00 2001 From: gnezim Date: Sat, 18 Apr 2026 12:43:33 +0300 Subject: [PATCH] Populate filter sidebar when clicking a popular request MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs prevented the popular-requests click from filling the filter: 1. OnlineBoardFilter seeded its fields from initial* props via useState(...), which only runs once. When a user clicked a popular request the parent pushed ?departure=SVO&arrival=LED into the URL and re-rendered with new initial* props, but the sidebar fields kept their previous empty values. Add an effect that diffs the initial* props against a ref and pushes the changes into local state, matching Angular's ngOnChanges behaviour. 2. CityAutocomplete's selectedCity only looked the value up in cityByCode. Airport codes like SVO aren't cities, so the header code label stayed blank. Fall back to airportByCode → city_code so the top-right code renders as 'MOW' when the input shows 'Шереметьево'. End-to-end behaviour now matches Angular: clicking 'Маршрут: Шереметьево - Санкт-Петербург' on the start page updates the URL, populates 'Шереметьево' / 'Санкт-Петербург' in the inputs, shows 'MOW' / 'LED' codes in the labels. --- .../components/OnlineBoardFilter.tsx | 42 ++++++++++++++++++- src/ui/city-autocomplete/CityAutocomplete.tsx | 16 ++++++- 2 files changed, 56 insertions(+), 2 deletions(-) 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 (