Commit Graph

80 Commits

Author SHA1 Message Date
gnezim 3f04ade411 CityPickerPopup: #ffffff → colors.$white (brand palette parity) 2026-04-20 15:44:24 +03:00
gnezim f823aafb35 FlightCard/FlightDetailsAccordion: #8a8a8a → $light-gray (brand palette parity) 2026-04-20 15:28:15 +03:00
gnezim daf3ed35a5 FlightStatus label colors: match icon palette (green for in-flight/arrived, orange for delayed) 2026-04-20 14:24:04 +03:00
gnezim 3c869198d6 FlightStatus icon colors: use brand palette (Angular statusColors parity) 2026-04-20 14:22:29 +03:00
gnezim b4aea2a6fd FlightList: empty state matches Angular page-empty-list (icon + title + text, desktop/mobile layouts) 2026-04-20 13:56:54 +03:00
gnezim d7a9ae5d79 FlightCard schedule grid: match Angular schedule-list-flight-header (80px number, minmax(45,240)px stations) 2026-04-20 13:52:59 +03:00
gnezim c28cfc2fd3 ScrollUpButton: 40×40, extra-blue bg, right: 30px / bottom: 80px (Angular parity) 2026-04-20 13:03:23 +03:00
gnezim 0e74d9d196 FlightCard schedule mode + DayGroupedFlightList headers: number column 80→60px (Angular parity) 2026-04-20 13:00:51 +03:00
gnezim a4e99fee64 FlightCard: row grid [60px|120px|100px|1fr|85-145px|120px|1fr|10px] + padding 15px/20px (Angular parity) 2026-04-20 12:59:28 +03:00
gnezim b306127cfc Breadcrumbs: middle-crumb links in solid white, only last crumb faded (Angular parity) 2026-04-20 12:55:41 +03:00
gnezim b7a358dadc CityAutocomplete item: 48px row height + bottom divider (Angular parity) 2026-04-20 12:42:04 +03:00
gnezim d2f418f494 Show CityAutocomplete clear (×) button for any truthy value, not just resolved cities
Previously hasValue was computed from `selectedCity` — which required
the dictionaries to be loaded AND the raw code to map to a known city.
If the dictionaries were slow or the user typed free text, the clear
button stayed hidden and the filter became stuck with no way to wipe it.

Angular's CityAutocomplete uses `[ngClass]="{'has-value': city}"` on
the raw two-way-bound model, so any truthy value reveals the clear
button. Mirror that: `hasValue` is now true whenever the resolved
city, the outbound code, or the AutoComplete inputValue (free text or
suggestion object) is truthy.
2026-04-20 11:55:48 +03:00
gnezim 706b8f444b Clear the last 19 lint warnings — make check now passes clean
- BuyTicketButton / FlightsMiniListItem: narrow firstLeg/lastLeg with
  explicit null guards (throw / return '').
- FlightSchedule.tsx: `match?.[1] ?? iso` for the regex capture.
- OnlineBoardSearchPage + schedule/api: `split('T')[0] ?? iso` for the
  date-prefix extraction.
- ServicesPanel: icon lookup uses a third '' fallback instead of `!`.
- buildCountryCityRows: explicit `break` if cities[i] is undefined.
- useAppSettings: `match?.[1]` null-check before parseInt.
- datetime/index.ts: guard bare HH:MM capture groups together.
- ScheduleDetailsCatchAllRoute: drop unused `t` + useTranslation import.
- ScheduleDetailsPage.tsx: prefix unused `getLegs` with underscore.
- 4 seo/json-ld tests: drop now-redundant eslint-disable comments.
- calendarRange.test + api.test: prefix unused helper names with `_`.

Warning count: 19 → 0. make check (typecheck + lint + test) exits 0.
2026-04-20 09:30:34 +03:00
gnezim 8d409572b7 Drop 11 more non-null assertions across 5 files
- ErrorPage.tsx: FALLBACK_CONFIG literal instead of ERROR_CONFIG["500"]!
- ErrorBoundary.tsx: hoist FALLBACK_RU / FALLBACK_EN to consts so
  pickStrings returns them without the bang.
- routesToPolylines.ts: narrow spider-mode block on filterState.departure
  truthy; guard each route-code lookup.
- FlightsMapStartPage.tsx: narrow firstRoute/depCode/arrCode together
  instead of asserting each individually.
- OnlineBoardDetailsPage.tsx: IIFE over legs[i+1] for TransferBar;
  `_canonicalOrigin` prefix for currently-unused prop.

Warning count: 30 → 19.
2026-04-20 09:22:49 +03:00
gnezim 298f007463 Drop 11 non-null assertions in api.ts, DayGroupedFlightList, FlightCard
Regex capture groups and array boundary accesses replaced with nullish
fallbacks / explicit guards. Warning count: 41 → 30.
2026-04-20 09:19:14 +03:00
gnezim 1fc96b603e Drop 14 non-null assertions in ScheduleFlightBody + CityPickerPopup
- ScheduleFlightBody.tsx: hms regex capture uses `?? "0"` fallback;
  inline IIFEs expose last-leg and transfer-to-next in narrowed scope.
- CityPickerPopup.tsx: row.city1/city2/city1Airports are lifted into
  locals so the narrowing survives into JSX event handler closures.

Warning count: 55 → 41.
2026-04-20 08:33:33 +03:00
gnezim a982d9a669 Fix lint: route sessionStorage through shared storage module, drop dead imports
- storage.ts: add sessionStore wrapper (getRaw/setRaw/delete/clear) so
  transientPrefill + ScheduleStartPage tests don't trip the
  no-restricted-globals rule.
- transientPrefill.ts + ScheduleStartPage.test.tsx: use sessionStore.
- closestFlight.ts: hoist bracket-index key so no newline-before-[ ASI.
- Test files: hoist typeof import(...) into named type alias with
  type-only namespace import.
- Drop unused imports: FlightCard (Link, languageToLocale),
  OnlineBoardDetailsPage (operatingCarrier),
  ScheduleSearchPage (FlightList, inline import() types),
  PageLayout (FeedbackButton).
- Drop react-hooks/exhaustive-deps disable comments for a rule not
  registered in eslint.config.js.
2026-04-20 08:15:21 +03:00
gnezim 68e7b3e9ec Normalize 4/6/8px radii to vars.$border-radius (3px) across 5 SCSS files 2026-04-20 07:10:12 +03:00
gnezim 3577745477 Style Breadcrumbs as Angular's translucent pill (dark-blue-opacity bg, 3/10 padding) 2026-04-20 06:40:33 +03:00
gnezim da3f2713ac CityPickerPopup: gps-button uses $blue-light / $white / $blue-light--hover tokens 2026-04-20 06:38:14 +03:00
gnezim b6ed257a6a ErrorPage: use design tokens for code/input/focus colors 2026-04-20 06:35:33 +03:00
gnezim ef845f587f Final token sweep: pastel blues, blues, greys to design tokens (8 files) 2026-04-20 06:14:55 +03:00
gnezim b8ab5af8aa Token cleanup for TimeGroup + FlightCard (#333/#f37b09/#fff/#e68200/#5b6b80 → tokens) 2026-04-20 06:08:43 +03:00
gnezim c8257baf26 Replace border-radius: 4px with vars.$border-radius (3px) to match Angular 2026-04-20 04:26:19 +03:00
gnezim 627f155f87 Replace #022040/#1a3a5c navy hex with colors.$blue-dark token 2026-04-20 04:21:47 +03:00
gnezim 2d7646d793 Replace custom brand hex with design tokens ($blue, $orange, $green, $red) across 13 SCSS files 2026-04-20 04:19:43 +03:00
gnezim fb82fc6ad1 Replace pastel-blue dividers/bg with $border and $blue-extra-light tokens (6 files) 2026-04-20 03:58:13 +03:00
gnezim 3bae0ee98f Replace hardcoded #e0e0e0 borders with $border-input and normalize $border-radius to 3px 2026-04-20 03:54:06 +03:00
gnezim 4aadab25e9 Normalize body-text #222 to colors.$text-color across 10 SCSS files 2026-04-20 03:50:54 +03:00
gnezim 1cd39b094e Match Angular per-carrier OperatorLogo aspect ratios (120x31 SU, etc) 2026-04-20 03:30:15 +03:00
gnezim 1156dd6f90 Replace hardcoded #6b7280/#1c2330/#8a8a8a greys with design tokens ($light-gray/$text-color) 2026-04-20 03:02:10 +03:00
gnezim c4ba540e1c Normalize caption/time-note greys to $light-gray (#657282) across FlightCard, CityAutocomplete, details accordion, board header, and filter 2026-04-20 02:57:41 +03:00
gnezim 7cf15f11ab Match Angular typography: terminal-link color, board header padding/grid 2026-04-20 02:31:27 +03:00
gnezim 74d7c119d5 Match Angular FlightCard grid tracks, tablet overrides, and search-frame radius 2026-04-20 02:28:09 +03:00
gnezim 353bd62296 Render branded 404 page on invalid URLs and malformed params
Replace the inline 'Invalid parameters' fallbacks and the framework's
default '404' text with the existing Aeroflot 404 screen. Unknown
locale, malformed flight/route/station params, and unmatched URLs
(including bad paths like onlineboard//route/...) now all land on the
same ErrorPage component.
2026-04-20 02:23:16 +03:00
gnezim 0e9191be05 Match Angular PageLayout: 24px tablet padding, single title wrapper 2026-04-20 02:07:51 +03:00
gnezim ddc8e9f6dc Wire Вы искали sidebar accordion to live search history
Each schedule + onlineboard search now records itself into the
existing useSearchHistory localStorage hook, with a structured
params payload (departure/arrival/dates/flightNumber). The
SearchHistory sidebar renders the rich Angular layout: clock or
plane icon, optional sub-title (e.g. "Расписание рейсов, в одну
сторону"), city pair, and date range, with inbound dates appended
for round-trip searches.
2026-04-20 00:12:58 +03:00
gnezim d6ef3c8433 Render Angular schedule expanded body in React
Schedule flight cards now expand into the rich Angular layout instead
of the online-board time/transition rows. Mirrors connecting-flight-
body / multi-flight-body: horizontal timeline summary, per-leg card
with section number + flight number + operator + aircraft + dep/arr
times + leg duration + stations, transfer-inline-extended pill
between legs (Пересадка, ground time, transit city), and the actions
row (share, Купить, Детали рейса).

Wired via a renderExpandedBody render prop on FlightCard/FlightList so
ui/flights doesn't need to know about schedule-specific bodies.
2026-04-20 00:01:24 +03:00
gnezim 4c487ab1b2 Render Connecting flights + Angular grid for schedule rows
- Connecting (multi-leg via transit) flights are now folded into a
  synthetic MultiLeg shape with combined flight numbers (SU 6188,
  SU 6233) and per-leg airline logos, matching Angular's
  schedule-list-flight-header.

- Schedule grid now uses Angular's 8-column layout
  (80/120/100/240/100/100/240/16). The middle status icon is
  replaced by a duration column with the blue clock icon and
  '3ч. 48мин.' / '4h 19m' formatting.

- Multi-leg airline logos use the round badge variant (separate
  round.png assets) so two carriers fit side-by-side without overlap.

- Action buttons removed from collapsed rows — Angular only shows
  flight-actions in the expanded body. Added chevron column for
  every schedule card and made schedule cards expandable by default.

- Removed 'Туда: MOW → KUF' subhead from outbound section, matching
  Angular's bare flight list under the column header.
2026-04-19 23:24:06 +03:00
gnezim b2abde9210 Inline Купить + Статус рейса buttons on schedule cards
Match Angular's flight-actions layout — schedule rows now show the
orange Buy and outlined Status рейса buttons inline at the right edge
of the row instead of inside the expanded panel.
2026-04-19 22:22:05 +03:00
gnezim a41c767dd1 Schedule per-leg operator logos + Купить button
- FlightCard: when the flight is multi-leg, render one OperatorLogo
  per leg in the header so code-share / multi-carrier journeys show
  both airline brands (Angular's `operator-logo-and-model x N` row).
  Direct flights keep the single logo.
- FlightCard: add an orange "Купить" (buy ticket) link rendered next
  to "Детали рейса" when the card is in the schedule context. Links
  to aeroflot.ru's booking flow per Angular's flight-actions wiring.
- Reverted earlier per-leg flight-number stack — IFlightLeg in React
  doesn't carry a per-leg flightId, so the parent SU number is the
  authoritative label. The Angular dual-number stack belongs to the
  ConnectingFlight shape (separate from MultiLeg) which the React
  code already renders flat.
2026-04-19 21:23:46 +03:00
gnezim 69706e023d Schedule + flights-map structural parity
- flights-map: default departure to Москва (MOW) when geolocation
  doesn't yield a city. Mirrors Angular which seeds the orange
  marker on Moscow regardless of geo permission. Hook now has two
  effects — a synchronous MOW fallback that fires once dictionaries
  load, and the existing geo callback that may upgrade to a closer
  city when permission is granted.
- Schedule: introduce DayGroupedFlightList. Buckets the flat result
  list by scheduled-departure date and renders each group under a
  `Воскресенье 19 Апреля`-style header (Intl-driven, weekday +
  genitive month). Single-day result skips the grouping noise.
- Schedule: introduce WeekTabs. Replaces the daily DayTabs in the
  schedule sticky-content with Monday-anchored 7-day windows like
  `13 апр - 19 апр`, matching Angular's week-tabs component.
  handleWeekChange recomputes both dateFrom (Monday) and dateTo
  (Sunday) when the tab changes.
- Schedule: aircraft model now visible in the collapsed FlightCard
  row when `direction === "schedule"` (Sukhoi SuperJet 100 / Airbus
  A321 etc., per Angular's operator-logo-and-model column).
- FlightCard / FlightList: extend `direction` union with `"schedule"`.

Tests updated: useGeolocationDefault tests now assert the MOW
fallback fires when permission is denied / API missing / arrival
already set (was previously expected to no-op).
2026-04-19 20:52:41 +03:00
gnezim e7cf11e799 Visual parity fixes — drop pixel mismatch on 6+ pages
- OperatorLogo: accept BCP-47 codes (`ru-ru`) by trimming to first 2
  chars before picking the en/ru asset variant. Fixes the Russian
  flight-details page rendering ROSSIYA (Latin) instead of РОССИЯ.
- FlightCard / FlightList: thread `direction` from the search page so
  arrival results show Высадка (deboarding) instead of Посадка
  (boarding) — Angular parity. The arrival side reads from
  arrivalLeg.transition.deboarding when direction === 'arrival'.
- OnlineBoardFilter:
  - Дата рейса starts blank with `ДД.ММ.ГГГГ` placeholder; submit
    handler defaults to today on empty.
  - Город вылета / Город прилета placeholders flip to
    `Все направления` when the opposite-direction field is filled.
  - Filter content row now flows with $space-l vertical gap to match
    Angular's accordion-content rhythm (was ~6 px tighter).
- FlightsMiniList: `display: none` on mobile. Avoids the duplicate
  summary card that was floating above the main details on small
  viewports — Angular hides the sidebar mini-list there.
- FlightsMap calendar trigger: override PrimeReact's filled-blue
  button to a transparent outline so it reads as a glyph (matches
  Angular's outline calendar icon).

Pixel-mismatch results (re-diffed via scripts/visual-diff.mjs):
  en-onlineboard-route       5.50% → 4.62%
  onlineboard-arrival        5.53% → 4.63%
  onlineboard-departure      5.92% → 5.03%
  onlineboard-route          5.16% → 4.78%
  mobile-onlineboard-start  23.51% → 20.37%
  mobile-flight-details     18.82% → 17.92%
  flight-details            carrier-logo verified visually; pixel
                            count unchanged (height delta dominates)
  onlineboard-start         14.56% → 14.52%

Larger remaining mismatches (schedule-route 14%, flights-map 34%,
flight-details 11%) are dominated by structural Angular features the
React port doesn't yet ship (day grouping, code-share bundling on
schedule; geo-driven origin marker on map; height-delta on details).
Tracked as P1 follow-ups in the comparison report.
2026-04-19 20:18:15 +03:00
gnezim ce2ca4a689 i18n: BCP-47 URL locales + complete EN translations
- URL surface now matches Angular: `/ru-ru/`, `/en-us/`, `/zh-cn/`, …
  (BCP-47). Bare short codes still work — the [lang]/layout auto-
  promotes them with a replace navigation. Internally everything that
  needs the short language (i18n file lookup, API path segment,
  Accept-Language header, dictionary `title[lang]` key, Intl
  formatters) reads it through the new `useLocale()` hook, which
  returns both `locale` (BCP-47) and `language` (short).
- ApiClient.locale is now mutable and is updated from the [lang]
  layout whenever the URL locale changes — was hard-coded to "ru" in
  the root layout before, so backend responses for /en/... still came
  back in Russian. Cities / airports / flight statuses now arrive in
  the active language.
- All 21 empty EN translation keys filled in (AIRPLANE.*, BOARD.
  PREVIOUS-FLIGHT, SCHEDULE.FILE-NAME, SEO.SCHEDULE.*, SEO.FLIGHTS-
  MAP.*, SHARED.FLIGHT-TRANSFER-PLURAL-*, SHARED.WEEK_FORMAT-WRONG)
  so /en-us renders without falling back to raw keys.
- Added BOARD.LOAD-FAILED-TITLE / -MESSAGE keys (RU + EN) and removed
  the three hardcoded Russian error strings from the search-page
  error card.
- FlightStatus now reads `FLIGHT-STATUSES.{Status}` from i18n instead
  of hardcoding the Russian labels.
- FlightCard's OperatorLogo now picks the en/ru carrier-logo variant
  from `useLocale().language` instead of always passing "ru" — the
  Aeroflot/Rossiya logos display in the active language where
  variants exist.
- registerPrimeLocales(): all 9 supported languages get a PrimeReact
  `addLocale` entry at module load (RU + EN hand-curated, others built
  from Intl). Calendar/AutoComplete widgets switch with the URL.
- ErrorBoundary catches outside the i18n provider, so it now ships
  its own minimal localised string table keyed off the URL locale —
  no more "Something went wrong" leaking on the Russian site.
- Hreflang URLs now emit BCP-47 (`/en-us/...`) while `hreflang="en"`
  stays the short Google-friendly form.
- Datetime helpers accept either short or BCP-47 locale (`isRussianLocale`)
  so callers can pass through whatever the route hands them.
2026-04-19 17:36:24 +03:00
gnezim b63fd8fb6b Visual parity fixes vs Angular reference
- SharePanel: fix wrong i18n key (SHARED.COPY → SHARE.COPY) and switch
  to the brand-icon-on-top + translated-label layout that Angular uses
  (renders as untranslated raw key + plain text list before).
- LastUpdate: stamp now reflects when the client received the data, not
  the API record's mutation timestamp — Angular sets `flight.lastUpdate
  = new Date()` in populate.logic.ts; we mirror that behavior so users
  no longer see stale 'updated' values from cached API rows.
- FlightCard: keep operator logo + plane icon + status text on mobile
  (previously hidden via display:none); regrid to 3-col layout so the
  card mirrors Angular's mobile pattern. Boarding status row gains the
  leading colour-coded dot Angular ships ('Уточняется' grey).
- OnlineBoardSearchPage: H1 for /flight/... search now reads
  'Рейс: SU 6497, Сегодня' instead of 'Номер рейса: SU6497' (matches
  Angular's title.service); add the '* Время в системе - МЕСТНОЕ.'
  footer note Angular's <page-footer-notes> renders.
- FlightsMap filter: drop the React-only 'Найдите свой маршрут'
  header; replace the horizontal swap glyph with vertical blue arrows
  (Angular rotates the same SVG 90deg); add Leaflet city-tooltip
  styling so labels render text-only with a white text-shadow halo
  rather than as PrimeReact-default white pills.
- DayQuickPick: new mobile-only 3-day quick-pick row above the manual
  date input on both onlineboard and flights-map filters, mirroring
  Angular's calendar-input.component .calendar--mobile block. Uses
  Intl.DateTimeFormat formatToParts to get the genitive month form.
2026-04-19 16:14:47 +03:00
gnezim 314889de2a Rotate FlightStatus plane icon 90° to point right
Angular's sprite has the plane nose-right, but the inline 24×24 path
bundled with FlightStatus is nose-up. Transform-rotate matches the
Angular direction without swapping the SVG asset.
2026-04-19 15:22:00 +03:00
gnezim b04e80d2d8 Add share icon to expanded flight card
Angular's expanded FlightCard bottom bar has a share button on the
left next to the 'Детали рейса' action on the right. Render an icon
button backed by /assets/img/share.svg with the same
justify-content: space-between layout. Click uses the Web Share API
when available and falls back to writing the current URL to the
clipboard.
2026-04-19 14:59:14 +03:00
gnezim 217971bd81 Match Angular TimeGroup + expanded time-row typography
Render the latest time on top (30px/light/#333) with the crossed-out
scheduled time below (12px/#f37b09/line-through), mirroring Angular's
'time' vs 'oldTime' pattern instead of the inverted order React had.
Drop the local .flight-card__time font-size override so TimeGroup owns
its own typography.

In the expanded panel, show both scheduled and latest time columns.
Angular falls back to estimatedBlockOff/On when actual is absent and
labels the column 'Ожидаемое' vs 'Фактическое' accordingly — mirror
that logic so flights with only an ETA surface it.
2026-04-19 14:56:13 +03:00
gnezim f9dec146f8 Flush DayTabs against flight list + mask viewport top on scroll
Drop the 8px margin below the day-tabs strip so the sticky card touches
the results frame, matching Angular's layout.

Add page-layout__scroll-overlay — a fixed 25px dark-blue strip across
the top of the viewport — so flight rows scrolling past the 20px-sticky
date row don't peek through the gap above it.

Realign DayTabs range to match Angular's boardSearchFrom=1 /
boardSearchTo=7 defaults (today-1 through today+7). Previous 2/14
window left today off-center in the 7-tab page.
2026-04-19 14:50:40 +03:00
gnezim f2661768b0 Match Angular day-tabs UI + fix flights-map SEO key
Rebuild DayTabs to mirror Angular's flat single-line tab strip: one label
per tab ('17 апр.' siblings, '19 апреля' active), 48px tall, 7 tabs per
page, brand blue label on light-blue background with white active cell.
Single Intl.DateTimeFormat call keeps Russian months in genitive case.

Drop the 60px sticky-content offset to 20px so the date strip aligns
with the left filter column (both already sticky at top:20px).

Correct SEO.FLIGHTS_MAP translation key to SEO.FLIGHTS-MAP.MAIN — the
underscored path never existed in the locale files, so the browser tab
title on /{lang}/flights-map fell back to the raw key.
2026-04-19 14:35:13 +03:00