Audit Schedule expanded rows per TZ 4.1.14.4 (multi-segment + connecting)
Gate Buy button to the TZ §4.1.14.4.4 window: visible only when departure UTC is > 2 hours ahead AND < 330 days ahead; first leg governs for multi/ connecting. Gate Status button (§4.1.14.4.5) to same-day departure only, based on UTC calendar date. Add separate Details button (§4.1.14.4.6) that is always visible when an onStatus handler is provided. Add SCSS for the new details-btn outline style. Add 25-test ScheduleFlightBody.test.tsx covering structure, transfer-box labels, buy gate, and status gate.
This commit is contained in:
@@ -224,6 +224,26 @@
|
||||
&:hover { background: colors.$blue--hover; }
|
||||
}
|
||||
|
||||
// §4.1.14.4.6 – Детали рейса: always-visible outline button (white bg,
|
||||
// blue border+text), mirrors Angular's secondary CTA style.
|
||||
&__details-btn {
|
||||
background: colors.$white;
|
||||
color: colors.$blue;
|
||||
border: 1px solid colors.$blue;
|
||||
border-radius: vars.$border-radius;
|
||||
padding: vars.$space-m 24px;
|
||||
font-size: fonts.$font-size-m;
|
||||
font-weight: fonts.$font-medium;
|
||||
cursor: pointer;
|
||||
min-width: 150px;
|
||||
transition: background-color 0.2s ease, color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: colors.$blue;
|
||||
color: colors.$white;
|
||||
}
|
||||
}
|
||||
|
||||
// ----- horizontal timeline (route summary) -----------------------------
|
||||
&__timeline {
|
||||
padding: vars.$space-l vars.$space-xl vars.$space-m;
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
// @vitest-environment jsdom
|
||||
/**
|
||||
* Tests for ScheduleFlightBody – TZ §4.1.14.4 compliance assertions.
|
||||
*
|
||||
* Coverage:
|
||||
* - Direct flight: leg number, flight-id, operator logo, dep/arr times, duration
|
||||
* - Transfer box between legs (multi-leg + connecting): label + duration + station info
|
||||
* - Buy button visibility gate: >2h ahead AND <330 days ahead (TZ §4.1.14.4.4)
|
||||
* - Status button visibility gate: same-day departure only (TZ §4.1.14.4.5)
|
||||
* - Share button always present
|
||||
* - Actions area rendered at bottom
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import type { ISimpleFlight, IFlightLeg } from "@/features/online-board/types.js";
|
||||
|
||||
// ── i18n stub ─────────────────────────────────────────────────────────────────
|
||||
vi.mock("@/i18n/provider.js", () => ({
|
||||
useTranslation: () => ({ t: (k: string) => k }),
|
||||
}));
|
||||
vi.mock("@/i18n/useLocale.js", () => ({
|
||||
useLocale: () => ({ language: "ru", locale: "ru-ru" }),
|
||||
}));
|
||||
|
||||
// ── UI stubs ──────────────────────────────────────────────────────────────────
|
||||
vi.mock("@/ui/flights/TimeGroup.js", () => ({
|
||||
TimeGroup: ({ scheduled }: { scheduled: string }) => (
|
||||
<span data-testid="time-group">{scheduled}</span>
|
||||
),
|
||||
}));
|
||||
vi.mock("@/ui/flights/StationDisplay.js", () => ({
|
||||
StationDisplay: ({ airportCode }: { airportCode: string }) => (
|
||||
<span data-testid="station-display">{airportCode}</span>
|
||||
),
|
||||
}));
|
||||
vi.mock("@/ui/flights/OperatorLogo.js", () => ({
|
||||
OperatorLogo: ({ carrier }: { carrier: string }) => (
|
||||
<span data-testid="operator-logo">{carrier}</span>
|
||||
),
|
||||
}));
|
||||
|
||||
import { ScheduleFlightBody } from "./ScheduleFlightBody.js";
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function makeTimesSet(utc: string, local: string) {
|
||||
return {
|
||||
scheduledDeparture: {
|
||||
utc,
|
||||
local,
|
||||
localTime: local,
|
||||
tzOffset: 0,
|
||||
dayChange: { value: 0, title: "" },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeArrivalTimesSet(utc: string, local: string) {
|
||||
return {
|
||||
scheduledArrival: {
|
||||
utc,
|
||||
local,
|
||||
localTime: local,
|
||||
tzOffset: 0,
|
||||
dayChange: { value: 0, title: "" },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeStation(code: string, city: string, airport: string): {
|
||||
scheduled: { airportCode: string; city: string; airport: string; cityCode: string; countryCode: string };
|
||||
terminal: string;
|
||||
} {
|
||||
return {
|
||||
scheduled: {
|
||||
airportCode: code,
|
||||
city,
|
||||
airport,
|
||||
cityCode: code,
|
||||
countryCode: "RU",
|
||||
},
|
||||
terminal: "",
|
||||
};
|
||||
}
|
||||
|
||||
function makeLeg(depUtc: string, depLocal: string, arrUtc: string, arrLocal: string, depCode = "SVO", arrCode = "LED"): IFlightLeg {
|
||||
return {
|
||||
departure: {
|
||||
...makeStation(depCode, "Moscow", "Sheremetyevo"),
|
||||
times: makeTimesSet(depUtc, depLocal),
|
||||
checkingStatus: "",
|
||||
},
|
||||
arrival: {
|
||||
...makeStation(arrCode, "Saint Petersburg", "Pulkovo"),
|
||||
times: makeArrivalTimesSet(arrUtc, arrLocal),
|
||||
},
|
||||
flyingTime: "02:00:00",
|
||||
equipment: {},
|
||||
operatingBy: {},
|
||||
status: "Scheduled",
|
||||
flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
|
||||
dayChange: 0,
|
||||
index: 0,
|
||||
updated: "",
|
||||
} as unknown as IFlightLeg;
|
||||
}
|
||||
|
||||
function makeDirectFlight(depUtc: string, depLocal = "10:00:00"): ISimpleFlight {
|
||||
const arrUtc = new Date(new Date(depUtc).getTime() + 2 * 3600 * 1000).toISOString();
|
||||
return {
|
||||
routeType: "Direct",
|
||||
id: "test-direct",
|
||||
flyingTime: "02:00:00",
|
||||
status: "Scheduled",
|
||||
flightId: { carrier: "SU", flightNumber: "1234", date: depUtc.slice(0, 10) },
|
||||
operatingBy: {},
|
||||
leg: makeLeg(depUtc, depLocal, arrUtc, "12:00:00"),
|
||||
} as unknown as ISimpleFlight;
|
||||
}
|
||||
|
||||
function makeMultiLegFlight(depUtc: string): ISimpleFlight {
|
||||
const mid = new Date(new Date(depUtc).getTime() + 2 * 3600 * 1000).toISOString();
|
||||
const midNext = new Date(new Date(mid).getTime() + 1.5 * 3600 * 1000).toISOString();
|
||||
const arr = new Date(new Date(midNext).getTime() + 3 * 3600 * 1000).toISOString();
|
||||
return {
|
||||
routeType: "MultiLeg",
|
||||
id: "test-multileg",
|
||||
flyingTime: "06:30:00",
|
||||
status: "Scheduled",
|
||||
flightId: { carrier: "SU", flightNumber: "5678", date: depUtc.slice(0, 10) },
|
||||
operatingBy: {},
|
||||
legs: [
|
||||
makeLeg(depUtc, "10:00", mid, "12:00", "SVO", "KJA"),
|
||||
makeLeg(midNext, "13:30", arr, "16:30", "KJA", "LED"),
|
||||
],
|
||||
} as unknown as ISimpleFlight;
|
||||
}
|
||||
|
||||
function makeConnectingFlight(depUtc: string): ISimpleFlight {
|
||||
const mid = new Date(new Date(depUtc).getTime() + 2 * 3600 * 1000).toISOString();
|
||||
const midNext = new Date(new Date(mid).getTime() + 2 * 3600 * 1000).toISOString();
|
||||
const arr = new Date(new Date(midNext).getTime() + 3 * 3600 * 1000).toISOString();
|
||||
return {
|
||||
routeType: "MultiLeg",
|
||||
id: "su6188+su6233",
|
||||
flyingTime: "07:00:00",
|
||||
status: "Scheduled",
|
||||
flightId: { carrier: "SU", flightNumber: "6188", date: depUtc.slice(0, 10) },
|
||||
operatingBy: {},
|
||||
legs: [
|
||||
makeLeg(depUtc, "10:00", mid, "12:00", "SVO", "KUF"),
|
||||
makeLeg(midNext, "14:00", arr, "17:00", "KUF", "LED"),
|
||||
],
|
||||
_childFlightIds: [
|
||||
{ carrier: "SU", flightNumber: "6188" },
|
||||
{ carrier: "SU", flightNumber: "6233" },
|
||||
],
|
||||
} as unknown as ISimpleFlight;
|
||||
}
|
||||
|
||||
// ── Buy-gate helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
/** 10 days from now — buy should be visible */
|
||||
const FUTURE_10D = new Date(Date.now() + 10 * 24 * 3600 * 1000).toISOString();
|
||||
/** 340 days from now — buy should be hidden (> 330 days) */
|
||||
const FUTURE_340D = new Date(Date.now() + 340 * 24 * 3600 * 1000).toISOString();
|
||||
/** 1 hour from now — buy should be hidden (< 2h threshold) */
|
||||
const FUTURE_1H = new Date(Date.now() + 1 * 3600 * 1000).toISOString();
|
||||
/** Yesterday — buy should be hidden (past) */
|
||||
const YESTERDAY = new Date(Date.now() - 24 * 3600 * 1000).toISOString();
|
||||
/** Today (same calendar day) — status button should be visible */
|
||||
function todayUtc(offsetHours = 3): string {
|
||||
const now = new Date();
|
||||
now.setHours(now.getHours() + offsetHours);
|
||||
return now.toISOString();
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe("ScheduleFlightBody – TZ §4.1.14.4", () => {
|
||||
|
||||
describe("Direct flight structure", () => {
|
||||
it("renders schedule-flight-body container", () => {
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByTestId("schedule-flight-body")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders leg number badge '1' for single leg", () => {
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByText("1")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders flight number SU 1234", () => {
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByText("SU 1234")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders operator logo for the leg carrier", () => {
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByTestId("operator-logo")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders departure and arrival station codes", () => {
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
|
||||
const stations = screen.getAllByTestId("station-display");
|
||||
expect(stations.some((s) => s.textContent === "SVO")).toBe(true);
|
||||
expect(stations.some((s) => s.textContent === "LED")).toBe(true);
|
||||
});
|
||||
|
||||
it("renders share button always (TZ §4.1.14.4.3)", () => {
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByTestId("schedule-share-button")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Multi-leg flight structure", () => {
|
||||
it("renders 2 leg rows for multi-leg flight", () => {
|
||||
render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
|
||||
// Both leg numbers should appear
|
||||
const nums = screen.getAllByText(/^[12]$/);
|
||||
expect(nums.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("renders horizontal timeline for multi-leg (TZ §4.1.14.4 timeline row)", () => {
|
||||
render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByTestId("schedule-timeline")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders transfer box between legs", () => {
|
||||
render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByTestId("flight-transfer")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows SHARED.INTERMEDIATE-LANDING-PLURAL-ONE for multi-leg transfer", () => {
|
||||
render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByText("SHARED.INTERMEDIATE-LANDING-PLURAL-ONE")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders transfer content area in transfer box", () => {
|
||||
render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} />);
|
||||
// Transfer box rendered; content (icon + label) is present
|
||||
const transfer = screen.getByTestId("flight-transfer");
|
||||
expect(transfer.textContent).toContain("SHARED.INTERMEDIATE-LANDING-PLURAL-ONE");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Connecting flight structure", () => {
|
||||
it("renders separate flight numbers per leg for connecting flight", () => {
|
||||
render(<ScheduleFlightBody flight={makeConnectingFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByText("SU 6188")).toBeTruthy();
|
||||
expect(screen.getByText("SU 6233")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("shows SHARED.FLIGHT-TRANSFER label for connecting transfer box", () => {
|
||||
render(<ScheduleFlightBody flight={makeConnectingFlight(FUTURE_10D)} />);
|
||||
expect(screen.getByText("SHARED.FLIGHT-TRANSFER")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// TZ §4.1.14.4.4 — Buy button visibility gate
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe("Buy button – TZ §4.1.14.4.4", () => {
|
||||
it("shows buy button when departure is 10 days ahead", () => {
|
||||
const onBuy = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} onBuy={onBuy} />);
|
||||
expect(screen.getByTestId("schedule-buy-button")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("hides buy button when departure is > 330 days ahead", () => {
|
||||
const onBuy = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_340D)} onBuy={onBuy} />);
|
||||
expect(screen.queryByTestId("schedule-buy-button")).toBeNull();
|
||||
});
|
||||
|
||||
it("hides buy button when departure is less than 2h away", () => {
|
||||
const onBuy = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_1H)} onBuy={onBuy} />);
|
||||
expect(screen.queryByTestId("schedule-buy-button")).toBeNull();
|
||||
});
|
||||
|
||||
it("hides buy button when departure is in the past", () => {
|
||||
const onBuy = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(YESTERDAY)} onBuy={onBuy} />);
|
||||
expect(screen.queryByTestId("schedule-buy-button")).toBeNull();
|
||||
});
|
||||
|
||||
it("hides buy button when onBuy prop not provided", () => {
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} />);
|
||||
expect(screen.queryByTestId("schedule-buy-button")).toBeNull();
|
||||
});
|
||||
|
||||
it("calls onBuy handler when buy button clicked", () => {
|
||||
const onBuy = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} onBuy={onBuy} />);
|
||||
fireEvent.click(screen.getByTestId("schedule-buy-button"));
|
||||
expect(onBuy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("uses first-leg departure UTC for multi-leg buy gate (TZ §4.1.14.4.4)", () => {
|
||||
const onBuy = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeMultiLegFlight(FUTURE_10D)} onBuy={onBuy} />);
|
||||
expect(screen.getByTestId("schedule-buy-button")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
// TZ §4.1.14.4.5 — Status button (today-only)
|
||||
// ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe("Status button – TZ §4.1.14.4.5", () => {
|
||||
it("shows status button when flight departs today", () => {
|
||||
const onStatus = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(todayUtc(3))} onStatus={onStatus} />);
|
||||
expect(screen.getByTestId("schedule-status-button")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("hides status button when flight departs 10 days from now", () => {
|
||||
const onStatus = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(FUTURE_10D)} onStatus={onStatus} />);
|
||||
expect(screen.queryByTestId("schedule-status-button")).toBeNull();
|
||||
});
|
||||
|
||||
it("hides status button when flight departed yesterday", () => {
|
||||
const onStatus = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(YESTERDAY)} onStatus={onStatus} />);
|
||||
expect(screen.queryByTestId("schedule-status-button")).toBeNull();
|
||||
});
|
||||
|
||||
it("hides status button when onStatus prop not provided", () => {
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(todayUtc())} />);
|
||||
expect(screen.queryByTestId("schedule-status-button")).toBeNull();
|
||||
});
|
||||
|
||||
it("calls onStatus handler when status button clicked", () => {
|
||||
const onStatus = vi.fn();
|
||||
render(<ScheduleFlightBody flight={makeDirectFlight(todayUtc(3))} onStatus={onStatus} />);
|
||||
fireEvent.click(screen.getByTestId("schedule-status-button"));
|
||||
expect(onStatus).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -80,6 +80,46 @@ function transferDuration(prev: IFlightLeg, next: IFlightLeg): string {
|
||||
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:00`;
|
||||
}
|
||||
|
||||
// ── TZ §4.1.14.4.4 – Buy-button visibility gate ─────────────────────────────
|
||||
// Visible when:
|
||||
// • Departure UTC is > 2 h from now (not about to depart / already departed)
|
||||
// • Departure UTC is < 330 days from now
|
||||
// Eligibility is assessed on the FIRST leg of the flight (TZ §4.1.14.4.4:
|
||||
// "рассчитывается исходя из времени первого сегмента").
|
||||
const BUY_MAX_DAYS = 330;
|
||||
const BUY_MIN_HOURS = 2;
|
||||
|
||||
function isBuyVisible(firstLegDepUtc: string | undefined): boolean {
|
||||
if (!firstLegDepUtc) return false;
|
||||
const depMs = new Date(firstLegDepUtc).getTime();
|
||||
if (Number.isNaN(depMs)) return false;
|
||||
const nowMs = Date.now();
|
||||
const diffMs = depMs - nowMs;
|
||||
if (diffMs <= 0) return false;
|
||||
if (diffMs < BUY_MIN_HOURS * 3600 * 1000) return false;
|
||||
if (diffMs > BUY_MAX_DAYS * 24 * 3600 * 1000) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── TZ §4.1.14.4.5 – Status-button visibility gate ──────────────────────────
|
||||
// Visible when the departure calendar day matches today (user's local day).
|
||||
// Eligibility is assessed on the FIRST leg (TZ: "рассчитывается исходя из
|
||||
// времени первого сегмента"). We compare using the UTC departure timestamp
|
||||
// against today's UTC date, which is a safe approximation (avoids needing
|
||||
// the station's tzOffset for boundary accuracy, consistent with Angular impl).
|
||||
function isStatusVisible(firstLegDepUtc: string | undefined): boolean {
|
||||
if (!firstLegDepUtc) return false;
|
||||
const depMs = new Date(firstLegDepUtc).getTime();
|
||||
if (Number.isNaN(depMs)) return false;
|
||||
const dep = new Date(depMs);
|
||||
const now = new Date();
|
||||
return (
|
||||
dep.getUTCFullYear() === now.getUTCFullYear() &&
|
||||
dep.getUTCMonth() === now.getUTCMonth() &&
|
||||
dep.getUTCDate() === now.getUTCDate()
|
||||
);
|
||||
}
|
||||
|
||||
export const ScheduleFlightBody: FC<ScheduleFlightBodyProps> = ({
|
||||
flight,
|
||||
onBuy,
|
||||
@@ -92,6 +132,14 @@ export const ScheduleFlightBody: FC<ScheduleFlightBodyProps> = ({
|
||||
flight.routeType === "Direct" ? [flight.leg] : flight.legs;
|
||||
if (legs.length === 0) return null;
|
||||
|
||||
const firstLeg = legs[0];
|
||||
const buyVisible =
|
||||
Boolean(onBuy) &&
|
||||
isBuyVisible(firstLeg?.departure.times.scheduledDeparture.utc);
|
||||
const statusVisible =
|
||||
Boolean(onStatus) &&
|
||||
isStatusVisible(firstLeg?.departure.times.scheduledDeparture.utc);
|
||||
|
||||
const childFlightIds = (flight as ISimpleFlight & {
|
||||
_childFlightIds?: ChildFlightId[];
|
||||
})._childFlightIds;
|
||||
@@ -355,28 +403,49 @@ export const ScheduleFlightBody: FC<ScheduleFlightBodyProps> = ({
|
||||
<img src="/assets/img/share.svg" alt="" aria-hidden="true" />
|
||||
</button>
|
||||
<div className="schedule-flight-body__spacer" />
|
||||
<button
|
||||
type="button"
|
||||
className="schedule-flight-body__buy-btn"
|
||||
data-testid="schedule-buy-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onBuy?.();
|
||||
}}
|
||||
>
|
||||
{t("SHARED.BUY-TICKET")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="schedule-flight-body__status-btn"
|
||||
data-testid="schedule-status-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onStatus?.();
|
||||
}}
|
||||
>
|
||||
{t("SHARED.FLIGHT-DETAILS")}
|
||||
</button>
|
||||
{/* TZ §4.1.14.4.4 – Buy: visible only within 2h–330d window */}
|
||||
{buyVisible && (
|
||||
<button
|
||||
type="button"
|
||||
className="schedule-flight-body__buy-btn"
|
||||
data-testid="schedule-buy-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onBuy?.();
|
||||
}}
|
||||
>
|
||||
{t("SHARED.BUY-TICKET")}
|
||||
</button>
|
||||
)}
|
||||
{/* TZ §4.1.14.4.5 – Status: visible only on the departure day */}
|
||||
{statusVisible && (
|
||||
<button
|
||||
type="button"
|
||||
className="schedule-flight-body__status-btn"
|
||||
data-testid="schedule-status-button"
|
||||
title={t("BOARD.STATUS-TOOLTIP")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onStatus?.();
|
||||
}}
|
||||
>
|
||||
{t("SHARED.FLIGHT-STATUS")}
|
||||
</button>
|
||||
)}
|
||||
{/* TZ §4.1.14.4.6 – Details: always visible when handler provided */}
|
||||
{onStatus && (
|
||||
<button
|
||||
type="button"
|
||||
className="schedule-flight-body__details-btn"
|
||||
data-testid="schedule-details-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onStatus?.();
|
||||
}}
|
||||
>
|
||||
{t("SHARED.FLIGHT-DETAILS")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user