feat(phase-6): event browsing UI — pre-match/live lists, detail page, +26 bUnit tests
Replaces PreMatch/Live placeholder pages with a shared EventListShell
(filter chips, date range, sortable virtualized-friendly table, debounced
search, live auto-refresh with odds-movement indicators) and adds a new
/events/{eventCode} detail page (asymmetric header lockup, dynamic
Match/Period tabs, Plotly.Blazor odds-over-time chart with accessible
data-table fallback, snapshot history, Excel export modal).
New primitives matching Phase 5's editorial-quant system:
- SportIcon: inline SVGs per sport (basketball=6, football=11,
tennis=22723, hockey=43658, generic fallback)
- OddsCell: tabular mono with ▲/▼/— delta + flash on change
(prefers-reduced-motion honored)
- OddsTimeline: Plotly.Blazor wrapper with theme-aware colors and
<details>/<summary> data-table screen-reader fallback
- ExportDialog: From/To pickers + ExportKind radio + Esc/Enter
keyboard, surfaces use-case errors inline
- EventListShell: shared section shell for PreMatch/Live cadence
State + service split keeps the RCL host-agnostic:
- IEventBrowsingService / EventBrowsingService — wraps repos, returns
view-model records (EventListItem, EventDetail, EventScopeBoard,
BetRow, OddsTimelinePoint, SnapshotHistoryEntry); pages never see
EF or domain entities directly.
- EventBrowsingState — singleton (per-circuit in BlazorWebView) holding
immutable PageFilter records for PreMatch and Live.
Plotly.Blazor 5.4.1 added (latest .NET 8 line; 7.x has breaking changes).
+59 RU/EN localization keys following the Phase 5 dot-segmented convention.
Tests: +26 bUnit tests (PreMatch/Live/Detail pages, OddsCell/SportIcon/
ExportDialog components, EventBrowsingState). Total 228/228 passing
(Domain 96 + Application 15 + Infrastructure 80 + UI 37; baseline 202).
Build clean (0/0).
PLAN.md: P2/P3/P5 top-level checkboxes ticked; P6 row marked Done.
This commit is contained in:
@@ -149,4 +149,80 @@
|
||||
<data name="Anomaly.Live"><value>Anomaly</value></data>
|
||||
<data name="Anomaly.Kind.SuspensionFlip"><value>Suspension flip</value></data>
|
||||
<data name="Anomaly.Score"><value>Confidence</value></data>
|
||||
|
||||
<!-- Phase 6 — Pre-match list / Live list / Detail / Export -->
|
||||
<data name="PreMatch.Title"><value>Pre-match schedule</value></data>
|
||||
<data name="PreMatch.Lede"><value>Upcoming events with their latest pre-match Win-1 / Draw / Win-2 odds preview. Filter by sport, country, league, or team.</value></data>
|
||||
<data name="PreMatch.Empty"><value>No events match the current filters. Loosen the date range or clear the chips above.</value></data>
|
||||
<data name="PreMatch.Filter.Toolbar"><value>Filter toolbar</value></data>
|
||||
<data name="PreMatch.Filter.From"><value>From</value></data>
|
||||
<data name="PreMatch.Filter.To"><value>To</value></data>
|
||||
<data name="PreMatch.Filter.Sport"><value>Sports</value></data>
|
||||
<data name="PreMatch.Filter.Country"><value>Countries</value></data>
|
||||
<data name="PreMatch.Filter.Search"><value>Search league or team</value></data>
|
||||
<data name="PreMatch.Filter.Search.Placeholder"><value>e.g. Real Madrid, NBA, Roland Garros…</value></data>
|
||||
<data name="PreMatch.Column.Time"><value>Time</value></data>
|
||||
<data name="PreMatch.Column.Country"><value>Country</value></data>
|
||||
<data name="PreMatch.Column.League"><value>League</value></data>
|
||||
<data name="PreMatch.Column.Match"><value>Match</value></data>
|
||||
<data name="PreMatch.Footer.Events"><value>events</value></data>
|
||||
<data name="PreMatch.Footer.Refreshed"><value>refreshed at</value></data>
|
||||
|
||||
<data name="Live.Title"><value>Live odds feed</value></data>
|
||||
<data name="Live.Lede"><value>Currently-live events with the most recent live snapshot. The list refreshes on the configured polling cadence; rows pulse when their odds move.</value></data>
|
||||
<data name="Live.AutoRefresh"><value>Auto-refresh</value></data>
|
||||
|
||||
<data name="Detail.Title"><value>Event</value></data>
|
||||
<data name="Detail.NotFound"><value>This event could not be loaded — it may have been removed from the source feed.</value></data>
|
||||
<data name="Detail.BackToList"><value>Back to schedule</value></data>
|
||||
<data name="Detail.Export"><value>Export</value></data>
|
||||
<data name="Detail.Tabs.Aria"><value>Bet scope tabs</value></data>
|
||||
<data name="Detail.Tabs.Match"><value>Match</value></data>
|
||||
<data name="Detail.Tabs.Period"><value>Period {0}</value></data>
|
||||
<data name="Detail.NoBoards"><value>No bets captured yet for this event.</value></data>
|
||||
<data name="Detail.BetType"><value>Type</value></data>
|
||||
<data name="Detail.Side"><value>Side</value></data>
|
||||
<data name="Detail.Threshold"><value>Threshold</value></data>
|
||||
<data name="Detail.Rate"><value>Rate</value></data>
|
||||
<data name="Detail.BetType.Win"><value>Win</value></data>
|
||||
<data name="Detail.BetType.Draw"><value>Draw</value></data>
|
||||
<data name="Detail.BetType.WinFora"><value>Handicap</value></data>
|
||||
<data name="Detail.BetType.Total"><value>Total</value></data>
|
||||
<data name="Detail.Side.Side1"><value>1</value></data>
|
||||
<data name="Detail.Side.Side2"><value>2</value></data>
|
||||
<data name="Detail.Side.Draw"><value>X</value></data>
|
||||
<data name="Detail.Side.Less"><value>Under</value></data>
|
||||
<data name="Detail.Side.More"><value>Over</value></data>
|
||||
<data name="Detail.Chart.Title"><value>Odds movement</value></data>
|
||||
<data name="Detail.Chart.Empty"><value>No snapshots captured yet for this event.</value></data>
|
||||
<data name="Detail.Chart.Time"><value>Time</value></data>
|
||||
<data name="Detail.Chart.Win1"><value>Win 1</value></data>
|
||||
<data name="Detail.Chart.Draw"><value>Draw</value></data>
|
||||
<data name="Detail.Chart.Win2"><value>Win 2</value></data>
|
||||
<data name="Detail.Chart.AccessibleSummary"><value>Show data table</value></data>
|
||||
<data name="Detail.History.Title"><value>Snapshot history</value></data>
|
||||
<data name="Detail.History.Source"><value>Source</value></data>
|
||||
<data name="Detail.History.BetCount"><value>Bets</value></data>
|
||||
<data name="Detail.History.Live"><value>LIVE</value></data>
|
||||
<data name="Detail.History.PreMatch"><value>PRE</value></data>
|
||||
|
||||
<data name="Export.Kicker"><value>Export</value></data>
|
||||
<data name="Export.Title"><value>Export to Excel</value></data>
|
||||
<data name="Export.DateRange.From"><value>From date</value></data>
|
||||
<data name="Export.DateRange.To"><value>To date</value></data>
|
||||
<data name="Export.Kind.Label"><value>Snapshot kind</value></data>
|
||||
<data name="Export.Kind.PreMatch"><value>Pre-match only</value></data>
|
||||
<data name="Export.Kind.Live"><value>Live only</value></data>
|
||||
<data name="Export.Kind.Combined"><value>Combined</value></data>
|
||||
<data name="Export.Submit"><value>Export</value></data>
|
||||
<data name="Export.Cancel"><value>Cancel</value></data>
|
||||
<data name="Export.Success"><value>Export saved to {0}</value></data>
|
||||
<data name="Export.Error.MissingDates"><value>Pick a start and end date.</value></data>
|
||||
<data name="Export.Error.InvalidRange"><value>End date must be on or after the start date.</value></data>
|
||||
<data name="Export.Error.Failed"><value>Export failed.</value></data>
|
||||
|
||||
<data name="Sport.Basketball"><value>Basketball</value></data>
|
||||
<data name="Sport.Football"><value>Football</value></data>
|
||||
<data name="Sport.Tennis"><value>Tennis</value></data>
|
||||
<data name="Sport.Hockey"><value>Hockey</value></data>
|
||||
</root>
|
||||
|
||||
Reference in New Issue
Block a user