From f1f0030b69c0989b22eb77ca1629f4672c494929 Mon Sep 17 00:00:00 2001 From: gnezim Date: Mon, 20 Apr 2026 01:17:58 +0300 Subject: [PATCH] Auto-expand today's day group + dynamic dates in visual diff MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../components/DayGroupedFlightList.tsx | 26 +++++++-- tests/parity/visual/screenshot-diff-multi.ts | 58 +++++++++++++++---- 2 files changed, 68 insertions(+), 16 deletions(-) diff --git a/src/features/schedule/components/DayGroupedFlightList.tsx b/src/features/schedule/components/DayGroupedFlightList.tsx index 58f5560f..34f4478e 100644 --- a/src/features/schedule/components/DayGroupedFlightList.tsx +++ b/src/features/schedule/components/DayGroupedFlightList.tsx @@ -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 = ({ const { language } = useLocale(); const { t } = useTranslation(); const [sortMode, setSortMode] = useState("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>(() => 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(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)); }; diff --git a/tests/parity/visual/screenshot-diff-multi.ts b/tests/parity/visual/screenshot-diff-multi.ts index b72eb262..713eb4ac 100644 --- a/tests/parity/visual/screenshot-diff-multi.ts +++ b/tests/parity/visual/screenshot-diff-multi.ts @@ -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, }, {