@using MeterVault.App.Energy @using MeterVault.Core.Analysis @using MeterVault.Infrastructure.Analysis @inject NavigationManager Nav @* The energy type's History (brief §7.3): the shared chart and table for the chosen metric, over the page's period and interval, compared with the chosen period (a calendar year for a year). "Total" charts the type's measures — never a breakdown on top of its parent or a calculated view on top of its sources (D-22); "Individual meters" charts the meters side by side and says how each one counts, so their bars are not read as adding up. Signed values stay signed. A bucket opens its finer detail (D-51). *@
@(_view?.IsIndividual == true ? S.EnergyView_ViewMetersHelp : S.EnergyView_ViewTotalHelp)
@if (_view is null || _quantities is null) { @* Nothing read yet: the page shows its own loading state. *@ } else if (_view.IsEmpty) { @EmptyText() } else if (_view.Main is { IsPending: true }) { } else if (_quantities.NotYetOccurred || NoData()) { } else { @if (_view.IsIndividual) { @if (_view.Hidden > 0) { @Loc.F(S.EnergyView_MoreMeters, _view.Shown.Count, _view.Shown.Count + _view.Hidden) @S.Nav_Analysis } @if (_view.Memberships.Count > 0) {
@S.EnergyView_HowCounted
    @foreach (var (series, membership) in _view.Memberships) {
  • }
} } else if (_view.Metric == AnalysisMetric.Cost && Analysis.Cost is { } cost) { }
}
@code { /// The page's committed value. [Parameter, EditorRequired] public EnergyAnalysis Analysis { get; set; } = null!; /// The page's analysis state. [Parameter, EditorRequired] public AnalysisQuery Query { get; set; } = null!; /// The page's defaults (drill-downs and "latest data" keep the page's other keys). [Parameter, EditorRequired] public AnalysisDefaults Defaults { get; set; } = null!; /// The metric shown (). [Parameter] public AnalysisMetric Metric { get; set; } /// The view key ( or ). [Parameter] public string View { get; set; } = EnergyPageKeys.ViewTotal; /// The view was switched; the page writes it into its address. [Parameter] public EventCallback ViewChanged { get; set; } /// Loads again (analysis being prepared). [Parameter] public EventCallback OnRefresh { get; set; } private readonly string _countsId = "mv-counts-" + Guid.NewGuid().ToString("N")[..8]; private object? _builtFrom; private EnergyHistoryView? _view; private AnalysisResult? _quantities; private IReadOnlyList _buckets = []; private IReadOnlyList? _pairs; private string _title = string.Empty; private EventCallback _onBucketClick; private Func? _drillHref; protected override void OnParametersSet() { // Rebuilt only for a new value, metric, view or comparison: the chart re-keys on a new list. var source = (Analysis, Metric, View, Query.Comparison); if (Equals(_builtFrom, source)) { return; } _builtFrom = source; _quantities = Analysis.Quantities; _view = EnergyHistoryView.Build(Analysis, Metric, View == EnergyPageKeys.ViewMeters, Query.Comparison); if (_view.Metric == AnalysisMetric.Cost && Analysis.Cost is { } cost) { _buckets = cost.Plan.Buckets; _pairs = Analysis.CostComparison?.Pairs is { Count: > 0 } pairs ? pairs : null; } else { _buckets = _quantities?.Plan.Buckets ?? []; _pairs = _quantities?.Comparison?.Buckets; } var typeName = Analysis.Type?.Name ?? string.Empty; _title = Loc.F(S.EnergyView_ChartTitle, Metric.Display(), typeName); // Buckets are clickable, and the table has its drill column, only when some bucket leads somewhere (D-51): monthly // data has no days to open, and a click that does nothing is a dead end. var drills = _buckets.Any(b => DrillHref(b) is not null); _onBucketClick = drills ? EventCallback.Factory.Create(this, DrillAsync) : default; _drillHref = drills ? DrillHref : null; } /// The buckets are finer than the data: open the interval that shows it (replacing the address, D-46). private void UseBucket(BucketSize size) => AnalysisNavigation.Replace(Nav, Query.WithBucket(size), Defaults); private async Task OnViewChangedAsync(string? view) => await ViewChanged.InvokeAsync(EnergyPageKeys.ResolveView(view)); private string EmptyText() { if (_view!.Metric == AnalysisMetric.Cost) { return S.EnergyView_CostPerMeterNote; } return _view.IsIndividual ? Loc.F(S.EnergyView_NoMetersForMetric, _view.Metric.Display()) : Loc.F(S.EnergyView_NoTotalForMetric, _view.Metric.Display()); } private bool NoData() { if (_view!.Metric == AnalysisMetric.Cost) { return false; } var shown = _view.IsIndividual ? _view.Shown : EnergyMetrics.MeasuresOf(_quantities, _view.Metric); return _view.HasNoData(shown); } private MeterVault.Core.Analysis.Coverage.AvailableRange? Availability() => _view?.Main?.Availability ?? _quantities?.Availability.Quantity; private ComparisonResolution? ComparisonResolution() => _view?.Metric == AnalysisMetric.Cost ? Analysis.CostComparison?.Resolution : _quantities?.Comparison?.Resolution; private string? LatestHref() => AnalysisNavigation.LatestData(Query, Availability()) is { } latest ? AnalysisNavigation.UriFor(Nav, latest, Defaults) : null; private string? DrillHref(AnalysisBucket bucket) => AnalysisNavigation.DrillInto(Query, bucket, _view?.Coarsest) is { } next ? AnalysisNavigation.UriFor(Nav, next, Defaults) : null; /// A chart bucket opens its finer detail (D-51), pushing a history entry so Back returns here. private void DrillAsync(AnalysisBucket bucket) { if (DrillHref(bucket) is { } href) { Nav.NavigateTo(href); } } }