diff --git a/src/features/online-board/components/BoardDetailsHeader/visibility/flightStatusVisibility.test.ts b/src/features/online-board/components/BoardDetailsHeader/visibility/flightStatusVisibility.test.ts new file mode 100644 index 00000000..6a18a334 --- /dev/null +++ b/src/features/online-board/components/BoardDetailsHeader/visibility/flightStatusVisibility.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect } from "vitest"; +import { canViewFlightStatus } from "./flightStatusVisibility.js"; +import { AIRLINES_WITH_STATUS } from "../airlines.js"; +import type { ISimpleFlight } from "../../../types.js"; + +function makeFlight(carrier: string, depUtc: string): ISimpleFlight { + return { + id: "X-1", + routeType: "Direct", + flyingTime: "1h", + status: "Scheduled", + flightId: { carrier, flightNumber: "0022", suffix: "", date: "20260417" }, + operatingBy: { carrier, flightNumber: "0022" }, + leg: { + arrival: { + scheduled: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" }, + latest: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" }, + dispatch: "", gate: "", terminal: "", + times: { scheduledArrival: { dayChange: { value: 0, title: "" }, local: "", localTime: "", tzOffset: 0, utc: "" } }, + }, + dayChange: 0, + departure: { + scheduled: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" }, + latest: { airport: "", airportCode: "", city: "", cityCode: "", countryCode: "" }, + dispatch: "", gate: "", terminal: "", checkingStatus: "Scheduled", parkingStand: "", + times: { scheduledDeparture: { dayChange: { value: 0, title: "" }, local: "", localTime: "", tzOffset: 0, utc: depUtc } }, + }, + equipment: {}, + flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false }, + flyingTime: "1h", + index: 0, + operatingBy: {}, + status: "Scheduled", + updated: "", + }, + } as ISimpleFlight; +} + +describe("canViewFlightStatus", () => { + it("returns false for carrier not in AIRLINES_WITH_STATUS", () => { + const now = new Date("2026-04-17T10:00:00Z"); + expect( + canViewFlightStatus(makeFlight("AF", "2026-04-17T12:00:00Z"), now, 24, AIRLINES_WITH_STATUS), + ).toBe(false); + }); + + it("returns false when departure is not same day as now (UTC)", () => { + const now = new Date("2026-04-17T23:59:59Z"); + expect( + canViewFlightStatus(makeFlight("SU", "2026-04-18T00:30:00Z"), now, 24, AIRLINES_WITH_STATUS), + ).toBe(false); + }); + + it("returns true when SU same-day and within availableFromHours before departure", () => { + const now = new Date("2026-04-17T08:00:00Z"); + expect( + canViewFlightStatus(makeFlight("SU", "2026-04-17T10:00:00Z"), now, 24, AIRLINES_WITH_STATUS), + ).toBe(true); + }); + + it("returns false when now is before availableFrom window", () => { + const now = new Date("2026-04-17T00:00:00Z"); + // Departure 23:00 same day, availableFromHours=1 → available only from 22:00 + expect( + canViewFlightStatus(makeFlight("SU", "2026-04-17T23:00:00Z"), now, 1, AIRLINES_WITH_STATUS), + ).toBe(false); + }); + + it("returns false when departure UTC is empty", () => { + const now = new Date(); + expect(canViewFlightStatus(makeFlight("SU", ""), now, 24, AIRLINES_WITH_STATUS)).toBe(false); + }); +}); diff --git a/src/features/online-board/components/BoardDetailsHeader/visibility/flightStatusVisibility.ts b/src/features/online-board/components/BoardDetailsHeader/visibility/flightStatusVisibility.ts new file mode 100644 index 00000000..1195c068 --- /dev/null +++ b/src/features/online-board/components/BoardDetailsHeader/visibility/flightStatusVisibility.ts @@ -0,0 +1,32 @@ +import { parseISO, subHours, isAfter, isSameDay } from "date-fns"; +import type { ISimpleFlight } from "../../../types.js"; + +/** + * Flight Status button is visible when: + * - the operating carrier is in airlinesWithStatus + * - departure is same-day as now + * - now is within `availableFromHours` before departure + */ +export function canViewFlightStatus( + flight: ISimpleFlight, + now: Date, + availableFromHours: number, + airlinesWithStatus: Set, +): boolean { + const carrier = flight.operatingBy.carrier; + if (!carrier || !airlinesWithStatus.has(carrier)) return false; + + const leg = flight.routeType === "Direct" ? flight.leg : flight.legs[0]; + if (!leg) return false; + + const depUtc = leg.departure.times.scheduledDeparture.utc; + if (!depUtc) return false; + + const departure = parseISO(depUtc); + if (isNaN(departure.getTime())) return false; + + if (!isSameDay(now, departure)) return false; + + const availableFrom = subHours(departure, availableFromHours); + return isAfter(now, availableFrom); +}