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.
28 KiB
Пользовательские истории: Навигация и элементы интерфейса
US-1: Навигация по основным вкладкам
Цель: Пользователь может переключаться между основными разделами приложения (Онлайн-Табло, Расписание, Карта полётов) для поиска информации о рейсах в разных режимах.
Путь клиента:
- Открытие приложения — пользователь заходит на сайт, срабатывает редирект с корневого пути на
/onlineboard, в верхней части страницы отображается компонентpage-tabsс двумя вкладками: «Онлайн-Табло» и «Расписание». - Отображение третьей вкладки — если включён feature flag
flightsMap, дополнительно появляется вкладка «Карта полётов» во втором ряду; без флага она не рендерится. - Индикация активной вкладки — текущая вкладка помечена CSS-классом
active; при наведении показывается tooltip с текстом из ключейSHARED.TAB-BOARD-TOOLTIP,SHARED.TAB-SCHEDULE-TOOLTIP,SHARED.TAB-FLIGHTS-MAP-TOOLTIP. - Клик по вкладке «Расписание» — Angular Router выполняет переход на
/schedule, URL обновляется, загружаетсяScheduleModule, активный класс переходит на новую вкладку. - Клик по вкладке «Карта полётов» — роутер через
FeatureFlagGuardпроверяет флаг и, если он включён, загружаетFlightsMapModuleпо пути/flights-map. - Возврат на Онлайн-Табло — пользователь кликает первую вкладку, URL меняется на
/onlineboard, загружаетсяOnlineBoardModule, активная вкладка снова подсвечивается.
Критерии приёмки:
- ✅ На старте пользователь попадает на
/onlineboard(редирект с/). - ✅ Видны ровно две вкладки, если
flightsMapвыключен, и три — если включён. - ✅ Клик по вкладке меняет URL на
/onlineboard,/scheduleили/flights-mapбез перезагрузки страницы. - ✅ Активная вкладка визуально отличается от неактивных (класс
active). - ✅ При наведении появляется tooltip с локализованным текстом.
- ✅ Переход между вкладками не вызывает ошибок в консоли.
Примечание: Видимость вкладки «Карта полётов» управляется feature flag flightsMap (FeatureFlagGuard + *ngIf="flightsMapEnabled" в page-tabs.component.html).
US-2: Переключение языка интерфейса
Цель: Пользователь может использовать приложение на одном из девяти поддерживаемых языков.
Путь клиента:
- Определение языка —
LocalizationServiceчитаетAPP_BASE_HREFформата/country-language(например,/ru-ru) и извлекает код языка из позиций 4-6. - Инициализация переводов — сервис вызывает
translate.addLangs(...)с полным списком (ru,en,es,fr,it,jp,ko,zh,de) и применяетtranslate.use(language). - Рендеринг интерфейса — все строки UI проходят через пайп
| translate, подставляются значения из соответствующих JSON-файлов переводов. - Выбор календарной локали — геттер
calendarTranslateвозвращает объект локализации PrimeNG Calendar (calendarRu,calendarEn, ...) согласно текущему языку. - Смена языка — пользователь переходит по URL с другим префиксом (например,
/en-us/onlineboard); приложение перезагружается с новымbaseHref, и весь интерфейс отображается на выбранном языке. - Редиректы с коротких кодов — запросы на
/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: Навигация по хлебным крошкам
Цель: Пользователь видит путь навигации на страницах результатов и деталей рейса и может вернуться на предыдущие уровни или на главную.
Путь клиента:
- Отображение компонента — на страницах результатов поиска и деталей рейса рендерится
flights-page-breadcrumbs, оборачивающий<p-breadcrumb>из PrimeNG. - Формирование списка — в
ngOnInitкомпонент подписывается наroute.dataи получает массивbreadcrumbsизIRouteData. - Первый элемент — в начало списка всегда подставляется
defaultMenuItemс ярлыкомSHARED.MAINи ссылкой наhttps://www.aeroflot.ru. - Локализация — все элементы прогоняются через
translation.instant(item.label), ключи переводятся в текущий язык. - Клик по элементу — пользователь нажимает на любой уровень, PrimeNG
p-breadcrumbвыполняет навигацию поurlэлемента (внешний URL или внутренний путь). - Отсутствие на главных страницах — на стартовых страницах разделов и на
/flights-mapмаршруты не содержатbreadcrumbsвdata, поэтому компонент не отображается.
Критерии приёмки:
- ✅ Хлебные крошки рендерятся только там, где в
route.dataесть массивbreadcrumbs. - ✅ Первый элемент всегда ведёт на
https://www.aeroflot.ruс ярлыком из ключаSHARED.MAIN. - ✅ Каждый элемент является кликабельной ссылкой.
- ✅ Метки переводятся на текущий язык интерфейса.
- ✅ На
/flights-mapи стартовых экранах разделов компонент отсутствует.
US-4: Кнопка обратной связи
Цель: Пользователь может открыть форму обратной связи и отправить сообщение разработчикам приложения.
Путь клиента:
- Отображение кнопки — в макете страницы присутствует компонент
feedback-buttonс текстом из ключаSHARED.FEEDBACK. - Клик по кнопке — обработчик
showForm()устанавливаетformVisible = true, что активирует*ngIf="formVisible"и монтирует компонентfeedback-form. - Отображение формы — на экране появляется блок
.feedback-formс кнопкой закрытия и встроенным<iframe>, загружающим Microsoft Forms по фиксированному URLhttps://forms.office.com/Pages/ResponsePage.aspx?.... - Заполнение формы — пользователь взаимодействует с формой непосредственно внутри iframe; вся логика валидации и отправки обеспечивается Microsoft Forms.
- Закрытие формы — клик по кнопке закрытия или клик за пределами формы (директива
clickOutsideуfeedback-form) вызываетhideForm(), который снимаетformVisible.
Критерии приёмки:
- ✅ Кнопка «Обратная связь» всегда видна на странице с локализованным текстом.
- ✅ Клик по кнопке показывает форму с iframe Microsoft Forms.
- ✅ Форма закрывается по клику вне её области или по кнопке закрытия.
- ✅ iframe загружается с тем же URL, что задан в шаблоне.
- ✅ Повторное открытие формы работает без перезагрузки страницы.
Примечание: Форма реализована через внешний iframe Microsoft Forms — собственной логики отправки в приложении нет.
US-5: Кнопка прокрутки к началу страницы
Цель: Пользователь может одним кликом вернуться к верху страницы при длинном списке результатов.
Путь клиента:
- Монтирование компонента —
scroll-up-buttonрендерит скрытый маркер#observer-targetи блок кнопки с классамиscroll-up/scroll-up--visible. - Наблюдение за позицией — в
ngOnInitсоздаётсяIntersectionObserverсthreshold: [1], который следит за пересечением#observer-targetс вьюпортом. - Показ кнопки при скролле — когда
entry.boundingClientRect.top < 0(цель ушла выше вьюпорта),buttonVisibleстановитсяtrueи добавляется модификаторscroll-up--visible. - Резервная проверка — каждые 500 мс вызывается
checkVisibility(), которая сверяет позицию.page-layout__headerи показывает кнопку, если шапка ушла выше вьюпорта. - Клик по кнопке —
handleClick()берёт элементflights-root, вычисляет егоgetBoundingClientRect().topи вызываетbody.scrollTo(0, body.scrollTop + top), после чего скрывает кнопку. - Уничтожение — в
ngOnDestroyнаблюдатель и интервал очищаются.
Критерии приёмки:
- ✅ Кнопка не видна, пока шапка страницы находится во вьюпорте.
- ✅ Кнопка появляется, когда пользователь прокрутил страницу ниже шапки.
- ✅ Клик по кнопке возвращает скролл body к началу корня приложения.
- ✅ Класс
scroll-up--visibleкорректно добавляется и снимается. - ✅ Интервал и IntersectionObserver освобождаются при уничтожении компонента.
US-6: Боковая панель с фильтрами
Цель: На странице Онлайн-Табло пользователь видит боковую панель с фильтрами поиска и может раскрывать нужные секции.
Путь клиента:
- Отображение секции — компонент
online-board-filterрендерит<section class="frame">с<p-accordion>от PrimeNG. - Секция «Номер рейса» — первый
p-accordionTab(data-testid="flight-filter") содержит заголовок из ключаBOARD.FLIGHT_NUMBER, иконкуarrow-down-iconи вложенныйonline-board-flight-number-filter. - Секция «Маршрут» — второй
p-accordionTab(data-testid="route-filter") содержит заголовокBOARD.ROUTE, такую же иконку иonline-board-route-filter. - Раскрытие секции — клик по заголовку вызывает событие
onOpenаккордеона; обработчикhandleOpen($event)запоминает активную секцию, иконка поворачивается (rotated). - Сворачивание — повторный клик вызывает
onCloseиhandleClose(), секция сворачивается. - Передача настроек — во вложенные фильтры передаются
settings.boardMinDateиsettings.boardMaxDate, событияonSearchпробрасываются черезhandleFlightNumberSearchиhandleRouteSearch.
Критерии приёмки:
- ✅ В панели ровно две секции: «Номер рейса» и «Маршрут».
- ✅ Заголовки переведены через ключи
BOARD.FLIGHT_NUMBERиBOARD.ROUTE. - ✅ Секции раскрываются и сворачиваются кликом по заголовку.
- ✅ Активная секция отмечена повёрнутой иконкой
arrow-down-icon. - ✅ Каждая секция имеет
data-testid(flight-filter,route-filter). - ✅ При поиске из секции вызываются соответствующие обработчики.
US-7: Информационные разделы на главной странице
Цель: На главной странице Онлайн-Табло пользователь видит блок популярных запросов и может одним кликом подставить параметры в форму поиска.
Путь клиента:
- Отображение блока — компонент
popular-requestsрендерит контейнер.popular-requestsс заголовком из ключаBOARD.POPULAR-CHAPTERS. - Рендеринг карточек — внутри контейнера последовательно выводятся до четырёх элементов
<popular-request>с*ngIf="requests[0..3]"— каждая карточка показывается только при наличии данных. - Содержимое карточки — конкретный шаблон карточки выбирается компонентом
popular-requestв зависимости от типа запроса (flight-number, arrival, departure, route). - Клик по карточке — событие
onClickподнимается вpopular-requests, обработчикhandleRequestClick($event)заполняет форму поиска параметрами выбранного запроса. - Скрытие при активном поиске — блок отображается только на стартовом экране раздела; после выполнения поиска родительский шаблон перестаёт его рендерить.
Критерии приёмки:
- ✅ Заголовок блока локализован через ключ
BOARD.POPULAR-CHAPTERS. - ✅ Максимум 4 карточки, каждая рендерится только при наличии данных.
- ✅ Клик по карточке вызывает
handleRequestClickс данными запроса. - ✅ Блок отсутствует на страницах результатов поиска.
- ✅ Карточки загружаются через
PopularRequestsModuleи связанные сервисы.
US-8: История поиска
Цель: Пользователь видит свои последние поисковые запросы на главных страницах Онлайн-Табло и Расписания и может быстро повторить любой из них.
Путь клиента:
- Отображение секции — компонент
search-historyрендерит<section class="frame search-history">, но только еслиhistoryItems.length > 0. - Аккордеон — внутри секции расположен
<p-accordion>с одним<p-accordionTab>, заголовок которого содержит ключBOARD.YOU_SEARCHиarrow-down-icon. - Список элементов — в содержимом таба через
*ngForвыводятся<search-history-item>для каждой записи изhistoryItems. - Иконка с тултипом типа поиска — каждая запись начинается с иконки: для онлайн-табло (
plane-icon) сpTooltipSHARED.LAST-SEARCH-BOARD, для расписания (alarm-clock-icon) сpTooltipSHARED.LAST-SEARCH-SCHEDULE(позицияtop, стильafl-tooltip). - Клик по элементу — обработчик
(click)="openHistoryUrl(item)"выполняет переход по сохранённому URL запроса, восстанавливая параметры формы. - Типы элементов — поддерживаются разные шаблоны записей:
online-board-flight-number-history-item,online-board-route-history-item,schedule-item— каждый отрисовывает свой набор полей. - Отсутствие на карте полётов —
search-historyне включён в шаблон страницы/flights-map, поэтому там секция не показывается.
Критерии приёмки:
- ✅ Секция видна только при наличии хотя бы одной записи.
- ✅ Заголовок таба переведён через ключ
BOARD.YOU_SEARCH. - ✅ Каждая запись — отдельный
<search-history-item>с собственным шаблоном. - ✅ Иконка типа записи сопровождается PrimeNG-тултипом (
SHARED.LAST-SEARCH-BOARDилиSHARED.LAST-SEARCH-SCHEDULE). - ✅ Клик по записи вызывает
openHistoryUrl(item)и выполняет переход. - ✅ На странице «Карта полётов» секция отсутствует.
US-9: Локализация заголовка страницы и мета-тегов
Цель: Заголовок вкладки браузера и мета-теги страницы отображаются на языке, выбранном пользователем.
Путь клиента:
- Инициализация локали — при загрузке модуля раздела
LocalizationServiceуже содержит корректныйLanguage, извлечённый изAPP_BASE_HREF. - Установка заголовка — резолвер маршрута (
SettingsResolver) подготавливает данные, а компоненты страницы черезTitleиMetaсервисы Angular устанавливаютdocument.titleи мета-теги. - Источник строк — ключи заголовков и описаний (например,
BOARD.TITLE,SCHEDULE.TITLE-TAB,FLIGHTS-MAP.TITLE) берутся из JSON-файлов переводов черезtranslate.instant. - Тултип заголовка страницы — компонент
page-titleдублирует текст заголовка вpTooltip(позицияbottom,hideDelay: 1000, стильafl-tooltip tooltip--one-line), чтобы длинные заголовки, усечённые CSS, были полностью доступны при наведении. - Применение языка — значения подставляются в
<title>,<meta name="description">и OpenGraph-теги на языке, соответствующемbaseHref. - Смена языка через URL — при переходе на префикс другого языка приложение перезагружается, резолвер и компоненты формируют мета-информацию уже на новом языке.
Критерии приёмки:
- ✅
document.titleсодержит переведённую строку, соответствующую текущему разделу. - ✅ Мета-теги
description/keywordsформируются из ключей перевода. - ✅ Смена языка через URL-префикс обновляет title и meta.
- ✅ Все три раздела (
onlineboard,schedule,flights-map) имеют собственные ключи заголовков. - ✅ Заголовок
<h1>сопровождаетсяpTooltip(bottom, hideDelay 1000) для доступности длинных усечённых заголовков. - ✅ При отсутствии перевода подставляется fallback-значение ключа.
US-10: Адаптивный дизайн
Цель: Интерфейс корректно отображается и используется на мобильных, планшетных и десктопных разрешениях.
Путь клиента:
- Мобильный экран — при ширине вьюпорта меньше breakpoint'а CSS-стили перестраивают макет: боковая панель фильтров занимает всю ширину, таблица результатов переключается на компактный режим отображения карточек.
- Планшет — на средних ширинах фильтры могут располагаться над списком результатов, шапка страницы и
page-tabsсохраняют горизонтальное расположение. - Десктоп — на широких экранах фильтры отображаются в боковой колонке, таблица результатов занимает основную область, включаются tooltip-элементы PrimeNG.
- Изменение ширины окна — пользователь меняет размер окна браузера, CSS media queries пересчитывают стили без перезагрузки страницы, компоненты PrimeNG адаптируются автоматически.
- Сохранение функциональности — все действия (поиск, фильтрация, открытие деталей, обратная связь, прокрутка вверх) остаются доступными на любом устройстве.
Критерии приёмки:
- ✅ Макет перестраивается при пересечении CSS-breakpoint'ов без перезагрузки.
- ✅ Боковая панель фильтров адаптируется под узкие экраны.
- ✅ Вкладки
page-tabsостаются кликабельными на мобильных устройствах. - ✅ Таблицы/списки результатов читаемы на малых ширинах.
- ✅ Все функции приложения доступны на мобильных, планшетных и десктопных разрешениях.
US-11: Отсутствие ошибок в консоли браузера
Цель: При типовых пользовательских сценариях в консоли браузера не появляются ошибки JavaScript и необработанные исключения.
Путь клиента:
- Открытие приложения — пользователь заходит на
/onlineboard; приложение инициализируетLocalizationService,SettingsResolverи модули без ошибок. - Навигация по вкладкам — переходы между
/onlineboard,/scheduleи (при включённом флаге)/flights-mapне вызывают исключений в Router и ленивой загрузке модулей. - Поиск и фильтрация — заполнение форм, применение фильтров и открытие результатов не приводят к ошибкам HTTP-слоя, интерцепторов или PrimeNG-компонентов.
- Открытие деталей рейса — переход на страницу деталей, обратная связь, прокрутка вверх, раскрытие истории поиска выполняются без предупреждений и ошибок.
- Проверка в DevTools — пользователь открывает вкладку Console, выполняет сценарии и не видит сообщений уровня
errorилиunhandled promise rejection.
Критерии приёмки:
- ✅ При начальной загрузке страницы в консоли нет ошибок.
- ✅ Навигация между всеми основными разделами не генерирует ошибок.
- ✅ Поиск, фильтрация и открытие деталей рейса завершаются без исключений.
- ✅ Интерактивные элементы (обратная связь, scroll-up, история) не выбрасывают ошибок.
- ✅ Отсутствуют предупреждения об устаревших API и необработанных промисах.