ScheduleStartPage: use shared CityAutocomplete (adds clear button + regional picker parity)

This commit is contained in:
2026-04-20 13:18:29 +03:00
parent c28cfc2fd3
commit 922e41e5c9
@@ -7,17 +7,16 @@
* @module
*/
import { type FC, useState, useCallback, useEffect, type FormEvent } from "react";
import { type FC, useState, useCallback, type FormEvent } from "react";
import { useNavigate } from "@modern-js/runtime/router";
import { useLocale } from "@/i18n/useLocale.js";
import { Calendar } from "primereact/calendar";
import { Slider, type SliderChangeEvent } from "primereact/slider";
import { AutoComplete, type AutoCompleteCompleteEvent } from "primereact/autocomplete";
import { useTranslation } from "@/i18n/provider.js";
import { useCitySearch, type CitySuggestion } from "@/shared/hooks/useCitySearch.js";
import { PageLayout } from "@/ui/layout/PageLayout.js";
import { PageTabs } from "@/ui/layout/PageTabs.js";
import { SearchHistory } from "@/ui/layout/SearchHistory.js";
import { CityAutocomplete } from "@/ui/city-autocomplete/index.js";
import { PopularRequestsPanel } from "@/features/popular-requests/components/PopularRequestsPanel.js";
import type { PopularRequest } from "@/features/popular-requests/types.js";
import {
@@ -90,38 +89,13 @@ export const ScheduleStartPage: FC = () => {
const today = new Date();
const [departureAirport, setDepartureAirport] = useState<CitySuggestion | string>(prefill.departure ?? "");
const [arrivalAirport, setArrivalAirport] = useState<CitySuggestion | string>(prefill.arrival ?? "");
// State is the IATA city code (string). The shared CityAutocomplete
// resolves the code to a localized display name internally — same
// component used on OnlineBoard and FlightsMap, so the clear (×)
// button, regional picker, and airport→city resolution come for free.
const [departureCode, setDepartureCode] = useState<string>(prefill.departure ?? "");
const [arrivalCode, setArrivalCode] = useState<string>(prefill.arrival ?? "");
// Prefill arrives as a bare IATA code ("MOW"). Once dictionaries are
// loaded, upgrade each state slot to a `{code, name}` CitySuggestion
// so the PrimeReact AutoComplete renders "Москва" rather than the raw
// code. Mirrors Angular's CityAutocomplete.writeValue + getCityOrAirport.
useEffect(() => {
if (!dictionaries) return;
const resolve = (code: string): CitySuggestion | null => {
const upper = code.toUpperCase();
const city = dictionaries.cityByCode.get(upper);
if (city) return { code: city.code, name: city.name };
const airport = dictionaries.airportByCode.get(upper);
if (airport) {
const parentCode = airport.city_code?.toUpperCase();
const parent = parentCode
? dictionaries.cityByCode.get(parentCode)
: null;
if (parent) return { code: parent.code, name: parent.name };
}
return null;
};
setDepartureAirport((current) => {
if (typeof current !== "string" || !current) return current;
return resolve(current) ?? current;
});
setArrivalAirport((current) => {
if (typeof current !== "string" || !current) return current;
return resolve(current) ?? current;
});
}, [dictionaries]);
// Start blank to match Angular's `ДД.ММ.ГГГГ - ДД.ММ.ГГГГ` placeholder
// (the "current week" pre-fill was a React-only convenience that
// pulled the date input out of parity). Submit handler defaults to
@@ -135,28 +109,12 @@ export const ScheduleStartPage: FC = () => {
const [returnDateTo, setReturnDateTo] = useState<Date | null>(null);
const [returnTimeRange, setReturnTimeRange] = useState<[number, number]>([0, 1440]);
// City autocomplete search
const { suggestions: departureSuggestions, search: searchDeparture } = useCitySearch();
const { suggestions: arrivalSuggestions, search: searchArrival } = useCitySearch();
const handleDepartureSearch = useCallback((event: AutoCompleteCompleteEvent) => {
void searchDeparture(event.query);
}, [searchDeparture]);
const handleArrivalSearch = useCallback((event: AutoCompleteCompleteEvent) => {
void searchArrival(event.query);
}, [searchArrival]);
const handleSubmit = useCallback(
(e: FormEvent) => {
e.preventDefault();
const dep = (typeof departureAirport === "string"
? departureAirport.trim().toUpperCase()
: departureAirport.code);
const arr = (typeof arrivalAirport === "string"
? arrivalAirport.trim().toUpperCase()
: arrivalAirport.code);
const dep = departureCode.trim().toUpperCase();
const arr = arrivalCode.trim().toUpperCase();
if (!dep || !arr) return;
// Empty dates default to the current week (today → today + 7) so
@@ -202,7 +160,7 @@ export const ScheduleStartPage: FC = () => {
void navigate(`/${locale}/${url}`);
},
[departureAirport, arrivalAirport, dateFrom, dateTo, timeRange, directOnly, isRoundTrip, returnDateFrom, returnDateTo, returnTimeRange, navigate, locale],
[departureCode, arrivalCode, dateFrom, dateTo, timeRange, directOnly, isRoundTrip, returnDateFrom, returnDateTo, returnTimeRange, navigate, locale],
);
const handlePopularRequestClick = useCallback(
@@ -240,31 +198,23 @@ export const ScheduleStartPage: FC = () => {
data-testid="schedule-search-form"
onSubmit={handleSubmit}
>
<div className="schedule-start__field">
<label htmlFor="schedule-departure">{t("SHARED.DEPARTURE_CITY")}</label>
<AutoComplete
value={departureAirport}
suggestions={departureSuggestions}
completeMethod={handleDepartureSearch}
field="name"
dropdown
onChange={(e) => setDepartureAirport(e.value as CitySuggestion | string)}
placeholder={t("SHARED.CITY_PLACEHOLDER")}
className="input--filter"
inputClassName="input--filter"
inputId="schedule-departure"
data-testid="departure-input"
/>
</div>
<CityAutocomplete
label={t("SHARED.DEPARTURE_CITY")}
placeholder={t("SHARED.CITY_PLACEHOLDER")}
value={departureCode}
onChange={setDepartureCode}
dictionaries={dictionaries}
testIdPrefix="schedule-departure"
/>
<div className="change-container">
<button
className="button-change"
type="button"
onClick={() => {
const tmp = departureAirport;
setDepartureAirport(arrivalAirport);
setArrivalAirport(tmp);
const tmp = departureCode;
setDepartureCode(arrivalCode);
setArrivalCode(tmp);
}}
data-testid="swap-cities-button"
>
@@ -274,22 +224,14 @@ export const ScheduleStartPage: FC = () => {
</button>
</div>
<div className="schedule-start__field">
<label htmlFor="schedule-arrival">{t("SHARED.ARRIVAL_CITY")}</label>
<AutoComplete
value={arrivalAirport}
suggestions={arrivalSuggestions}
completeMethod={handleArrivalSearch}
field="name"
dropdown
onChange={(e) => setArrivalAirport(e.value as CitySuggestion | string)}
placeholder={t("SHARED.CITY_PLACEHOLDER")}
className="input--filter"
inputClassName="input--filter"
inputId="schedule-arrival"
data-testid="arrival-input"
/>
</div>
<CityAutocomplete
label={t("SHARED.ARRIVAL_CITY")}
placeholder={t("SHARED.CITY_PLACEHOLDER")}
value={arrivalCode}
onChange={setArrivalCode}
dictionaries={dictionaries}
testIdPrefix="schedule-arrival"
/>
<div className="schedule-start__field">
<label htmlFor="schedule-date-from">{t("SHARED.SCHEDULES_DATE")}</label>