feat: add yearly summary page with navigation and formatting improvements
All checks were successful
Publish Container / publish (push) Successful in 3m17s

This commit is contained in:
MaddoScientisto 2026-04-20 23:56:23 +02:00
commit 0d003903cf
12 changed files with 406 additions and 70 deletions

View file

@ -6,6 +6,7 @@
@using System.Globalization
@inject global::WorkTracker.Services.WorkDays.IWorkDayService WorkDayService
@inject IJSRuntime JS
@inject NavigationManager Navigation
<PageTitle>Monthly Summary</PageTitle>
@ -15,6 +16,7 @@
<button class="btn btn-outline-secondary btn-sm" @onclick="PreviousMonth">&laquo; Prev</button>
<h2 class="h5 mb-0">@currentMonth.ToString("MMMM yyyy")</h2>
<button class="btn btn-outline-secondary btn-sm" @onclick="NextMonth">Next &raquo;</button>
<a class="btn btn-outline-primary btn-sm ms-auto" href="yearly-summary/@currentMonth.Year">Yearly Summary</a>
</div>
<div class="form-check mb-3">
@ -219,17 +221,9 @@ else if (viewMode == SummaryViewMode.Timesheet && timesheet is not null)
private global::WorkTracker.Domain.MonthlyTimesheetModel? timesheet;
private SummaryViewMode viewMode = SummaryViewMode.Timesheet;
protected override async Task OnInitializedAsync()
protected override async Task OnParametersSetAsync()
{
if (!string.IsNullOrEmpty(YearMonth) && DateTime.TryParseExact(YearMonth, "yyyy-MM", null, System.Globalization.DateTimeStyles.None, out var parsed))
{
currentMonth = new DateOnly(parsed.Year, parsed.Month, 1);
}
else
{
currentMonth = new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1);
}
currentMonth = ParseCurrentMonth();
await LoadSummary();
}
@ -264,16 +258,18 @@ else if (viewMode == SummaryViewMode.Timesheet && timesheet is not null)
loading = false;
}
private async Task PreviousMonth()
private Task PreviousMonth()
{
currentMonth = currentMonth.AddMonths(-1);
await LoadSummary();
Navigation.NavigateTo($"/summary/{currentMonth:yyyy-MM}");
return Task.CompletedTask;
}
private async Task NextMonth()
private Task NextMonth()
{
currentMonth = currentMonth.AddMonths(1);
await LoadSummary();
Navigation.NavigateTo($"/summary/{currentMonth:yyyy-MM}");
return Task.CompletedTask;
}
private void SetViewMode(SummaryViewMode mode)
@ -286,6 +282,16 @@ else if (viewMode == SummaryViewMode.Timesheet && timesheet is not null)
return ItalianCulture.TextInfo.ToTitleCase(date.ToString("ddd", ItalianCulture));
}
private DateOnly ParseCurrentMonth()
{
if (!string.IsNullOrEmpty(YearMonth) && DateTime.TryParseExact(YearMonth, "yyyy-MM", null, DateTimeStyles.None, out var parsed))
{
return new DateOnly(parsed.Year, parsed.Month, 1);
}
return new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1);
}
private static string GetDayColumnClass(global::WorkTracker.Domain.MonthlyTimesheetDayModel day)
{
if (day.IsWeekend || day.IsHoliday)
@ -320,15 +326,12 @@ else if (viewMode == SummaryViewMode.Timesheet && timesheet is not null)
private static string FormatDecimalHours(decimal value)
{
return value.ToString("0.##", ItalianCulture);
return DurationFormatter.FormatHours(value, blankWhenZero: true);
}
private static string FormatHours(decimal value)
{
var totalMinutes = (int)Math.Round(value * 60m, MidpointRounding.AwayFromZero);
var hours = totalMinutes / 60;
var minutes = totalMinutes % 60;
return $"{hours:00}:{minutes:00}";
return DurationFormatter.FormatHours(value);
}
private enum SummaryViewMode