Fix flights map calendar lower bound
This commit is contained in:
@@ -40,6 +40,25 @@ describe("getMinDate / getMaxDate", () => {
|
|||||||
const max = getMaxDate();
|
const max = getMaxDate();
|
||||||
expect(max.getTime()).toBe(expected.getTime());
|
expect(max.getTime()).toBe(expected.getTime());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("derives date-only bounds from the provided yyyyMMdd anchor", () => {
|
||||||
|
const min = getMinDate("20260506");
|
||||||
|
const max = getMaxDate("20260506");
|
||||||
|
|
||||||
|
expect(min.toISOString()).toBe(new Date(2026, 4, 5).toISOString());
|
||||||
|
expect(max.toISOString()).toBe(new Date(2026, 10, 6).toISOString());
|
||||||
|
expect(min.getHours()).toBe(0);
|
||||||
|
expect(max.getHours()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to the runtime clock when the provided anchor is invalid", () => {
|
||||||
|
const min = getMinDate("20269999");
|
||||||
|
const expected = addDays(new Date(), -1);
|
||||||
|
|
||||||
|
expect(min.getFullYear()).toBe(expected.getFullYear());
|
||||||
|
expect(min.getMonth()).toBe(expected.getMonth());
|
||||||
|
expect(min.getDate()).toBe(expected.getDate());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildDisabledDates", () => {
|
describe("buildDisabledDates", () => {
|
||||||
|
|||||||
@@ -12,19 +12,33 @@
|
|||||||
import { mapWindowBounds } from "@/shared/dateWindow.js";
|
import { mapWindowBounds } from "@/shared/dateWindow.js";
|
||||||
|
|
||||||
/** Today with time set to 00:00:00 local. */
|
/** Today with time set to 00:00:00 local. */
|
||||||
export function today(): Date {
|
export function today(baseYyyymmdd?: string): Date {
|
||||||
|
if (baseYyyymmdd) {
|
||||||
|
const parsed = parseYyyymmdd(baseYyyymmdd);
|
||||||
|
if (parsed) return parsed;
|
||||||
|
}
|
||||||
const d = new Date();
|
const d = new Date();
|
||||||
d.setHours(0, 0, 0, 0);
|
d.setHours(0, 0, 0, 0);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** minDate = today - 1 day (Angular parity). */
|
/** minDate = today - 1 day (Angular parity). */
|
||||||
export function getMinDate(): Date {
|
export function getMinDate(baseYyyymmdd?: string): Date {
|
||||||
|
if (baseYyyymmdd) {
|
||||||
|
const d = today(baseYyyymmdd);
|
||||||
|
d.setDate(d.getDate() - 1);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
return mapWindowBounds()[0];
|
return mapWindowBounds()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** maxDate = today + 6 months (Angular parity). */
|
/** maxDate = today + 6 months (Angular parity). */
|
||||||
export function getMaxDate(): Date {
|
export function getMaxDate(baseYyyymmdd?: string): Date {
|
||||||
|
if (baseYyyymmdd) {
|
||||||
|
const d = today(baseYyyymmdd);
|
||||||
|
d.setMonth(d.getMonth() + 6);
|
||||||
|
return d;
|
||||||
|
}
|
||||||
return mapWindowBounds()[1];
|
return mapWindowBounds()[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,3 +106,20 @@ function toYyyymmdd(d: Date): string {
|
|||||||
const day = d.getDate().toString().padStart(2, "0");
|
const day = d.getDate().toString().padStart(2, "0");
|
||||||
return `${y}${m}${day}`;
|
return `${y}${m}${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseYyyymmdd(value: string): Date | null {
|
||||||
|
if (!/^\d{8}$/.test(value)) return null;
|
||||||
|
const y = Number(value.slice(0, 4));
|
||||||
|
const m = Number(value.slice(4, 6));
|
||||||
|
const d = Number(value.slice(6, 8));
|
||||||
|
const parsed = new Date(y, m - 1, d);
|
||||||
|
parsed.setHours(0, 0, 0, 0);
|
||||||
|
if (
|
||||||
|
parsed.getFullYear() !== y ||
|
||||||
|
parsed.getMonth() !== m - 1 ||
|
||||||
|
parsed.getDate() !== d
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,6 +92,25 @@ describe("FlightsMapFilter — Calendar wiring", () => {
|
|||||||
expect(max.getTime()).toBe(expectedMax.getTime());
|
expect(max.getTime()).toBe(expectedMax.getTime());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses the provided SSR today anchor for date-only calendar bounds", () => {
|
||||||
|
const onChange = vi.fn();
|
||||||
|
render(
|
||||||
|
<FlightsMapFilter
|
||||||
|
value={filter()}
|
||||||
|
today="20260506"
|
||||||
|
onChange={onChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const min = lastCalendarProps!["minDate"] as Date;
|
||||||
|
const max = lastCalendarProps!["maxDate"] as Date;
|
||||||
|
|
||||||
|
expect(min.toISOString()).toBe(new Date(2026, 4, 5).toISOString());
|
||||||
|
expect(max.toISOString()).toBe(new Date(2026, 10, 6).toISOString());
|
||||||
|
expect(min.getHours()).toBe(0);
|
||||||
|
expect(min.getMinutes()).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
it("disables every date when availableDays is empty", () => {
|
it("disables every date when availableDays is empty", () => {
|
||||||
const onChange = vi.fn();
|
const onChange = vi.fn();
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import type { IFlightsMapFilterState } from "../types.js";
|
|||||||
export interface FlightsMapFilterProps {
|
export interface FlightsMapFilterProps {
|
||||||
value: IFlightsMapFilterState;
|
value: IFlightsMapFilterState;
|
||||||
availableDays?: string[];
|
availableDays?: string[];
|
||||||
|
today?: string;
|
||||||
onChange: (state: IFlightsMapFilterState) => void;
|
onChange: (state: IFlightsMapFilterState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ function dateToYyyymmdd(value: Date): string {
|
|||||||
export const FlightsMapFilter: FC<FlightsMapFilterProps> = ({
|
export const FlightsMapFilter: FC<FlightsMapFilterProps> = ({
|
||||||
value,
|
value,
|
||||||
availableDays,
|
availableDays,
|
||||||
|
today: todayYmd,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -76,8 +78,8 @@ export const FlightsMapFilter: FC<FlightsMapFilterProps> = ({
|
|||||||
);
|
);
|
||||||
}, [dictionaries, onChange, value]);
|
}, [dictionaries, onChange, value]);
|
||||||
|
|
||||||
const minDate = useMemo(() => getMinDate(), []);
|
const minDate = useMemo(() => getMinDate(todayYmd), [todayYmd]);
|
||||||
const maxDate = useMemo(() => getMaxDate(), []);
|
const maxDate = useMemo(() => getMaxDate(todayYmd), [todayYmd]);
|
||||||
|
|
||||||
const disabledDates = useMemo(
|
const disabledDates = useMemo(
|
||||||
() => buildDisabledDates(minDate, maxDate, availableDays ?? []),
|
() => buildDisabledDates(minDate, maxDate, availableDays ?? []),
|
||||||
|
|||||||
@@ -393,6 +393,7 @@ export const FlightsMapStartPage: FC<FlightsMapStartPageProps> = ({
|
|||||||
<FlightsMapFilter
|
<FlightsMapFilter
|
||||||
value={filterState}
|
value={filterState}
|
||||||
availableDays={availableDays}
|
availableDays={availableDays}
|
||||||
|
today={todayYmd}
|
||||||
onChange={handleFilterChange}
|
onChange={handleFilterChange}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user