@page "/consumables" @inject ConsumableService ConsumablesSvc @inject Microsoft.Extensions.Options.IOptions Options @using MudBlazor MeterVault — @S.Consumables_PageTitle
@S.Nav_Consumables @S.Common_RangeLast12Months @S.Common_RangeLast24Months @S.Common_RangeLast5Years @S.Common_RangeAllTime
@* A consumable meter without a tank has no capacity or calibration, so it cannot be summarised — but it must not just be missing from the page, with nothing saying where it went. *@ @foreach (var meter in _unconfigured) { @Loc.F(S.Consumables_TankNotConfigured, meter.Name) @S.MeterDetail_SetUpTank } @if (_items is null) { } else if (_items.Count == 0 && _unconfigured.Count == 0) { @S.Consumables_NoMetersLead @MeterMode.ConsumableBalance.Display() @S.Consumables_NoMetersTail @S.Nav_Meters@S.Consumables_NoMetersOrImport @S.Nav_Import. } else { @foreach (var item in _items) { @* The actions for a tank are the ones done standing next to it, so they sit on its card. *@
@item.Name @S.MeterDetail_RecordTankLevel @S.Consumables_RecordDelivery
@S.Consumables_TankLevel @(item.CurrentLevel is { } l ? $"{Format.Number(l, 0)} {item.Unit}" : "—") @Loc.F(S.Consumables_FillOfCapacity, Format.Number(item.FillFraction * 100, 0), Format.Number(item.Capacity, 0), item.Unit) @if (item.PhysicalLevel is { } cm && item.PhysicalUnit is "cm") { · @Format.Number(cm, 0) cm } @if (item.LevelAsOf is { } asOf) { · @Loc.F(S.Consumables_AsOf, Local(asOf).ToString("yyyy-MM-dd")) } @S.Consumables_UsedRange @Format.Number(item.ConsumptionInRange, 0) @item.Unit @S.Consumables_BurnerRuntime @(item.BurnerHours is { } h ? $"{Format.Number(h, 0)} h" : "—") @S.Consumables_EffectiveRate @if (item.FixedRate is { } fr) { @Format.Number(fr, 2) @item.Unit/h } else if (item.EffectiveRate is { } er) { @Format.Number(er, 2) @item.Unit/h } else { } @item.RateMode.Display() @S.Common_CostRange @Format.Euro(item.CostInRange) @S.Consumables_ForecastEmpty @(item.ForecastEmpty is { } fe ? fe.ToString("yyyy-MM-dd") : "—") @if (item.AveragePerDay is { } apd) { @Loc.F(S.Consumables_PerDay, Format.Number(apd, 1), item.Unit) } @S.Consumables_ConsumptionByMonth @Loc.F(S.Consumables_DeliveriesCount, item.Deliveries.Count) @if (item.Deliveries.Count == 0) { @S.Consumables_NoDeliveries } else {
@S.Common_Date@S.Common_Amount @foreach (var delivery in item.Deliveries) { @Local(delivery.Time).ToString("yyyy-MM-dd") @Format.Number(delivery.Amount, 0) @(delivery.Unit ?? item.Unit) }
}
} } @code { private int _months = 60; private bool _loading; private IReadOnlyList? _items; private IReadOnlyList _unconfigured = []; private TimeZoneInfo _tz = TimeZoneInfo.Utc; protected override Task OnInitializedAsync() { _tz = LocalTimeEntry.Resolve(Options.Value.TimeZone); return LoadAsync(); } private DateTimeOffset Local(DateTimeOffset instant) => TimeZoneInfo.ConvertTime(instant, _tz); private async Task OnRangeChanged(int months) { _months = months; await LoadAsync(); } private async Task LoadAsync() { if (_loading) { return; } _loading = true; _items = null; try { var asOf = DateOnly.FromDateTime(DateTime.UtcNow); var from = asOf.AddMonths(-_months); _unconfigured = await ConsumablesSvc.GetUnconfiguredAsync(); _items = await ConsumablesSvc.GetConsumablesAsync(new DateOnly(from.Year, from.Month, 1), asOf.AddMonths(1)); } finally { _loading = false; } } private static IReadOnlyList ChartFor(ConsumableSummary item) { var points = item.Months .Select(m => new SeriesChart.Point(Format.MonthLabel(m.Period), m.Consumption)) .ToList(); return [new SeriesChart.SeriesDef(Loc.F(S.Consumables_UnitUsed, item.Unit), ApexCharts.SeriesType.Bar, points)]; } private static Color FillColor(double fraction) => fraction switch { < 0.15 => Color.Error, < 0.30 => Color.Warning, _ => Color.Success, }; }