d9d92ea8fd
- MyBets: add a "Find event" MudAutocomplete over upcoming events (loaded once, filtered client-side) that fills the Event ID; the manual ID field stays as a fallback. Backed by IBetJournalService.GetUpcomingEventOptionsAsync. - Add a "Log bet" CTA on the anomaly detail page that deep-links to /my-bets?eventId=<code>; the journal prefills the Event ID from the query. - Render the new SteamMove anomaly kind with a localized label in the card and detail KindLabel switches (was falling through to the raw enum name). - Localization (en+ru) for all new strings.
122 lines
5.2 KiB
Plaintext
122 lines
5.2 KiB
Plaintext
@page "/anomalies/{Id:guid}"
|
|
@using Marathon.UI.Components
|
|
@inject IStringLocalizer<SharedResource> L
|
|
@inject IAnomalyBrowsingService Anomalies
|
|
@inject NavigationManager Nav
|
|
|
|
<PageTitle>@L["App.Title"] · @L["Anomaly.Title"]</PageTitle>
|
|
|
|
<section class="m-shell">
|
|
@if (_loading && _detail is null)
|
|
{
|
|
<div class="m-list-empty">
|
|
<MudProgressCircular Indeterminate="true" Size="Size.Small" />
|
|
<span class="m-mono">@L["Common.Loading"]</span>
|
|
</div>
|
|
}
|
|
else if (_detail is null)
|
|
{
|
|
<div class="m-list-empty">
|
|
<span class="m-kicker" style="border-color: var(--m-c-ink-soft); color: var(--m-c-ink-soft);">404</span>
|
|
<p style="color: var(--m-c-ink-soft);">@L["Anomaly.Detail.NotFound"]</p>
|
|
<MudButton Variant="Variant.Outlined" OnClick='() => Nav.NavigateTo("/anomalies")'>
|
|
@L["Anomaly.Detail.BackToFeed"]
|
|
</MudButton>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<header class="m-detail-header m-rise m-rise-1">
|
|
<div class="m-detail-header__lockup">
|
|
<span class="m-kicker" style="color: var(--m-c-anomaly); border-color: var(--m-c-anomaly);">
|
|
@KindLabel(_detail.Item.Kind) · @_detail.Item.CountryCode · @_detail.Item.LeagueId
|
|
</span>
|
|
<h1 class="m-display" style="font-size: clamp(1.75rem, 3vw, 2.5rem); margin-top: var(--m-space-2);">
|
|
@_detail.Item.EventTitle
|
|
</h1>
|
|
<div class="m-mono" style="margin-top: var(--m-space-2); color: var(--m-c-ink-soft); text-transform: uppercase; letter-spacing: 0.14em; font-size: 0.75rem;">
|
|
@L["Anomaly.Card.DetectedAt"] @_detail.Item.DetectedAt.ToString("dd MMM yyyy · HH:mm:ss") · MSK
|
|
</div>
|
|
</div>
|
|
<aside class="m-detail-header__odds">
|
|
<div class="m-detail-header__odds-row">
|
|
<span class="m-detail-header__odds-label">@L["Anomaly.Card.Score"]</span>
|
|
<SeverityBadge Severity="_detail.Item.Severity" Score="_detail.Item.Score" />
|
|
</div>
|
|
<div class="m-detail-header__odds-row">
|
|
<span class="m-detail-header__odds-label">@L["Anomaly.Card.GapSeconds"]</span>
|
|
<span class="m-mono" data-test="suspension-duration">@FormatGap(_detail.Item.SuspensionGapSeconds)</span>
|
|
</div>
|
|
<MudButton Variant="Variant.Outlined"
|
|
StartIcon="@Icons.Material.Outlined.OpenInNew"
|
|
OnClick="@(() => Nav.NavigateTo($"/events/{Uri.EscapeDataString(_detail.Item.EventId.Value)}"))"
|
|
Class="m-detail-header__export"
|
|
data-test="link-back-to-event">
|
|
@L["Anomaly.Detail.LinkBackToEvent"]
|
|
</MudButton>
|
|
<MudButton Variant="Variant.Outlined"
|
|
StartIcon="@Icons.Material.Outlined.Receipt"
|
|
OnClick="@(() => Nav.NavigateTo($"/my-bets?eventId={Uri.EscapeDataString(_detail.Item.EventId.Value)}"))"
|
|
Class="m-detail-header__export"
|
|
data-test="log-bet">
|
|
@L["Action.LogBet"]
|
|
</MudButton>
|
|
</aside>
|
|
</header>
|
|
|
|
<hr class="m-rule" />
|
|
|
|
<article class="m-card m-card--anomaly m-rise m-rise-2">
|
|
<span class="m-kicker" style="color: var(--m-c-anomaly); border-color: var(--m-c-anomaly);">
|
|
@L["Anomaly.Detail.EvidenceTitle"]
|
|
</span>
|
|
<div style="margin-top: var(--m-space-4);">
|
|
<AnomalyEvidence Pre="_detail.Pre"
|
|
Post="_detail.Post"
|
|
SuspensionGapSeconds="_detail.Item.SuspensionGapSeconds"
|
|
IsTwoWay="_detail.Item.IsTwoWay" />
|
|
</div>
|
|
</article>
|
|
}
|
|
</section>
|
|
|
|
@code {
|
|
[Parameter] public Guid Id { get; set; }
|
|
|
|
private AnomalyDetailVm? _detail;
|
|
private bool _loading = true;
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
_loading = true;
|
|
try
|
|
{
|
|
_detail = await Anomalies.GetByIdAsync(Id, CancellationToken.None);
|
|
}
|
|
catch
|
|
{
|
|
_detail = null;
|
|
}
|
|
finally
|
|
{
|
|
_loading = false;
|
|
}
|
|
}
|
|
|
|
private string KindLabel(AnomalyKind kind) => kind switch
|
|
{
|
|
AnomalyKind.SuspensionFlip => L["Anomaly.Kind.SuspensionFlip"],
|
|
AnomalyKind.SteamMove => L["Anomaly.Kind.SteamMove"],
|
|
_ => kind.ToString(),
|
|
};
|
|
|
|
private static string FormatGap(int seconds)
|
|
{
|
|
if (seconds <= 0) return "—";
|
|
var ts = TimeSpan.FromSeconds(seconds);
|
|
if (ts.TotalSeconds < 60) return string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}s", (int)ts.TotalSeconds);
|
|
if (ts.TotalMinutes < 60) return string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}m {1:00}s", (int)ts.TotalMinutes, ts.Seconds);
|
|
return string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}h {1:00}m", (int)ts.TotalHours, ts.Minutes);
|
|
}
|
|
}
|