diff --git a/src/features/online-board/components/OnlineBoardSearchPage.tsx b/src/features/online-board/components/OnlineBoardSearchPage.tsx index f0e0a8bb..cd1b02af 100644 --- a/src/features/online-board/components/OnlineBoardSearchPage.tsx +++ b/src/features/online-board/components/OnlineBoardSearchPage.tsx @@ -36,6 +36,10 @@ import { useCalendarDays } from "../hooks/useCalendarDays.js"; import { buildOnlineBoardUrl } from "../url.js"; import { buildFlightListJsonLd } from "../json-ld.js"; import { sortFlights } from "../sortFlights.js"; +import { + PobedaAuroraBanner, + shouldShowPobedaAuroraBanner, +} from "./PobedaAuroraBanner.js"; import type { SortMode } from "../sortFlights.js"; import type { OnlineBoardParams } from "../url.js"; import type { SearchFlightsParams, CalendarParams } from "../api.js"; @@ -575,8 +579,27 @@ export const OnlineBoardSearchPage: FC = ({ )} + {/* TZ §4.1.10.1 — flight-number search that returns only Pobeda/Aurora + flights shows a redirect banner to the subsidiary carrier sites + instead of the normal list. */} + {!error && params.type === "flight" && !loading && (() => { + const banner = shouldShowPobedaAuroraBanner(displayFlights); + return banner.show ? ( +
+ +
+ ) : null; + })()} + {/* Flight list — wrapped in .frame for the white card + shadow */} - {!error && ( + {!error && !( + params.type === "flight" && + !loading && + shouldShowPobedaAuroraBanner(displayFlights).show + ) && (
{ + it("returns show=false for empty results", () => { + expect(shouldShowPobedaAuroraBanner([])).toEqual({ + show: false, + hasPobeda: false, + hasAurora: false, + }); + }); + + it("returns show=true when all flights are Pobeda (DP)", () => { + const r = shouldShowPobedaAuroraBanner([flight("DP"), flight("DP")]); + expect(r).toEqual({ show: true, hasPobeda: true, hasAurora: false }); + }); + + it("returns show=true when all flights are Aurora (HZ)", () => { + const r = shouldShowPobedaAuroraBanner([flight("HZ")]); + expect(r).toEqual({ show: true, hasPobeda: false, hasAurora: true }); + }); + + it("returns show=true with both Pobeda and Aurora", () => { + const r = shouldShowPobedaAuroraBanner([flight("DP"), flight("HZ")]); + expect(r).toEqual({ show: true, hasPobeda: true, hasAurora: true }); + }); + + it("returns show=false when any flight is another carrier (e.g. SU)", () => { + const r = shouldShowPobedaAuroraBanner([flight("DP"), flight("SU")]); + expect(r.show).toBe(false); + }); + + it("uses flight-number range when operatingBy is empty (SU5000-5399 → DP)", () => { + const r = shouldShowPobedaAuroraBanner([flight("DP", "5200", true)]); + expect(r).toEqual({ show: true, hasPobeda: true, hasAurora: false }); + }); + + it("uses flight-number range when operatingBy is empty (SU5400-5799 → HZ)", () => { + const r = shouldShowPobedaAuroraBanner([flight("HZ", "5500", true)]); + expect(r).toEqual({ show: true, hasPobeda: false, hasAurora: true }); + }); +}); diff --git a/src/features/online-board/components/PobedaAuroraBanner.tsx b/src/features/online-board/components/PobedaAuroraBanner.tsx new file mode 100644 index 00000000..d91f2ddc --- /dev/null +++ b/src/features/online-board/components/PobedaAuroraBanner.tsx @@ -0,0 +1,79 @@ +/** + * TZ §4.1.10.1: when a flight-number search returns only Pobeda ("DP") + * and/or Aurora ("HZ") flights, show a redirect banner with links to + * the subsidiary carrier websites instead of the normal flight list. + * + * Render only in "flight" search mode with non-empty results where every + * flight's operating carrier is DP or HZ. + */ + +import type { FC } from "react"; +import { useTranslation } from "@/i18n/provider.js"; +import { operatingCarrier } from "../types.js"; +import type { ISimpleFlight } from "../types.js"; +import { resolveCarrierByFlightNumber } from "@/shared/operatorIcon.js"; +import "./PobedaAuroraBanner.scss"; + +const POBEDA_URL = "https://www.pobeda.aero"; +const AURORA_URL = "https://www.flyaurora.ru"; + +export function shouldShowPobedaAuroraBanner( + flights: readonly ISimpleFlight[], +): { show: boolean; hasPobeda: boolean; hasAurora: boolean } { + if (flights.length === 0) return { show: false, hasPobeda: false, hasAurora: false }; + let hasPobeda = false; + let hasAurora = false; + for (const f of flights) { + const carrier = + operatingCarrier(f.operatingBy) ?? + (f.flightId.carrier === "SU" + ? resolveCarrierByFlightNumber(f.flightId.flightNumber) + : f.flightId.carrier); + if (carrier === "DP") hasPobeda = true; + else if (carrier === "HZ") hasAurora = true; + else return { show: false, hasPobeda: false, hasAurora: false }; + } + return { show: hasPobeda || hasAurora, hasPobeda, hasAurora }; +} + +export interface PobedaAuroraBannerProps { + hasPobeda: boolean; + hasAurora: boolean; +} + +export const PobedaAuroraBanner: FC = ({ + hasPobeda, + hasAurora, +}) => { + const { t } = useTranslation(); + return ( +
+

+ {t("BOARD.POBEDA-AURORA-TITLE")} +

+

+ {t("BOARD.POBEDA-AURORA-BODY")} +

+ +
+ ); +}; diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index a300a2dd..0b14254f 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -48,7 +48,11 @@ "ERROR-TIMEOUT": "The server did not respond in time. Please try again.", "ERROR-4XX": "Invalid search parameters. Please check your input and try again.", "ERROR-5XX": "The server is temporarily unavailable. Please try again later.", - "OPERATED-BY": "Operated by" + "OPERATED-BY": "Operated by", + "POBEDA-AURORA-TITLE": "Die gewählten Flüge werden von Tochtergesellschaften betrieben", + "POBEDA-AURORA-BODY": "Den aktuellen Flugstatus finden Sie auf den Websites der Fluggesellschaften.", + "POBEDA-AURORA-LINK-POBEDA": "Website der Fluggesellschaft Pobeda", + "POBEDA-AURORA-LINK-AURORA": "Website der Fluggesellschaft Aurora" }, "BOARDING-STATUSES": { "Expected": "Erwartet", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 2d103fcc..7e4768b0 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -50,7 +50,11 @@ "ERROR-TIMEOUT": "The server did not respond in time. Please try again.", "ERROR-4XX": "Invalid search parameters. Please check your input and try again.", "ERROR-5XX": "The server is temporarily unavailable. Please try again later.", - "OPERATED-BY": "Operated by" + "OPERATED-BY": "Operated by", + "POBEDA-AURORA-TITLE": "The selected flights are operated by subsidiary airlines", + "POBEDA-AURORA-BODY": "Up-to-date flight status is available on the carrier websites.", + "POBEDA-AURORA-LINK-POBEDA": "Pobeda airline website", + "POBEDA-AURORA-LINK-AURORA": "Aurora airline website" }, "BREADCRUMBS": { "ONLINEBOARD": "Online Board", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 857c6e9c..3cb1e111 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -48,7 +48,11 @@ "ERROR-TIMEOUT": "The server did not respond in time. Please try again.", "ERROR-4XX": "Invalid search parameters. Please check your input and try again.", "ERROR-5XX": "The server is temporarily unavailable. Please try again later.", - "OPERATED-BY": "Operated by" + "OPERATED-BY": "Operated by", + "POBEDA-AURORA-TITLE": "Los vuelos seleccionados son operados por aerolíneas subsidiarias", + "POBEDA-AURORA-BODY": "La información actualizada sobre el estado de los vuelos está disponible en los sitios web de las compañías.", + "POBEDA-AURORA-LINK-POBEDA": "Sitio web de la aerolínea Pobeda", + "POBEDA-AURORA-LINK-AURORA": "Sitio web de la aerolínea Aurora" }, "BOARDING-STATUSES": { "Expected": "Prevista", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index c1a625b0..9b056f6c 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -48,7 +48,11 @@ "ERROR-TIMEOUT": "The server did not respond in time. Please try again.", "ERROR-4XX": "Invalid search parameters. Please check your input and try again.", "ERROR-5XX": "The server is temporarily unavailable. Please try again later.", - "OPERATED-BY": "Operated by" + "OPERATED-BY": "Operated by", + "POBEDA-AURORA-TITLE": "Les vols sélectionnés sont opérés par des filiales", + "POBEDA-AURORA-BODY": "Les informations à jour sur le statut des vols sont disponibles sur les sites des transporteurs.", + "POBEDA-AURORA-LINK-POBEDA": "Site de la compagnie Pobeda", + "POBEDA-AURORA-LINK-AURORA": "Site de la compagnie Aurora" }, "BOARDING-STATUSES": { "Expected": "Prévu", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 214b3f58..c23e0bac 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -48,7 +48,11 @@ "ERROR-TIMEOUT": "The server did not respond in time. Please try again.", "ERROR-4XX": "Invalid search parameters. Please check your input and try again.", "ERROR-5XX": "The server is temporarily unavailable. Please try again later.", - "OPERATED-BY": "Operated by" + "OPERATED-BY": "Operated by", + "POBEDA-AURORA-TITLE": "I voli selezionati sono operati da compagnie sussidiarie", + "POBEDA-AURORA-BODY": "Le informazioni aggiornate sullo stato dei voli sono disponibili sui siti dei vettori.", + "POBEDA-AURORA-LINK-POBEDA": "Sito della compagnia Pobeda", + "POBEDA-AURORA-LINK-AURORA": "Sito della compagnia Aurora" }, "BOARDING-STATUSES": { "Expected": "Previsto", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index f194a7ac..9e61c3e7 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -48,7 +48,11 @@ "ERROR-TIMEOUT": "The server did not respond in time. Please try again.", "ERROR-4XX": "Invalid search parameters. Please check your input and try again.", "ERROR-5XX": "The server is temporarily unavailable. Please try again later.", - "OPERATED-BY": "Operated by" + "OPERATED-BY": "Operated by", + "POBEDA-AURORA-TITLE": "選択された便は子会社が運航しています", + "POBEDA-AURORA-BODY": "最新の運航状況は各航空会社のウェブサイトでご確認いただけます。", + "POBEDA-AURORA-LINK-POBEDA": "ポベーダ航空のウェブサイト", + "POBEDA-AURORA-LINK-AURORA": "オーロラ航空のウェブサイト" }, "BOARDING-STATUSES": { "Expected": "予測", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index c4b9ae18..d152d551 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -48,7 +48,11 @@ "ERROR-TIMEOUT": "The server did not respond in time. Please try again.", "ERROR-4XX": "Invalid search parameters. Please check your input and try again.", "ERROR-5XX": "The server is temporarily unavailable. Please try again later.", - "OPERATED-BY": "Operated by" + "OPERATED-BY": "Operated by", + "POBEDA-AURORA-TITLE": "선택하신 항공편은 자회사 항공사가 운항합니다", + "POBEDA-AURORA-BODY": "최신 항공편 상태는 해당 항공사 웹사이트에서 확인할 수 있습니다.", + "POBEDA-AURORA-LINK-POBEDA": "포베다 항공 웹사이트", + "POBEDA-AURORA-LINK-AURORA": "오로라 항공 웹사이트" }, "BOARDING-STATUSES": { "Expected": "예상 ", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 2da9064a..1bc0be63 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -50,7 +50,11 @@ "ERROR-TIMEOUT": "Время ожидания ответа от сервера истекло. Попробуйте снова.", "ERROR-4XX": "Неверные параметры поиска. Проверьте введённые данные и попробуйте снова.", "ERROR-5XX": "Сервер временно недоступен. Пожалуйста, повторите попытку позже.", - "OPERATED-BY": "Выполняет рейс" + "OPERATED-BY": "Выполняет рейс", + "POBEDA-AURORA-TITLE": "Выбранные рейсы выполняются дочерними авиакомпаниями", + "POBEDA-AURORA-BODY": "Актуальную информацию о статусе рейсов можно посмотреть на сайтах перевозчиков.", + "POBEDA-AURORA-LINK-POBEDA": "Сайт авиакомпании Победа", + "POBEDA-AURORA-LINK-AURORA": "Сайт авиакомпании Аврора" }, "BREADCRUMBS": { "ONLINEBOARD": "Онлайн-табло", diff --git a/src/i18n/locales/zh/common.json b/src/i18n/locales/zh/common.json index bf9d8804..2523ceed 100644 --- a/src/i18n/locales/zh/common.json +++ b/src/i18n/locales/zh/common.json @@ -48,7 +48,11 @@ "ERROR-TIMEOUT": "The server did not respond in time. Please try again.", "ERROR-4XX": "Invalid search parameters. Please check your input and try again.", "ERROR-5XX": "The server is temporarily unavailable. Please try again later.", - "OPERATED-BY": "Operated by" + "OPERATED-BY": "Operated by", + "POBEDA-AURORA-TITLE": "所选航班由子公司运营", + "POBEDA-AURORA-BODY": "最新航班状态请访问承运人网站。", + "POBEDA-AURORA-LINK-POBEDA": "胜利航空官方网站", + "POBEDA-AURORA-LINK-AURORA": "奥罗拉航空官方网站" }, "BOARDING-STATUSES": { "Expected": "预计",