Schedule search → details links emit ?request= + details page shows leaf breadcrumb (TZ §4.1.2 row 11, §4.1.4 rows 11-13)
This commit is contained in:
@@ -423,7 +423,7 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
|
|||||||
// city (the station). We show only that city. See AGENTS.md Conflicts register.
|
// city (the station). We show only that city. See AGENTS.md Conflicts register.
|
||||||
const detailsCrumbs = useMemo(() => {
|
const detailsCrumbs = useMemo(() => {
|
||||||
const baseCrumbs = [{ label: t("BOARD.TITLE"), url: `/${locale}/onlineboard` }];
|
const baseCrumbs = [{ label: t("BOARD.TITLE"), url: `/${locale}/onlineboard` }];
|
||||||
if (!parentRequest) return baseCrumbs;
|
if (!parentRequest || parentRequest.area !== "onlineboard") return baseCrumbs;
|
||||||
|
|
||||||
const backUrl = (() => {
|
const backUrl = (() => {
|
||||||
switch (parentRequest.kind) {
|
switch (parentRequest.kind) {
|
||||||
@@ -484,7 +484,7 @@ export const OnlineBoardDetailsPage: FC<OnlineBoardDetailsPageProps> = ({
|
|||||||
}, [parentRequest, locale, t]);
|
}, [parentRequest, locale, t]);
|
||||||
|
|
||||||
const parentParams = useMemo(() => {
|
const parentParams = useMemo(() => {
|
||||||
if (!parentRequest) return null;
|
if (!parentRequest || parentRequest.area !== "onlineboard") return null;
|
||||||
const d = parentRequest.date;
|
const d = parentRequest.date;
|
||||||
const isoDate = `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`;
|
const isoDate = `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`;
|
||||||
const dateFrom = `${isoDate}T00:00:00`;
|
const dateFrom = `${isoDate}T00:00:00`;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { FC } from "react";
|
import type { FC } from "react";
|
||||||
import { useCallback } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { Link } from "@modern-js/runtime/router";
|
import { Link, useSearchParams } from "@modern-js/runtime/router";
|
||||||
import { useTranslation } from "@/i18n/provider.js";
|
import { useTranslation } from "@/i18n/provider.js";
|
||||||
import { localeToLanguage, normalizeLocaleParam, DEFAULT_LANGUAGE } from "@/i18n/resolver.js";
|
import { localeToLanguage, normalizeLocaleParam, DEFAULT_LANGUAGE } from "@/i18n/resolver.js";
|
||||||
import { FlightCard } from "@/ui/flights/FlightCard.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 { PageLayout } from "@/ui/layout/PageLayout.js";
|
||||||
import { PageTabs } from "@/ui/layout/PageTabs.js";
|
import { PageTabs } from "@/ui/layout/PageTabs.js";
|
||||||
import { JsonLdRenderer } from "@/shared/seo/json-ld.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 { useScheduleDetails } from "../hooks/useScheduleDetails.js";
|
||||||
import { buildScheduleDetailsSeo } from "../seo.js";
|
import { buildScheduleDetailsSeo } from "../seo.js";
|
||||||
import { buildScheduleFlightJsonLd } from "../json-ld.js";
|
import { buildScheduleFlightJsonLd } from "../json-ld.js";
|
||||||
@@ -83,6 +85,72 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
|
|||||||
const pageTabs = <PageTabs viewType="schedule" />;
|
const pageTabs = <PageTabs viewType="schedule" />;
|
||||||
const scheduleHref = `/${locale}/schedule`;
|
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.
|
// `Купить` button — opens Aeroflot's booking flow in a new tab.
|
||||||
// Mirrors BoardDetailsHeader's BuyTicketButton / Schedule search page.
|
// Mirrors BoardDetailsHeader's BuyTicketButton / Schedule search page.
|
||||||
const language =
|
const language =
|
||||||
@@ -128,7 +196,7 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
|
|||||||
<PageLayout
|
<PageLayout
|
||||||
headerLeft={pageTabs}
|
headerLeft={pageTabs}
|
||||||
title={<h1 className="text--white page-title">{title}</h1>}
|
title={<h1 className="text--white page-title">{title}</h1>}
|
||||||
breadcrumbs={[{ label: t("SCHEDULE.TITLE"), url: scheduleHref }]}
|
breadcrumbs={breadcrumbs}
|
||||||
>
|
>
|
||||||
<section className="frame">
|
<section className="frame">
|
||||||
<FlightListSkeleton count={flightIds.length} />
|
<FlightListSkeleton count={flightIds.length} />
|
||||||
@@ -142,7 +210,7 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
|
|||||||
<PageLayout
|
<PageLayout
|
||||||
headerLeft={pageTabs}
|
headerLeft={pageTabs}
|
||||||
title={<h1 className="text--white page-title">{title}</h1>}
|
title={<h1 className="text--white page-title">{title}</h1>}
|
||||||
breadcrumbs={[{ label: t("SCHEDULE.TITLE"), url: scheduleHref }]}
|
breadcrumbs={breadcrumbs}
|
||||||
>
|
>
|
||||||
<section className="frame">
|
<section className="frame">
|
||||||
<div
|
<div
|
||||||
@@ -162,7 +230,7 @@ export const ScheduleDetailsPage: FC<ScheduleDetailsPageProps> = ({
|
|||||||
<PageLayout
|
<PageLayout
|
||||||
headerLeft={pageTabs}
|
headerLeft={pageTabs}
|
||||||
title={<h1 className="text--white page-title">{title}</h1>}
|
title={<h1 className="text--white page-title">{title}</h1>}
|
||||||
breadcrumbs={[{ label: t("SCHEDULE.TITLE"), url: scheduleHref }]}
|
breadcrumbs={breadcrumbs}
|
||||||
>
|
>
|
||||||
<section className="frame">
|
<section className="frame">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { JsonLdRenderer } from "@/shared/seo/json-ld.js";
|
|||||||
import { useScheduleSearch } from "../hooks/useScheduleSearch.js";
|
import { useScheduleSearch } from "../hooks/useScheduleSearch.js";
|
||||||
import { buildScheduleUrl } from "../url.js";
|
import { buildScheduleUrl } from "../url.js";
|
||||||
import { buildFlightUrlParams } from "../../online-board/url.js";
|
import { buildFlightUrlParams } from "../../online-board/url.js";
|
||||||
|
import { buildDetailsRequestParam } from "@/shared/detailsRequestParam.js";
|
||||||
import { buildScheduleFlightListJsonLd } from "../json-ld.js";
|
import { buildScheduleFlightListJsonLd } from "../json-ld.js";
|
||||||
import type { ScheduleParams } from "../url.js";
|
import type { ScheduleParams } from "../url.js";
|
||||||
import type { IScheduleSearchRequest, ISimpleFlight } from "../types.js";
|
import type { IScheduleSearchRequest, ISimpleFlight } from "../types.js";
|
||||||
@@ -137,9 +138,39 @@ export const ScheduleSearchPage: FC<ScheduleSearchPageProps> = ({ params }) => {
|
|||||||
...(flight.flightId.suffix ? { suffix: flight.flightId.suffix } : {}),
|
...(flight.flightId.suffix ? { suffix: flight.flightId.suffix } : {}),
|
||||||
date: flight.flightId.date,
|
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
|
// `Купить` button — opens Aeroflot's booking flow in a new tab, same
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
"FLIGHT-NUMBER": "",
|
"FLIGHT-NUMBER": "",
|
||||||
"DEPARTURE": "",
|
"DEPARTURE": "",
|
||||||
"ARRIVAL": "",
|
"ARRIVAL": "",
|
||||||
"ROUTE": ""
|
"ROUTE": "",
|
||||||
|
"SCHEDULE-ROUTE": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,8 @@
|
|||||||
"FLIGHT-NUMBER": "Flight: {flightNumber}",
|
"FLIGHT-NUMBER": "Flight: {flightNumber}",
|
||||||
"DEPARTURE": "Departure: {city}",
|
"DEPARTURE": "Departure: {city}",
|
||||||
"ARRIVAL": "Arrival: {city}",
|
"ARRIVAL": "Arrival: {city}",
|
||||||
"ROUTE": "Route: {departureCity}-{arrivalCity}"
|
"ROUTE": "Route: {departureCity}-{arrivalCity}",
|
||||||
|
"SCHEDULE-ROUTE": "{departureCity}-{arrivalCity}"
|
||||||
},
|
},
|
||||||
"DETAILS": {
|
"DETAILS": {
|
||||||
"REGISTRATION": "Check-in",
|
"REGISTRATION": "Check-in",
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
"FLIGHT-NUMBER": "",
|
"FLIGHT-NUMBER": "",
|
||||||
"DEPARTURE": "",
|
"DEPARTURE": "",
|
||||||
"ARRIVAL": "",
|
"ARRIVAL": "",
|
||||||
"ROUTE": ""
|
"ROUTE": "",
|
||||||
|
"SCHEDULE-ROUTE": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
"FLIGHT-NUMBER": "",
|
"FLIGHT-NUMBER": "",
|
||||||
"DEPARTURE": "",
|
"DEPARTURE": "",
|
||||||
"ARRIVAL": "",
|
"ARRIVAL": "",
|
||||||
"ROUTE": ""
|
"ROUTE": "",
|
||||||
|
"SCHEDULE-ROUTE": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
"FLIGHT-NUMBER": "",
|
"FLIGHT-NUMBER": "",
|
||||||
"DEPARTURE": "",
|
"DEPARTURE": "",
|
||||||
"ARRIVAL": "",
|
"ARRIVAL": "",
|
||||||
"ROUTE": ""
|
"ROUTE": "",
|
||||||
|
"SCHEDULE-ROUTE": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
"FLIGHT-NUMBER": "",
|
"FLIGHT-NUMBER": "",
|
||||||
"DEPARTURE": "",
|
"DEPARTURE": "",
|
||||||
"ARRIVAL": "",
|
"ARRIVAL": "",
|
||||||
"ROUTE": ""
|
"ROUTE": "",
|
||||||
|
"SCHEDULE-ROUTE": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
"FLIGHT-NUMBER": "",
|
"FLIGHT-NUMBER": "",
|
||||||
"DEPARTURE": "",
|
"DEPARTURE": "",
|
||||||
"ARRIVAL": "",
|
"ARRIVAL": "",
|
||||||
"ROUTE": ""
|
"ROUTE": "",
|
||||||
|
"SCHEDULE-ROUTE": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,8 @@
|
|||||||
"FLIGHT-NUMBER": "Номер рейса: {flightNumber}",
|
"FLIGHT-NUMBER": "Номер рейса: {flightNumber}",
|
||||||
"DEPARTURE": "Вылет: {city}",
|
"DEPARTURE": "Вылет: {city}",
|
||||||
"ARRIVAL": "Прилет: {city}",
|
"ARRIVAL": "Прилет: {city}",
|
||||||
"ROUTE": "Маршрут: {departureCity}-{arrivalCity}"
|
"ROUTE": "Маршрут: {departureCity}-{arrivalCity}",
|
||||||
|
"SCHEDULE-ROUTE": "{departureCity}-{arrivalCity}"
|
||||||
},
|
},
|
||||||
"DETAILS": {
|
"DETAILS": {
|
||||||
"REGISTRATION": "Регистрация",
|
"REGISTRATION": "Регистрация",
|
||||||
|
|||||||
@@ -418,6 +418,7 @@
|
|||||||
"FLIGHT-NUMBER": "",
|
"FLIGHT-NUMBER": "",
|
||||||
"DEPARTURE": "",
|
"DEPARTURE": "",
|
||||||
"ARRIVAL": "",
|
"ARRIVAL": "",
|
||||||
"ROUTE": ""
|
"ROUTE": "",
|
||||||
|
"SCHEDULE-ROUTE": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user