This commit is contained in:
@@ -102,6 +102,10 @@ vi.mock("@/shared/dictionaries/index.js", () => ({
|
||||
getCityCodeByAirportCode: () => undefined,
|
||||
}));
|
||||
|
||||
vi.mock("../hooks/useScheduleCalendar.js", () => ({
|
||||
useScheduleCalendar: () => ({ days: [], loading: false, loaded: false }),
|
||||
}));
|
||||
|
||||
let geoMockEnabled = false;
|
||||
|
||||
vi.mock("@/shared/hooks/useGeoCityDefault.js", () => ({
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/**
|
||||
* Schedule start page -- search form for route-based schedule search.
|
||||
*
|
||||
* No API calls on load. Pure form that navigates to the appropriate
|
||||
* search route on submit.
|
||||
* No schedule-search API calls on load. Once both cities are selected,
|
||||
* fetches route operating days so unavailable dates are greyed out before
|
||||
* submit, matching Angular's schedule-filter.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { type FC, useState, useCallback, useEffect, useRef, type FormEvent } from "react";
|
||||
import { type FC, useState, useCallback, useEffect, useMemo, useRef, type FormEvent } from "react";
|
||||
import { useNavigate } from "@modern-js/runtime/router";
|
||||
import { useLocale } from "@/i18n/useLocale.js";
|
||||
import { Calendar } from "primereact/calendar";
|
||||
@@ -36,6 +37,8 @@ import { useGeoCityDefault } from "@/shared/hooks/useGeoCityDefault.js";
|
||||
import { buildScheduleUrl } from "../url.js";
|
||||
import { scheduleWindowBounds } from "@/shared/dateWindow.js";
|
||||
import { formatScheduleDateRangeWithCurrentWeek } from "../dateLabels.js";
|
||||
import { useScheduleCalendar } from "../hooks/useScheduleCalendar.js";
|
||||
import type { IScheduleCalendarParams } from "../types.js";
|
||||
import "./ScheduleStartPage.scss";
|
||||
|
||||
function toCityCode(code: string, dictionaries: IDictionaries | null): string {
|
||||
@@ -56,6 +59,33 @@ function dateToYyyymmdd(value: Date): string {
|
||||
return `${y}${m}${d}`;
|
||||
}
|
||||
|
||||
function dateToIsoYmd(value: Date): string {
|
||||
const y = value.getFullYear().toString();
|
||||
const m = (value.getMonth() + 1).toString().padStart(2, "0");
|
||||
const d = value.getDate().toString().padStart(2, "0");
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
function computeDisabledDates(
|
||||
availableYmd: string[],
|
||||
minDate: Date,
|
||||
maxDate: Date,
|
||||
): Date[] {
|
||||
const available = new Set(availableYmd);
|
||||
const disabled: Date[] = [];
|
||||
const cursor = new Date(minDate);
|
||||
cursor.setHours(0, 0, 0, 0);
|
||||
|
||||
while (cursor.getTime() <= maxDate.getTime()) {
|
||||
if (!available.has(dateToIsoYmd(cursor))) {
|
||||
disabled.push(new Date(cursor));
|
||||
}
|
||||
cursor.setDate(cursor.getDate() + 1);
|
||||
}
|
||||
|
||||
return disabled;
|
||||
}
|
||||
|
||||
function addDays(base: Date, days: number): Date {
|
||||
const result = new Date(base);
|
||||
result.setDate(result.getDate() + days);
|
||||
@@ -245,6 +275,59 @@ export const ScheduleStartPage: FC = () => {
|
||||
|
||||
const scheduleMinDate = useRef(getScheduleMinDate()).current;
|
||||
const scheduleMaxDate = useRef(getScheduleMaxDate()).current;
|
||||
const scheduleCalendarBaseDate = useMemo(
|
||||
() => dateToIsoYmd(scheduleMinDate),
|
||||
[scheduleMinDate],
|
||||
);
|
||||
|
||||
const outboundCalendarParams = useMemo<IScheduleCalendarParams | null>(() => {
|
||||
const dep = toCityCode(departureCode.trim().toUpperCase(), dictionaries);
|
||||
const arr = toCityCode(arrivalCode.trim().toUpperCase(), dictionaries);
|
||||
if (!dep || !arr || dep === arr) return null;
|
||||
return {
|
||||
date: scheduleCalendarBaseDate,
|
||||
departure: dep,
|
||||
arrival: arr,
|
||||
connections: !directOnly,
|
||||
};
|
||||
}, [departureCode, arrivalCode, dictionaries, directOnly, scheduleCalendarBaseDate]);
|
||||
|
||||
const returnCalendarParams = useMemo<IScheduleCalendarParams | null>(() => {
|
||||
if (!isRoundTrip) return null;
|
||||
const dep = toCityCode(departureCode.trim().toUpperCase(), dictionaries);
|
||||
const arr = toCityCode(arrivalCode.trim().toUpperCase(), dictionaries);
|
||||
if (!dep || !arr || dep === arr) return null;
|
||||
return {
|
||||
date: scheduleCalendarBaseDate,
|
||||
departure: arr,
|
||||
arrival: dep,
|
||||
connections: !directOnly,
|
||||
};
|
||||
}, [departureCode, arrivalCode, dictionaries, directOnly, isRoundTrip, scheduleCalendarBaseDate]);
|
||||
|
||||
const {
|
||||
days: outboundAvailableDays,
|
||||
loaded: outboundCalendarLoaded,
|
||||
} = useScheduleCalendar(outboundCalendarParams);
|
||||
const {
|
||||
days: returnAvailableDays,
|
||||
loaded: returnCalendarLoaded,
|
||||
} = useScheduleCalendar(returnCalendarParams);
|
||||
|
||||
const outboundDisabledDates = useMemo(
|
||||
() =>
|
||||
!outboundCalendarLoaded
|
||||
? []
|
||||
: computeDisabledDates(outboundAvailableDays, scheduleMinDate, scheduleMaxDate),
|
||||
[outboundAvailableDays, outboundCalendarLoaded, scheduleMinDate, scheduleMaxDate],
|
||||
);
|
||||
const returnDisabledDates = useMemo(
|
||||
() =>
|
||||
!returnCalendarLoaded
|
||||
? []
|
||||
: computeDisabledDates(returnAvailableDays, scheduleMinDate, scheduleMaxDate),
|
||||
[returnAvailableDays, returnCalendarLoaded, scheduleMinDate, scheduleMaxDate],
|
||||
);
|
||||
|
||||
// TZ §4.1.9 Table 14 / Angular CalendarInputWeekComponent: when the
|
||||
// selected date range contains today, the Calendar input shows
|
||||
@@ -486,6 +569,7 @@ export const ScheduleStartPage: FC = () => {
|
||||
selectionMode="range"
|
||||
minDate={scheduleMinDate}
|
||||
maxDate={scheduleMaxDate}
|
||||
disabledDates={outboundDisabledDates}
|
||||
selectOtherMonths
|
||||
dateFormat="dd.mm.yy"
|
||||
placeholder={`${t("SHARED.DATE_FORMAT")} - ${t("SHARED.DATE_FORMAT")}`}
|
||||
@@ -552,6 +636,7 @@ export const ScheduleStartPage: FC = () => {
|
||||
selectionMode="range"
|
||||
minDate={scheduleMinDate}
|
||||
maxDate={scheduleMaxDate}
|
||||
disabledDates={returnDisabledDates}
|
||||
selectOtherMonths
|
||||
dateFormat="dd.mm.yy"
|
||||
placeholder={`${t("SHARED.DATE_FORMAT")} - ${t("SHARED.DATE_FORMAT")}`}
|
||||
|
||||
Reference in New Issue
Block a user