From d942cb55bc2cd2973432af4517f4702032a5259c Mon Sep 17 00:00:00 2001 From: gnezim Date: Mon, 20 Apr 2026 20:02:45 +0300 Subject: [PATCH] Add IFlyWarning component shown on details pages for SU5801-5948 flights (Angular parity) --- .../components/OnlineBoardDetailsPage.tsx | 6 +++ .../components/ScheduleDetailsPage.tsx | 5 ++ src/ui/flights/IFlyWarning.scss | 31 +++++++++++++ src/ui/flights/IFlyWarning.test.tsx | 46 +++++++++++++++++++ src/ui/flights/IFlyWarning.tsx | 33 +++++++++++++ src/ui/flights/index.ts | 3 ++ 6 files changed, 124 insertions(+) create mode 100644 src/ui/flights/IFlyWarning.scss create mode 100644 src/ui/flights/IFlyWarning.test.tsx create mode 100644 src/ui/flights/IFlyWarning.tsx diff --git a/src/features/online-board/components/OnlineBoardDetailsPage.tsx b/src/features/online-board/components/OnlineBoardDetailsPage.tsx index 00467a7f..d9956284 100644 --- a/src/features/online-board/components/OnlineBoardDetailsPage.tsx +++ b/src/features/online-board/components/OnlineBoardDetailsPage.tsx @@ -12,6 +12,7 @@ import { useNavigate, useSearchParams } from "@modern-js/runtime/router"; import { useTranslation } from "@/i18n/provider.js"; import "./OnlineBoardDetailsPage.scss"; import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js"; +import { IFlyWarning } from "@/ui/flights/IFlyWarning.js"; import { JsonLdRenderer } from "@/shared/seo/json-ld.js"; import { PageLayout } from "@/ui/layout/PageLayout.js"; import { useAppSettings } from "@/shared/hooks/useAppSettings.js"; @@ -561,6 +562,11 @@ export const OnlineBoardDetailsPage: FC = ({ + {/* iFly operator warning (SU5801-5948) — Angular embeds + this inside flight-schedule-details.component.html via + a flightNumber range guard. */} + + {displayFlight.routeType === "MultiLeg" && ( )} diff --git a/src/features/schedule/components/ScheduleDetailsPage.tsx b/src/features/schedule/components/ScheduleDetailsPage.tsx index 944d562d..eaaa1bec 100644 --- a/src/features/schedule/components/ScheduleDetailsPage.tsx +++ b/src/features/schedule/components/ScheduleDetailsPage.tsx @@ -15,6 +15,7 @@ import { useTranslation } from "@/i18n/provider.js"; import { localeToLanguage, normalizeLocaleParam, DEFAULT_LANGUAGE } from "@/i18n/resolver.js"; import { FlightCard } from "@/ui/flights/FlightCard.js"; import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js"; +import { IFlyWarning } from "@/ui/flights/IFlyWarning.js"; import { SeoHead } from "@/ui/seo/SeoHead.js"; import { PageLayout } from "@/ui/layout/PageLayout.js"; import { PageTabs } from "@/ui/layout/PageTabs.js"; @@ -230,6 +231,10 @@ export const ScheduleDetailsPage: FC = ({ {/* Collapsed summary row, then the rich per-leg body. */} + {/* iFly operator warning (SU5801-5948) — Angular + renders this inside flight-schedule-details between + the header and the route strip. */} + {renderBody(flight)} {/* Angular's flight-schedule-details renders the weekly diff --git a/src/ui/flights/IFlyWarning.scss b/src/ui/flights/IFlyWarning.scss new file mode 100644 index 00000000..0fe7a5c3 --- /dev/null +++ b/src/ui/flights/IFlyWarning.scss @@ -0,0 +1,31 @@ +@use "../../styles/colors" as colors; +@use "../../styles/variables" as vars; +@use "../../styles/fonts" as fonts; + +// Mirrors Angular `warning-ifly-carrier-detail.component.scss`: +// a narrow orange vertical bar + highlighted leading caption, used +// on details pages for flights in the iFly-operated SU5801–5948 range. +.ifly-warning { + display: flex; + align-items: center; + padding: vars.$space-m vars.$space-xl; + + &__bar { + width: 0.3rem; + height: 80px; + background-color: colors.$orange--hover; + flex-shrink: 0; + } + + &__text { + text-align: left; + padding: vars.$space-xl; + font-size: fonts.$font-size-m; + line-height: 1.5; + } + + &__highlight { + font-weight: fonts.$font-bold; + color: colors.$orange--hover; + } +} diff --git a/src/ui/flights/IFlyWarning.test.tsx b/src/ui/flights/IFlyWarning.test.tsx new file mode 100644 index 00000000..ee4420ae --- /dev/null +++ b/src/ui/flights/IFlyWarning.test.tsx @@ -0,0 +1,46 @@ +/** + * @vitest-environment jsdom + */ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { IFlyWarning } from "./IFlyWarning.js"; + +vi.mock("@/i18n/provider.js", () => ({ + useTranslation: () => ({ + t: (key: string) => key, + i18n: { language: "ru" }, + }), +})); + +describe("IFlyWarning", () => { + it("renders warning for flights in the iFly range (5801-5948)", () => { + const { container } = render(); + expect(container.querySelector(".ifly-warning")).toBeTruthy(); + expect(screen.getByText("WARNING.IFLY_HIGHLIGHT")).toBeTruthy(); + }); + + it("accepts numeric flight numbers", () => { + const { container } = render(); + expect(container.querySelector(".ifly-warning")).toBeTruthy(); + }); + + it("does not render for flights below the iFly range", () => { + const { container } = render(); + expect(container.querySelector(".ifly-warning")).toBeNull(); + }); + + it("does not render for flights above the iFly range", () => { + const { container } = render(); + expect(container.querySelector(".ifly-warning")).toBeNull(); + }); + + it("does not render for regular flights (SU100)", () => { + const { container } = render(); + expect(container.querySelector(".ifly-warning")).toBeNull(); + }); + + it("does not render for non-numeric input", () => { + const { container } = render(); + expect(container.querySelector(".ifly-warning")).toBeNull(); + }); +}); diff --git a/src/ui/flights/IFlyWarning.tsx b/src/ui/flights/IFlyWarning.tsx new file mode 100644 index 00000000..8b8249f3 --- /dev/null +++ b/src/ui/flights/IFlyWarning.tsx @@ -0,0 +1,33 @@ +import type { FC } from "react"; +import { useTranslation } from "@/i18n/provider.js"; +import "./IFlyWarning.scss"; + +export interface IFlyWarningProps { + flightNumber: string | number; +} + +function isIFlyRange(num: string | number): boolean { + const n = typeof num === "string" ? parseInt(num, 10) : num; + if (!Number.isFinite(n)) return false; + return n > 5800 && n < 5949; +} + +export const IFlyWarning: FC = ({ flightNumber }) => { + const { t } = useTranslation(); + if (!isIFlyRange(flightNumber)) return null; + return ( +
+ + ); +}; diff --git a/src/ui/flights/index.ts b/src/ui/flights/index.ts index 80bff118..b604bff1 100644 --- a/src/ui/flights/index.ts +++ b/src/ui/flights/index.ts @@ -21,3 +21,6 @@ export type { FlightListSkeletonProps } from "./FlightListSkeleton.js"; export { FlightList } from "./FlightList.js"; export type { FlightListProps } from "./FlightList.js"; + +export { IFlyWarning } from "./IFlyWarning.js"; +export type { IFlyWarningProps } from "./IFlyWarning.js";