@page "/solar" @using MeterVault.App.Components.Pages.Specialized @using MeterVault.Core.Analysis @implements IDisposable @inject NavigationManager Nav @inject InstanceClock Clock @inject SolarService SolarSvc @inject ILogger Logger @* The Solar view (brief §7.5, D-54): the shared header, toolbar and missing-data semantics around the specialised measures — generation, self-consumption, feed-in, autarky and what the generation is worth — one section per energy type with generation. Each figure carries its status and says how it was obtained; a role the figures need and nobody holds gets a setup card with a scoped path into the meter editor. *@ @if (_query is not null) { } @if (view.Analysis.Sites.Count == 0) {
} else if (!view.Analysis.IsRefused) { @foreach (var site in view.Sites) { } }
@code { private static readonly AnalysisDefaults Defaults = AnalysisDefaults.History; private readonly LoadSequencer _loads = new(); private readonly LoadState _state = new(); private AnalysisQuery? _query; /// One committed result: the query it answers, the read model and every section's series. private sealed record SolarPageView(AnalysisQuery Query, SolarAnalysis Analysis, IReadOnlyList Sites); protected override void OnInitialized() => Nav.LocationChanged += OnLocationChanged; protected override Task OnParametersSetAsync() => ReloadIfChangedAsync(); private void OnLocationChanged(object? sender, LocationChangedEventArgs e) => _ = InvokeAsync(ReloadIfChangedAsync); private async Task ReloadIfChangedAsync() { var query = AnalysisQuery.Parse(Nav.Uri, Defaults); if (query == _query) { return; } _query = query; await LoadAsync(query); StateHasChanged(); } private Task RetryAsync() => _query is null ? Task.CompletedTask : LoadAsync(_query); private Task LoadAsync(AnalysisQuery query) => _loads.RunAsync(_state, async token => { // "Now" is read once per load (D-01); "all" spans what the energy types with generation have data for (D-19). var now = Clock.Now; var availability = query.Period == PeriodPreset.AllHistory ? await SolarSvc.GetAvailabilityAsync(now, token) : null; var period = query.Resolve(now, SolarSvc.Zone, availability); var analysis = await SolarSvc.GetAsync(new SolarRequest(period) { Bucket = query.Bucket, Comparison = query.Comparison }, token); return new SolarPageView(query, analysis, [.. analysis.Sites.Select(s => SolarSiteView.Build(s, query, analysis.Currency))]); }, Logger); private void OnQueryChanged(AnalysisQuery query) => AnalysisNavigation.Replace(Nav, query, Defaults); public void Dispose() { Nav.LocationChanged -= OnLocationChanged; _loads.Dispose(); } }