Commit Graph

158 Commits

Author SHA1 Message Date
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 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
gnezim 330f9787a2 Bring flight row + details page closer to Angular
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.
2026-04-17 23:32:50 +03:00
gnezim 84e6d265fc Align board search + details with Angular visual parity
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.
2026-04-17 23:14:59 +03:00
gnezim 79fcf2bdc1 Fix onlineboard empty results and flights-map polyline zoom hazard
Online board: the /board endpoint treats dateFrom/dateTo as a half-open
interval, so sending the same date for both yielded zero rows on routes
that obviously have flights (e.g. SVO-LED). Mirror Angular's
OnlineBoardApiService.getFlightsByRoute and use dateTo = date + 1 day.

Flights map: two stacked problems made arcs disappear on zoom.
 - syncPolylines gated endpoints on map.hasLayer(marker); when
   syncVisibility removed a zoom-tier layer, its arc went with it.
 - The zoomend and toggle effects both called syncPolylines, which
   captured a stale closure from the first render (polylines = []) and
   wiped the layer. Polyline coords are geographic — Leaflet rescales
   them on zoom — so the rebuild was never necessary.
Arcs now render once per polylines prop change and stay put through
zoom and filter toggles.
2026-04-17 22:48:18 +03:00
gnezim 18ab969e1c Keep flights-map arcs visible when zoom tier layers are removed
syncPolylines filtered endpoints by map.hasLayer(marker), which drops
polylines whose endpoint's zoom-tier layer was just removed by
syncVisibility. Result: in spider mode, arcs to small/distant cities
vanished when the user zoomed out.

Arc coordinates are fixed by the city's lat/lng; render them as long as
the markers exist in the index. User-level filtering (domestic /
international) already happens upstream in filterRoutes, so the
hasLayer gate was both incorrect and redundant.
2026-04-17 22:40:46 +03:00
gnezim 5551cb1821 Fix flights-map date format and dev proxy empty-body status parsing
Upstream /destinations and /days endpoints expect yyyy-MM-DD (dashed),
matching Angular's ApiFormatterService.formatDateOnly output. React was
sending the internal compact yyyyMMdd, triggering silent 400s.

Also fix dev-server.mjs status-code parsing: empty-body curl responses
start with the appended "\n%{http_code}" separator at index 0, so
`lastNewline > 0` mis-treated the status as body and defaulted to 200,
hiding real upstream 4xx/5xx responses. Changed to `>= 0`.
2026-04-17 22:25:48 +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 b54746c74c Fix flights-map tile URL to Angular parity /map/api/tile/{z}/{x}/{y}.jpeg
The tile URL was built as `${env.API_BASE_URL}/tiles/{z}/{x}/{y}.png`,
which has three problems on a real deployment:

1. Wrong path segment: the backend serves tiles under /map/api/tile/
   (singular), not /tiles/.
2. Wrong extension: the backend emits .jpeg, not .png.
3. Wrong base: API_BASE_URL may be empty or point at the JSON API host.
   Tiles are served by a dedicated upstream behind the same reverse
   proxy that fronts the React SSR, so they must be same-origin and
   relative (matches Angular environment.mapApiUrl).

With this fix, the map renders its base tiles on the deployed site
instead of issuing doomed requests and showing a blank canvas behind
the markers. The Leaflet marker layer was already rendering; only the
tile layer was missing.
2026-04-17 18:40:52 +03:00
gnezim 896e6bd83d Switch filter time-selector to Angular compact layout 2026-04-17 15:16:49 +03:00
gnezim 373f049e90 Use CityAutocomplete for FlightsMapFilter with geolocate on departure 2026-04-17 15:13:20 +03:00
gnezim b8d5de6ca7 Use CityAutocomplete for OnlineBoardFilter Route tab departure + arrival 2026-04-17 15:11:47 +03:00
gnezim c4ae1ef7aa Invoke useGeolocationDefault on FlightsMapStartPage mount 2026-04-17 12:22:29 +03:00
gnezim 0c65755553 Test FlightsMapFilter Calendar min/max/disabledDates + snap effect 2026-04-17 12:21:13 +03:00
gnezim 78b3e86418 Wire availableDays into FlightsMapFilter Calendar with snap-to-nearest 2026-04-17 12:20:37 +03:00
gnezim 73a3d03469 Add useGeolocationDefault hook for flights-map departure pre-fill 2026-04-17 12:17:11 +03:00
gnezim ef04c19e13 Add calendarRange helpers for flights-map date picker window and snapping 2026-04-17 12:16:22 +03:00
gnezim f4b96b8248 Test FlightsMapStartPage filterRoutes + popups + auto-fallback wiring 2026-04-17 11:06:16 +03:00
gnezim 4e92e79a99 Wire filterRoutes, auto-fallback, and buy-ticket popups into Flights Map
routesToPolylines + intermediateCityIds now normalize airport codes to city
codes via the dictionaries so API responses resolve correctly. The page adds
effectiveConnections state + two effects for Angular-parity fallback
(retry connections=1 on empty direct-route result, then flip the UI toggle),
a filterRoutes memo feeding polylines and intermediateIds, and a popups memo
rendering departure + arrival buy-ticket popups in route mode only.
2026-04-17 11:00:40 +03:00
gnezim 77272423c1 Add buildBuyTicketUrl + escapeHtml helpers for popup content 2026-04-17 10:52:32 +03:00
gnezim 40f170f87a Add filterRoutes pure helper with airport-code normalization 2026-04-17 10:52:27 +03:00
gnezim 76e9270f5e Test FlightsMapStartPage polyline + intermediateIds wiring 2026-04-17 10:14:06 +03:00
gnezim 4e103d8073 Drive polylines and intermediateIds from useFlightsMapSearch routes 2026-04-17 10:12:15 +03:00
gnezim a9ed92466f Draw routes as city-code polylines and force-open intermediate tooltips
- routesToPolylines + intermediateCityIds pure helpers with unit coverage.
- IMapPolyline reshaped from points to cityIds for Angular-parity drawing.
- MapCanvas resolves coords via markerIndex, filters invisible cities on
  every zoom/toggle change, and runs a second tooltip pass that keeps
  intermediate-city tooltips open regardless of zoom/highlight rules.
2026-04-17 10:08:44 +03:00
gnezim a9b47036b5 Test FlightsMapStartPage marker construction from dictionaries 2026-04-17 08:46:43 +03:00
gnezim 1f24ee7159 Populate FlightsMapStartPage markers from dictionaries with zoom tiers 2026-04-17 08:44:40 +03:00
gnezim 725a048315 Add categorized rendering to MapCanvas: zoom-tier layers, highlight layer, tooltip rules 2026-04-17 08:41:42 +03:00
gnezim 54f9282a99 Extend IMapMarker with zoomLevel, countryType, highlighted fields 2026-04-17 08:35:27 +03:00
gnezim a61457bc90 Port Angular CityCategoryService to feature-local cityCategory module 2026-04-17 08:35:01 +03:00
gnezim 9a7fcba6ff Test FlightsMapStartPage dictionaries loading/error wiring 2026-04-17 03:21:39 +03:00
gnezim cfc6e12dc9 Wire useDictionaries into FlightsMapStartPage loading/error states 2026-04-17 03:20:01 +03:00
gnezim 6805b8fe4d Wire FullRouteTimeline and TransferBar into OnlineBoardDetailsPage
Multi-leg flights now render a full-route timeline header and interleave
a transfer-bar between consecutive legs, surfacing station change and
intermediate-landing duration inline with the leg details.
2026-04-17 02:38:24 +03:00
gnezim 4c87a3b362 Add FullRouteTimeline wrapper component 2026-04-17 02:33:52 +03:00
gnezim b8197b2db5 Add Timeline component with 2-leg carousel for multi-leg flights 2026-04-17 02:32:42 +03:00
gnezim dd43ea6905 Add TransferBar component for multi-leg transfer info 2026-04-17 02:31:42 +03:00
gnezim 01b2981407 Add TransferTime component for layover duration display 2026-04-17 02:30:16 +03:00
gnezim 391db7c948 Add StationChange component for multi-leg timeline 2026-04-17 02:29:43 +03:00
gnezim 2d01e1a37e Add Station component for multi-leg timeline 2026-04-17 02:28:59 +03:00
gnezim 81d04bdc49 Add computeTransferMinutes and formatMinutesAsDuration helpers 2026-04-17 02:26:33 +03:00
gnezim 6854d93344 Add detectStationChange helper for multi-leg timeline 2026-04-17 02:26:33 +03:00
gnezim 009c6a3aa1 Extend IFlightLeg with optional estimatedDuration/scheduledDuration 2026-04-17 02:25:20 +03:00
gnezim 6fd42585c1 Wire DetailsBackButton and FlightSchedule into OnlineBoardDetailsPage 2026-04-17 02:07:58 +03:00
gnezim 4093a2f1b5 Add DetailsBackButton component for header navigation 2026-04-17 02:05:21 +03:00
gnezim 00f88406db Add FlightSchedule accordion with days-of-week strip 2026-04-17 02:04:36 +03:00