Files
flights_web/docs/user-stories-1-navigation-ui.md
gnezim 71d0c983fd
CI / ci (push) Failing after 28s
Deploy / build-and-deploy (push) Failing after 5s
Fix API calls: bind fetch to globalThis, fix date format for calendar
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

28 KiB
Raw Permalink Blame History

Пользовательские истории: Навигация и элементы интерфейса

US-1: Навигация по основным вкладкам

Цель: Пользователь может переключаться между основными разделами приложения (Онлайн-Табло, Расписание, Карта полётов) для поиска информации о рейсах в разных режимах.

Путь клиента:

  1. Открытие приложения — пользователь заходит на сайт, срабатывает редирект с корневого пути на /onlineboard, в верхней части страницы отображается компонент page-tabs с двумя вкладками: «Онлайн-Табло» и «Расписание».
  2. Отображение третьей вкладки — если включён feature flag flightsMap, дополнительно появляется вкладка «Карта полётов» во втором ряду; без флага она не рендерится.
  3. Индикация активной вкладки — текущая вкладка помечена CSS-классом active; при наведении показывается tooltip с текстом из ключей SHARED.TAB-BOARD-TOOLTIP, SHARED.TAB-SCHEDULE-TOOLTIP, SHARED.TAB-FLIGHTS-MAP-TOOLTIP.
  4. Клик по вкладке «Расписание» — Angular Router выполняет переход на /schedule, URL обновляется, загружается ScheduleModule, активный класс переходит на новую вкладку.
  5. Клик по вкладке «Карта полётов» — роутер через FeatureFlagGuard проверяет флаг и, если он включён, загружает FlightsMapModule по пути /flights-map.
  6. Возврат на Онлайн-Табло — пользователь кликает первую вкладку, URL меняется на /onlineboard, загружается OnlineBoardModule, активная вкладка снова подсвечивается.

Критерии приёмки:

  • На старте пользователь попадает на /onlineboard (редирект с /).
  • Видны ровно две вкладки, если flightsMap выключен, и три — если включён.
  • Клик по вкладке меняет URL на /onlineboard, /schedule или /flights-map без перезагрузки страницы.
  • Активная вкладка визуально отличается от неактивных (класс active).
  • При наведении появляется tooltip с локализованным текстом.
  • Переход между вкладками не вызывает ошибок в консоли.

Примечание: Видимость вкладки «Карта полётов» управляется feature flag flightsMap (FeatureFlagGuard + *ngIf="flightsMapEnabled" в page-tabs.component.html).


US-2: Переключение языка интерфейса

Цель: Пользователь может использовать приложение на одном из девяти поддерживаемых языков.

Путь клиента:

  1. Определение языкаLocalizationService читает APP_BASE_HREF формата /country-language (например, /ru-ru) и извлекает код языка из позиций 4-6.
  2. Инициализация переводов — сервис вызывает translate.addLangs(...) с полным списком (ru, en, es, fr, it, jp, ko, zh, de) и применяет translate.use(language).
  3. Рендеринг интерфейса — все строки UI проходят через пайп | translate, подставляются значения из соответствующих JSON-файлов переводов.
  4. Выбор календарной локали — геттер calendarTranslate возвращает объект локализации PrimeNG Calendar (calendarRu, calendarEn, ...) согласно текущему языку.
  5. Смена языка — пользователь переходит по URL с другим префиксом (например, /en-us/onlineboard); приложение перезагружается с новым baseHref, и весь интерфейс отображается на выбранном языке.
  6. Редиректы с коротких кодов — запросы на /ru, /en, /es, /fr, /it, /ja, /ko, /zh, /de перенаправляются на /onlineboard с сохранением языка из baseHref.

Критерии приёмки:

  • Поддерживаются все 9 языков из enum Language.
  • Текущий язык определяется из APP_BASE_HREF при старте приложения.
  • Все тексты, метки кнопок и плейсхолдеры локализованы через ngx-translate.
  • Календарь PrimeNG отображается на выбранном языке.
  • Короткие языковые пути в роутинге ведут на /onlineboard.

Примечание: Переключатель языка находится в шапке внешней площадки aeroflot.ru и не входит в состав Angular-приложения; внутри модуля язык фиксируется через baseHref.


US-3: Навигация по хлебным крошкам

Цель: Пользователь видит путь навигации на страницах результатов и деталей рейса и может вернуться на предыдущие уровни или на главную.

Путь клиента:

  1. Отображение компонента — на страницах результатов поиска и деталей рейса рендерится flights-page-breadcrumbs, оборачивающий <p-breadcrumb> из PrimeNG.
  2. Формирование списка — в ngOnInit компонент подписывается на route.data и получает массив breadcrumbs из IRouteData.
  3. Первый элемент — в начало списка всегда подставляется defaultMenuItem с ярлыком SHARED.MAIN и ссылкой на https://www.aeroflot.ru.
  4. Локализация — все элементы прогоняются через translation.instant(item.label), ключи переводятся в текущий язык.
  5. Клик по элементу — пользователь нажимает на любой уровень, PrimeNG p-breadcrumb выполняет навигацию по url элемента (внешний URL или внутренний путь).
  6. Отсутствие на главных страницах — на стартовых страницах разделов и на /flights-map маршруты не содержат breadcrumbs в data, поэтому компонент не отображается.

Критерии приёмки:

  • Хлебные крошки рендерятся только там, где в route.data есть массив breadcrumbs.
  • Первый элемент всегда ведёт на https://www.aeroflot.ru с ярлыком из ключа SHARED.MAIN.
  • Каждый элемент является кликабельной ссылкой.
  • Метки переводятся на текущий язык интерфейса.
  • На /flights-map и стартовых экранах разделов компонент отсутствует.

US-4: Кнопка обратной связи

Цель: Пользователь может открыть форму обратной связи и отправить сообщение разработчикам приложения.

Путь клиента:

  1. Отображение кнопки — в макете страницы присутствует компонент feedback-button с текстом из ключа SHARED.FEEDBACK.
  2. Клик по кнопке — обработчик showForm() устанавливает formVisible = true, что активирует *ngIf="formVisible" и монтирует компонент feedback-form.
  3. Отображение формы — на экране появляется блок .feedback-form с кнопкой закрытия и встроенным <iframe>, загружающим Microsoft Forms по фиксированному URL https://forms.office.com/Pages/ResponsePage.aspx?....
  4. Заполнение формы — пользователь взаимодействует с формой непосредственно внутри iframe; вся логика валидации и отправки обеспечивается Microsoft Forms.
  5. Закрытие формы — клик по кнопке закрытия или клик за пределами формы (директива clickOutside у feedback-form) вызывает hideForm(), который снимает formVisible.

Критерии приёмки:

  • Кнопка «Обратная связь» всегда видна на странице с локализованным текстом.
  • Клик по кнопке показывает форму с iframe Microsoft Forms.
  • Форма закрывается по клику вне её области или по кнопке закрытия.
  • iframe загружается с тем же URL, что задан в шаблоне.
  • Повторное открытие формы работает без перезагрузки страницы.

Примечание: Форма реализована через внешний iframe Microsoft Forms — собственной логики отправки в приложении нет.


US-5: Кнопка прокрутки к началу страницы

Цель: Пользователь может одним кликом вернуться к верху страницы при длинном списке результатов.

Путь клиента:

  1. Монтирование компонентаscroll-up-button рендерит скрытый маркер #observer-target и блок кнопки с классами scroll-up / scroll-up--visible.
  2. Наблюдение за позицией — в ngOnInit создаётся IntersectionObserver с threshold: [1], который следит за пересечением #observer-target с вьюпортом.
  3. Показ кнопки при скролле — когда entry.boundingClientRect.top < 0 (цель ушла выше вьюпорта), buttonVisible становится true и добавляется модификатор scroll-up--visible.
  4. Резервная проверка — каждые 500 мс вызывается checkVisibility(), которая сверяет позицию .page-layout__header и показывает кнопку, если шапка ушла выше вьюпорта.
  5. Клик по кнопкеhandleClick() берёт элемент flights-root, вычисляет его getBoundingClientRect().top и вызывает body.scrollTo(0, body.scrollTop + top), после чего скрывает кнопку.
  6. Уничтожение — в ngOnDestroy наблюдатель и интервал очищаются.

Критерии приёмки:

  • Кнопка не видна, пока шапка страницы находится во вьюпорте.
  • Кнопка появляется, когда пользователь прокрутил страницу ниже шапки.
  • Клик по кнопке возвращает скролл body к началу корня приложения.
  • Класс scroll-up--visible корректно добавляется и снимается.
  • Интервал и IntersectionObserver освобождаются при уничтожении компонента.

US-6: Боковая панель с фильтрами

Цель: На странице Онлайн-Табло пользователь видит боковую панель с фильтрами поиска и может раскрывать нужные секции.

Путь клиента:

  1. Отображение секции — компонент online-board-filter рендерит <section class="frame"> с <p-accordion> от PrimeNG.
  2. Секция «Номер рейса» — первый p-accordionTab (data-testid="flight-filter") содержит заголовок из ключа BOARD.FLIGHT_NUMBER, иконку arrow-down-icon и вложенный online-board-flight-number-filter.
  3. Секция «Маршрут» — второй p-accordionTab (data-testid="route-filter") содержит заголовок BOARD.ROUTE, такую же иконку и online-board-route-filter.
  4. Раскрытие секции — клик по заголовку вызывает событие onOpen аккордеона; обработчик handleOpen($event) запоминает активную секцию, иконка поворачивается (rotated).
  5. Сворачивание — повторный клик вызывает onClose и handleClose(), секция сворачивается.
  6. Передача настроек — во вложенные фильтры передаются settings.boardMinDate и settings.boardMaxDate, события onSearch пробрасываются через handleFlightNumberSearch и handleRouteSearch.

Критерии приёмки:

  • В панели ровно две секции: «Номер рейса» и «Маршрут».
  • Заголовки переведены через ключи BOARD.FLIGHT_NUMBER и BOARD.ROUTE.
  • Секции раскрываются и сворачиваются кликом по заголовку.
  • Активная секция отмечена повёрнутой иконкой arrow-down-icon.
  • Каждая секция имеет data-testid (flight-filter, route-filter).
  • При поиске из секции вызываются соответствующие обработчики.

US-7: Информационные разделы на главной странице

Цель: На главной странице Онлайн-Табло пользователь видит блок популярных запросов и может одним кликом подставить параметры в форму поиска.

Путь клиента:

  1. Отображение блока — компонент popular-requests рендерит контейнер .popular-requests с заголовком из ключа BOARD.POPULAR-CHAPTERS.
  2. Рендеринг карточек — внутри контейнера последовательно выводятся до четырёх элементов <popular-request> с *ngIf="requests[0..3]" — каждая карточка показывается только при наличии данных.
  3. Содержимое карточки — конкретный шаблон карточки выбирается компонентом popular-request в зависимости от типа запроса (flight-number, arrival, departure, route).
  4. Клик по карточке — событие onClick поднимается в popular-requests, обработчик handleRequestClick($event) заполняет форму поиска параметрами выбранного запроса.
  5. Скрытие при активном поиске — блок отображается только на стартовом экране раздела; после выполнения поиска родительский шаблон перестаёт его рендерить.

Критерии приёмки:

  • Заголовок блока локализован через ключ BOARD.POPULAR-CHAPTERS.
  • Максимум 4 карточки, каждая рендерится только при наличии данных.
  • Клик по карточке вызывает handleRequestClick с данными запроса.
  • Блок отсутствует на страницах результатов поиска.
  • Карточки загружаются через PopularRequestsModule и связанные сервисы.

US-8: История поиска

Цель: Пользователь видит свои последние поисковые запросы на главных страницах Онлайн-Табло и Расписания и может быстро повторить любой из них.

Путь клиента:

  1. Отображение секции — компонент search-history рендерит <section class="frame search-history">, но только если historyItems.length > 0.
  2. Аккордеон — внутри секции расположен <p-accordion> с одним <p-accordionTab>, заголовок которого содержит ключ BOARD.YOU_SEARCH и arrow-down-icon.
  3. Список элементов — в содержимом таба через *ngFor выводятся <search-history-item> для каждой записи из historyItems.
  4. Иконка с тултипом типа поиска — каждая запись начинается с иконки: для онлайн-табло (plane-icon) с pTooltip SHARED.LAST-SEARCH-BOARD, для расписания (alarm-clock-icon) с pTooltip SHARED.LAST-SEARCH-SCHEDULE (позиция top, стиль afl-tooltip).
  5. Клик по элементу — обработчик (click)="openHistoryUrl(item)" выполняет переход по сохранённому URL запроса, восстанавливая параметры формы.
  6. Типы элементов — поддерживаются разные шаблоны записей: online-board-flight-number-history-item, online-board-route-history-item, schedule-item — каждый отрисовывает свой набор полей.
  7. Отсутствие на карте полётовsearch-history не включён в шаблон страницы /flights-map, поэтому там секция не показывается.

Критерии приёмки:

  • Секция видна только при наличии хотя бы одной записи.
  • Заголовок таба переведён через ключ BOARD.YOU_SEARCH.
  • Каждая запись — отдельный <search-history-item> с собственным шаблоном.
  • Иконка типа записи сопровождается PrimeNG-тултипом (SHARED.LAST-SEARCH-BOARD или SHARED.LAST-SEARCH-SCHEDULE).
  • Клик по записи вызывает openHistoryUrl(item) и выполняет переход.
  • На странице «Карта полётов» секция отсутствует.

US-9: Локализация заголовка страницы и мета-тегов

Цель: Заголовок вкладки браузера и мета-теги страницы отображаются на языке, выбранном пользователем.

Путь клиента:

  1. Инициализация локали — при загрузке модуля раздела LocalizationService уже содержит корректный Language, извлечённый из APP_BASE_HREF.
  2. Установка заголовка — резолвер маршрута (SettingsResolver) подготавливает данные, а компоненты страницы через Title и Meta сервисы Angular устанавливают document.title и мета-теги.
  3. Источник строк — ключи заголовков и описаний (например, BOARD.TITLE, SCHEDULE.TITLE-TAB, FLIGHTS-MAP.TITLE) берутся из JSON-файлов переводов через translate.instant.
  4. Тултип заголовка страницы — компонент page-title дублирует текст заголовка в pTooltip (позиция bottom, hideDelay: 1000, стиль afl-tooltip tooltip--one-line), чтобы длинные заголовки, усечённые CSS, были полностью доступны при наведении.
  5. Применение языка — значения подставляются в <title>, <meta name="description"> и OpenGraph-теги на языке, соответствующем baseHref.
  6. Смена языка через URL — при переходе на префикс другого языка приложение перезагружается, резолвер и компоненты формируют мета-информацию уже на новом языке.

Критерии приёмки:

  • document.title содержит переведённую строку, соответствующую текущему разделу.
  • Мета-теги description/keywords формируются из ключей перевода.
  • Смена языка через URL-префикс обновляет title и meta.
  • Все три раздела (onlineboard, schedule, flights-map) имеют собственные ключи заголовков.
  • Заголовок <h1> сопровождается pTooltip (bottom, hideDelay 1000) для доступности длинных усечённых заголовков.
  • При отсутствии перевода подставляется fallback-значение ключа.

US-10: Адаптивный дизайн

Цель: Интерфейс корректно отображается и используется на мобильных, планшетных и десктопных разрешениях.

Путь клиента:

  1. Мобильный экран — при ширине вьюпорта меньше breakpoint'а CSS-стили перестраивают макет: боковая панель фильтров занимает всю ширину, таблица результатов переключается на компактный режим отображения карточек.
  2. Планшет — на средних ширинах фильтры могут располагаться над списком результатов, шапка страницы и page-tabs сохраняют горизонтальное расположение.
  3. Десктоп — на широких экранах фильтры отображаются в боковой колонке, таблица результатов занимает основную область, включаются tooltip-элементы PrimeNG.
  4. Изменение ширины окна — пользователь меняет размер окна браузера, CSS media queries пересчитывают стили без перезагрузки страницы, компоненты PrimeNG адаптируются автоматически.
  5. Сохранение функциональности — все действия (поиск, фильтрация, открытие деталей, обратная связь, прокрутка вверх) остаются доступными на любом устройстве.

Критерии приёмки:

  • Макет перестраивается при пересечении CSS-breakpoint'ов без перезагрузки.
  • Боковая панель фильтров адаптируется под узкие экраны.
  • Вкладки page-tabs остаются кликабельными на мобильных устройствах.
  • Таблицы/списки результатов читаемы на малых ширинах.
  • Все функции приложения доступны на мобильных, планшетных и десктопных разрешениях.

US-11: Отсутствие ошибок в консоли браузера

Цель: При типовых пользовательских сценариях в консоли браузера не появляются ошибки JavaScript и необработанные исключения.

Путь клиента:

  1. Открытие приложения — пользователь заходит на /onlineboard; приложение инициализирует LocalizationService, SettingsResolver и модули без ошибок.
  2. Навигация по вкладкам — переходы между /onlineboard, /schedule и (при включённом флаге) /flights-map не вызывают исключений в Router и ленивой загрузке модулей.
  3. Поиск и фильтрация — заполнение форм, применение фильтров и открытие результатов не приводят к ошибкам HTTP-слоя, интерцепторов или PrimeNG-компонентов.
  4. Открытие деталей рейса — переход на страницу деталей, обратная связь, прокрутка вверх, раскрытие истории поиска выполняются без предупреждений и ошибок.
  5. Проверка в DevTools — пользователь открывает вкладку Console, выполняет сценарии и не видит сообщений уровня error или unhandled promise rejection.

Критерии приёмки:

  • При начальной загрузке страницы в консоли нет ошибок.
  • Навигация между всеми основными разделами не генерирует ошибок.
  • Поиск, фильтрация и открытие деталей рейса завершаются без исключений.
  • Интерактивные элементы (обратная связь, scroll-up, история) не выбрасывают ошибок.
  • Отсутствуют предупреждения об устаревших API и необработанных промисах.