Match Angular form controls: time range slider, date placeholders, button colors
Online board filter: replace time input fields with PrimeReact Slider matching Angular's p-slider range selector. Initialize dates to null so placeholder "ДД.ММ.ГГГГ" shows instead of today's date. Schedule filter: same time slider replacement, add missing "Только прямые рейсы" (direct flights only) checkbox, date placeholders. Error page: fix "На главную" button to use outlined style (transparent bg + blue border) matching Angular's blue-home class. Remove max-width on description text.
This commit is contained in:
@@ -178,27 +178,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
.time-selector {
|
||||
.wrapper--time-selector {
|
||||
margin-top: vars.$space-xl;
|
||||
|
||||
&__inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: vars.$space-m;
|
||||
}
|
||||
|
||||
&__separator {
|
||||
.time-selector__label {
|
||||
font-size: fonts.$font-size-s;
|
||||
color: colors.$gray;
|
||||
font-size: fonts.$font-size-l;
|
||||
margin-bottom: vars.$space-s;
|
||||
}
|
||||
|
||||
.input--time {
|
||||
flex: 1;
|
||||
height: vars.$standard-button-height;
|
||||
border: 1px solid colors.$border-input;
|
||||
border-radius: vars.$border-radius;
|
||||
padding: 0 vars.$space-m;
|
||||
font-size: fonts.$font-size-l;
|
||||
.time-selector {
|
||||
padding: 0 vars.$space-s;
|
||||
}
|
||||
|
||||
.time-selector__value {
|
||||
font-size: fonts.$font-size-s;
|
||||
color: colors.$gray;
|
||||
margin-top: vars.$space-s;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,19 @@
|
||||
import { type FC, useState, useCallback, type FormEvent } from "react";
|
||||
import { useNavigate, useParams } from "@modern-js/runtime/router";
|
||||
import { Calendar } from "primereact/calendar";
|
||||
import { Slider, type SliderChangeEvent } from "primereact/slider";
|
||||
import { AutoComplete, type AutoCompleteCompleteEvent } from "primereact/autocomplete";
|
||||
import { useTranslation } from "@/i18n/provider.js";
|
||||
import { useCitySearch, type CitySuggestion } from "@/shared/hooks/useCitySearch.js";
|
||||
import { buildOnlineBoardUrl } from "../url.js";
|
||||
import "./OnlineBoardFilter.scss";
|
||||
|
||||
function minutesToTime(minutes: number): string {
|
||||
const h = Math.floor(minutes / 60);
|
||||
const m = minutes % 60;
|
||||
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
type AccordionTab = "flight" | "route";
|
||||
|
||||
function dateToYyyymmdd(value: Date): string {
|
||||
@@ -48,15 +55,14 @@ export const OnlineBoardFilter: FC = () => {
|
||||
|
||||
// Flight number fields
|
||||
const [flightNumber, setFlightNumber] = useState("");
|
||||
const [flightDate, setFlightDate] = useState<Date>(new Date());
|
||||
const [flightDate, setFlightDate] = useState<Date | null>(null);
|
||||
const [flightNumberError, setFlightNumberError] = useState<string | null>(null);
|
||||
|
||||
// Route fields
|
||||
const [routeDeparture, setRouteDeparture] = useState<CitySuggestion | string>("");
|
||||
const [routeArrival, setRouteArrival] = useState<CitySuggestion | string>("");
|
||||
const [routeDate, setRouteDate] = useState<Date>(new Date());
|
||||
const [timeFrom, setTimeFrom] = useState("");
|
||||
const [timeTo, setTimeTo] = useState("");
|
||||
const [routeDate, setRouteDate] = useState<Date | null>(null);
|
||||
const [timeRange, setTimeRange] = useState<[number, number]>([0, 1440]);
|
||||
|
||||
// City autocomplete search
|
||||
const { suggestions: routeDepSuggestions, search: searchRouteDep } = useCitySearch();
|
||||
@@ -192,8 +198,9 @@ export const OnlineBoardFilter: FC = () => {
|
||||
</label>
|
||||
<Calendar
|
||||
value={flightDate}
|
||||
onChange={(e) => setFlightDate(e.value as Date)}
|
||||
onChange={(e) => setFlightDate(e.value as Date | null)}
|
||||
dateFormat="dd.mm.yy"
|
||||
placeholder={t("SHARED.DATE_FORMAT")}
|
||||
showIcon
|
||||
className="input--filter"
|
||||
data-testid="date-input"
|
||||
@@ -293,8 +300,9 @@ export const OnlineBoardFilter: FC = () => {
|
||||
</label>
|
||||
<Calendar
|
||||
value={routeDate}
|
||||
onChange={(e) => setRouteDate(e.value as Date)}
|
||||
onChange={(e) => setRouteDate(e.value as Date | null)}
|
||||
dateFormat="dd.mm.yy"
|
||||
placeholder={t("SHARED.DATE_FORMAT")}
|
||||
showIcon
|
||||
className="input--filter"
|
||||
data-testid="date-input"
|
||||
@@ -303,25 +311,21 @@ export const OnlineBoardFilter: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="time-selector" data-testid="time-selector">
|
||||
<label className="label--filter">{t("SHARED.FLIGHT_TIME")}</label>
|
||||
<div className="time-selector__inputs">
|
||||
<input
|
||||
type="time"
|
||||
className="input--filter input--time"
|
||||
value={timeFrom}
|
||||
onChange={(e) => setTimeFrom(e.target.value)}
|
||||
data-testid="time-from-input"
|
||||
/>
|
||||
<span className="time-selector__separator">—</span>
|
||||
<input
|
||||
type="time"
|
||||
className="input--filter input--time"
|
||||
value={timeTo}
|
||||
onChange={(e) => setTimeTo(e.target.value)}
|
||||
data-testid="time-to-input"
|
||||
<div className="wrapper--time-selector full-view" data-testid="time-selector">
|
||||
<div className="time-selector__label">{t("SHARED.FLIGHT_TIME")}</div>
|
||||
<div className="time-selector">
|
||||
<Slider
|
||||
value={timeRange}
|
||||
onChange={(e: SliderChangeEvent) => setTimeRange(e.value as [number, number])}
|
||||
range
|
||||
min={0}
|
||||
max={1440}
|
||||
step={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="time-selector__value">
|
||||
{minutesToTime(timeRange[0])} — {minutesToTime(timeRange[1])}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="filter-button">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { type FC, useState, useCallback, type FormEvent } from "react";
|
||||
import { useNavigate, useParams } from "@modern-js/runtime/router";
|
||||
import { Calendar } from "primereact/calendar";
|
||||
import { Slider, type SliderChangeEvent } from "primereact/slider";
|
||||
import { AutoComplete, type AutoCompleteCompleteEvent } from "primereact/autocomplete";
|
||||
import { useTranslation } from "@/i18n/provider.js";
|
||||
import { useCitySearch, type CitySuggestion } from "@/shared/hooks/useCitySearch.js";
|
||||
@@ -21,6 +22,12 @@ import type { PopularRequest } from "@/features/popular-requests/types.js";
|
||||
import { buildScheduleUrl } from "../url.js";
|
||||
import "./ScheduleStartPage.scss";
|
||||
|
||||
function minutesToTime(minutes: number): string {
|
||||
const h = Math.floor(minutes / 60);
|
||||
const m = minutes % 60;
|
||||
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function dateToYyyymmdd(value: Date): string {
|
||||
const y = value.getFullYear().toString();
|
||||
const m = (value.getMonth() + 1).toString().padStart(2, "0");
|
||||
@@ -44,15 +51,14 @@ export const ScheduleStartPage: FC = () => {
|
||||
|
||||
const [departureAirport, setDepartureAirport] = useState<CitySuggestion | string>("");
|
||||
const [arrivalAirport, setArrivalAirport] = useState<CitySuggestion | string>("");
|
||||
const [dateFrom, setDateFrom] = useState<Date>(today);
|
||||
const [dateTo, setDateTo] = useState<Date>(addDays(today, 7));
|
||||
const [timeFrom, setTimeFrom] = useState("");
|
||||
const [timeTo, setTimeTo] = useState("");
|
||||
const [dateFrom, setDateFrom] = useState<Date | null>(null);
|
||||
const [dateTo, setDateTo] = useState<Date | null>(null);
|
||||
const [timeRange, setTimeRange] = useState<[number, number]>([0, 1440]);
|
||||
const [directOnly, setDirectOnly] = useState(false);
|
||||
const [isRoundTrip, setIsRoundTrip] = useState(false);
|
||||
const [returnDateFrom, setReturnDateFrom] = useState<Date>(addDays(today, 7));
|
||||
const [returnDateTo, setReturnDateTo] = useState<Date>(addDays(today, 14));
|
||||
const [returnTimeFrom, setReturnTimeFrom] = useState("");
|
||||
const [returnTimeTo, setReturnTimeTo] = useState("");
|
||||
const [returnDateFrom, setReturnDateFrom] = useState<Date | null>(null);
|
||||
const [returnDateTo, setReturnDateTo] = useState<Date | null>(null);
|
||||
const [returnTimeRange, setReturnTimeRange] = useState<[number, number]>([0, 1440]);
|
||||
|
||||
// City autocomplete search
|
||||
const { suggestions: departureSuggestions, search: searchDeparture } = useCitySearch();
|
||||
@@ -87,8 +93,8 @@ export const ScheduleStartPage: FC = () => {
|
||||
const outbound: { departure: string; arrival: string; dateFrom: string; dateTo: string; timeFrom?: string; timeTo?: string } = {
|
||||
departure: dep, arrival: arr, dateFrom: dateFromParam, dateTo: dateToParam,
|
||||
};
|
||||
if (timeFrom) outbound.timeFrom = timeFrom;
|
||||
if (timeTo) outbound.timeTo = timeTo;
|
||||
if (timeRange[0] > 0) outbound.timeFrom = minutesToTime(timeRange[0]).replace(":", "");
|
||||
if (timeRange[1] < 1440) outbound.timeTo = minutesToTime(timeRange[1]).replace(":", "");
|
||||
|
||||
if (isRoundTrip) {
|
||||
if (!returnDateFrom || !returnDateTo) return;
|
||||
@@ -98,8 +104,8 @@ export const ScheduleStartPage: FC = () => {
|
||||
const inbound: { departure: string; arrival: string; dateFrom: string; dateTo: string; timeFrom?: string; timeTo?: string } = {
|
||||
departure: arr, arrival: dep, dateFrom: retDateFromParam, dateTo: retDateToParam,
|
||||
};
|
||||
if (returnTimeFrom) inbound.timeFrom = returnTimeFrom;
|
||||
if (returnTimeTo) inbound.timeTo = returnTimeTo;
|
||||
if (returnTimeRange[0] > 0) inbound.timeFrom = minutesToTime(returnTimeRange[0]).replace(":", "");
|
||||
if (returnTimeRange[1] < 1440) inbound.timeTo = minutesToTime(returnTimeRange[1]).replace(":", "");
|
||||
|
||||
url = buildScheduleUrl({
|
||||
type: "roundtrip",
|
||||
@@ -115,7 +121,7 @@ export const ScheduleStartPage: FC = () => {
|
||||
|
||||
void navigate(`/${lang}/${url}`);
|
||||
},
|
||||
[departureAirport, arrivalAirport, dateFrom, dateTo, timeFrom, timeTo, isRoundTrip, returnDateFrom, returnDateTo, returnTimeFrom, returnTimeTo, navigate, lang],
|
||||
[departureAirport, arrivalAirport, dateFrom, dateTo, timeRange, directOnly, isRoundTrip, returnDateFrom, returnDateTo, returnTimeRange, navigate, lang],
|
||||
);
|
||||
|
||||
const handlePopularRequestClick = useCallback((_request: PopularRequest) => {
|
||||
@@ -164,8 +170,9 @@ export const ScheduleStartPage: FC = () => {
|
||||
<label htmlFor="schedule-date-from">{t("SHARED.DEPARTURE_DATE")}</label>
|
||||
<Calendar
|
||||
value={dateFrom}
|
||||
onChange={(e) => setDateFrom(e.value as Date)}
|
||||
onChange={(e) => setDateFrom(e.value as Date | null)}
|
||||
dateFormat="dd.mm.yy"
|
||||
placeholder={t("SHARED.DATE_FORMAT")}
|
||||
showIcon
|
||||
className="input--filter"
|
||||
inputId="schedule-date-from"
|
||||
@@ -177,8 +184,9 @@ export const ScheduleStartPage: FC = () => {
|
||||
<label htmlFor="schedule-date-to">{t("SHARED.ARRIVAL_DATE")}</label>
|
||||
<Calendar
|
||||
value={dateTo}
|
||||
onChange={(e) => setDateTo(e.value as Date)}
|
||||
onChange={(e) => setDateTo(e.value as Date | null)}
|
||||
dateFormat="dd.mm.yy"
|
||||
placeholder={t("SHARED.DATE_FORMAT")}
|
||||
showIcon
|
||||
className="input--filter"
|
||||
inputId="schedule-date-to"
|
||||
@@ -186,27 +194,33 @@ export const ScheduleStartPage: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="schedule-start__field schedule-start__time-row">
|
||||
<label htmlFor="schedule-time-from">{t("SHARED.DEPARTURE_TIME")}</label>
|
||||
<div className="schedule-start__time-inputs">
|
||||
<input
|
||||
type="time"
|
||||
id="schedule-time-from"
|
||||
value={timeFrom}
|
||||
onChange={(e) => setTimeFrom(e.target.value)}
|
||||
className="input--filter"
|
||||
data-testid="time-from-input"
|
||||
/>
|
||||
<span className="schedule-start__time-sep">—</span>
|
||||
<input
|
||||
type="time"
|
||||
id="schedule-time-to"
|
||||
value={timeTo}
|
||||
onChange={(e) => setTimeTo(e.target.value)}
|
||||
className="input--filter"
|
||||
data-testid="time-to-input"
|
||||
<div className="wrapper--time-selector full-view" data-testid="time-selector">
|
||||
<div className="time-selector__label">{t("SHARED.DEPARTURE_TIME")}</div>
|
||||
<div className="time-selector">
|
||||
<Slider
|
||||
value={timeRange}
|
||||
onChange={(e: SliderChangeEvent) => setTimeRange(e.value as [number, number])}
|
||||
range
|
||||
min={0}
|
||||
max={1440}
|
||||
step={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="time-selector__value">
|
||||
{minutesToTime(timeRange[0])} — {minutesToTime(timeRange[1])}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="schedule-start__field">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={directOnly}
|
||||
onChange={(e) => setDirectOnly(e.target.checked)}
|
||||
data-testid="direct-only-toggle"
|
||||
/>
|
||||
{t("SHARED.DIRECT_FLIGHT_ONLY")}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="schedule-start__field">
|
||||
@@ -249,27 +263,21 @@ export const ScheduleStartPage: FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="schedule-start__field schedule-start__time-row">
|
||||
<label htmlFor="schedule-return-time-from">{t("SHARED.RETURN_FLIGHT_TIME")}</label>
|
||||
<div className="schedule-start__time-inputs">
|
||||
<input
|
||||
type="time"
|
||||
id="schedule-return-time-from"
|
||||
value={returnTimeFrom}
|
||||
onChange={(e) => setReturnTimeFrom(e.target.value)}
|
||||
className="input--filter"
|
||||
data-testid="return-time-from-input"
|
||||
/>
|
||||
<span className="schedule-start__time-sep">—</span>
|
||||
<input
|
||||
type="time"
|
||||
id="schedule-return-time-to"
|
||||
value={returnTimeTo}
|
||||
onChange={(e) => setReturnTimeTo(e.target.value)}
|
||||
className="input--filter"
|
||||
data-testid="return-time-to-input"
|
||||
<div className="wrapper--time-selector full-view" data-testid="return-time-selector">
|
||||
<div className="time-selector__label">{t("SHARED.RETURN_FLIGHT_TIME")}</div>
|
||||
<div className="time-selector">
|
||||
<Slider
|
||||
value={returnTimeRange}
|
||||
onChange={(e: SliderChangeEvent) => setReturnTimeRange(e.value as [number, number])}
|
||||
range
|
||||
min={0}
|
||||
max={1440}
|
||||
step={60}
|
||||
/>
|
||||
</div>
|
||||
<div className="time-selector__value">
|
||||
{minutesToTime(returnTimeRange[0])} — {minutesToTime(returnTimeRange[1])}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
}
|
||||
|
||||
&__description {
|
||||
max-width: 480px;
|
||||
color: colors.$light-gray;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -175,12 +174,13 @@
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
background-color: colors.$blue-dark;
|
||||
color: colors.$white;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: colors.$blue-light;
|
||||
border: 1.3px solid colors.$blue-light;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
border-color: colors.$blue;
|
||||
color: colors.$blue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user