From 6f0d74b56e663096964250f62551d5d4e3355ba9 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Fri, 29 May 2026 15:15:21 +0300 Subject: [PATCH] =?UTF-8?q?feat(ui):=20designed=20Velocity=20dashboard=20?= =?UTF-8?q?=E2=80=94=20market-chip=20signal=20feed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure the Home signal feed to the redesign mockup's composition: each row now shows the sport icon, a time·sport·country meta line, the uppercase team lockup, the 1/X/2 pre→post market chips (lime when odds drift out, red when they shorten), a severity pill and a slammed Oswald score number. All data was already on AnomalyListItem (pre/post rates per outcome, IsTwoWay, sport, country, severity, score) — no service changes; the feed just wasn't rendering it. Hero, stat strip and pipeline panel were already on-composition from the re-skin. Build clean, all 568 tests green. --- src/Marathon.UI/Pages/Home.razor | 140 +++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 18 deletions(-) diff --git a/src/Marathon.UI/Pages/Home.razor b/src/Marathon.UI/Pages/Home.razor index 458c894..5098a8c 100644 --- a/src/Marathon.UI/Pages/Home.razor +++ b/src/Marathon.UI/Pages/Home.razor @@ -69,24 +69,29 @@ } else { -
+
@foreach (var signal in _summary.LatestSignals) { - -
- @FormatSignalTime(signal.DetectedAt) -
-
-
@signal.EventTitle
-
- @SportLabel(signal.Sport.Value) · @SeverityLabel(signal.Severity) + + +
+
+ @FormatSignalTime(signal.DetectedAt) · @SportLabel(signal.Sport.Value) · @signal.CountryCode +
+
@signal.EventTitle
+
+ @Chip("1", signal.PreWin1Rate, signal.PostWin1Rate) + @if (!signal.IsTwoWay) + { + @Chip("X", signal.PreDrawRate, signal.PostDrawRate) + } + @Chip("2", signal.PreWin2Rate, signal.PostWin2Rate)
- - - @signal.Score.ToString("0.00", CultureInfo.InvariantCulture) - +
+ + @signal.Score.ToString("0.00", CultureInfo.InvariantCulture) +
}
@@ -111,6 +116,75 @@
+ + @code { private DashboardSummary _summary = DashboardSummary.Empty; @@ -157,10 +231,40 @@ private string SportLabel(int code) => SportLabels.Resolve(L, code); - private string SeverityLabel(AnomalySeverity severity) => severity switch + // Direction of an odds move for the market chip colour: up = drifted out, dn = shortened. + private static string ChipDir(decimal? pre, decimal? post) => + pre is { } p && post is { } q && p != q ? (q > p ? "up" : "dn") : string.Empty; + + private static string FormatRate(decimal? r) => + r is { } v ? v.ToString("0.00", CultureInfo.InvariantCulture) : "—"; + + // A single 1 / X / 2 market chip: label · struck pre · → · post (coloured by direction). + private RenderFragment Chip(string label, decimal? pre, decimal? post) => builder => { - AnomalySeverity.High => L["Anomaly.Severity.High"], - AnomalySeverity.Medium => L["Anomaly.Severity.Medium"], - _ => L["Anomaly.Severity.Low"], + var dir = ChipDir(pre, post); + builder.OpenElement(0, "span"); + builder.AddAttribute(1, "class", dir.Length > 0 ? $"m-signal__mkt m-signal__mkt--{dir}" : "m-signal__mkt"); + + builder.OpenElement(2, "span"); + builder.AddAttribute(3, "class", "m-signal__mkt-k"); + builder.AddContent(4, label); + builder.CloseElement(); + + builder.OpenElement(5, "span"); + builder.AddAttribute(6, "class", "m-signal__mkt-pre"); + builder.AddContent(7, FormatRate(pre)); + builder.CloseElement(); + + builder.OpenElement(8, "span"); + builder.AddAttribute(9, "class", "m-signal__mkt-arrow"); + builder.AddContent(10, "→"); + builder.CloseElement(); + + builder.OpenElement(11, "span"); + builder.AddAttribute(12, "class", "m-signal__mkt-post"); + builder.AddContent(13, FormatRate(post)); + builder.CloseElement(); + + builder.CloseElement(); }; }