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.
23 KiB
Пользовательские истории: Онлайн доска полётов
US-12: Поиск по номеру рейса
Цель: Пользователь хочет найти конкретный рейс, зная его номер и дату вылета.
Путь клиента:
- Открытие фильтра — пользователь открывает аккордеон «Номер рейса» в панели фильтров онлайн-табло; активная вкладка выделяется цветом.
- Ввод номера — в составном поле слева зафиксирован префикс «SU», справа — текстовое поле с плейсхолдером (пример «1234»), максимальная длина 5 символов.
- Валидация формата — сервис проверяет регулярное выражение
^\d\d\d\d?[A-Za-z]?$: 3–4 цифры и опциональный буквенный суффикс; при ошибке показывается кастомный компонент<tooltip>с локализованным сообщением (не PrimeNGpTooltip, а отдельный всплывающий блок ошибки). - Выбор даты — ниже расположен
calendar-inputс меткой «Дата вылета»; диапазон ограниченboardMinDate/boardMaxDate, недоступные даты заблокированы. - Автодополнение номера — при потере фокуса метод
addZeros()дополняет номер нулями до 4 цифр и отделяет буквенный суффикс (например, ввод14→0014,1402A→1402+ суффиксA). - Запуск поиска — кнопка «Найти» активна; при успешной валидации вызывается
handleFlightNumberSearch, происходит переход на страницу результатов. - Очистка поля — кнопка-крестик справа от ввода очищает номер одним кликом.
Критерии приёмки:
- ✅ Префикс «SU» всегда отображается и не редактируется.
- ✅ Валидация отклоняет буквы в цифровой части и номера длиннее 5 символов.
- ✅ Пустой номер показывает ошибку
FLIGHT_NUMBER-ERROR-EMPTY. - ✅ Некорректный формат показывает ошибку
FLIGHT_NUMBER-ERROR-ONLY-NUMBER. - ✅ Поиск не запускается, если не выбрана валидная дата.
- ✅ При потере фокуса
addZeros()дополняет номер до 4 цифр и выделяет буквенный суффикс. - ✅ Кнопка очистки сбрасывает значение номера рейса.
US-13: Выбор даты для поиска в онлайн-табло
Цель: Пользователь хочет указать дату вылета в пределах разрешённого окна онлайн-табло.
Путь клиента:
- Открытие календаря — пользователь кликает по
calendar-inputс меткой «Дата вылета»; открывается поповер с месячной сеткой. - Ограничение диапазона — календарь принимает
minDate = boardMinDate(вчера, −1 день) иmaxDate = boardMaxDate(по умолчанию +7 дней от сегодня, настраивается через серверный контрактboardSearchTo). - Блокировка недоступных дат — массив
disabledDatesзатемняет даты, по которым нет расписания, клик по ним игнорируется. - Быстрые кнопки — под календарной сеткой отображаются кнопки «Сегодня» и «Завтра» (локализованные), позволяющие выбрать дату одним кликом без навигации по месяцам.
- Выбор даты — при клике на активную дату значение записывается в модель фильтра, поповер закрывается.
- Автообновление диапазона в полночь — таймер отслеживает смену суток и автоматически сдвигает
boardMinDate/boardMaxDateбез перезагрузки страницы. - Отображение ошибки — некорректное значение (пустое или невалидное) показывает сообщение
SHARED.DATE_FORMAT-WRONGпод полем. - Синхронизация с результатами — выбранная дата передаётся в страницу поиска и синхронизируется с компонентом
day-tabs.
Критерии приёмки:
- ✅ Недоступны даты раньше
boardMinDateи позжеboardMaxDate. - ✅ Даты из
disabledDatesвизуально выключены и не кликабельны. - ✅ Невалидная дата вызывает показ сообщения об ошибке.
- ✅ Быстрые кнопки «Сегодня»/«Завтра» позволяют выбрать дату одним кликом.
- ✅ При смене суток min/max даты автоматически сдвигаются через таймер.
- ✅ Выбранная дата сохраняется в модели и передаётся в URL при поиске.
- ✅ Календарь закрывается после выбора даты.
US-14: Поиск по городу отправления
Цель: Пользователь хочет посмотреть все вылеты из указанного города на выбранную дату.
Путь клиента:
- Открытие аккордеона «Маршрут» — раскрывается вкладка с формой маршрутного поиска внутри
online-board-filter. - Ввод города отправления — компонент
city-autocompleteс меткой «Город отправления» принимает ввод и показывает выпадающий список подсказок. Поддерживается конвертация раскладки клавиатуры (ruKeyboardLayout): пользователь может набирать латиницей при включённой русской раскладке, и наоборот. - Выбор из подсказок — по мере набора сервис
cities-search-serviceвозвращает совпадения; клик по элементу подставляет город в поле. В выпадающем спискеcity-select, если у города несколько аэропортов, каждый аэропорт сопровождаетсяpTooltipс полным названием аэропорта (airport.name, позицияtop). - Оставление поля прибытия пустым — для поиска только по отправлению пользователь не заполняет второе поле.
- Выбор даты и запуск поиска — указывается дата в
calendar-input, клик по «Найти» переходит на страницу результатов вылетов. - Обработка ошибки — если город не выбран из списка, показывается ошибка
departureErrorпод полем.
Критерии приёмки:
- ✅ Автодополнение работает при вводе минимум нескольких символов.
- ✅ Поддерживается конвертация раскладки клавиатуры (русская ↔ латиница).
- ✅ При точном совпадении ввода с кодом или названием города происходит авто-выбор.
- ✅ Принимается только город, выбранный из списка подсказок.
- ✅ При наличии нескольких аэропортов у города каждый показывает
pTooltipс полным названием. - ✅ Ошибка отображается, если город введён, но не распознан.
- ✅ Поиск возможен только с заполненным полем отправления и валидной датой.
- ✅ Результаты содержат только рейсы с вылетом из указанного города.
US-15: Поиск полётов с прибытием в конкретный город
Цель: Пользователь хочет увидеть все рейсы, прибывающие в указанный город на выбранную дату.
Путь клиента:
- Открытие маршрутного фильтра — раскрывается вкладка «Маршрут» в
online-board-filter. - Ввод города прибытия — во втором поле
city-autocompleteс меткой «Город прибытия» пользователь вводит название. - Выбор из автодополнения — подсказки формируются сервисом, клик подставляет валидный город в модель.
- Пустое поле отправления — поле отправления остаётся пустым для поиска по направлению прибытия.
- Ввод даты и запуск — выбирается дата, клик «Найти» инициирует поиск типа прибытия.
- Переход на страницу прибытий — результаты отображаются как список рейсов, прибывающих в город.
Критерии приёмки:
- ✅ Автодополнение выдаёт подсказки для города прибытия.
- ✅ При невалидном вводе показывается ошибка
arrivalError. - ✅ Поиск с заполненным только полем прибытия корректно запускается.
- ✅ Результаты ограничены рейсами с прибытием в указанный город.
- ✅ Выбранная дата передаётся в запрос.
US-16: Поиск маршрута между двумя городами
Цель: Пользователь хочет найти рейсы между конкретной парой городов на выбранную дату.
Путь клиента:
- Заполнение отправления — в
city-autocompleteс меткой «Город отправления» выбирается город из подсказок. - Заполнение прибытия — во втором поле аналогично выбирается город назначения.
- Кнопка обмена — между полями расположена кнопка со SVG-иконкой
changeCity; клик вызывает методexchange()и меняет значения полей местами. - Выбор даты — используется
calendar-inputс ограничениямиboardMinDate/boardMaxDate. - Опциональный фильтр времени — под датой компонент
time-selectorв режимеfullView=falseпозволяет ограничить диапазон времени вылета. - Автозаполнение по геолокации — если
UserLocationServiceопределил местоположение пользователя и поле отправления пусто, оно предзаполняется кодом ближайшей станции. - Запуск поиска — кнопка «Найти» вызывает
handleRouteSearch, результаты открываются на маршрутной странице.
Критерии приёмки:
- ✅ Оба города должны быть выбраны из автодополнения.
- ✅ Кнопка обмена меняет значения полей отправления и прибытия местами.
- ✅ Ошибки для полей отправления/прибытия отображаются независимо.
- ✅ Результаты содержат только рейсы между выбранной парой городов.
- ✅ Значение
timeRangeпередаётся в запрос вместе с датой.
US-17: Переключение дня вылета в результатах
Цель: Пользователь хочет быстро переключиться на соседний день, оставаясь в результатах поиска.
Путь клиента:
- Отображение дневных вкладок — над списком результатов расположен компонент
day-tabsс заголовком «Дата вылета». - Генерация диапазона — вкладки формируются между
boardMinDateиboardMaxDate(до 16 дней), недоступные даты изdisabledDatesвыключены. - Выделение текущего дня — выбранная дата (
searchDate) визуально подсвечена как активная. - Клик по другому дню — событие
tabClickвызываетhandleDateChanged($event), который перезапускает поиск для новой даты. - Синхронизация с фильтром — новая дата попадает в URL, значение в
calendar-inputфильтра обновляется одновременно.
Критерии приёмки:
- ✅ Вкладки отображаются только для дат в диапазоне
boardMinDate..boardMaxDate. - ✅ Даты из
disabledDatesотрисованы как неактивные и не кликабельны. - ✅ Активная вкладка соответствует текущему
searchDate. - ✅ Клик по вкладке запускает новый поиск без полного перезагруза страницы.
- ✅ URL и состояние фильтра остаются согласованными с активной датой.
US-18: Фильтр по времени вылета или прибытия
Цель: Пользователь хочет ограничить результаты рейсами в заданном временном окне.
Путь клиента:
- Проверка флага — компонент
time-selectorв блоке результатов отображается только при включённом фичефлагеRESULTS_TIME_SELECTOR. - Выбор метки — метка динамически переключается между «Время вылета» и «Время прибытия» в зависимости от
requestType. - Режим полного отображения —
fullView=true: слайдер с двумя ползунками и подписями начала/конца диапазона. - Перетаскивание ползунков — пользователь задаёт
timeFromиtimeTo, значение пишется в модельtimeRange. - Завершение жеста — событие
slideCompleteвызываетhandleTimeChanged(), список рейсов перезапрашивается с новым диапазоном. - Блокировка для «рейса по номеру» — при
requestType === Flightселектор визуально отключён.
Критерии приёмки:
- ✅ Селектор скрыт, если фичефлаг
RESULTS_TIME_SELECTORвыключен. - ✅ Метка соответствует типу запроса (вылет/прибытие).
- ✅ Перетаскивание ползунков обновляет модель
timeRange. - ✅ Новый запрос отправляется только после отпускания ползунка (
slideComplete). - ✅ Для поиска по номеру рейса селектор неактивен.
US-19: Расширенный просмотр рейса в результатах
Цель: Пользователь хочет увидеть детальную информацию по конкретному рейсу прямо в списке результатов.
Путь клиента:
- Просмотр списка — компонент
board-search-resultотображает список рейсов, каждый элемент кликабелен. - Выделение текущего рейса — ближайший к текущему времени рейс автоматически определяется утилитой
find-closest-flightи помечается какcurrentFlight. - Клик по рейсу — событие
changeCurrentFlightобновляет активный рейс в списке. - Переход к деталям — событие
toDetailsвызываетhandleToDetailsEvent($event), который навигирует на страницуflight-details. - Сохранение контекста — URL содержит параметры поиска, чтобы при возврате список сохранил состояние.
Критерии приёмки:
- ✅ Ближайший по времени рейс помечен как текущий при первом рендере.
- ✅ Клик по строке рейса меняет выделенный
currentFlight. - ✅ Переход к деталям сохраняет параметры исходного поиска.
- ✅ При возврате назад список результатов остаётся на той же позиции.
Примечание: В Angular-коде детальный просмотр реализован как отдельная страница flight-details, а не как инлайн-расширение строки.
US-20: Обработка пустых результатов поиска
Цель: Пользователь должен получить понятное сообщение, если по его критериям рейсы не найдены.
Путь клиента:
- Завершение поиска — компонент
online-board-searchполучает пустой массивflightsи скрывает список результатов. - Показ заглушки — вместо списка отображается
page-empty-listсо скроллом к заглушке (scrollTo=true). - Особый случай для поиска по номеру — если
requestType === Flightи сервис вернул партнёров, вместоpage-empty-listпоказываетсяpartners-redirect-noteсо списком партнёров. - Сохранение фильтров — панель фильтра и дневные вкладки остаются видимыми, чтобы пользователь мог изменить критерии без перезагрузки.
- Повторный поиск — изменение даты, времени или критерия автоматически инициирует новый запрос.
Критерии приёмки:
- ✅ При пустом списке рейсов и не-Flight запросе отображается
page-empty-list. - ✅ Для поиска по номеру рейса с партнёрами отображается
partners-redirect-note. - ✅ Панель фильтров и
day-tabsостаются доступными для повторного поиска. - ✅ Заглушка прокручивается в зону видимости пользователя.
- ✅ Изменение критерия сразу запускает новый запрос.
US-22: Индикатор загрузки при поиске
Цель: Пользователь должен видеть чёткий индикатор процесса загрузки и иметь возможность отменить его.
Путь клиента:
- Запуск запроса — при инициировании поиска
dataSource.loadingустанавливается вtrue. - Блокировка фильтра и заголовка — компонент
spin-lockнакладывается наheader-leftиcontent-left, блокируя взаимодействие с фильтром и вкладками. - Полноэкранный лоадер — в основной области показывается
page-loaderс кнопкой отмены запроса. - Отмена поиска — клик по кнопке отмены в
page-loaderвызываетcancel()→handleRequestCancellation(), прерывая запрос. - Завершение — при получении ответа
loadingстановитсяfalse, лоадеры исчезают, отображается список рейсов либо пустая заглушка.
Критерии приёмки:
- ✅ Во время запроса отображаются
spin-lockнад фильтром иpage-loaderв контенте. - ✅ Кнопка отмены в
page-loaderпрерывает активный запрос. - ✅ После отмены пользователь остаётся на странице с предыдущим результатом или пустым состоянием.
- ✅ После завершения загрузки все индикаторы скрываются.
- ✅ Повторный поиск корректно перезапускает цикл загрузки.