Polish Flights Map page with PageLayout, tabs, and filter styling
CI / ci (push) Failing after 38s
Deploy / build-and-deploy (push) Failing after 6s

Flights Map now uses PageLayout with PageTabs (flights-map tab active),
filter in content-left column, and map in a .frame section. Added SCSS
for filter panel and map wrapper matching Angular structure.
This commit is contained in:
2026-04-15 19:35:02 +03:00
parent 74750e091f
commit dee10544e0
2 changed files with 220 additions and 65 deletions
@@ -0,0 +1,136 @@
@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;
.flights-map-start-page {
section.frame {
padding: 0;
overflow: hidden;
}
.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;
}
}
.flights-map-start {
&__map-wrapper {
position: relative;
min-height: 500px;
}
&__map {
width: 100%;
height: 500px;
}
&__loader,
&__error,
&__empty {
padding: vars.$space-xl;
text-align: center;
color: colors.$light-gray;
}
&__error {
color: colors.$red;
}
}
}
// Filter panel styling for content-left column
.flights-map-filter {
@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;
&__field {
display: flex;
flex-direction: column;
gap: vars.$space-s;
label {
@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;
padding-left: vars.$space-m !important;
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;
border-color: colors.$blue-light;
box-shadow: 0 0 0 0.2em colors.$focus-shadow;
}
}
}
&__exchange {
align-self: center;
width: vars.$standard-button-height;
height: vars.$standard-button-height;
background: colors.$white;
@include shadows.control-border-shadow();
cursor: pointer;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
transition-duration: 0.2s;
&:hover {
border-color: colors.$blue-light;
}
}
&__toggles {
display: flex;
flex-direction: column;
gap: vars.$space-s;
label {
display: flex;
align-items: center;
gap: vars.$space-s;
cursor: pointer;
@include fonts.font-small(colors.$gray);
}
}
}
@@ -4,10 +4,15 @@
* Manages filter state, drives the map and calendar hooks, and renders
* the filter panel + map canvas + loading/empty overlays.
*
* Uses PageLayout for the two-column layout matching the Angular version.
*
* @module
*/
import { type FC, lazy, Suspense, useState, useCallback, useMemo } from "react";
import { useTranslation } from "@/i18n/provider.js";
import { PageLayout } from "@/ui/layout/PageLayout.js";
import { PageTabs } from "@/ui/layout/PageTabs.js";
import { ClientOnly } from "./ClientOnly.js";
import { FlightsMapFilter } from "./FlightsMapFilter.js";
import { useFlightsMapSearch } from "../hooks/useFlightsMapSearch.js";
@@ -20,6 +25,7 @@ import type {
IMapMarker,
IMapPolyline,
} from "../types.js";
import "./FlightsMapStartPage.scss";
const MapCanvas = lazy(() =>
import("./MapCanvas.js").then((m) => ({ default: m.MapCanvas })),
@@ -54,6 +60,7 @@ function addMonthsYyyymmdd(base: string, months: number): string {
export const FlightsMapStartPage: FC = () => {
const env = getEnv();
const { t } = useTranslation();
const [filterState, setFilterState] = useState<IFlightsMapFilterState>({
connections: false,
@@ -121,72 +128,84 @@ export const FlightsMapStartPage: FC = () => {
const tileUrl = `${env.API_BASE_URL}/tiles/{z}/{x}/{y}.png`;
return (
<div className="flights-map-start" data-testid="flights-map-start">
<h1 className="flights-map-start__title">Flight Map</h1>
<FlightsMapFilter
value={filterState}
availableDays={availableDays}
onChange={handleFilterChange}
/>
<div className="flights-map-start__map-wrapper">
<ClientOnly
fallback={
<div aria-busy="true" data-testid="map-loading">
Loading map...
</div>
}
>
<Suspense
fallback={
<div aria-busy="true" data-testid="map-loading">
Loading map...
</div>
}
>
<MapCanvas
markers={markers}
polylines={polylines}
tileUrl={tileUrl}
onMarkerClick={handleMarkerClick}
className="flights-map-start__map"
/>
</Suspense>
</ClientOnly>
{loading && (
<div
className="flights-map-start__loader"
aria-busy="true"
data-testid="map-loader"
>
Loading routes...
</div>
)}
{!loading && error && (
<div
className="flights-map-start__error"
role="alert"
data-testid="map-error"
>
Failed to load routes. Please try again.
</div>
)}
{!loading &&
!error &&
searchParams !== null &&
routes.length === 0 && (
<div
className="flights-map-start__empty"
data-testid="map-no-directions"
<div className="flights-map-start-page" data-testid="flights-map-start">
<PageLayout
headerLeft={
<PageTabs viewType="flights-map" showFlightsMap />
}
title={
<h1 className="text--white page-title">
{t("FLIGHTS-MAP.TITLE")}
</h1>
}
contentLeft={
<FlightsMapFilter
value={filterState}
availableDays={availableDays}
onChange={handleFilterChange}
/>
}
>
<section className="frame">
<div className="flights-map-start__map-wrapper">
<ClientOnly
fallback={
<div aria-busy="true" data-testid="map-loading">
Loading map...
</div>
}
>
No directions found.
</div>
)}
</div>
<Suspense
fallback={
<div aria-busy="true" data-testid="map-loading">
Loading map...
</div>
}
>
<MapCanvas
markers={markers}
polylines={polylines}
tileUrl={tileUrl}
onMarkerClick={handleMarkerClick}
className="flights-map-start__map"
/>
</Suspense>
</ClientOnly>
{loading && (
<div
className="flights-map-start__loader"
aria-busy="true"
data-testid="map-loader"
>
Loading routes...
</div>
)}
{!loading && error && (
<div
className="flights-map-start__error"
role="alert"
data-testid="map-error"
>
Failed to load routes. Please try again.
</div>
)}
{!loading &&
!error &&
searchParams !== null &&
routes.length === 0 && (
<div
className="flights-map-start__empty"
data-testid="map-no-directions"
>
No directions found.
</div>
)}
</div>
</section>
</PageLayout>
</div>
);
};