@page "/calendar" @page "/calendar/{YearMonth}" @attribute [Authorize] @rendermode InteractiveServer @inject IWorkDayService WorkDayService @inject IItalianFestivitySource FestivitySource @inject NavigationManager Navigation Calendar

Calendar

@firstOfMonth.ToString("MMMM yyyy")

@if (loading) {

Loading...

} else {
@foreach (var week in weeks) { @foreach (var cell in week) { @if (cell is null) { } else { } } }
Mon Tue Wed Thu Fri Sat Sun
@cell.Date.Day
@if (cell.Entry is not null) { @cell.Entry.DayType
@cell.Entry.WorkedHoursFinal.ToString("N1")h
}

Legend

Work Home Closure Illness DayOff Holiday
} @code { [Parameter] public string? YearMonth { get; set; } private DateOnly firstOfMonth; private bool loading = true; private List weeks = []; private IReadOnlyCollection festivities = []; protected override async Task OnInitializedAsync() { if (!string.IsNullOrEmpty(YearMonth) && DateTime.TryParseExact(YearMonth, "yyyy-MM", null, System.Globalization.DateTimeStyles.None, out var parsed)) { firstOfMonth = new DateOnly(parsed.Year, parsed.Month, 1); } else { firstOfMonth = new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1); } await LoadMonth(); } private async Task LoadMonth() { loading = true; festivities = FestivitySource.GetFestivities(firstOfMonth.Year); var lastDay = firstOfMonth.AddMonths(1).AddDays(-1); var entries = await WorkDayService.GetRangeAsync(firstOfMonth, lastDay); var lookup = entries.ToDictionary(e => e.Date); // Build calendar grid (ISO weeks: Monday = 0) weeks = []; var currentWeek = new CalendarCell?[7]; var dayOfWeek = ((int)firstOfMonth.DayOfWeek + 6) % 7; // Mon=0 for (var d = firstOfMonth; d <= lastDay; d = d.AddDays(1)) { currentWeek[dayOfWeek] = new CalendarCell { Date = d, IsWeekend = d.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday, IsFestivity = festivities.Contains(d), Entry = lookup.GetValueOrDefault(d) }; dayOfWeek++; if (dayOfWeek == 7) { weeks.Add(currentWeek); currentWeek = new CalendarCell?[7]; dayOfWeek = 0; } } if (dayOfWeek > 0) { weeks.Add(currentWeek); } loading = false; } private async Task PreviousMonth() { firstOfMonth = firstOfMonth.AddMonths(-1); await LoadMonth(); } private async Task NextMonth() { firstOfMonth = firstOfMonth.AddMonths(1); await LoadMonth(); } private void NavigateToDay(DateOnly date) => Navigation.NavigateTo($"/workday/{date:yyyy-MM-dd}"); private string GetCellClass(CalendarCell cell) { if (cell.IsWeekend || cell.IsFestivity) return "calendar-weekend"; if (cell.Entry is null) return ""; return cell.Entry.DayType switch { DayType.Closure => "calendar-closure", DayType.Illness => "calendar-illness", DayType.DayOff => "calendar-dayoff", DayType.Holiday => "calendar-holiday", _ => "" }; } private static string GetBadgeClass(DayType type) => type switch { DayType.Work => "bg-primary", DayType.Home => "bg-success", DayType.Closure => "bg-warning text-dark", DayType.Illness => "bg-info text-dark", DayType.DayOff => "bg-secondary", DayType.Holiday => "bg-danger", _ => "bg-light text-dark" }; private sealed class CalendarCell { public DateOnly Date { get; set; } public bool IsWeekend { get; set; } public bool IsFestivity { get; set; } public WorkDayDocument? Entry { get; set; } } }