From f5e41a7911bfd4813de69c7bb77c1ebd25974ca2 Mon Sep 17 00:00:00 2001 From: gnezim Date: Wed, 29 Apr 2026 20:23:24 +0300 Subject: [PATCH] Add flight details button to schedule search results - Add flight details button to ScheduleFlightBody component - Button positioned after Buy button (matching Angular layout) - Button uses SHARED.FLIGHT-DETAILS translation key - Add onFlightDetails callback to ScheduleFlightBody props - Add handleFlightDetails to DayGroupedFlightList - Pass onFlightDetails to ScheduleFlightBody - Add E2E tests for flight details button functionality --- .../components/DayGroupedFlightList.tsx | 17 +++- .../components/ScheduleFlightBody.tsx | 24 ++++- .../schedule-flight-details-button.spec.ts | 88 +++++++++++++++++++ 3 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/schedule-flight-details-button.spec.ts diff --git a/src/features/schedule/components/DayGroupedFlightList.tsx b/src/features/schedule/components/DayGroupedFlightList.tsx index a245dc51..0e1e6d8e 100644 --- a/src/features/schedule/components/DayGroupedFlightList.tsx +++ b/src/features/schedule/components/DayGroupedFlightList.tsx @@ -143,6 +143,14 @@ export const DayGroupedFlightList: FC = ({ if (onSortChange) onSortChange(value); if (sortModeProp === undefined) setInternalSortMode(value); }; + const handleFlightDetails = useCallback( + (flight: ISimpleFlight) => { + if (onFlightClick) { + onFlightClick(flight); + } + }, + [onFlightClick], + ); // Track which days the user has expanded. Default: today's day group // (if it's in scope). Angular's `p-accordion` is `[multiple]="true"` // and `[activeIndex]` defaults to the index of today's date when @@ -252,9 +260,14 @@ export const DayGroupedFlightList: FC = ({ // FlightList's `buyUrlFor` prop is independent of this strip. const renderScheduleBody = useCallback( (f: ISimpleFlight) => ( - + ), - [locale], + [locale, handleFlightDetails], ); if (loading) return ; diff --git a/src/features/schedule/components/ScheduleFlightBody.tsx b/src/features/schedule/components/ScheduleFlightBody.tsx index 28fd5cfb..cced04a3 100644 --- a/src/features/schedule/components/ScheduleFlightBody.tsx +++ b/src/features/schedule/components/ScheduleFlightBody.tsx @@ -44,6 +44,12 @@ export interface ScheduleFlightBodyProps { showActions?: boolean; /** Locale used to build the buy-ticket URL when `showActions` is true. */ locale?: string; + /** + * Callback fired when the user clicks the flight details button. + * Mirrors Angular's `flight-details-body-actions` → `flight-actions` + * → `flight-details-button` → `toDetails` event. + */ + onFlightDetails?: (flight: ISimpleFlight) => void; } interface ChildFlightId { @@ -96,6 +102,7 @@ export const ScheduleFlightBody: FC = ({ flight, showActions = false, locale = "ru-ru", + onFlightDetails, }) => { const { t } = useTranslation(); const { language } = useLocale(); @@ -379,7 +386,9 @@ export const ScheduleFlightBody: FC = ({ defaults (share+buy+register+status, no print). The schedule details page suppresses these (page-level summary owns them), so callers opt in via `showActions`. Buy/Status visibility is - gated by `` (TZ §4.1.14.4.4 / §4.1.14.4.5). */} + gated by `` (TZ §4.1.14.4.4 / §4.1.14.4.5). + The flight details button is rendered after the other actions, + matching Angular's `flight-actions` component layout. */} {showActions && (
= ({ showRegister showStatus /> + {onFlightDetails && ( + + )}
)} diff --git a/tests/e2e/schedule-flight-details-button.spec.ts b/tests/e2e/schedule-flight-details-button.spec.ts new file mode 100644 index 00000000..600d5a62 --- /dev/null +++ b/tests/e2e/schedule-flight-details-button.spec.ts @@ -0,0 +1,88 @@ +import { test, expect } from "./fixtures/console-gate"; + +test.describe("Schedule flight details button", () => { + test("flight details button is visible in expanded flight body", async ({ + page, + }) => { + await page.goto("/ru-ru/schedule/route/SVO-LED-20260415"); + + const cards = page.locator(".flight-card--clickable"); + await expect(cards.first()).toBeVisible({ timeout: 30000 }); + + await cards.first().click(); + + const actions = page.locator('[data-testid="schedule-flight-body-actions"]'); + await expect(actions).toBeVisible({ timeout: 10000 }); + + const detailsBtn = actions.locator('[data-testid="flight-details-button"]'); + await expect(detailsBtn).toBeVisible(); + }); + + test("flight details button has correct label (Russian)", async ({ page }) => { + await page.goto("/ru-ru/schedule/route/SVO-LED-20260415"); + + const cards = page.locator(".flight-card--clickable"); + await expect(cards.first()).toBeVisible({ timeout: 30000 }); + + await cards.first().click(); + + const detailsBtn = page.locator('[data-testid="flight-details-button"]'); + await expect(detailsBtn).toBeVisible(); + const text = await detailsBtn.textContent(); + expect(text).toContain("Детали"); + }); + + test("flight details button navigates to flight details page", async ({ + page, + }) => { + await page.goto("/ru-ru/schedule/route/SVO-LED-20260415"); + + const cards = page.locator(".flight-card--clickable"); + await expect(cards.first()).toBeVisible({ timeout: 30000 }); + + await cards.first().click(); + + const detailsBtn = page.locator('[data-testid="flight-details-button"]'); + await detailsBtn.click(); + + await expect(page).toHaveURL(/\/ru-ru\/schedule\/[A-Z]{3}\/SU\d+-\d{8}\/[A-Z]{3}/); + }); + + test("flight details button works for connecting flights", async ({ + page, + }) => { + await page.goto("/ru-ru/schedule/route/MOW-MMK-20260427-20260503"); + + const cards = page.locator(".flight-card--clickable"); + await expect(cards.first()).toBeVisible({ timeout: 30000 }); + + const firstCard = cards.first(); + await firstCard.click(); + + const detailsBtn = page.locator('[data-testid="flight-details-button"]'); + await expect(detailsBtn).toBeVisible({ timeout: 10000 }); + await detailsBtn.click(); + + await expect(page).toHaveURL( + /\/ru-ru\/schedule\/[A-Z]{3}\/SU\d+-\d{8}\/[A-Z]{3}\/SU\d+-\d{8}\/[A-Z]{3}/, + ); + }); + + test("flight details button preserves search context in URL", async ({ + page, + }) => { + await page.goto("/ru-ru/schedule/route/SVO-LED-20260415"); + + const cards = page.locator(".flight-card--clickable"); + await expect(cards.first()).toBeVisible({ timeout: 30000 }); + + await cards.first().click(); + + const detailsBtn = page.locator('[data-testid="flight-details-button"]'); + await detailsBtn.click(); + + const url = page.url(); + expect(url).toContain("?request="); + expect(url).toContain("schedule-route-SVO-LED-20260415"); + }); +});