Commit Graph

52 Commits

Author SHA1 Message Date
gnezim 9aed10c281 Add useIsMobileViewport hook + Online-Board mobile time defaults per TZ 4.1.1-R4/R5 2026-04-21 19:03:54 +03:00
gnezim b31204c543 Add shared useGeoCityDefault hook (generalized from flights-map) 2026-04-21 18:58:23 +03:00
gnezim ef33b557f6 Clamp projectScheduleToBoard.date to today only when schedule dateFrom is out-of-window 2026-04-21 18:34:20 +03:00
gnezim ef0e1e38e5 Assert date-window clamp on Board ← Schedule projection per TZ 4.1.8-R2 2026-04-21 18:08:42 +03:00
gnezim 986313248e Add cross-section navigation store with Board↔Schedule projection per TZ 4.1.8 Table 10 2026-04-21 17:57:06 +03:00
gnezim 12807cf085 Extend detailsRequestParam codec for area:schedule (one-way + round-trip + connections) 2026-04-21 17:42:54 +03:00
gnezim 531ace6abc Add ?request= query-param codec for Online-Board details URLs per TZ 4.1.2 Table 5 row 6 2026-04-21 16:33:16 +03:00
gnezim 750a328528 Add centralized date-window constants per TZ 4.1.2-R12 (board/schedule/map) 2026-04-21 16:30:04 +03:00
gnezim bb49a5d609 Fix Schedule "Details" button + search-history sync
Schedule:
- ScheduleSearchPage wires handleFlightClick to DayGroupedFlightList so
  the "Детали рейса" button in the expanded flight body navigates to
  /{lang}/schedule/{carrier}{flightNumber}-{yyyyMMdd} (Angular's
  ScheduleNavigationService.toDetailsPage equivalent). Previously the
  Details button fired onStatus → no handler → no-op.

Search history:
- useSearchHistory now broadcasts a custom `afl:search-history-changed`
  window event on add/clear and listens for it in a useEffect. Fixes
  the case where a route-level component (ScheduleSearchPage) adds to
  storage while a sibling SearchHistory sidebar had already captured
  an empty initial value via useState — the sidebar now re-reads
  storage and shows the history without a page reload.
2026-04-20 16:34:52 +03:00
gnezim ae133c1e36 useAppSettings: align fallback defaults to Angular AppSettings (1/7 board, 1/330 schedule, 2h flight status) 2026-04-20 15:21:52 +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 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 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 8ccf560bf5 Resolve airport codes to parent city in popular requests
Angular's getCityOrAirport walks airport→city when the input code is
an airport (SVO → Москва), only falling back to the airport name when
no parent city is dictionarised. React was returning the raw airport
name (Шереметьево) on the popular requests panel.
2026-04-19 22:37:12 +03:00
gnezim 9acfeb4052 i18n: switch URL locale codes to xx-xx (Angular contract)
Angular's LocalizationService reads `Country = baseHref[1..3]` and
`Language = baseHref[4..6]` — both halves are the same 2-letter
language code (`/ru-ru/`, `/en-en/`, `/zh-zh/`, …), confirmed by
the spec fixtures using `/en-en/onlineboard/...`. The previous
shipping codes mixed in IETF region codes (`en-us`, `ja-jp`, `ko-kr`,
`zh-cn`) which do not match the customer's URL surface.

Renamed:
  en-us → en-en
  ja-jp → ja-ja
  ko-kr → ko-ko
  zh-cn → zh-zh

The `LANGUAGE_TO_LOCALE_CODE` table now mirrors Angular exactly.
Resolver/hreflang tests + layout 404 message updated.
2026-04-19 18:39:51 +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 b8e595dc25 URL surface parity with Angular for /popular and start-page prefill
- Drop the React-only standalone /popular route (and its e2e
  smoketest). Angular returns 404 for /ru-ru/popular; popular
  requests are surfaced inline on onlineboard/schedule start pages
  via PopularRequestsPanel (which stays). Matching the URL surface
  is a contractual requirement for the MF remote.
- Replace ?tab/?departure/?arrival/?return query-string prefill on
  the onlineboard and schedule start pages with a sessionStorage
  transient slot. Mirrors Angular's OnlineBoardFiltersStateService /
  ScheduleFiltersStateService cross-page singletons: URLs stay
  clean of query strings, the start-page form still seeds itself
  from a popular-request click, and a fresh page reload (which
  bypasses the in-memory state in Angular) lands on a pristine form.
  Same-page popular clicks remount the filter via key bump so the
  useState initializers pick up the new prefill.
2026-04-19 16:51:31 +03:00
gnezim 7c0fb6a0d8 Format UTC offset as 'UTC +HH:MM' with non-breaking space (Angular parity)
Angular's captioned-time-group renders 'UTC {{ 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 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 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 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 c8d0caa9cf Fix five console-level issues surfaced by live-deploy Playwright audit
1. FlightsMap tiles didn't render: MapCanvas inline height:100% resolved
   to 0 against min-height parents. Hand sizing to consumer CSS so
   .flights-map-start__map height:500px wins.

2. FlightsMap /map/api/tile/{z}/{x}/{y}.jpeg requests fell through to
   Modern.js SSR (HTML body). Dev proxy now forwards /map/* to the
   test env via curl with image headers and binary-safe piping.

3. PopularRequestsPanel duplicate React key (Route-SVO-LED appears
   twice in upstream). Suffix the key with the visible index.

4. OnlineBoardDetailsPage /onlineboard/details 400. Upstream expects
   an ISO datetime (yyyy-MM-DDTHH:mm:ss), matching Angular's
   ApiFormatterService.formatDate. Append T00:00:00.

5. Browser-level SignalR CORS errors on every details page: the
   default SIGNALR_HUB_URL pointed at an unreachable placeholder.
   Default to empty + skip the connection in useLiveFlights when
   blank. Also configureLogging(LogLevel.None) so SignalR stops
   writing its own negotiation failures to console. Live updates
   re-enable by setting SIGNALR_HUB_URL on a deployment.
2026-04-17 21:55:44 +03:00
gnezim 03a720179c Expose dictionaries module barrel 2026-04-17 03:18:35 +03:00
gnezim 715b09fd18 Add useDictionaries hook wiring api + transform 2026-04-17 03:16:42 +03:00
gnezim e575c1baa1 Add dictionary lookup helpers and findCityByCoord 2026-04-17 03:14:28 +03:00
gnezim 64dd5421e2 Cover enrichment, lookup-map, and region ordering in transform tests 2026-04-17 03:13:46 +03:00
gnezim e5b49acecf Add dictionaries transform with filtering and partitioning rules 2026-04-17 03:13:18 +03:00
gnezim dc373553d2 Add fetchDictionaries parallel-fetch layer 2026-04-17 03:10:26 +03:00
gnezim da605f0576 Add Flights Map dictionaries type module 2026-04-17 03:09:39 +03:00
gnezim aa60c29685 Extend useAppSettings with flightStatus and buyTicket button config 2026-04-17 01:20:11 +03:00
gnezim a1dfa5f628 Add useAppSettings hook for parsing app config day ranges 2026-04-17 00:17:55 +03:00
gnezim c19309a828 Add getAppSettings API function 2026-04-17 00:16:21 +03:00
gnezim 71d0c983fd Fix API calls: bind fetch to globalThis, fix date format for calendar
CI / ci (push) Failing after 28s
Deploy / build-and-deploy (push) Failing after 5s
Root cause of search not working: globalThis.fetch stored as a class
field loses its Window binding, causing 'Illegal invocation'. Fixed
with fetch.bind(globalThis).

Also fix calendar days endpoint date format from yyyyMMdd to
yyyy-MM-ddT00:00:00 matching Angular's ApiFormatterService.
2026-04-15 22:32:51 +03:00
gnezim 5fc67f81bd Wire city autocomplete to dictionary API
CI / ci (push) Failing after 37s
Deploy / build-and-deploy (push) Failing after 6s
useCitySearch hook loads cities from /api/dictionary/1/cities on first
use, then searches in-memory by name prefix and code -- matching the
Angular CitiesSearchService behavior. Wired into OnlineBoardFilter,
ScheduleStartPage, and FlightsMapFilter AutoComplete components.
2026-04-15 21:32:39 +03:00
gnezim f61e050e8c Configure dev proxy to flights.test.aeroflot.ru and fix API endpoint paths
API functions now build the full localized path matching the Angular
EndpointService pattern (/api/flights/{version}/{locale}/{endpoint}).
The dev proxy forwards /api and /flights to the test backend.
2026-04-15 21:32:28 +03:00
gnezim e8935276a0 Fix SignalR connection error handling for offline hub
Wrap connection.start() in try/catch so that when the SignalR hub is
unreachable the status transitions to "offline" silently instead of
throwing unhandled errors that flood the browser console.
2026-04-15 20:58:01 +03:00
gnezim 031ad7c15d Fix lint warnings and test environment for Phase 5 tests
Remove unused type alias, unused variable, add jsdom environment
directive, and use container.textContent for cross-element text
assertions.
2026-04-15 10:09:56 +03:00
gnezim 99af1fe00d Add Phase 5D useSearchHistory hook with per-language namespacing
Persists search history to localStorage via @/shared/storage with
language-scoped keys (afl_history_{lang}). Supports dedup by URL,
max 10 items, and clear functionality.
2026-04-15 10:01:31 +03:00
gnezim b3ab73253d Add data model types, datetime utils, and dictionary hook
Port Angular flight types (ISimpleFlight, IFlightLeg, ITimesSet, etc.)
to minimal React-friendly interfaces. Add formatDuration/formatTime/
formatDate/isDayChange as pure functions. Stub useCityName hook as
passthrough pending customer dictionary API endpoint.
2026-04-15 07:55:00 +03:00
gnezim 2742568a85 Implement safe storage abstraction with Zod validation and namespace prefix 2026-04-15 00:47:22 +03:00
gnezim ebe6c38713 Fix lint warnings in SignalR wrapper and hook tests
Remove unused imports, eliminate non-null assertions,
drop invalid eslint-disable comment for missing rule.
2026-04-15 00:44:31 +03:00
gnezim 4c52bb4032 Add useLiveFlights SSR-safe hook with tests
Generic hook wrapping SignalR subscription with SSR guard
(typeof window check). Includes tests for subscribe, data
updates, cleanup, and SSR path.
2026-04-15 00:42:51 +03:00
gnezim 7052052742 Add SignalRConnection ref-counted wrapper with tests
Reference-counted connection management with grace period,
dynamic import to keep @microsoft/signalr out of SSR bundle,
and singleton sharing via getSharedConnection.
2026-04-15 00:41:28 +03:00
gnezim 599f35f14a Add JsonLdRenderer and serializeJsonLd with schema-dts typing 2026-04-15 00:20:07 +03:00
gnezim 8abe8acf70 Add buildHreflangSet for 9 languages + x-default 2026-04-15 00:18:35 +03:00
gnezim 7992d2705a Add ApiClient React context provider with useApiClient hook 2026-04-14 23:46:20 +03:00
gnezim 04c5432aef Add CachedApiClient decorator layered above ApiClient 2026-04-14 23:45:56 +03:00
gnezim 65c8c8b55f Add ApiClient with retry, timeout, and error mapping 2026-04-14 23:42:22 +03:00
gnezim cb5e5b0106 Add three cache types (request-scoped, client TTL, server byte-capped LRU) 2026-04-14 23:39:35 +03:00