Polish Flights Map page with PageLayout, tabs, and filter styling
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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user