Files
flights_web/docs/user-stories-2-online-board.md
T
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

23 KiB
Raw Blame History

Пользовательские истории: Онлайн доска полётов

US-12: Поиск по номеру рейса

Цель: Пользователь хочет найти конкретный рейс, зная его номер и дату вылета.

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

  1. Открытие фильтра — пользователь открывает аккордеон «Номер рейса» в панели фильтров онлайн-табло; активная вкладка выделяется цветом.
  2. Ввод номера — в составном поле слева зафиксирован префикс «SU», справа — текстовое поле с плейсхолдером (пример «1234»), максимальная длина 5 символов.
  3. Валидация формата — сервис проверяет регулярное выражение ^\d\d\d\d?[A-Za-z]?$: 3–4 цифры и опциональный буквенный суффикс; при ошибке показывается кастомный компонент <tooltip> с локализованным сообщением (не PrimeNG pTooltip, а отдельный всплывающий блок ошибки).
  4. Выбор даты — ниже расположен calendar-input с меткой «Дата вылета»; диапазон ограничен boardMinDate/boardMaxDate, недоступные даты заблокированы.
  5. Автодополнение номера — при потере фокуса метод addZeros() дополняет номер нулями до 4 цифр и отделяет буквенный суффикс (например, ввод 140014, 1402A1402 + суффикс A).
  6. Запуск поиска — кнопка «Найти» активна; при успешной валидации вызывается handleFlightNumberSearch, происходит переход на страницу результатов.
  7. Очистка поля — кнопка-крестик справа от ввода очищает номер одним кликом.

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

  • Префикс «SU» всегда отображается и не редактируется.
  • Валидация отклоняет буквы в цифровой части и номера длиннее 5 символов.
  • Пустой номер показывает ошибку FLIGHT_NUMBER-ERROR-EMPTY.
  • Некорректный формат показывает ошибку FLIGHT_NUMBER-ERROR-ONLY-NUMBER.
  • Поиск не запускается, если не выбрана валидная дата.
  • При потере фокуса addZeros() дополняет номер до 4 цифр и выделяет буквенный суффикс.
  • Кнопка очистки сбрасывает значение номера рейса.

US-13: Выбор даты для поиска в онлайн-табло

Цель: Пользователь хочет указать дату вылета в пределах разрешённого окна онлайн-табло.

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

  1. Открытие календаря — пользователь кликает по calendar-input с меткой «Дата вылета»; открывается поповер с месячной сеткой.
  2. Ограничение диапазона — календарь принимает minDate = boardMinDate (вчера, −1 день) и maxDate = boardMaxDate (по умолчанию +7 дней от сегодня, настраивается через серверный контракт boardSearchTo).
  3. Блокировка недоступных дат — массив disabledDates затемняет даты, по которым нет расписания, клик по ним игнорируется.
  4. Быстрые кнопки — под календарной сеткой отображаются кнопки «Сегодня» и «Завтра» (локализованные), позволяющие выбрать дату одним кликом без навигации по месяцам.
  5. Выбор даты — при клике на активную дату значение записывается в модель фильтра, поповер закрывается.
  6. Автообновление диапазона в полночь — таймер отслеживает смену суток и автоматически сдвигает boardMinDate/boardMaxDate без перезагрузки страницы.
  7. Отображение ошибки — некорректное значение (пустое или невалидное) показывает сообщение SHARED.DATE_FORMAT-WRONG под полем.
  8. Синхронизация с результатами — выбранная дата передаётся в страницу поиска и синхронизируется с компонентом day-tabs.

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

  • Недоступны даты раньше boardMinDate и позже boardMaxDate.
  • Даты из disabledDates визуально выключены и не кликабельны.
  • Невалидная дата вызывает показ сообщения об ошибке.
  • Быстрые кнопки «Сегодня»/«Завтра» позволяют выбрать дату одним кликом.
  • При смене суток min/max даты автоматически сдвигаются через таймер.
  • Выбранная дата сохраняется в модели и передаётся в URL при поиске.
  • Календарь закрывается после выбора даты.

US-14: Поиск по городу отправления

Цель: Пользователь хочет посмотреть все вылеты из указанного города на выбранную дату.

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

  1. Открытие аккордеона «Маршрут» — раскрывается вкладка с формой маршрутного поиска внутри online-board-filter.
  2. Ввод города отправления — компонент city-autocomplete с меткой «Город отправления» принимает ввод и показывает выпадающий список подсказок. Поддерживается конвертация раскладки клавиатуры (ruKeyboardLayout): пользователь может набирать латиницей при включённой русской раскладке, и наоборот.
  3. Выбор из подсказок — по мере набора сервис cities-search-service возвращает совпадения; клик по элементу подставляет город в поле. В выпадающем списке city-select, если у города несколько аэропортов, каждый аэропорт сопровождается pTooltip с полным названием аэропорта (airport.name, позиция top).
  4. Оставление поля прибытия пустым — для поиска только по отправлению пользователь не заполняет второе поле.
  5. Выбор даты и запуск поиска — указывается дата в calendar-input, клик по «Найти» переходит на страницу результатов вылетов.
  6. Обработка ошибки — если город не выбран из списка, показывается ошибка departureError под полем.

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

  • Автодополнение работает при вводе минимум нескольких символов.
  • Поддерживается конвертация раскладки клавиатуры (русская ↔ латиница).
  • При точном совпадении ввода с кодом или названием города происходит авто-выбор.
  • Принимается только город, выбранный из списка подсказок.
  • При наличии нескольких аэропортов у города каждый показывает pTooltip с полным названием.
  • Ошибка отображается, если город введён, но не распознан.
  • Поиск возможен только с заполненным полем отправления и валидной датой.
  • Результаты содержат только рейсы с вылетом из указанного города.

US-15: Поиск полётов с прибытием в конкретный город

Цель: Пользователь хочет увидеть все рейсы, прибывающие в указанный город на выбранную дату.

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

  1. Открытие маршрутного фильтра — раскрывается вкладка «Маршрут» в online-board-filter.
  2. Ввод города прибытия — во втором поле city-autocomplete с меткой «Город прибытия» пользователь вводит название.
  3. Выбор из автодополнения — подсказки формируются сервисом, клик подставляет валидный город в модель.
  4. Пустое поле отправления — поле отправления остаётся пустым для поиска по направлению прибытия.
  5. Ввод даты и запуск — выбирается дата, клик «Найти» инициирует поиск типа прибытия.
  6. Переход на страницу прибытий — результаты отображаются как список рейсов, прибывающих в город.

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

  • Автодополнение выдаёт подсказки для города прибытия.
  • При невалидном вводе показывается ошибка arrivalError.
  • Поиск с заполненным только полем прибытия корректно запускается.
  • Результаты ограничены рейсами с прибытием в указанный город.
  • Выбранная дата передаётся в запрос.

US-16: Поиск маршрута между двумя городами

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

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

  1. Заполнение отправления — в city-autocomplete с меткой «Город отправления» выбирается город из подсказок.
  2. Заполнение прибытия — во втором поле аналогично выбирается город назначения.
  3. Кнопка обмена — между полями расположена кнопка со SVG-иконкой changeCity; клик вызывает метод exchange() и меняет значения полей местами.
  4. Выбор даты — используется calendar-input с ограничениями boardMinDate/boardMaxDate.
  5. Опциональный фильтр времени — под датой компонент time-selector в режиме fullView=false позволяет ограничить диапазон времени вылета.
  6. Автозаполнение по геолокации — если UserLocationService определил местоположение пользователя и поле отправления пусто, оно предзаполняется кодом ближайшей станции.
  7. Запуск поиска — кнопка «Найти» вызывает handleRouteSearch, результаты открываются на маршрутной странице.

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

  • Оба города должны быть выбраны из автодополнения.
  • Кнопка обмена меняет значения полей отправления и прибытия местами.
  • Ошибки для полей отправления/прибытия отображаются независимо.
  • Результаты содержат только рейсы между выбранной парой городов.
  • Значение timeRange передаётся в запрос вместе с датой.

US-17: Переключение дня вылета в результатах

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

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

  1. Отображение дневных вкладок — над списком результатов расположен компонент day-tabs с заголовком «Дата вылета».
  2. Генерация диапазона — вкладки формируются между boardMinDate и boardMaxDate (до 16 дней), недоступные даты из disabledDates выключены.
  3. Выделение текущего дня — выбранная дата (searchDate) визуально подсвечена как активная.
  4. Клик по другому дню — событие tabClick вызывает handleDateChanged($event), который перезапускает поиск для новой даты.
  5. Синхронизация с фильтром — новая дата попадает в URL, значение в calendar-input фильтра обновляется одновременно.

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

  • Вкладки отображаются только для дат в диапазоне boardMinDate..boardMaxDate.
  • Даты из disabledDates отрисованы как неактивные и не кликабельны.
  • Активная вкладка соответствует текущему searchDate.
  • Клик по вкладке запускает новый поиск без полного перезагруза страницы.
  • URL и состояние фильтра остаются согласованными с активной датой.

US-18: Фильтр по времени вылета или прибытия

Цель: Пользователь хочет ограничить результаты рейсами в заданном временном окне.

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

  1. Проверка флага — компонент time-selector в блоке результатов отображается только при включённом фичефлаге RESULTS_TIME_SELECTOR.
  2. Выбор метки — метка динамически переключается между «Время вылета» и «Время прибытия» в зависимости от requestType.
  3. Режим полного отображенияfullView=true: слайдер с двумя ползунками и подписями начала/конца диапазона.
  4. Перетаскивание ползунков — пользователь задаёт timeFrom и timeTo, значение пишется в модель timeRange.
  5. Завершение жеста — событие slideComplete вызывает handleTimeChanged(), список рейсов перезапрашивается с новым диапазоном.
  6. Блокировка для «рейса по номеру» — при requestType === Flight селектор визуально отключён.

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

  • Селектор скрыт, если фичефлаг RESULTS_TIME_SELECTOR выключен.
  • Метка соответствует типу запроса (вылет/прибытие).
  • Перетаскивание ползунков обновляет модель timeRange.
  • Новый запрос отправляется только после отпускания ползунка (slideComplete).
  • Для поиска по номеру рейса селектор неактивен.

US-19: Расширенный просмотр рейса в результатах

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

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

  1. Просмотр списка — компонент board-search-result отображает список рейсов, каждый элемент кликабелен.
  2. Выделение текущего рейса — ближайший к текущему времени рейс автоматически определяется утилитой find-closest-flight и помечается как currentFlight.
  3. Клик по рейсу — событие changeCurrentFlight обновляет активный рейс в списке.
  4. Переход к деталям — событие toDetails вызывает handleToDetailsEvent($event), который навигирует на страницу flight-details.
  5. Сохранение контекста — URL содержит параметры поиска, чтобы при возврате список сохранил состояние.

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

  • Ближайший по времени рейс помечен как текущий при первом рендере.
  • Клик по строке рейса меняет выделенный currentFlight.
  • Переход к деталям сохраняет параметры исходного поиска.
  • При возврате назад список результатов остаётся на той же позиции.

Примечание: В Angular-коде детальный просмотр реализован как отдельная страница flight-details, а не как инлайн-расширение строки.


US-20: Обработка пустых результатов поиска

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

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

  1. Завершение поиска — компонент online-board-search получает пустой массив flights и скрывает список результатов.
  2. Показ заглушки — вместо списка отображается page-empty-list со скроллом к заглушке (scrollTo=true).
  3. Особый случай для поиска по номеру — если requestType === Flight и сервис вернул партнёров, вместо page-empty-list показывается partners-redirect-note со списком партнёров.
  4. Сохранение фильтров — панель фильтра и дневные вкладки остаются видимыми, чтобы пользователь мог изменить критерии без перезагрузки.
  5. Повторный поиск — изменение даты, времени или критерия автоматически инициирует новый запрос.

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

  • При пустом списке рейсов и не-Flight запросе отображается page-empty-list.
  • Для поиска по номеру рейса с партнёрами отображается partners-redirect-note.
  • Панель фильтров и day-tabs остаются доступными для повторного поиска.
  • Заглушка прокручивается в зону видимости пользователя.
  • Изменение критерия сразу запускает новый запрос.

US-22: Индикатор загрузки при поиске

Цель: Пользователь должен видеть чёткий индикатор процесса загрузки и иметь возможность отменить его.

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

  1. Запуск запроса — при инициировании поиска dataSource.loading устанавливается в true.
  2. Блокировка фильтра и заголовка — компонент spin-lock накладывается на header-left и content-left, блокируя взаимодействие с фильтром и вкладками.
  3. Полноэкранный лоадер — в основной области показывается page-loader с кнопкой отмены запроса.
  4. Отмена поиска — клик по кнопке отмены в page-loader вызывает cancel()handleRequestCancellation(), прерывая запрос.
  5. Завершение — при получении ответа loading становится false, лоадеры исчезают, отображается список рейсов либо пустая заглушка.

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

  • Во время запроса отображаются spin-lock над фильтром и page-loader в контенте.
  • Кнопка отмены в page-loader прерывает активный запрос.
  • После отмены пользователь остаётся на странице с предыдущим результатом или пустым состоянием.
  • После завершения загрузки все индикаторы скрываются.
  • Повторный поиск корректно перезапускает цикл загрузки.