Wrap Schedule page in PageLayout with tabs and Angular-matching styles

Schedule start page now uses PageLayout with PageTabs (schedule tab active),
filter form in content-left column, and the info section with titles-container
and PopularRequests in the main content area. SCSS matches Angular start.scss.
This commit is contained in:
2026-04-15 19:34:45 +03:00
parent 7c11e2dca5
commit 9d0e62b952
2 changed files with 279 additions and 153 deletions
@@ -1,47 +1,147 @@
@use "../../../styles/variables" as vars;
@use "../../../styles/fonts" as fonts;
@use "../../../styles/colors" as colors;
@use "../../../styles/screen" as screen;
@use "../../../styles/shadows" as shadows;
.schedule-start {
&__title {
font-size: 22px;
margin: 0 0 vars.$space-xl;
}
.schedule-start-page {
section.frame {
padding: 0;
&__form {
display: flex;
flex-direction: column;
gap: vars.$space-m;
.card {
display: flex;
flex-direction: column;
h2 {
padding: 50px;
padding-bottom: 20px;
}
@include screen.mobile {
.card {
padding: vars.$space-xl vars.$space-xl 0;
.titles-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
padding: 0 50px;
padding-bottom: 50px;
.title {
width: 50%;
padding: 30px;
padding-right: 50px;
padding-left: 65px;
background-repeat: no-repeat;
background-position: left center;
a {
cursor: default;
font-size: fonts.$font-size-xl;
}
div {
color: colors.$gray;
padding-top: vars.$space-s;
}
&.title1 {
background-image: url('/assets/img/schedule-title-icon-1.svg');
}
&.title2 {
background-image: url('/assets/img/schedule-title-icon-2.svg');
}
&.title3 {
background-image: url('/assets/img/schedule-title-icon-3.svg');
}
&.title4 {
background-image: url('/assets/img/schedule-title-icon-4.svg');
}
}
}
}
&__field {
@media (max-width: vars.$media-breakpoint-mobile) {
section.frame h2 {
padding: 20px;
font-size: 20px;
line-height: 28px;
padding-bottom: 0px;
padding-top: 30px;
}
.titles-container {
padding: 20px !important;
padding-top: 0 !important;
div.title {
width: 100% !important;
padding: 20px !important;
padding-left: 50px !important;
background-size: 35px auto !important;
a {
font-size: 16px;
}
}
}
}
.page-title {
width: auto;
display: inline-block;
max-width: 100%;
white-space: normal !important;
}
h1.text--white {
@include fonts.font-overflow();
@include screen.smTablet {
font-size: fonts.$font-size-xxxl--tablet;
margin-bottom: vars.$space-m + vars.$space-s;
overflow: visible;
white-space: normal;
}
@include screen.mobile {
font-size: fonts.$font-size-xxxl--mobile;
margin-bottom: vars.$space-m + vars.$space-s;
margin-top: vars.$space-m;
overflow: visible;
white-space: normal;
}
}
// Schedule filter panel in content-left column
.schedule-start__form {
@include shadows.box-shadow-small;
background-color: colors.$white;
border-radius: vars.$border-radius;
padding: vars.$space-xl;
display: flex;
flex-direction: column;
gap: vars.$space-m;
}
.schedule-start__field {
display: flex;
flex-direction: column;
gap: vars.$space-s;
label {
font-weight: 500;
@include fonts.font-small(colors.$gray);
margin-bottom: vars.$label-margin-bottom;
}
input[type="text"],
input[type="date"] {
@include shadows.control-border-shadow();
height: vars.$standard-button-height;
padding: 0 vars.$space-l;
border: 1px solid colors.$border-input;
border-radius: vars.$border-radius;
font-size: 16px;
transition: border-color 0.2s;
font-size: fonts.$font-size-l;
font-weight: fonts.$font-regular;
color: colors.$text-color;
width: 100%;
transition-duration: 0.2s;
@include fonts.font-overflow();
&:focus {
outline: none;
@@ -51,17 +151,21 @@
}
}
&__submit {
.schedule-start__submit {
margin-top: vars.$space-xl;
width: 100%;
height: vars.$standard-button-height;
background-color: colors.$blue-light;
background-color: colors.$blue;
color: colors.$white;
border: none;
border-radius: vars.$border-radius;
font-size: 16px;
cursor: pointer;
transition-duration: 0.2s;
font-weight: fonts.$font-bold;
font-size: fonts.$font-size-m;
&:hover {
background-color: colors.$blue-light--hover;
background-color: colors.$blue--hover;
}
&:focus {
@@ -69,39 +173,3 @@
}
}
}
.schedule-home-page-header {
display: flex;
margin: vars.$space-xl;
@include screen.mobile {
margin-left: 0;
margin-right: 0;
}
}
.schedule-home-page-header-border {
border-bottom: 1px solid colors.$border;
@include screen.mobile {
display: none;
}
}
.schedule-controls {
.card {
display: flex;
flex-direction: column;
}
@include screen.mobile {
.card {
padding: vars.$space-xl vars.$space-xl 0;
}
&__tabs {
display: flex;
flex-direction: column;
}
}
}
@@ -9,6 +9,11 @@
import { type FC, useState, useCallback, type FormEvent } from "react";
import { useNavigate, useParams } from "@modern-js/runtime/router";
import { useTranslation } from "@/i18n/provider.js";
import { PageLayout } from "@/ui/layout/PageLayout.js";
import { PageTabs } from "@/ui/layout/PageTabs.js";
import { PopularRequestsPanel } from "@/features/popular-requests/components/PopularRequestsPanel.js";
import type { PopularRequest } from "@/features/popular-requests/types.js";
import { buildScheduleUrl } from "../url.js";
import "./ScheduleStartPage.scss";
@@ -43,6 +48,7 @@ function addDaysToDateInput(value: string, days: number): string {
export const ScheduleStartPage: FC = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const routeParams = useParams<{ lang: string }>();
const lang = routeParams.lang ?? "ru";
@@ -92,109 +98,161 @@ export const ScheduleStartPage: FC = () => {
[departureAirport, arrivalAirport, dateFrom, dateTo, isRoundTrip, returnDateFrom, returnDateTo, navigate, lang],
);
return (
<div className="schedule-start" data-testid="schedule-start">
<h1 className="schedule-start__title">Flight Schedule</h1>
const handlePopularRequestClick = useCallback((_request: PopularRequest) => {
// Navigation handled by PopularRequestItem internally
}, []);
<form
className="schedule-start__form"
data-testid="schedule-search-form"
onSubmit={handleSubmit}
>
<div className="schedule-start__field">
<label htmlFor="schedule-departure">Departure</label>
const scheduleFilter = (
<form
className="schedule-start__form"
data-testid="schedule-search-form"
onSubmit={handleSubmit}
>
<div className="schedule-start__field">
<label htmlFor="schedule-departure">Departure</label>
<input
id="schedule-departure"
type="text"
placeholder="e.g. SVO"
maxLength={3}
value={departureAirport}
onChange={(e) => setDepartureAirport(e.target.value)}
data-testid="departure-input"
/>
</div>
<div className="schedule-start__field">
<label htmlFor="schedule-arrival">Arrival</label>
<input
id="schedule-arrival"
type="text"
placeholder="e.g. LED"
maxLength={3}
value={arrivalAirport}
onChange={(e) => setArrivalAirport(e.target.value)}
data-testid="arrival-input"
/>
</div>
<div className="schedule-start__field">
<label htmlFor="schedule-date-from">Date from</label>
<input
id="schedule-date-from"
type="date"
value={dateFrom}
onChange={(e) => setDateFrom(e.target.value)}
data-testid="date-from-input"
/>
</div>
<div className="schedule-start__field">
<label htmlFor="schedule-date-to">Date to</label>
<input
id="schedule-date-to"
type="date"
value={dateTo}
onChange={(e) => setDateTo(e.target.value)}
data-testid="date-to-input"
/>
</div>
<div className="schedule-start__field">
<label>
<input
id="schedule-departure"
type="text"
placeholder="e.g. SVO"
maxLength={3}
value={departureAirport}
onChange={(e) => setDepartureAirport(e.target.value)}
data-testid="departure-input"
type="checkbox"
checked={isRoundTrip}
onChange={(e) => setIsRoundTrip(e.target.checked)}
data-testid="round-trip-toggle"
/>
</div>
Round trip
</label>
</div>
<div className="schedule-start__field">
<label htmlFor="schedule-arrival">Arrival</label>
<input
id="schedule-arrival"
type="text"
placeholder="e.g. LED"
maxLength={3}
value={arrivalAirport}
onChange={(e) => setArrivalAirport(e.target.value)}
data-testid="arrival-input"
/>
</div>
<div className="schedule-start__field">
<label htmlFor="schedule-date-from">Date from</label>
<input
id="schedule-date-from"
type="date"
value={dateFrom}
onChange={(e) => setDateFrom(e.target.value)}
data-testid="date-from-input"
/>
</div>
<div className="schedule-start__field">
<label htmlFor="schedule-date-to">Date to</label>
<input
id="schedule-date-to"
type="date"
value={dateTo}
onChange={(e) => setDateTo(e.target.value)}
data-testid="date-to-input"
/>
</div>
<div className="schedule-start__field">
<label>
{isRoundTrip && (
<>
<div className="schedule-start__field">
<label htmlFor="schedule-return-date-from">Return date from</label>
<input
type="checkbox"
checked={isRoundTrip}
onChange={(e) => setIsRoundTrip(e.target.checked)}
data-testid="round-trip-toggle"
id="schedule-return-date-from"
type="date"
value={returnDateFrom}
onChange={(e) => setReturnDateFrom(e.target.value)}
data-testid="return-date-from-input"
/>
Round trip
</label>
</div>
</div>
{isRoundTrip && (
<>
<div className="schedule-start__field">
<label htmlFor="schedule-return-date-from">Return date from</label>
<input
id="schedule-return-date-from"
type="date"
value={returnDateFrom}
onChange={(e) => setReturnDateFrom(e.target.value)}
data-testid="return-date-from-input"
/>
<div className="schedule-start__field">
<label htmlFor="schedule-return-date-to">Return date to</label>
<input
id="schedule-return-date-to"
type="date"
value={returnDateTo}
onChange={(e) => setReturnDateTo(e.target.value)}
data-testid="return-date-to-input"
/>
</div>
</>
)}
<button
type="submit"
className="schedule-start__submit"
data-testid="schedule-search-submit"
>
Search
</button>
</form>
);
return (
<div className="schedule-start-page" data-testid="schedule-start">
<PageLayout
headerLeft={
<PageTabs viewType="schedule" />
}
title={
<h1 className="text--white page-title">
{t("SCHEDULE.TITLE")}
</h1>
}
contentLeft={scheduleFilter}
>
<section className="frame">
<h2>{t("SCHEDULE.SCHEDULE-START")}</h2>
<div className="titles-container">
<div className="title title1">
<a>{t("SCHEDULE.SCHEDULE-START-TITLE1")}</a>
<div>
{t("SCHEDULE.SCHEDULE-START-TITLE1-DESCRIPTION")}
</div>
</div>
<div className="schedule-start__field">
<label htmlFor="schedule-return-date-to">Return date to</label>
<input
id="schedule-return-date-to"
type="date"
value={returnDateTo}
onChange={(e) => setReturnDateTo(e.target.value)}
data-testid="return-date-to-input"
/>
<div className="title title2">
<a>{t("SCHEDULE.SCHEDULE-START-TITLE2")}</a>
<div>
{t("SCHEDULE.SCHEDULE-START-TITLE2-DESCRIPTION")}
</div>
</div>
</>
)}
<button
type="submit"
className="schedule-start__submit"
data-testid="schedule-search-submit"
>
Search
</button>
</form>
<div className="title title3">
<a>{t("SCHEDULE.SCHEDULE-START-TITLE3")}</a>
<div>
{t("SCHEDULE.SCHEDULE-START-TITLE3-DESCRIPTION")}
</div>
</div>
<div className="title title4">
<a>{t("SCHEDULE.SCHEDULE-START-TITLE4")}</a>
<div>
{t("SCHEDULE.SCHEDULE-START-TITLE4-DESCRIPTION")}
</div>
</div>
</div>
<PopularRequestsPanel onRequestClick={handlePopularRequestClick} />
</section>
</PageLayout>
</div>
);
};