@inherits RecordTabBase @using Microsoft.Extensions.DependencyInjection @using MeterVault.Infrastructure.Ingestion @inject IServiceScopeFactory Scopes @inject IDialogService DialogService @inject ISnackbar Snackbar @* A meter's events (brief §7.2, D-50): swaps and resets that keep a register's history continuous, a tank's levels and deliveries, notes. Recorded through MeterEventService (the dialog), deleted there too — an imported one only by reverting its import. Paged and filtered by the page period. *@
@EventsHint @foreach (var type in MeterEventRules.RecordableFor(Detail.Mode)) { var chosen = type; @($"{chosen.Display()}…") }
@if (view.MeterId == Detail.Id) { @if (view.Page.Rows.Count == 0) { @(Detail.HasEvents ? S.MeterDetail_NoEventsInPeriod : S.MeterDetail_NoEvents) @if (Detail.HasEvents && view.Range.IsBounded) { @S.MeterDetail_ShowAllDates } } else { @Loc.F(S.MeterDetail_TimesIn, Periods.Zone.Id)
@S.MeterDetail_Time @S.Common_Type @S.Common_Amount @S.MeterDetail_PrevNew @S.MeterDetail_Notes @S.Common_Actions @foreach (var e in view.Page.Rows) { @Local(e.Time) @if (IsAfterNow(e.Time)) { }
} }
@code { /// Opens the event dialog for a type (the page owns it). [Parameter] public EventCallback OnRecordEvent { get; set; } private string EventsHint => Detail.Mode switch { MeterMode.ConsumableBalance => S.MeterDetail_EventsHintTank, MeterMode.CumulativeCounter or MeterMode.GenerationCounter or MeterMode.RuntimeCounter => S.MeterDetail_EventsHintRegister, _ => S.MeterDetail_EventsHintNote, }; protected override Task> FetchAsync(int meterId, RecordRange range, RecordCursor? cursor, CancellationToken cancellationToken) => Details.GetEventsAsync(meterId, range, cursor, cancellationToken); private static string PrevNewText(EventRow e) => (e.PrevValue, e.NewValue) switch { (null, null) => Format.Unknown, ({ } prev, var next) => $"{Format.Number(prev, 2)} → {(next is { } n ? Format.Number(n, 2) : Format.Unknown)}", (null, { } next) => $"→ {Format.Number(next, 2)}", }; private async Task DeleteAsync(EventRow meterEvent) { var message = MeterEventRules.IsRegisterBoundary(meterEvent.Type) ? Loc.F(S.MeterDetail_DeleteBoundaryConfirm, meterEvent.Type.Display(), Local(meterEvent.Time)) : Loc.F(S.MeterDetail_DeleteEventConfirm, meterEvent.Type.Display(), Local(meterEvent.Time)); if (!await Confirm.DeleteAsync(DialogService, S.MeterDetail_DeleteEvent, message)) { return; } try { await using var scope = Scopes.CreateAsyncScope(); var result = await scope.ServiceProvider.GetRequiredService().DeleteEventAsync(Detail.Id, meterEvent.Id); Snackbar.Add(result.Succeeded ? S.MeterDetail_EventDeleted : MeterEventText.Problem(result.Problem), result.Succeeded ? Severity.Success : Severity.Error); } catch (Exception ex) when (ex is not OperationCanceledException) { // The service's transaction has rolled back; report rather than end the circuit. LoggerFactory.CreateLogger().LogError(ex, "Deleting an event on meter {MeterId} failed", Detail.Id); Snackbar.Add(S.MeterEvent_ActionFailed, Severity.Error); } await OnChanged.InvokeAsync(); } }