Auto-expand today's day group + dynamic dates in visual diff

Schedule list day accordion stays collapsed by default but auto-
opens the day matching today's date when it's in the visible week
window — mirrors Angular's p-accordion default-active behaviour
where today's flights are visible without a click. The user can
still collapse it; we never re-open after that for the same date.

Visual-diff URLs were hardcoded to a past date with the wrong React
URL format (Angular path-style /AAQ/16042026 instead of React's
single-segment /AAQ-20260420-00002400). Switch to dynamic
yyyyMMdd of today for onlineboard pages and Mon→Sun of the current
week for schedule. Schedule-route diff dropped from ~91% to ~28%
on desktop after these two fixes.
This commit is contained in:
2026-04-20 01:17:58 +03:00
parent 6a3edeb0e7
commit f1f0030b69
2 changed files with 68 additions and 16 deletions
@@ -10,7 +10,7 @@
* (e.g. single-day search) — no header noise.
*/
import { type FC, useCallback, useMemo, useState } from "react";
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
import { useTranslation } from "@/i18n/provider.js";
import { FlightList } from "@/ui/flights/FlightList.js";
import { FlightListSkeleton } from "@/ui/flights/FlightListSkeleton.js";
@@ -121,15 +121,33 @@ export const DayGroupedFlightList: FC<DayGroupedFlightListProps> = ({
const { language } = useLocale();
const { t } = useTranslation();
const [sortMode, setSortMode] = useState<SortMode>("none");
// Track which days the user has expanded. Default: empty — matches
// Angular's `p-accordion` collapsed-on-load behaviour, where the
// user clicks a day header to reveal its flights.
// Track which days the user has expanded. Default: today's day group
// (if it's in scope). Angular's `p-accordion` is `[multiple]="true"`
// and `[activeIndex]` defaults to the index of today's date when
// present; only the user's clicks deviate after that.
const [expandedDays, setExpandedDays] = useState<Set<string>>(() => new Set());
const groups = useMemo(() => {
const g = groupFlightsByDay(flights);
return g.map((day) => ({ ...day, flights: sortFlights(day.flights, sortMode) }));
}, [flights, sortMode]);
// Auto-open today's day group on first render (and when the visible
// window shifts). The user can collapse it; we never re-open after
// that for the same date.
const [autoOpenedFor, setAutoOpenedFor] = useState<string | null>(null);
useEffect(() => {
const now = new Date();
const todayIso = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
const todayInScope = groups.some((g) => g.date === todayIso);
if (!todayInScope || autoOpenedFor === todayIso) return;
setExpandedDays((prev) => {
const next = new Set(prev);
next.add(todayIso);
return next;
});
setAutoOpenedFor(todayIso);
}, [groups, autoOpenedFor]);
const setSort = (mode: SortMode) => {
setSortMode((cur) => (cur === mode ? "none" : mode));
};
+46 -12
View File
@@ -86,6 +86,40 @@ interface RouteEntry {
maskSelectors?: string[];
}
/**
* URL conventions
*
* Angular and React use *different* path separators in their search URLs:
* Angular: `/onlineboard/departure/AAQ/16042026-0000-2400` (slash between
* station and date, hyphen between dateParts and time range)
* React: `/onlineboard/departure/AAQ-16042026-00002400` (one
* single-segment param: STATION-yyyyMMdd-HHHHHHHH)
*
* Schedule URLs share the same `MOW-KUF-yyyyMMdd-yyyyMMdd` shape on both.
*
* Dates default to today (yyyyMMdd) and the current Monday→Sunday week,
* so the diff stays valid across runs and the API actually returns data.
* Override via env: TODAY=20260420 / SCHEDULE_FROM=20260420 / SCHEDULE_TO=20260426.
*/
function ymd(d: Date): string {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}${m}${day}`;
}
const TODAY_YMD = process.env.TODAY ?? ymd(new Date());
const _today = new Date(
Number(TODAY_YMD.slice(0, 4)),
Number(TODAY_YMD.slice(4, 6)) - 1,
Number(TODAY_YMD.slice(6, 8)),
);
const _monday = new Date(_today);
_monday.setDate(_today.getDate() - ((_today.getDay() + 6) % 7));
const _sunday = new Date(_monday);
_sunday.setDate(_monday.getDate() + 6);
const SCHEDULE_FROM = process.env.SCHEDULE_FROM ?? ymd(_monday);
const SCHEDULE_TO = process.env.SCHEDULE_TO ?? ymd(_sunday);
const ROUTES: RouteEntry[] = [
{
name: "onlineboard-start",
@@ -94,32 +128,32 @@ const ROUTES: RouteEntry[] = [
},
{
name: "onlineboard-departure",
angular: "/onlineboard/departure/AAQ/16042026-0000-2400",
react: "/ru/onlineboard/departure/AAQ/16042026-0000-2400",
angular: `/onlineboard/departure/AAQ/${TODAY_YMD}-0000-2400`,
react: `/ru/onlineboard/departure/AAQ-${TODAY_YMD}-00002400`,
waitMs: 3000,
},
{
name: "onlineboard-arrival",
angular: "/onlineboard/arrival/AAQ/16042026-0000-2400",
react: "/ru/onlineboard/arrival/AAQ/16042026-0000-2400",
angular: `/onlineboard/arrival/AAQ/${TODAY_YMD}-0000-2400`,
react: `/ru/onlineboard/arrival/AAQ-${TODAY_YMD}-00002400`,
waitMs: 3000,
},
{
name: "onlineboard-route",
angular: "/onlineboard/route/MOW-AER/16042026-0000-2400",
react: "/ru/onlineboard/route/MOW-AER/16042026-0000-2400",
angular: `/onlineboard/route/MOW-AER/${TODAY_YMD}-0000-2400`,
react: `/ru/onlineboard/route/MOW-AER-${TODAY_YMD}-00002400`,
waitMs: 3000,
},
{
name: "onlineboard-flight",
angular: "/onlineboard/flight/SU0022/16042026",
react: "/ru/onlineboard/flight/SU0022/16042026",
angular: `/onlineboard/flight/SU0022/${TODAY_YMD}`,
react: `/ru/onlineboard/flight/SU0022-${TODAY_YMD}`,
waitMs: 3000,
},
{
name: "onlineboard-details",
angular: "/onlineboard/SU0022-16042026",
react: "/ru/onlineboard/SU0022-16042026",
angular: `/onlineboard/SU0022-${TODAY_YMD}`,
react: `/ru/onlineboard/SU0022-${TODAY_YMD}`,
waitMs: 3000,
},
{
@@ -129,8 +163,8 @@ const ROUTES: RouteEntry[] = [
},
{
name: "schedule-route",
angular: "/schedule/route/MOW-KUF-20260416-20260423",
react: "/ru/schedule/route/MOW-KUF-20260416-20260423",
angular: `/schedule/route/MOW-KUF-${SCHEDULE_FROM}-${SCHEDULE_TO}`,
react: `/ru/schedule/route/MOW-KUF-${SCHEDULE_FROM}-${SCHEDULE_TO}`,
waitMs: 3000,
},
{