feat(phase-8-frontend): results loader UI + browsing list + 41 localization keys

* Pages/Results/ResultsList.razor — completed-events list with date range,
  sport/winner filter, search, footer count.
* Pages/Results/ResultsLoader.razor — driver page with two modes (load all
  in range / load selected events), live progress reporting via
  IProgress<PullResultsProgress>, summary line, cancellable.
* Replaces the Phase 5 Pages/Results.razor placeholder.

Service layer:
* IResultsBrowsingService + ResultsBrowsingService (Scoped, mirrors the
  Event/Anomaly browsing-service pattern). Reads IResultRepository +
  IEventRepository, projects to immutable view-model records.
* UiServicesExtensions: registers ResultsBrowsingService; also fixes an
  unrelated localization resolver bug (drop ResourcesPath since
  SharedResource lives in the Marathon.UI.Resources namespace already).

Localization:
* 41 new Results.* keys (RU+EN parity) covering both pages, filter chips,
  loader modes, progress states, and footer copy.

Tests:
* ResultsListTests + ResultsLoaderTests — 22 new bUnit tests covering
  filter narrowing, mode switching, progress aggregation, and empty
  states.
* FakeResultsBrowsingService support type for tests.
* MarathonTestContext registers the fake; TestData adds factories for
  EventResult/EventResultListItem.
This commit is contained in:
2026-05-09 15:10:49 +03:00
parent 9c5d3df1f2
commit 9f090cec1f
13 changed files with 1407 additions and 6 deletions
@@ -271,4 +271,47 @@
<data name="Sport.Football"><value>Футбол</value></data>
<data name="Sport.Tennis"><value>Теннис</value></data>
<data name="Sport.Hockey"><value>Хоккей</value></data>
<data name="Results.Title"><value>Результаты матчей</value></data>
<data name="Results.Lede"><value>Финальные счета загруженных событий. Обходим страницу события, ждём matchIsComplete=true, фиксируем сторону-победителя.</value></data>
<data name="Results.Action.LoadNew"><value>Загрузить результаты</value></data>
<data name="Results.Action.OpenList"><value>К списку</value></data>
<data name="Results.Filter.From"><value>С</value></data>
<data name="Results.Filter.To"><value>По</value></data>
<data name="Results.Filter.Search"><value>Поиск</value></data>
<data name="Results.Filter.Search.Placeholder"><value>Команда, лига, категория…</value></data>
<data name="Results.Filter.Sport"><value>Спорт</value></data>
<data name="Results.Filter.Winner"><value>Победитель</value></data>
<data name="Results.Filter.Winner.All"><value>Любой</value></data>
<data name="Results.Filter.Winner.Side1"><value>Команда 1</value></data>
<data name="Results.Filter.Winner.Side2"><value>Команда 2</value></data>
<data name="Results.Filter.Winner.Draw"><value>Ничья</value></data>
<data name="Results.Column.Time"><value>Время</value></data>
<data name="Results.Column.Country"><value>Страна</value></data>
<data name="Results.Column.League"><value>Лига</value></data>
<data name="Results.Column.Match"><value>Матч</value></data>
<data name="Results.Column.Score"><value>Счёт</value></data>
<data name="Results.Column.Winner"><value>Победитель</value></data>
<data name="Results.Column.CompletedAt"><value>Завершено</value></data>
<data name="Results.Empty"><value>Результатов в выбранном диапазоне ещё нет. Запустите загрузку или подождите завершения матчей.</value></data>
<data name="Results.Footer.Items"><value>результатов</value></data>
<data name="Results.Loader.Kicker"><value>Загрузка</value></data>
<data name="Results.Loader.Title"><value>Загрузить результаты</value></data>
<data name="Results.Loader.Lede"><value>Опросим страницу каждого события, заберём финальный счёт и сторону-победителя. Выберите диапазон или конкретные события.</value></data>
<data name="Results.Loader.Mode"><value>Режим</value></data>
<data name="Results.Loader.Mode.AllInRange"><value>Все в диапазоне</value></data>
<data name="Results.Loader.Mode.Selected"><value>Выбранные события</value></data>
<data name="Results.Loader.Selected.Empty"><value>Все события в этом диапазоне уже имеют результат.</value></data>
<data name="Results.Loader.Selected.CountFormat"><value>{0} выбрано</value></data>
<data name="Results.Loader.Action.Load"><value>Загрузить</value></data>
<data name="Results.Loader.Action.Cancel"><value>Отменить</value></data>
<data name="Results.Loader.Action.Back"><value>Назад</value></data>
<data name="Results.Loader.Progress.Format"><value>{0} / {1}</value></data>
<data name="Results.Loader.Progress.Loaded"><value>Загружено</value></data>
<data name="Results.Loader.Progress.AlreadyLoaded"><value>Уже было</value></data>
<data name="Results.Loader.Progress.NotYetComplete"><value>Не завершено</value></data>
<data name="Results.Loader.Progress.Failed"><value>Ошибка</value></data>
<data name="Results.Loader.Summary.Format"><value>Загружено {0}, пропущено {1}, всего обработано {2}.</value></data>
<data name="Results.Loader.Empty.NoCandidates"><value>Нет событий для загрузки в этом диапазоне.</value></data>
</root>