Fix online board details date selection

This commit is contained in:
2026-05-06 00:10:59 +03:00
parent cb48dcc706
commit 19ae50af80
5 changed files with 155 additions and 5 deletions
@@ -12,6 +12,7 @@ import { Link } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import type { ISimpleFlight, IFlightLeg } from "../../types.js";
import { buildOnlineBoardUrl } from "../../url.js";
import { getFlightSearchDate } from "../../flightSearchDate.js";
import {
formatLocalTime,
formatDayMonthYear,
@@ -51,7 +52,7 @@ export const FlightsMiniListItem = forwardRef<HTMLAnchorElement, FlightsMiniList
carrier: flight.flightId.carrier,
flightNumber: flight.flightId.flightNumber,
...(flight.flightId.suffix ? { suffix: flight.flightId.suffix } : {}),
date: flight.flightId.date,
date: getFlightSearchDate(flight),
})}`;
const depIso = getDepTimeIso(dep);
@@ -22,6 +22,7 @@ import { useOnlineBoard } from "../hooks/useOnlineBoard.js";
import { parseDetailsRequestParam } from "@/shared/detailsRequestParam.js";
import { buildFlightJsonLd } from "../json-ld.js";
import { buildOnlineBoardUrl } from "../url.js";
import { getFlightSearchDate } from "../flightSearchDate.js";
import { useCityName, useStationDisplayName } from "@/shared/hooks/useDictionaries.js";
import { FlightDetailsAccordion } from "./details-panels/FlightDetailsAccordion.js";
import { FlightsMiniList } from "./FlightsMiniList/index.js";
@@ -388,9 +389,9 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
// Pick the flight matching the URL's flightId (date-based match). The API
// response may contain multiple flights with the same flight number on
// different dates; we need the one the user actually navigated to.
// different dates; match Angular's dateToSearchBy, not backend flightId.date.
const flight =
allFlights.find((f) => f.flightId.date === flightId.date) ?? firstFlight;
allFlights.find((f) => getFlightSearchDate(f) === flightId.date) ?? firstFlight;
// Live updates via SignalR
const { flight: liveFlight, connectionStatus } = useLiveFlightDetails(
@@ -36,6 +36,7 @@ import { useCalendarDays } from "../hooks/useCalendarDays.js";
import { buildOnlineBoardUrl } from "../url.js";
import { buildFlightListJsonLd } from "../json-ld.js";
import { sortFlights } from "../sortFlights.js";
import { getFlightSearchDate } from "../flightSearchDate.js";
import {
PobedaAuroraBanner,
shouldShowPobedaAuroraBanner,
@@ -388,13 +389,13 @@ export const OnlineBoardSearchPage: FC<OnlineBoardSearchPageProps> = ({
carrier: flight.flightId.carrier,
flightNumber: flight.flightId.flightNumber,
suffix: flight.flightId.suffix,
date: flight.flightId.date,
date: getFlightSearchDate(flight),
}
: {
type: "details",
carrier: flight.flightId.carrier,
flightNumber: flight.flightId.flightNumber,
date: flight.flightId.date,
date: getFlightSearchDate(flight),
};
const detailsUrl = buildOnlineBoardUrl(detailsParams);
void navigate(`/${locale}/${detailsUrl}`);
@@ -0,0 +1,113 @@
import { describe, expect, it } from "vitest";
import { getFlightSearchDate } from "./flightSearchDate.js";
import type { IDirectFlight, IMultiLegFlight, IFlightLeg } from "./types.js";
function leg(local: string): IFlightLeg {
return {
arrival: {
scheduled: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" },
times: {
scheduledArrival: {
dayChange: { value: 0, title: "" },
local,
localTime: "",
tzOffset: 0,
utc: "",
},
},
},
dayChange: 0,
departure: {
scheduled: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" },
checkingStatus: "Scheduled",
times: {
scheduledDeparture: {
dayChange: { value: 0, title: "" },
local,
localTime: "",
tzOffset: 0,
utc: "",
},
},
},
equipment: {},
flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
flyingTime: "",
index: 0,
operatingBy: {},
status: "Scheduled",
updated: "",
};
}
describe("getFlightSearchDate", () => {
it("uses scheduled local departure date instead of backend flightId.date", () => {
const flight: IDirectFlight = {
id: "SU6951",
routeType: "Direct",
flyingTime: "",
operatingBy: {},
status: "Arrived",
flightId: {
carrier: "SU",
flightNumber: "6951",
suffix: "",
date: "2026-05-04",
dateLT: "2026-05-05",
},
leg: leg("2026-05-05T00:30:00+03:00"),
};
expect(getFlightSearchDate(flight)).toBe("20260505");
});
it("uses the first leg for multi-leg flights", () => {
const flight: IMultiLegFlight = {
id: "SU100",
routeType: "MultiLeg",
flyingTime: "",
operatingBy: {},
status: "Scheduled",
flightId: {
carrier: "SU",
flightNumber: "0100",
suffix: "",
date: "20260504",
},
legs: [
leg("2026-05-05T23:30:00+03:00"),
leg("2026-05-06T01:30:00+03:00"),
],
};
expect(getFlightSearchDate(flight)).toBe("20260505");
});
it("falls back to dateLT and then flightId.date when leg date is unavailable", () => {
const flight: IDirectFlight = {
id: "SU42",
routeType: "Direct",
flyingTime: "",
operatingBy: {},
status: "Scheduled",
flightId: {
carrier: "SU",
flightNumber: "0042",
suffix: "",
date: "2026-05-04",
dateLT: "2026-05-05",
},
leg: leg("10:00"),
};
expect(getFlightSearchDate(flight)).toBe("20260505");
const { dateLT: _dateLT, ...flightIdWithoutDateLt } = flight.flightId;
const withoutDateLt: IDirectFlight = {
...flight,
flightId: flightIdWithoutDateLt,
};
expect(getFlightSearchDate(withoutDateLt)).toBe("20260504");
});
});
@@ -0,0 +1,34 @@
import type { IFlightLeg, ISimpleFlight } from "./types.js";
function compactDate(value: string | undefined): string | null {
if (!value) return null;
if (/^\d{8}$/.test(value)) return value;
const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
if (!match) return null;
return `${match[1]}${match[2]}${match[3]}`;
}
function getFirstLeg(flight: ISimpleFlight): IFlightLeg | null {
if (flight.routeType === "Direct") return flight.leg;
return flight.legs[0] ?? null;
}
/**
* Angular's FlightDateUtils.getFlightDate derives the details URL date from
* the first leg's scheduled local departure date, not from flightId.date.
* Overnight flights can have flightId.date set to the previous backend
* service day while dateLT / scheduled local departure belongs to the board
* day the user clicked.
*/
export function getFlightSearchDate(flight: ISimpleFlight): string {
const firstLeg = getFirstLeg(flight);
return (
compactDate(firstLeg?.departure.times.scheduledDeparture.local) ??
compactDate(flight.flightId.dateLT) ??
compactDate(flight.flightId.date) ??
flight.flightId.date.replace(/-/g, "")
);
}