ci / build-test (push) Successful in 1m12s
Editing a meter (any admin dialog, select dropdown, snackbar, dark-mode toggle) did nothing: pages declared @rendermode InteractiveServer individually, but MainLayout — which hosts MudDialogProvider/MudPopoverProvider/MudSnackbarProvider — was rendered by <Routes>, which was static. Inline MudDialogs and MudSelect popovers render through those providers, so with the providers non-interactive no dialog could ever open. Set the render mode on <Routes> and <HeadOutlet> in App.razor (global interactivity) and removed the now-redundant per-page @rendermode declarations (they would otherwise throw "parent already has a render mode"). Why it slipped through: the integration render tests only issue a GET (static prerender), which never exercises the SignalR circuit. Verified this fix with a real headless-browser run (Playwright): the meter edit dialog opens and the "Sub-meter of (upstream meters)" multi-select opens with options — proving the layout providers are now interactive. 116 tests still green. Claude-Session: https://claude.ai/code/session_01Lz2RqAsnQhetqWNoCDfexK
102 lines
4.2 KiB
Plaintext
102 lines
4.2 KiB
Plaintext
@page "/"
|
|
@inject DashboardService Dash
|
|
|
|
<PageTitle>MeterVault — Overview</PageTitle>
|
|
|
|
<MudText Typo="Typo.h4" Class="mb-4">Overview</MudText>
|
|
|
|
@if (_summary is null)
|
|
{
|
|
<MudProgressLinear Indeterminate="true" Color="Color.Primary" />
|
|
}
|
|
else
|
|
{
|
|
<MudGrid>
|
|
<MudItem xs="12" sm="4">
|
|
<MudPaper Class="pa-4" Elevation="2">
|
|
<MudText Typo="Typo.overline" Color="Color.Secondary">This month</MudText>
|
|
<MudText Typo="Typo.h5">@Format.Euro(_summary.Month.Current)</MudText>
|
|
<DeltaChip Kpi="_summary.Month" />
|
|
</MudPaper>
|
|
</MudItem>
|
|
<MudItem xs="12" sm="4">
|
|
<MudPaper Class="pa-4" Elevation="2">
|
|
<MudText Typo="Typo.overline" Color="Color.Secondary">This year</MudText>
|
|
<MudText Typo="Typo.h5">@Format.Euro(_summary.Year.Current)</MudText>
|
|
<DeltaChip Kpi="_summary.Year" />
|
|
</MudPaper>
|
|
</MudItem>
|
|
<MudItem xs="12" sm="4">
|
|
<MudPaper Class="pa-4" Elevation="2">
|
|
<MudText Typo="Typo.overline" Color="Color.Secondary">Latest month with data</MudText>
|
|
<MudText Typo="Typo.h5">@Format.Euro(_summary.LatestMonthCost)</MudText>
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<MudItem xs="12" md="6">
|
|
<MudPaper Class="pa-4" Elevation="2" Style="height:100%">
|
|
<MudText Typo="Typo.h6" Class="mb-2">What costs most (this year)</MudText>
|
|
@if (_breakdown is { Count: > 0 })
|
|
{
|
|
<CategoryDonut Slices="_breakdown" />
|
|
<MudSimpleTable Dense="true" Hover="true" Class="mt-2">
|
|
<tbody>
|
|
@foreach (var slice in _breakdown)
|
|
{
|
|
<tr>
|
|
<td>@slice.Name</td>
|
|
<td style="text-align:right">@Format.Euro(slice.Cost)</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</MudSimpleTable>
|
|
}
|
|
else
|
|
{
|
|
<MudText Typo="Typo.body2" Color="Color.Secondary">No cost data yet — import a sheet or add tariffs.</MudText>
|
|
}
|
|
</MudPaper>
|
|
</MudItem>
|
|
|
|
<MudItem xs="12" md="6">
|
|
<MudPaper Class="pa-4" Elevation="2" Style="height:100%">
|
|
<MudText Typo="Typo.h6" Class="mb-2">What cost more / less (year vs last year)</MudText>
|
|
<MudSimpleTable Dense="true" Hover="true">
|
|
<thead>
|
|
<tr><th>Category</th><th style="text-align:right">Now</th><th style="text-align:right">Prev</th><th style="text-align:right">Δ</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var row in _difference)
|
|
{
|
|
<tr>
|
|
<td>@row.Name</td>
|
|
<td style="text-align:right">@Format.Euro(row.Current)</td>
|
|
<td style="text-align:right">@Format.Euro(row.Previous)</td>
|
|
<td style="text-align:right" class="@(row.Delta >= 0 ? "mv-up" : "mv-down")">
|
|
@Format.DirectionIcon(Math.Sign(row.Delta)) @Format.Euro(Math.Abs(row.Delta))
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</MudSimpleTable>
|
|
</MudPaper>
|
|
</MudItem>
|
|
</MudGrid>
|
|
}
|
|
|
|
@code {
|
|
private DashboardSummary? _summary;
|
|
private IReadOnlyList<CategorySlice> _breakdown = [];
|
|
private IReadOnlyList<DifferenceRow> _difference = [];
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
var asOf = DateOnly.FromDateTime(DateTime.UtcNow);
|
|
_summary = await Dash.GetSummaryAsync(asOf);
|
|
|
|
var yearStart = new DateOnly(asOf.Year, 1, 1);
|
|
_breakdown = await Dash.GetCategoryBreakdownAsync(yearStart, asOf.AddMonths(1));
|
|
_difference = await Dash.GetCategoryDifferenceAsync(yearStart, yearStart.AddYears(-1), asOf.AddMonths(1));
|
|
}
|
|
}
|