diff --git a/src/features/online-board/components/OnlineBoardDetailsPage.tsx b/src/features/online-board/components/OnlineBoardDetailsPage.tsx index ae9ee1b2..cf78e438 100644 --- a/src/features/online-board/components/OnlineBoardDetailsPage.tsx +++ b/src/features/online-board/components/OnlineBoardDetailsPage.tsx @@ -423,7 +423,7 @@ export const OnlineBoardDetailsPage: FC = ({ // city (the station). We show only that city. See AGENTS.md Conflicts register. const detailsCrumbs = useMemo(() => { const baseCrumbs = [{ label: t("BOARD.TITLE"), url: `/${locale}/onlineboard` }]; - if (!parentRequest) return baseCrumbs; + if (!parentRequest || parentRequest.area !== "onlineboard") return baseCrumbs; const backUrl = (() => { switch (parentRequest.kind) { @@ -484,7 +484,7 @@ export const OnlineBoardDetailsPage: FC = ({ }, [parentRequest, locale, t]); const parentParams = useMemo(() => { - if (!parentRequest) return null; + if (!parentRequest || parentRequest.area !== "onlineboard") return null; const d = parentRequest.date; const isoDate = `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`; const dateFrom = `${isoDate}T00:00:00`; diff --git a/src/features/schedule/components/ScheduleDetailsPage.tsx b/src/features/schedule/components/ScheduleDetailsPage.tsx index 113ae1fd..786ff7e0 100644 --- a/src/features/schedule/components/ScheduleDetailsPage.tsx +++ b/src/features/schedule/components/ScheduleDetailsPage.tsx @@ -9,8 +9,8 @@ */ import type { FC } from "react"; -import { useCallback } from "react"; -import { Link } from "@modern-js/runtime/router"; +import { useCallback, useMemo } from "react"; +import { Link, useSearchParams } from "@modern-js/runtime/router"; import { useTranslation } from "@/i18n/provider.js"; import { localeToLanguage, normalizeLocaleParam, DEFAULT_LANGUAGE } from "@/i18n/resolver.js"; import { FlightCard } from "@/ui/flights/FlightCard.js"; @@ -20,6 +20,8 @@ import { SeoHead } from "@/ui/seo/SeoHead.js"; import { PageLayout } from "@/ui/layout/PageLayout.js"; import { PageTabs } from "@/ui/layout/PageTabs.js"; import { JsonLdRenderer } from "@/shared/seo/json-ld.js"; +import { parseDetailsRequestParam } from "@/shared/detailsRequestParam.js"; +import { buildScheduleUrl } from "../url.js"; import { useScheduleDetails } from "../hooks/useScheduleDetails.js"; import { buildScheduleDetailsSeo } from "../seo.js"; import { buildScheduleFlightJsonLd } from "../json-ld.js"; @@ -83,6 +85,72 @@ export const ScheduleDetailsPage: FC = ({ const pageTabs = ; const scheduleHref = `/${locale}/schedule`; + // Parse ?request= to build the leaf breadcrumb (TZ §4.1.4 Table 7 rows 11-13) + const [searchParams] = useSearchParams(); + const parentRequest = useMemo(() => { + const raw = searchParams.get("request"); + return raw ? parseDetailsRequestParam(raw) : null; + }, [searchParams]); + + const breadcrumbs = useMemo(() => { + const baseCrumbs = [{ label: t("SCHEDULE.TITLE"), url: scheduleHref }]; + if (!parentRequest || parentRequest.area !== "schedule") return baseCrumbs; + + // Build the back URL from the parsed request context + const backUrl = `/${locale}/${buildScheduleUrl( + parentRequest.returnTrip + ? { + type: "roundtrip", + outbound: { + departure: parentRequest.departure, + arrival: parentRequest.arrival, + dateFrom: parentRequest.dateFrom, + dateTo: parentRequest.dateTo, + ...(parentRequest.timeFrom && parentRequest.timeTo + ? { timeFrom: parentRequest.timeFrom, timeTo: parentRequest.timeTo } + : {}), + ...(parentRequest.connections !== undefined + ? { connections: parentRequest.connections } + : {}), + }, + inbound: { + departure: parentRequest.returnTrip.departure, + arrival: parentRequest.returnTrip.arrival, + dateFrom: parentRequest.returnTrip.dateFrom, + dateTo: parentRequest.returnTrip.dateTo, + ...(parentRequest.returnTrip.timeFrom && parentRequest.returnTrip.timeTo + ? { timeFrom: parentRequest.returnTrip.timeFrom, timeTo: parentRequest.returnTrip.timeTo } + : {}), + ...(parentRequest.returnTrip.connections !== undefined + ? { connections: parentRequest.returnTrip.connections } + : {}), + }, + } + : { + type: "route", + outbound: { + departure: parentRequest.departure, + arrival: parentRequest.arrival, + dateFrom: parentRequest.dateFrom, + dateTo: parentRequest.dateTo, + ...(parentRequest.timeFrom && parentRequest.timeTo + ? { timeFrom: parentRequest.timeFrom, timeTo: parentRequest.timeTo } + : {}), + ...(parentRequest.connections !== undefined + ? { connections: parentRequest.connections } + : {}), + }, + }, + )}`; + + const leafLabel = t("BREADCRUMBS.SCHEDULE-ROUTE", { + departureCity: parentRequest.departure, + arrivalCity: parentRequest.arrival, + }); + + return [...baseCrumbs, { label: leafLabel, url: backUrl }]; + }, [parentRequest, locale, scheduleHref, t]); + // `Купить` button — opens Aeroflot's booking flow in a new tab. // Mirrors BoardDetailsHeader's BuyTicketButton / Schedule search page. const language = @@ -128,7 +196,7 @@ export const ScheduleDetailsPage: FC = ({ {title}} - breadcrumbs={[{ label: t("SCHEDULE.TITLE"), url: scheduleHref }]} + breadcrumbs={breadcrumbs} >
@@ -142,7 +210,7 @@ export const ScheduleDetailsPage: FC = ({ {title}} - breadcrumbs={[{ label: t("SCHEDULE.TITLE"), url: scheduleHref }]} + breadcrumbs={breadcrumbs} >
= ({ {title}} - breadcrumbs={[{ label: t("SCHEDULE.TITLE"), url: scheduleHref }]} + breadcrumbs={breadcrumbs} >
= ({ params }) => { ...(flight.flightId.suffix ? { suffix: flight.flightId.suffix } : {}), date: flight.flightId.date, }); - void navigate(`/${locale}/schedule/${segment}`); + const requestParam = buildDetailsRequestParam({ + area: "schedule", + kind: "route", + departure: outbound.departure, + arrival: outbound.arrival, + dateFrom: outbound.dateFrom, + dateTo: outbound.dateTo, + ...(outbound.timeFrom && outbound.timeTo + ? { timeFrom: outbound.timeFrom, timeTo: outbound.timeTo } + : {}), + ...(outbound.connections !== undefined + ? { connections: outbound.connections } + : {}), + ...(inbound + ? { + returnTrip: { + departure: inbound.departure, + arrival: inbound.arrival, + dateFrom: inbound.dateFrom, + dateTo: inbound.dateTo, + ...(inbound.timeFrom && inbound.timeTo + ? { timeFrom: inbound.timeFrom, timeTo: inbound.timeTo } + : {}), + ...(inbound.connections !== undefined + ? { connections: inbound.connections } + : {}), + }, + } + : {}), + }); + void navigate(`/${locale}/schedule/${segment}?request=${encodeURIComponent(requestParam)}`); }, - [locale, navigate], + [locale, navigate, outbound, inbound], ); // `Купить` button — opens Aeroflot's booking flow in a new tab, same diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 0f501ce9..c2773a1e 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -418,6 +418,7 @@ "FLIGHT-NUMBER": "", "DEPARTURE": "", "ARRIVAL": "", - "ROUTE": "" + "ROUTE": "", + "SCHEDULE-ROUTE": "" } } diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index d5a9130e..b61587f2 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -53,7 +53,8 @@ "FLIGHT-NUMBER": "Flight: {flightNumber}", "DEPARTURE": "Departure: {city}", "ARRIVAL": "Arrival: {city}", - "ROUTE": "Route: {departureCity}-{arrivalCity}" + "ROUTE": "Route: {departureCity}-{arrivalCity}", + "SCHEDULE-ROUTE": "{departureCity}-{arrivalCity}" }, "DETAILS": { "REGISTRATION": "Check-in", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index 72f62c7e..610cc3d4 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -418,6 +418,7 @@ "FLIGHT-NUMBER": "", "DEPARTURE": "", "ARRIVAL": "", - "ROUTE": "" + "ROUTE": "", + "SCHEDULE-ROUTE": "" } } diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index d207a713..64e19fd2 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -418,6 +418,7 @@ "FLIGHT-NUMBER": "", "DEPARTURE": "", "ARRIVAL": "", - "ROUTE": "" + "ROUTE": "", + "SCHEDULE-ROUTE": "" } } diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 10a2cb7d..1f1fac68 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -418,6 +418,7 @@ "FLIGHT-NUMBER": "", "DEPARTURE": "", "ARRIVAL": "", - "ROUTE": "" + "ROUTE": "", + "SCHEDULE-ROUTE": "" } } diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index a059a23c..5ccb0963 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -418,6 +418,7 @@ "FLIGHT-NUMBER": "", "DEPARTURE": "", "ARRIVAL": "", - "ROUTE": "" + "ROUTE": "", + "SCHEDULE-ROUTE": "" } } diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index ab082555..e9a6183b 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -418,6 +418,7 @@ "FLIGHT-NUMBER": "", "DEPARTURE": "", "ARRIVAL": "", - "ROUTE": "" + "ROUTE": "", + "SCHEDULE-ROUTE": "" } } diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 6f835d01..e137c513 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -53,7 +53,8 @@ "FLIGHT-NUMBER": "Номер рейса: {flightNumber}", "DEPARTURE": "Вылет: {city}", "ARRIVAL": "Прилет: {city}", - "ROUTE": "Маршрут: {departureCity}-{arrivalCity}" + "ROUTE": "Маршрут: {departureCity}-{arrivalCity}", + "SCHEDULE-ROUTE": "{departureCity}-{arrivalCity}" }, "DETAILS": { "REGISTRATION": "Регистрация", diff --git a/src/i18n/locales/zh/common.json b/src/i18n/locales/zh/common.json index 09c5883a..dec1d26f 100644 --- a/src/i18n/locales/zh/common.json +++ b/src/i18n/locales/zh/common.json @@ -418,6 +418,7 @@ "FLIGHT-NUMBER": "", "DEPARTURE": "", "ARRIVAL": "", - "ROUTE": "" + "ROUTE": "", + "SCHEDULE-ROUTE": "" } }