Commit Graph

415 Commits

Author SHA1 Message Date
gnezim ef85ae6ea1 Inject MAP_TILE_URL into window.__ENV__ via html.tags + Docker build-arg
CI / ci (push) Failing after 32s
Deploy / build-and-deploy (push) Failing after 6s
http://flights-ui.devwebzavod.ru/ru/flights-map was still hitting the
same-origin tile path after adding the k8s env: Modern.js renders the
<Suspense> fallback on the server (i18n isn't preloaded), so the route
component that reads getEnv() never actually runs during SSR. The page
hydrates client-side, where process.env is Rspack's empty stub and
MAP_TILE_URL is never set — getEnv() falls back to the default.

Move the value into window.__ENV__ instead:

- modern.config.ts: inline a <script> into html.tags that sets
  window.__ENV__ = { MAP_TILE_URL: <value> } at SSR-server startup.
  The snippet is authored once at server boot, so the HTML template
  baked into dist/standalone/html/main/index.html always carries the
  pod's tile URL.
- src/env/index.ts: merge window.__ENV__ on top of process.env so the
  browser prefers the injected value (process.env only has NODE_ENV
  after Rspack's polyfill).
- Dockerfile.react: accept MAP_TILE_URL as a build ARG and expose it
  as ENV before `pnpm build:standalone`, so Modern.js picks it up when
  building the HTML template. k8s env still flows into the Node SSR
  process; the build-arg path guarantees correctness even when the
  runtime env is stripped.
- deployment/build-docker.sh: forward MAP_TILE_URL through as a
  build-arg (default keeps the same-origin path). CI on the
  devwebzavod cluster can export MAP_TILE_URL=https://flights.test.aeroflot.ru/map/api/tile/{z}/{x}/{y}.jpeg
  before running build-docker.sh and the resulting image will serve
  tiles from the upstream the real Aeroflot ingress terminates.
2026-04-18 23:26:56 +03:00
gnezim 496a72e7d7 Track k8s manifest; teach make sync to mirror deployment/
CI / ci (push) Failing after 32s
Deploy / build-and-deploy (push) Failing after 5s
The flights-front deploy repo ships k8s manifests at deployment/k8s/,
a sibling of Aeroflot.Flights.Front/. Previously the sync script only
copied the app source, so any env change landed on the k8s side had
to be hand-edited in the deploy repo and was never reflected back.

- Bring deployment/k8s/flights-ui.yaml into this repo (with the new
  MAP_TILE_URL env pointing at flights.test.aeroflot.ru) so the
  cluster config lives next to the code that reads it.
- sync-to-flights-front.sh resolves the deploy-repo root from the
  target path and mirrors this repo's deployment/ directory there,
  mkdir'ing and copying contents without wiping unrelated files.
- Bump step numbering (1/6..6/6) and the summary now lists the synced
  deployment files in addition to the app files.
2026-04-18 22:51:26 +03:00
gnezim 6813bf902e Make Leaflet tile URL configurable via MAP_TILE_URL env
The flights-map tile URL was hardcoded as the same-origin path
'/map/api/tile/{z}/{x}/{y}.jpeg' (matching Angular's environment.ts).
On deployments where the ingress routes /map/api/** to the upstream
tile service (prod, flights.test.aeroflot.ru) this works. On
deployments without that rule (e.g. flights-ui.devwebzavod.ru) the
Modern.js SSR catch-all answers every tile URL with the SPA index
page, so Leaflet renders the marker + controls but never paints the
raster layer.

Expose the URL through MAP_TILE_URL env with the same-origin path as
the default, read it on the server route (where process.env is
available), and pass the resolved URL to FlightsMapStartPage as a
prop so the client bundle uses whatever the operator configured.
Prod and same-origin deployments stay unchanged; dev clusters can
point at an absolute URL like https://flights.test.aeroflot.ru/map/api/tile/...
instead.
2026-04-18 22:34:41 +03:00
gnezim 4aa0bbe5e6 Stack breadcrumb above page title (Angular parity)
Angular renders the breadcrumb trail on its own row above the H1 title.
React had them in the same flex row with justify-content:space-between,
which squeezed the breadcrumb column and forced 'Главная / Онлайн-Табло'
to wrap onto two lines. Switch the header-right container to column
layout so breadcrumbs and title stack vertically regardless of width.
2026-04-18 21:38:40 +03:00
gnezim b5759215b1 Replace PrimeReact Accordion on Расписание рейса with matching custom header
Angular keeps the 'Расписание рейса' collapse chevron on the right of the
header and styles the header like the Детали рейса row above it. React
was rendering the PrimeReact chevron on the LEFT with its own pill style.
Swap to the same lightweight accordion markup the details block uses so
both collapses look identical.
2026-04-18 21:32:42 +03:00
gnezim e014db17d0 Use '/' as breadcrumb separator (Angular parity) 2026-04-18 21:28:28 +03:00
gnezim 315385ccbd Breadcrumbs: use BOARD.TITLE, drop flight-number leaf
Angular's details breadcrumb trail is just 'Главная / Онлайн-Табло'
(BOARD.TITLE with capital Т) — the flight number itself is NOT a
breadcrumb entry. React was using the lowercase 'Онлайн-табло'
translation and appending 'SU 6272'. Align both the leaf text and the
list depth with Angular.
2026-04-18 21:24:27 +03:00
gnezim 7c0fb6a0d8 Format UTC offset as 'UTC +HH:MM' with non-breaking space (Angular parity)
Angular's captioned-time-group renders 'UTC&nbsp;{{ utc }}', producing
'UTC +03:00' on screen. React was emitting 'UTC+03:00' without the
separator, making the time details read slightly differently. Insert a
U+00A0 non-breaking space between 'UTC' and the signed offset so the
time-table values ('15:30 UTC +03:00 18.04.2026') line up with Angular.
2026-04-18 21:16:21 +03:00
gnezim f4b4c53816 Match Angular duration format + UTC offset + scheduled path time
- formatDuration(locale='ru') now emits 'Xч. Xмин.' (and 'Xд. Xч. Xмин.')
  with trailing dots, matching Angular's DurationPipe + SHARED.SHORT-HOUR
  translations. Every 'В пути', 'До прилета', and 'Время в пути' label on
  the details page now reads identically to Angular.
- FlightSchedule shows the SCHEDULED duration (dep→arr from the timestamps)
  instead of the actual flyingTime the API reports, so the Расписание рейса
  row reads '1ч. 30мин.' for a 15:30→17:00 schedule even after an early
  landing. The Вылет / Прилет columns also surface the 'UTC+HH:MM' offset
  below each time, matching the Angular layout.
2026-04-18 21:11:58 +03:00
gnezim b6920cbf60 Final details-page Angular parity: move time-note, horizontal Борт, 3-col schedule
- Relabel the meal row 'Питание на борту' (SHARED.FOOD) instead of the
  shorter 'Питание' (DETAILS.MEAL) Angular stopped using.
- Replace AircraftPanel's vertical label/value table with a horizontal
  strip of (Название | Количество мест | Эконом | Комфорт | Бизнес |
  Предыдущий рейс) cells to match flight-details-airplane layout.
- Render the '* Время в системе - МЕСТНОЕ.' note inline after the last
  visible transition row (Регистрация/Посадка/Высадка) inside the
  Детали рейса accordion, dropping the separate footer-notes block —
  Angular anchors the note exactly there.
- Rework FlightSchedule body into a 3-column grid (Вылет по расписанию |
  Прилет по расписанию | Время в пути) and humanize flyingTime '1:19' →
  '1ч 19м' so the value reads consistently with the rest of the page.
2026-04-18 19:54:11 +03:00
gnezim 87f38fec9e Skip useOnlineBoard fetch when dateFrom/dateTo are empty
Details page calls useOnlineBoard to populate the sibling mini-list,
passing empty-string params when the URL has no ?request=... context.
The empty params were reaching the backend as dateFrom=&dateTo=, which
returns HTTP 400 and surfaces as an error in the browser console.

Short-circuit the effect so we just emit an empty result when either
range bound is missing — same no-fetch behavior, no console noise.
2026-04-18 19:12:59 +03:00
gnezim d89e6449cc Hide standalone 'Общее время в пути' line; distinct accordion icons
- Drop the visible 'Общее время в пути: Xч Xм' row above the flight
  schedule block — Angular keeps the total duration inside the
  FlightSchedule accordion, not as a separate caption. Mark the
  existing div visually-hidden so testids keep resolving.
- Redraw the three transition icons so Регистрация looks like a person
  with an ID badge (Angular's #service), Посадка reads as an ascending
  escalator with a passenger (#board), and Высадка mirrors it going
  down (#deboard). The previous placeholders were too abstract to read
  at a glance.
2026-04-18 18:52:58 +03:00
gnezim 3bda018996 Surface dayChange offsets in accordion time columns
Angular stamps a small '-1' (or '+1') next to the time whenever the
transition start/end falls on a different calendar day than the leg
itself (e.g. registration opening the day before a 00:05 departure).
Read start.dayChange.value and end.dayChange.value on each transition
and render the offset as a superscript next to the time. Keeps the
blue accent color used elsewhere in the row for date lines.
2026-04-18 18:42:51 +03:00
gnezim ef171c5e18 Point plane marker right on progress bar (Angular parity) 2026-04-18 18:38:42 +03:00
gnezim 3838ab691c Right-align 'Прибыл' + place plane marker at end for finished legs
Angular anchors the status label ('Прибыл') above the right end of the
progress bar and parks the green plane icon at 100%. React was keeping
both centered even after the flight landed; move the plane marker to
the bar's end (100%) for finished as well as in-flight, and make the
status text flex-end so it lines up with the arrival column.
2026-04-18 18:35:27 +03:00
gnezim 512f22bf1b Align header actions/last-update right; hide duration on finished legs
- Restructure BoardDetailsHeader so the Share icon sits top-right next to
  the flight-number badge, and 'Последнее обновление' sits on its own row
  below, right-aligned, matching Angular's flight-details header layout.
- FlightEvents badges only render when changeRoute/reroute are actually
  set, avoiding an empty row on normal flights.
- Hide the leg.flyingTime under the route-status bar once the flight is
  Arrived/Landed/Cancelled — Angular leaves that slot blank in those
  states since the in-flight 'В пути / До прилета' split no longer
  applies.
2026-04-18 18:31:48 +03:00
gnezim 2d77e86c88 Match Angular details layout when no parent request is present
- Drop the duplicate FlightCard summary between the header and the route
  strip — Angular's details page shows the route strip directly under the
  board-details-header, with no 'SU 6272 Russia 15:30/15:22 ...' row.
- Keep the mini-list sidebar visible even when allFlights has only one
  entry; fall back to rendering the current flight as a single item,
  matching Angular's flights-details-list-flight behavior.
2026-04-18 16:41:35 +03:00
gnezim 583fe45c14 Match Angular details layout: flat accordion rows, progress labels, mini-list
- Accordion now renders flat rows (icon + caption/status on the left,
  Время начала / Время окончания columns on the right) under a single
  collapse toggle, matching Angular's flight-details-wrapper layout.
- Aircraft row moves the model title into the row subtitle and drops the
  duplicate 'Борт' property, so the row reads 'Борт / Sukhoi SuperJet 100'.
- Route strip grows a green in-flight state with a plane marker on the
  progress bar plus 'В пути Xч Xм' / 'До прилета Xч Xм' durations derived
  from actual-departure and scheduled-arrival.
- Mini-list sidebar now fetches sibling flights from the departure station
  parsed from the '?request=onlineboard-departure-LED-...' URL param, and
  the item layout gains city + airport labels with formatted time/date
  columns (replacing raw ISO timestamps and IATA codes).
- Tests and mocks updated: add useSearchParams / useOnlineBoard mocks,
  relocate aircraft-title assertions to the accordion level, and expect
  city names on mini-list items.
2026-04-18 16:26:39 +03:00
gnezim 54d1991a8f Add estimated-time note after leg route and accordion icons
- Move the '* Время прилета...' note from the footer to right after the
  LegRoute, matching Angular's position between the route strip and the
  Детали рейса accordion.
- Add inline SVG icons for each FlightDetailsAccordion row
  (Регистрация, Посадка, Высадка, Воздушное судно, Питание, Услуги) in
  blue to mirror Angular's sprite-based icons.
2026-04-18 16:00:29 +03:00
gnezim ec67111d10 Rebuild details leg block to Angular layout
Replace the vertically-stacked station blocks with Angular's
route-strip + time-table layout:

- Top row: big time + city + airport/terminal on both sides, with
  the status label and a progress bar in the middle. Scheduled time
  shows as a small strike-through line under the actual when delayed.
  Arrival time picks up the '+1' day-change marker when the flight
  crosses midnight.
- Bottom row: 'По расписанию' + 'Фактическое / Ожидаемое' detail
  rows for both departure and arrival, with UTC offsets and dates
  styled exactly like the Angular design.

The progress bar colors switch between blue (in-flight), green
(finished/arrived) and red (cancelled). The status text localizes
via FLIGHT-STATUSES.*.

Integration tests switched from asserting IATA codes to asserting
the city names, which now render in the promoted row (matches
Angular and the audit feedback).
2026-04-18 15:53:50 +03:00
gnezim ceeae1a7b1 Strip dashes from date when building flight-details URL
The API returns flight.flightId.date as 'yyyy-MM-dd' (dashed). Our URL
builder pasted it verbatim, producing /onlineboard/SU6162-2026-04-18
which the route parser (expecting yyyyMMdd) rejected. Normalise the
date to compact form inside buildFlightUrlParams so the URL always
matches the route pattern, regardless of whether the caller passes a
compact or dashed date.
2026-04-18 15:41:14 +03:00
gnezim 76f7acb0dd Implement inline expandable flight-card details
Clicking a row on the board search results page now toggles an inline
details panel instead of immediately navigating away. The layout
matches Angular's board-flight-header:

- Aircraft model ('Sukhoi SuperJet 100') appears below the flight
  number when expanded.
- 'Время' detail row: По расписанию / Фактическое times with UTC
  offsets for both the departure and the arrival sides.
- 'Посадка' detail row: boarding status (через the
  BOARDING-STATUSES.* keys), start and end times.
- 'Детали рейса' button (blue) in the bottom-right navigates to the
  full details page.
- Active rows get a blue left border + light-blue background.
- Chevron icon on the right rotates on expand.

Wire-up: FlightCard has two new props (expandable, onViewDetails).
FlightList automatically passes expandable=true when a click handler
is provided. Added SHARED.BOARDING-START / SHARED.BOARDING-END keys
across all nine locales for the time captions.
2026-04-18 15:36:14 +03:00
gnezim 916e594f06 Fix flights-map popup showing raw i18n key
The arrival-city popup called t('FLIGHTS-MAP.BUY-TICKET') but the key
in every locale file is FLIGHTS-MAP.BUY_TICKET_BTN (it was renamed
earlier). Map popups rendered the raw key 'FLIGHTS-MAP.BUY-TICKET'
instead of 'Купить билет'. Point the call at the existing key.
2026-04-18 15:21:25 +03:00
gnezim a0176cc336 Keep DayTabs enabled until calendar API resolves
With empty availableDates every day button rendered as [disabled],
leaving the user unable to navigate between days while the /days API
loads (or for routes where the calendar endpoint hasn't been wired
yet). Treat an empty availableDates array as 'unknown' — don't disable
anything, matching Angular's behaviour where tabs are tappable until
the upstream tells us a specific day has no flights.
2026-04-18 14:12:33 +03:00
gnezim cb61cafbf1 Fix three parity issues from final audit
1. Route heading uses airport name when a code maps only to an airport
   (SVO → 'Шереметьево') but prefers the city when the code is a city
   too (LED → 'Санкт-Петербург', not 'Пулково'). Angular does the
   same. Apply the new lookup order in both the onlineboard and
   schedule search pages.

2. Append ', Сегодня' (or 'DD.MM.YYYY' for other dates) to the board
   search heading, matching Angular.

3. Render the '+1' day-change marker on FlightCard even when only
   scheduled times are known. Previously the fallback pulled the value
   from `actualBlockOff/On.dayChange`, which is undefined for
   scheduled-only flights — so overnight flights like SU 6805
   (23:30 → 00:55 +1) showed no indicator. Read
   `scheduledDeparture/Arrival.dayChange.value` when the actual block
   time is missing.

4. Localize the PrimeReact Calendar widget: register a Russian locale
   in [lang]/layout.tsx and set the active one on every locale change,
   so 'Choose Date' reads 'Выбрать дату' and month/day names localize.
2026-04-18 14:10:26 +03:00
gnezim 4e91e9dca1 Display wall-clock times in TimeGroup instead of reprojecting
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.
2026-04-18 14:00:03 +03:00
gnezim 22025b3ab4 Drop duplicate leg row on direct schedule details
For a single-leg flight the FlightCard summary already shows
SVO → ALA with times; the extra 'SVO→ALA / 00:05 - 06:30' line
below was redundant noise. Render the per-leg row only when the
route has multiple legs (transfer case).
2026-04-18 13:58:17 +03:00
gnezim 13fb633ec4 Fix schedule details date format
Upstream /schedule/details returns 400 when dates are sent as
yyyy-MM-dd; it wants the full ISO datetime (yyyy-MM-ddT00:00:00), same
as Angular's ApiFormatterService.formatDate output. Update the date
helper in ScheduleDetailsPage to append T00:00:00.

Verified with curl: request now returns 200 with the full flight
payload for SU1942 on 2026-04-18.
2026-04-18 13:56:10 +03:00
gnezim cd398fb8d9 Hide redundant 'Operated by' text on details page
The badge already conveys the operating carrier via the airline logo
(e.g. РОССИЯ for FV-operated Aeroflot flights), and code-share flights
get an additional 'KL 123, AF 456...' line below the flight number
from DetailsHeaderBadge. The separate 'Выполняет рейс: FV' line
duplicated that information.

Keep the div in the DOM with a visually-hidden class so tests that
target the data-testid still find it. Also add the .visually-hidden
utility class to layout.scss.

Test updated to assert the slot still exists but is hidden.
2026-04-18 13:53:13 +03:00
gnezim abc387ac3c Omit blank departure/arrival in schedule details request
Upstream returned HTTP 400 for empty departure/arrival values. Angular
derives them from per-leg data when missing; easier fix here is to
simply not send them — the backend accepts the request without those
query params.
2026-04-18 13:47:19 +03:00
gnezim 34d74b44c5 Add filter sidebar to schedule search
Schedule results previously had nothing in the left rail. Angular fills
it with the route filter + search history. Reuse the OnlineBoardFilter
component pre-populated with current URL params plus SearchHistory
below it, so the layout matches the board pages.
2026-04-18 13:43:28 +03:00
gnezim 13708fce8e Raise ApiClient default timeout to 30s
The upstream /schedule endpoint regularly returns 7MB+ payloads and
takes 6-10s to complete. The 5s default was aborting those fetches
mid-body, cascading into a retry loop that showed ERR_ABORTED in
DevTools and 'Failed to load data' on the UI — even though the backend
eventually answered with HTTP 200. Matches Angular's default.
2026-04-18 13:41:40 +03:00
gnezim 6f634092b2 Fix schedule search endpoint (GET not POST, dateTo + 1 day)
Angular's ScheduleApiService.getFlights uses GET with query params
and a half-open date interval (sends dateTo = requested end + 1 day).
React sent POST with a JSON body matching the params verbatim, which
returned HTTP 500 from the backend.

searchSchedule now:
- GETs flights/1/{lang}/schedule with ?departure=...&arrival=...
- extends dateTo by one day
- coerces connections to '0'/'1' and drops empty timeFrom/timeTo

Verified the call returns 200 with real flight data for
SVO→LED 2026-04-18..2026-04-25. Updated the api tests accordingly.
2026-04-18 13:38:15 +03:00
gnezim 08bddc0db6 Translate remaining raw status labels
- ScheduleDetailsPage flight status: rendered raw
  `flight.status` (English enum 'Scheduled'); now routes through
  FLIGHT-STATUSES.* keys for localized Russian labels.
- FlightsMiniListItem status icon aria-label: same fix, so screen
  readers say 'Запланирован' instead of 'Scheduled'.
2026-04-18 13:34:45 +03:00
gnezim 58631a3d73 Translate remaining aria-labels on shared widgets
Six more aria-labels that still read in English:
  - CityAutocomplete clear button ('clear')
  - CityAutocomplete picker trigger ('open regional picker')
  - Breadcrumbs nav landmark ('breadcrumb')
  - Timeline prev/next buttons ('Previous legs' / 'Next legs')

Routed through new SHARED.A11Y-* keys (translated into all nine
locales). This is screen-reader-only text but part of the parity
budget.
2026-04-18 13:32:35 +03:00
gnezim 0c660671ea Close the remaining high-impact parity gaps
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.
2026-04-18 13:27:56 +03:00
gnezim 96235d5534 Wrap ScheduleDetailsPage in PageLayout + frame
The schedule-details view rendered bare content on the blue background
with no PageLayout, no PageTabs, no breadcrumbs, no white card — so
the flight-number heading, leg list and skeletons were invisible
against the page background. Wrap the component in PageLayout with
the schedule tab + breadcrumb + 'Информация о рейсе: SU 6805' title,
and put each branch inside a <section class='frame'> (error,
not-found, empty, success) so it sits on the same white card as the
board pages.

Also fix the same React key warning the board page had: the leg map
used leg.index as the key, which is undefined for Direct flights.
Fall back to the array index.
2026-04-18 13:11:28 +03:00
gnezim 77821c3893 Style schedule-start checkboxes to match Angular
The two filter toggles ('Только прямые рейсы' / 'Показать обратные рейсы')
were rendering as the browser default 16px native checkboxes, which look
inconsistent across browsers and thin against the rest of the page.
Add an appearance:none + custom box with a blue tick mark when checked,
matching Angular's filter card.
2026-04-18 13:00:07 +03:00
gnezim 96adf785aa Extract schedule \$.tsx body to a readable module
Modern.js routes catch-all paths via a file literally named \`\$.tsx\`.
The name is framework-mandated but cryptic at a glance. Move the real
component to src/features/schedule/ScheduleDetailsCatchAllRoute.tsx
and make \$.tsx a 12-line re-export, so every grep/import/stack-frame
shows the readable name and the \$.tsx file stays just a framework
shim.
2026-04-18 12:52:13 +03:00
gnezim b01fc2f0c9 Populate filter sidebar when clicking a popular request
Two bugs prevented the popular-requests click from filling the filter:

1. OnlineBoardFilter seeded its fields from initial* props via
   useState(...), which only runs once. When a user clicked a popular
   request the parent pushed ?departure=SVO&arrival=LED into the URL
   and re-rendered with new initial* props, but the sidebar fields
   kept their previous empty values. Add an effect that diffs the
   initial* props against a ref and pushes the changes into local
   state, matching Angular's ngOnChanges behaviour.

2. CityAutocomplete's selectedCity only looked the value up in
   cityByCode. Airport codes like SVO aren't cities, so the header
   code label stayed blank. Fall back to airportByCode → city_code so
   the top-right code renders as 'MOW' when the input shows
   'Шереметьево'.

End-to-end behaviour now matches Angular: clicking
'Маршрут: Шереметьево - Санкт-Петербург' on the start page updates
the URL, populates 'Шереметьево' / 'Санкт-Петербург' in the inputs,
shows 'MOW' / 'LED' codes in the labels.
2026-04-18 12:43:33 +03:00
gnezim db163f5645 Resolve popular-request codes to city/airport names
useCityName was a 'phase 2 stub' that returned the IATA code
unchanged, so the popular-requests panel read 'Маршрут: SVO - LED'
instead of 'Маршрут: Шереметьево - Санкт-Петербург'. Rewire the hook
to read the current locale and look up cityByCode / airportByCode
from the loaded dictionaries, falling back to the code only while
dictionaries are loading or for unknown codes (matches Angular's
DictionariesService.getCityOrAirport).

Tests expanded to cover the city/airport resolutions and the
loading / unknown fallback paths (5 total, 1258 suite green).
2026-04-18 11:02:12 +03:00
gnezim 51f997e642 Match Angular layout in flights-map filter sidebar
- Add the 'Найдите свой маршрут' heading (uses the existing
  FLIGHTS-MAP.ROUTE key, previously unused).
- Reorder the fields to Angular's order: cities → info → toggles →
  date (date was previously stuck in the middle).
- Replace the three native checkboxes with styled pill toggles +
  sliding knobs — matches p-inputswitch used in the Angular filter.
- Add 'ДД.ММ.ГГГГ' placeholder to the date input so the empty state
  reads the same as Angular.
2026-04-18 10:26:24 +03:00
gnezim 25986dfca2 Route operatingBy.carrier lookups through operatingCarrier()
Several components still read `flight.operatingBy.carrier` directly.
The API actually returns `{scheduled, actual}` — I kept `.carrier` on
IOperatingBy as a deprecated alias during the refactor, so nothing
fails typechecking, but at runtime the lookup is always undefined,
which silently disabled the Registration, Flight-status and
Operated-by UIs (the Reg button did nothing on click, the status
visibility check always returned false).

Swap the affected sites — RegistrationButton, FlightStatusButton, the
canRegister / canViewFlightStatus visibility helpers, and the
'Operated by' block in OnlineBoardDetailsPage — to use the
operatingCarrier() helper that reads actual/scheduled/legacy in turn.
2026-04-18 10:04:22 +03:00
gnezim 0633e6de39 Redirect /\[lang\] to /\[lang\]/onlineboard
/ru rendered a blank page because no index route existed under
src/routes/\[lang\]/. Angular's app-routing.module.ts redirects any
empty top-level path to /onlineboard; mirror that in React by adding
a tiny <Navigate replace> index route.
2026-04-18 10:00:01 +03:00
gnezim cd4864c82e Translate remaining route-level English strings
- Flights-map empty/error states: 'Failed to load routes. Please try
  again.' and 'No directions found.' now use existing translation keys
  (BOARD.LOAD-FAILED + FLIGHTS-MAP.NO_DIRECTIONS_INFO).
- Flights-map feature-flag fallback '404 - Page Not Found / This
  feature is currently unavailable.' reduced to the translated
  PAGE404.HEADER string.
- Suspense fallbacks on /onlineboard, /schedule, /flights-map and
  /popular route pages now render the new SHARED.LOADING ('Загрузка…')
  instead of hardcoded 'Loading...'.
2026-04-18 00:45:00 +03:00
gnezim de22fc3722 Rebuild schedule results page for Angular parity
Previously the /schedule/route results page rendered everything on the
dark-blue background and dumped the raw 382-char bitmask from the
/days endpoint straight into the DOM. Changes:

- Wrap the page in PageLayout with PageTabs, breadcrumb and an H1
  matching Angular ('Маршрут: SVO - LED').
- Swap the inline calendar loop for the shared <DayTabs> component
  (weekday + day + month labels, paging arrows).
- Replace the broken comma-split parser in getScheduleCalendarDays
  with the same bitmask-to-dates conversion the board endpoint uses,
  so the calendar now yields real yyyy-MM-dd strings.
- Frame the results in <section class='frame'> so they sit on a white
  card (matches the board pages).
- Translate the 'Invalid …' parameter errors on every route page to
  SHARED.INVALID-PARAMS ('Неверные параметры URL.') and wire t() into
  the two route files that still lacked useTranslation.
2026-04-18 00:35:37 +03:00
gnezim 692fb5e292 Translate schedule headers and connection badges
- Schedule round-trip page: 'Outbound' / 'Return' section headings now
  use SCHEDULE.OUTBOUND / SCHEDULE.RETURN keys ('Туда' / 'Обратно' in
  Russian).
- SignalR connection badge: 'Live' / 'Reconnecting…' / 'Offline' now
  route through SHARED.CONNECTION-* keys ('Онлайн' / 'Соединение…' /
  'Нет связи' in Russian). Applied on both board search and details
  pages.
- Bag-belt label on leg stations switched to the existing
  DETAILS.BAG_BELT key (SHARED variant doesn't exist).
- Integration tests updated to match the new badge keys under mocked t.
2026-04-18 00:27:09 +03:00
gnezim a444b71bcd Translate remaining English strings and color statuses
- 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`.
2026-04-18 00:15:46 +03:00
gnezim 4d73e2fd3c Clean up detail-page time formatting
Render ISO timestamps as "23:30 UTC+03:00 17.04.2026" instead of the
raw "2026-04-17T23:30:00+03:00" that the API returns. Applies to the
per-leg station blocks (new LegStation helper) plus the
Registration / Boarding / Deboarding panels.

formatLocalTime now preserves the wall-clock in the source string
(previously `new Date(...)` would shift it to the runtime's locale).
Added formatUtcOffset (UTC±HH:mm) and formatDayMonthYear (DD.MM.YYYY)
helpers next to it.

Also ship the Angular footer notes on the details page (estimated
times / local-time disclaimer) and added BOARD.LOCAL-TIME-NOTE,
BOARD.ESTIMATED-TIME-NOTE keys to all nine locales.
2026-04-17 23:58:22 +03:00
gnezim ad8367c203 Refine Angular parity: titles, airline header, labels
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.
2026-04-17 23:48:06 +03:00