diff --git a/src/features/online-board/components/BoardDetailsHeader/DetailsHeaderBadge.test.tsx b/src/features/online-board/components/BoardDetailsHeader/DetailsHeaderBadge.test.tsx
index 956de2ad..b6a318a3 100644
--- a/src/features/online-board/components/BoardDetailsHeader/DetailsHeaderBadge.test.tsx
+++ b/src/features/online-board/components/BoardDetailsHeader/DetailsHeaderBadge.test.tsx
@@ -88,6 +88,13 @@ describe("DetailsHeaderBadge", () => {
expect(screen.getByText("SU 0022")).toBeTruthy();
});
+ it("TIRREDESIGN-13: renders suffix in primary flight number", () => {
+ const flight = makeDirect();
+ flight.flightId = { ...flight.flightId, flightNumber: "0038", suffix: "D" };
+ render();
+ expect(screen.getByText("SU 0038D")).toBeTruthy();
+ });
+
it("renders operator-logo", () => {
render();
expect(screen.getByTestId("operator-logo")).toBeTruthy();
diff --git a/src/features/online-board/components/BoardDetailsHeader/DetailsHeaderBadge.tsx b/src/features/online-board/components/BoardDetailsHeader/DetailsHeaderBadge.tsx
index 699ee772..d13dcde9 100644
--- a/src/features/online-board/components/BoardDetailsHeader/DetailsHeaderBadge.tsx
+++ b/src/features/online-board/components/BoardDetailsHeader/DetailsHeaderBadge.tsx
@@ -39,7 +39,7 @@ export const DetailsHeaderBadge: FC = ({
}) => {
const { t } = useTranslation();
const codeshareLegs = getCodeshareLegs(flight);
- const primaryNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
+ const primaryNumber = `${flight.flightId.carrier} ${flight.flightId.flightNumber}${flight.flightId.suffix ?? ""}`;
const carrier = operatingCarrier(flight.operatingBy) ?? flight.flightId.carrier;
return (
diff --git a/src/features/online-board/components/FlightsMiniList/FlightsMiniListItem.test.tsx b/src/features/online-board/components/FlightsMiniList/FlightsMiniListItem.test.tsx
index 04e0d163..cdc5aebf 100644
--- a/src/features/online-board/components/FlightsMiniList/FlightsMiniListItem.test.tsx
+++ b/src/features/online-board/components/FlightsMiniList/FlightsMiniListItem.test.tsx
@@ -60,6 +60,15 @@ describe("FlightsMiniListItem", () => {
expect(screen.getByText(/SU\s*0022/)).toBeTruthy();
});
+ it("TIRREDESIGN-13: renders suffix in flight number", () => {
+ const flight = makeDirectFlight({
+ id: "SU0038D-20260514",
+ flightId: { carrier: "SU", flightNumber: "0038", suffix: "D", date: "20260514" },
+ });
+ render();
+ expect(screen.getByText(/SU\s*0038D/)).toBeTruthy();
+ });
+
it("renders departure and arrival times", () => {
const flight = makeDirectFlight();
render();
diff --git a/src/features/online-board/components/FlightsMiniList/FlightsMiniListItem.tsx b/src/features/online-board/components/FlightsMiniList/FlightsMiniListItem.tsx
index f8771fb3..9739fa81 100644
--- a/src/features/online-board/components/FlightsMiniList/FlightsMiniListItem.tsx
+++ b/src/features/online-board/components/FlightsMiniList/FlightsMiniListItem.tsx
@@ -127,14 +127,14 @@ export const FlightsMiniListItem = forwardRef
{(() => {
const childIds = (flight as typeof flight & {
- _childFlightIds?: { carrier: string; flightNumber: string }[];
+ _childFlightIds?: { carrier: string; flightNumber: string; suffix?: string }[];
})._childFlightIds;
if (childIds && childIds.length > 1) {
return childIds
- .map((c) => `${c.carrier} ${c.flightNumber}`)
+ .map((c) => `${c.carrier} ${c.flightNumber}${c.suffix ?? ""}`)
.join(", ");
}
- return `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
+ return `${flight.flightId.carrier} ${flight.flightId.flightNumber}${flight.flightId.suffix ?? ""}`;
})()}
{
expect(screen.getAllByText("SU 100").length).toBeGreaterThanOrEqual(1);
});
+ it("TIRREDESIGN-13: renders suffix in the details page flight number", () => {
+ const suffixFlight = {
+ ...mockFlight,
+ id: "SU0038D-20260514",
+ flightId: { carrier: "SU", flightNumber: "0038", suffix: "D", date: "20260514" },
+ } as IDirectFlight;
+ mockState = {
+ flight: suffixFlight,
+ allFlights: [suffixFlight],
+ daysOfFlight: ["20260514"],
+ loading: false,
+ error: null,
+ };
+ render(
+ ,
+ );
+ expect(screen.getAllByText("SU 0038D").length).toBeGreaterThanOrEqual(1);
+ });
+
it("renders loading skeleton", () => {
mockState = { flight: null, allFlights: [], daysOfFlight: [], loading: true, error: null };
render();
@@ -440,6 +463,20 @@ describe("OnlineBoardDetailsPage", () => {
expect(link?.getAttribute("href")).toContain("/ru/onlineboard/flight/SU1234-20260515");
});
+ it("TIRREDESIGN-13: flight context keeps suffix in breadcrumb back URL", () => {
+ mockSearchParamsInstance = new URLSearchParams("request=onlineboard-flight-SU0038D-20260514");
+ const { container } = render(
+ ,
+ );
+ const link = getCrumbLink(container, "BREADCRUMBS.FLIGHT-NUMBER");
+ expect(link).toBeTruthy();
+ expect(link?.getAttribute("href")).toContain("/ru/onlineboard/flight/SU0038D-20260514");
+ });
+
it("route context → leaf 'Маршрут: …' linking back to /route/", () => {
mockSearchParamsInstance = new URLSearchParams("request=onlineboard-route-MOW-LED-20260515");
const { container } = render(
diff --git a/src/features/online-board/components/OnlineBoardDetailsPage.tsx b/src/features/online-board/components/OnlineBoardDetailsPage.tsx
index 34a2d0d8..30706f44 100644
--- a/src/features/online-board/components/OnlineBoardDetailsPage.tsx
+++ b/src/features/online-board/components/OnlineBoardDetailsPage.tsx
@@ -21,7 +21,7 @@ import { useLiveFlightDetails } from "../hooks/useLiveFlightDetails.js";
import { useOnlineBoard } from "../hooks/useOnlineBoard.js";
import { parseDetailsRequestParam } from "@/shared/detailsRequestParam.js";
import { buildFlightJsonLd } from "../json-ld.js";
-import { buildOnlineBoardUrl } from "../url.js";
+import { buildOnlineBoardUrl, parseFlightUrlParams } from "../url.js";
import { getFlightSearchDate } from "../flightSearchDate.js";
import { useCityName, useStationDisplayName } from "@/shared/hooks/useDictionaries.js";
import { FlightDetailsAccordion } from "./details-panels/FlightDetailsAccordion.js";
@@ -65,6 +65,10 @@ export interface OnlineBoardDetailsPageProps {
canonicalOrigin: string;
}
+function parseParentFlightRequest(flightNumber: string, date: string): IParsedFlightId | null {
+ return parseFlightUrlParams(`${flightNumber}-${date}`);
+}
+
/**
* One side of a leg's station block — station code + airport name + city
* + terminal, plus scheduled / expected / actual times formatted as
@@ -449,13 +453,14 @@ export const OnlineBoardDetailsPage: FC = ({
const backUrl = (() => {
switch (parentRequest.kind) {
case "flight": {
- const m = parentRequest.flightNumber.match(/^([A-Z]{2,3})(\d+)$/);
- if (!m || !m[1] || !m[2]) return `/${locale}/onlineboard`;
+ const parsed = parseParentFlightRequest(parentRequest.flightNumber, parentRequest.date);
+ if (!parsed) return `/${locale}/onlineboard`;
return `/${locale}/${buildOnlineBoardUrl({
type: "flight",
- carrier: m[1],
- flightNumber: m[2],
- date: parentRequest.date,
+ carrier: parsed.carrier,
+ flightNumber: parsed.flightNumber,
+ ...(parsed.suffix ? { suffix: parsed.suffix } : {}),
+ date: parsed.date,
})}`;
}
case "departure":
@@ -483,8 +488,10 @@ export const OnlineBoardDetailsPage: FC = ({
switch (parentRequest.kind) {
case "flight": {
// Angular renders "Рейс: SU 6188" — carrier and number space-separated
- const m = parentRequest.flightNumber.match(/^([A-Z]{2,3})(\d+)$/);
- const formatted = m?.[1] && m?.[2] ? `${m[1]} ${m[2]}` : parentRequest.flightNumber;
+ const parsed = parseParentFlightRequest(parentRequest.flightNumber, parentRequest.date);
+ const formatted = parsed
+ ? `${parsed.carrier} ${parsed.flightNumber}${parsed.suffix ?? ""}`
+ : parentRequest.flightNumber;
return t("BREADCRUMBS.FLIGHT-NUMBER", { flightNumber: formatted });
}
case "departure":
@@ -594,7 +601,7 @@ export const OnlineBoardDetailsPage: FC = ({
}
const legs = getLegs(displayFlight);
- const flightNumber = `${displayFlight.flightId.carrier} ${displayFlight.flightId.flightNumber}`;
+ const flightNumber = `${displayFlight.flightId.carrier} ${displayFlight.flightId.flightNumber}${displayFlight.flightId.suffix ?? ""}`;
const firstLeg = legs[0];
const lastLeg = legs[legs.length - 1];
diff --git a/src/features/online-board/json-ld.test.ts b/src/features/online-board/json-ld.test.ts
index 96eb419d..afeb25a0 100644
--- a/src/features/online-board/json-ld.test.ts
+++ b/src/features/online-board/json-ld.test.ts
@@ -94,6 +94,14 @@ describe("buildFlightJsonLd", () => {
expect(result.flightNumber).toBe("SU0100");
});
+ it("TIRREDESIGN-13: includes suffix in flightNumber", () => {
+ const flight = makeDirectFlight({ carrier: "SU", flightNumber: "0038" });
+ flight.flightId.suffix = "D";
+ const result = buildFlightJsonLd(flight);
+
+ expect(result.flightNumber).toBe("SU0038D");
+ });
+
it("maps departure airport", () => {
const flight = makeDirectFlight({ depCode: "SVO", depAirport: "Sheremetyevo" });
const result = buildFlightJsonLd(flight);
diff --git a/src/features/online-board/json-ld.ts b/src/features/online-board/json-ld.ts
index f3ebc3c2..1281ebcf 100644
--- a/src/features/online-board/json-ld.ts
+++ b/src/features/online-board/json-ld.ts
@@ -48,11 +48,11 @@ export function buildFlightJsonLd(flight: ISimpleFlight): Flight {
const firstLeg = getFirstLeg(flight);
const lastLeg = getLastLeg(flight);
- const { carrier, flightNumber } = flight.flightId;
+ const { carrier, flightNumber, suffix } = flight.flightId;
const result: Flight = {
"@type": "Flight",
- flightNumber: `${carrier}${flightNumber}`,
+ flightNumber: `${carrier}${flightNumber}${suffix ?? ""}`,
provider: {
"@type": "Airline",
name: "Aeroflot",
diff --git a/src/ui/flights/FlightCard.test.tsx b/src/ui/flights/FlightCard.test.tsx
index 9a5572a8..4b7ad9b0 100644
--- a/src/ui/flights/FlightCard.test.tsx
+++ b/src/ui/flights/FlightCard.test.tsx
@@ -170,6 +170,15 @@ describe("4.1.13.3 Table 23 — Direct flight collapsed row", () => {
expect(screen.getByTestId("flight-carrier-number").textContent).toContain("SU 0022");
});
+ it("TIRREDESIGN-13: renders suffix in collapsed row flight number", () => {
+ const flight = {
+ ...makeFlight({}),
+ flightId: { carrier: "SU", flightNumber: "0038", suffix: "D", date: "20260514" },
+ } as ISimpleFlight;
+ render();
+ expect(screen.getByTestId("flight-carrier-number").textContent).toContain("SU 0038D");
+ });
+
it("T23-C2: renders operator logo (full, not mini/round) for direct flight", () => {
render();
const logo = screen.getByTestId("operator-logo");
diff --git a/src/ui/flights/FlightCard.tsx b/src/ui/flights/FlightCard.tsx
index 25e9c154..7f7d891d 100644
--- a/src/ui/flights/FlightCard.tsx
+++ b/src/ui/flights/FlightCard.tsx
@@ -184,7 +184,7 @@ export const FlightCard: FC = ({
? childFlightIds
.map((id) => `${id.carrier} ${id.flightNumber}${id.suffix ?? ""}`)
.join(", ")
- : `${flight.flightId.carrier} ${flight.flightId.flightNumber}`;
+ : `${flight.flightId.carrier} ${flight.flightId.flightNumber}${flight.flightId.suffix ?? ""}`;
// TZ §4.1.22: when OperatingBy is null, resolve carrier from flight-number
// ranges (SU1-2999→SU, SU5000-5399→DP, SU5400-5799→HZ, etc.).
// Falls back to the flight's own carrier code for non-SU flights or when