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
@@ -33,7 +33,13 @@ public static class UiServicesExtensions
services.AddMudServices();
services.AddLocalization(options => options.ResourcesPath = "Resources");
// No ResourcesPath: the SharedResource type already lives in the
// Marathon.UI.Resources namespace, so its compiled .resources name
// is "Marathon.UI.Resources.SharedResource.{culture}.resources" which
// matches the type's FullName directly. Setting ResourcesPath="Resources"
// here would cause the resolver to look for "...Resources.Resources..."
// and silently fall back to displaying the keys.
services.AddLocalization();
// Strongly typed options bound to appsettings.json sections.
services.Configure<LocalizationOptions>(configuration.GetSection(LocalizationOptions.SectionName));
@@ -51,6 +57,7 @@ public static class UiServicesExtensions
// Browsing facades — Scoped so they capture the per-circuit repository scope.
services.AddScoped<IEventBrowsingService, EventBrowsingService>();
services.AddScoped<IAnomalyBrowsingService, AnomalyBrowsingService>();
services.AddScoped<IResultsBrowsingService, ResultsBrowsingService>();
// Settings writer — file path is host-resolved.
services.AddSingleton<ISettingsWriter>(_ => new JsonSettingsWriter(settingsLocalPath));