formatTime runs new Date(iso).getHours() which reprojects the
timestamp through the browser's local timezone. For a flight arriving
at 06:30 in Almaty (GMT+5) a viewer in Moscow saw '04:30'. Switch the
TimeGroup component to formatLocalTime which reads the wall-clock
directly out of the offset-aware ISO string, matching the rest of the
details/timetable views.
Batch of fixes identified by the comparison audit:
Schedule search page (ScheduleSearchPage):
- Resolve IATA codes to city/airport names, so the H1 reads
'Маршрут: Шереметьево - Санкт-Петербург' instead of 'SVO - LED'.
- Breadcrumb trail now includes the human-friendly route as its
last entry.
Details page (OnlineBoardDetailsPage):
- Hide the 'Перелет N' leg header for single-leg flights (Angular
parity — that label is only meaningful for multi-leg routes).
- Translate the leg status through FLIGHT-STATUSES.* instead of
emitting the raw enum ('Cancelled' → 'Отменен', etc.).
- Humanize leg and total flying time through formatDuration so the
page reads '1ч 25м' rather than '01:25:00'.
Details meal panel (MealPanel):
- Use the same FOOD.* translation keys as Angular, so labels become
'Эконом класс / Комфорт класс / Бизнес класс / Специальное
питание'.
- Add the Special-meal icon + link (was stubbed out previously).
Accessibility:
- Route the English aria-labels through new SHARED.A11Y-* keys in
DayTabs pagination, FlightListSkeleton, ScrollUpButton and
PrintButton.
Breadcrumbs:
- Render the 'Главная' crumb as a link even when it's the only /
last item (it was dropping to plain text on start pages). Angular
always links it to aeroflot.ru.
Tests updated to assert the new translated labels and duration
formatting; 1258 tests passing.
- FlightList empty-state, Operated-by label, details/schedule error
and not-found messages now route through i18n instead of hardcoded
English. Added BOARD.FLIGHT-NOT-FOUND, BOARD.LOAD-FAILED,
BOARD.OPERATED-BY, SHARED.RETRY to all nine locales.
- FlightStatus label now picks up the same colour as the plane icon
(red for Cancelled, green for Arrived/Landed, blue for In Flight,
orange for Delayed) — matches Angular's flight-status text treatment
so 'Отменен' reads at a glance.
- Tests updated to expect the translation keys under the mocked `t`.
Search page:
- Title and breadcrumb now read the station dictionaries and render the
human-friendly route heading (e.g. 'Маршрут: Шереметьево - Пулково')
for route/departure/arrival/flight search URLs, mirroring Angular.
Details page:
- Main H1 becomes 'Информация о рейсе: SU 6805, Москва - Санкт-Петербург'
(carrier + flight number + origin/destination cities), not a bare
flight number.
- Add 'Детали рейса' section header above the accordion to match
Angular's flight-details-wrapper layout.
- Promote the airline block in BoardDetailsHeader: drop the legacy
OperatorLogo copy with broken asset paths and hand off to the shared
<OperatorLogo> under src/ui/flights. Render it with the
'авиакомпания' caption beside the enlarged flight number.
- Replace hardcoded English 'Leg' / 'Total flying time' / 'Aircraft:'
with i18n keys, added to all nine locale files.
Test harness:
- Add vi.mock for useDictionaries in the three suites that render
OnlineBoardSearchPage (the new heading helper calls the hook and
crashed without ApiClientProvider). 1256 tests passing.
Search row now shows the full Angular header layout: flight number,
operator logo, scheduled/actual departure time, departure city +
terminal, plane icon with status label, mirrored arrival block. The
city input in the filter sidebar now shows the city name
('Шереметьево') instead of the IATA code.
Details page: expand the first accordion panel by default (Angular
parity), hide Print/Share on the board details view, and rewrite the
Aircraft panel as a property table with total/economy/comfort/business
seat counts and the previous-flight identifier — all pulled from the
real API shape, which is `{ seats: [{type, count}] }` rather than the
legacy string config.
Supporting work:
- New <OperatorLogo> component with the full carrier → asset mapping
ported from ClientApp/src/styles (SU, FV, HZ, S7, …).
- Extend StationDisplay with a cityFirst variant for row usage.
- New FlightStatus icon-over-label layout, translated labels.
- Update IEquipmentFull types: configuration is an object with seats[],
plus scheduled/actual/previousFlight; new IOperatingBy union.
- Tests + fixtures updated for the new shapes; 1262 passing.
Both pages were rendering content directly on the dark-blue page
background, which made the flight list and details card effectively
invisible. Angular wraps the same content in a white card (section.frame)
with drop shadow.
Changes:
- Wrap FlightList in <section class='frame'> on the search page and wrap
the details body the same way.
- Replace the inline numbered .calendar-day strip on the search page
with the existing <DayTabs> component — the same one the details page
already uses (weekday + day + month labels, ‹/› paging).
- Pass onFlightClick through FlightList into FlightCard so whole rows
are keyboard-accessible buttons, matching Angular's row-level click.
The off-screen data-testid='flight-link-*' buttons stay for e2e.
- Fix 'Leg NaN' header + the React key warning in FlightLegs when
the API returns a Direct leg without an index field.
- Update the existing flight-search integration test to target the
DayTabs testid instead of the old ad-hoc calendar-strip one.
A docs/parity-report-2026-04-17.md file captures the diffs I applied
and a punch list of the remaining parity gaps (operator logo on rows,
delay/day-change glyphs, Share button visibility on board details, the
aircraft panel as a table). Those need per-component work against the
Angular templates and will follow in a separate pass.
Ported Angular SCSS for station, time-group, flight-status, duration,
flight-card, flight-list, and flight-list-skeleton to React equivalents.
Aligned class names in JSX with Angular BEM conventions and added SCSS
imports to all flight display components.
StationDisplay, TimeGroup, FlightStatus, DurationDisplay compose into
FlightCard; FlightList renders a list of cards with skeleton loading.
All components are props-driven with no data fetching.