@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
{
| Mon |
Tue |
Wed |
Thu |
Fri |
Sat |
Sun |
@foreach (var week in weeks)
{
@foreach (var cell in week)
{
@if (cell is null)
{
|
}
else
{
NavigateToDay(cell.Date)" role="button">
@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; }
}
}