Add FullRouteTimeline wrapper component

This commit is contained in:
2026-04-17 02:33:52 +03:00
parent b8197b2db5
commit 4c87a3b362
4 changed files with 204 additions and 0 deletions
@@ -0,0 +1,105 @@
.full-route-timeline {
background: #fff;
border-radius: 8px;
padding: 16px 24px;
margin-bottom: 16px;
@media (max-width: 768px) {
display: none;
}
}
.timeline {
display: flex;
align-items: center;
gap: 16px;
&__arrow {
background: transparent;
border: 1px solid #d0dae5;
border-radius: 50%;
width: 32px;
height: 32px;
cursor: pointer;
color: #2060c0;
font-size: 16px;
&:disabled { opacity: 0.3; cursor: not-allowed; }
}
&__content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
&__row {
display: flex;
align-items: center;
gap: 8px;
&--times { font-size: 18px; font-weight: 500; color: #1a3a5c; }
&--stations { font-size: 14px; color: #666; }
}
}
.timeline-section {
position: relative;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
&__separator {
flex: 1;
height: 1px;
background: #d0dae5;
}
&__number {
position: absolute;
top: -20px;
background: #e3f0ff;
color: #2060c0;
border-radius: 10px;
padding: 2px 8px;
font-size: 12px;
font-weight: 600;
}
&__duration {
font-size: 12px;
color: #666;
padding: 0 8px;
background: #fff;
&--specifying { color: #ff9000; }
}
}
.station {
display: flex;
flex-direction: column;
gap: 2px;
&--right { align-items: flex-end; text-align: right; }
&--center { align-items: center; text-align: center; }
&__city { font-size: 14px; font-weight: 500; color: #1a3a5c; }
&__code { font-size: 12px; color: #666; }
&__terminal { font-size: 11px; color: #999; }
&--large { .station__city { font-size: 22px; } }
&--small { .station__city { font-size: 12px; } }
}
.station-change {
display: flex;
align-items: center;
gap: 8px;
&__arrow { color: #2060c0; }
&__city { font-size: 12px; color: #666; }
&__code { font-size: 14px; font-weight: 500; color: #1a3a5c; }
}
@@ -0,0 +1,79 @@
// @vitest-environment jsdom
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import type { IFlightLeg } from "../../types.js";
vi.mock("@/i18n/provider.js", () => ({
useTranslation: () => ({ t: (k: string) => k }),
}));
import { FullRouteTimeline } from "./FullRouteTimeline.js";
function makeLeg(i: number): IFlightLeg {
return {
arrival: {
scheduled: {
airport: "",
airportCode: "AAA",
city: `City${i}`,
cityCode: `C${i}`,
countryCode: "RU",
},
terminal: "",
times: {
scheduledArrival: {
dayChange: { value: 0, title: "" },
local: `a${i}`,
localTime: `a${i}`,
tzOffset: 0,
utc: `2026-04-17T0${i}:00:00Z`,
},
},
},
departure: {
scheduled: {
airport: "",
airportCode: "BBB",
city: `Dep${i}`,
cityCode: `D${i}`,
countryCode: "RU",
},
terminal: "",
checkingStatus: "",
times: {
scheduledDeparture: {
dayChange: { value: 0, title: "" },
local: `d${i}`,
localTime: `d${i}`,
tzOffset: 0,
utc: `2026-04-17T0${i}:00:00Z`,
},
},
},
dayChange: 0,
equipment: {},
flags: { checkinAvailable: false, returnToAirport: false, routeChanged: false },
flyingTime: `${i}h`,
index: i,
operatingBy: {},
status: "Scheduled",
updated: "",
} as IFlightLeg;
}
describe("FullRouteTimeline", () => {
it("returns null when fewer than 2 legs", () => {
const { container } = render(<FullRouteTimeline legs={[makeLeg(0)]} viewType="Schedule" />);
expect(container.firstChild).toBeNull();
});
it("renders container with data-testid=full-route-timeline when 2+ legs", () => {
render(<FullRouteTimeline legs={[makeLeg(0), makeLeg(1)]} viewType="Schedule" />);
expect(screen.getByTestId("full-route-timeline")).toBeTruthy();
});
it("renders inner Timeline content (expect d0 visible when 2 legs)", () => {
render(<FullRouteTimeline legs={[makeLeg(0), makeLeg(1)]} viewType="Schedule" />);
expect(screen.getByText("d0")).toBeTruthy();
});
});
@@ -0,0 +1,18 @@
import type { FC } from "react";
import type { IFlightLeg } from "../../types.js";
import { Timeline } from "./Timeline.js";
import "./FullRouteTimeline.scss";
export interface FullRouteTimelineProps {
legs: IFlightLeg[];
viewType: "Onlineboard" | "Schedule";
}
export const FullRouteTimeline: FC<FullRouteTimelineProps> = ({ legs, viewType }) => {
if (legs.length < 2) return null;
return (
<section className="full-route-timeline" data-testid="full-route-timeline">
<Timeline legs={legs} canChange={viewType === "Onlineboard"} />
</section>
);
};
@@ -0,0 +1,2 @@
export { FullRouteTimeline } from "./FullRouteTimeline.js";
export type { FullRouteTimelineProps } from "./FullRouteTimeline.js";