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

@ -180,36 +180,20 @@ public sealed class CouchbaseLiteWorkDayService : IWorkDayService
public async Task<MonthlySummaryModel> GetMonthlySummaryAsync(int year, int month, bool includePreview, CancellationToken cancellationToken = default)
{
var from = new DateOnly(year, month, 1);
var to = from.AddMonths(1).AddDays(-1);
var days = await GetRangeAsync(from, to, cancellationToken);
return await BuildMonthlySummaryAsync(from, includePreview, cancellationToken);
}
var includedUnits = days
.SelectMany(day => day.WorkUnits.Where(unit => includePreview || !unit.IsPreview).Select(unit => new { day.Date, Unit = unit }))
.ToList();
public async Task<IReadOnlyList<MonthlySummaryModel>> GetYearlySummaryAsync(int year, bool includePreview, CancellationToken cancellationToken = default)
{
var summaries = new List<MonthlySummaryModel>(12);
var previewUnits = days
.SelectMany(day => day.WorkUnits.Where(unit => unit.IsPreview).Select(unit => new { day.Date, Unit = unit }))
.ToList();
return new MonthlySummaryModel
for (var month = 1; month <= 12; month++)
{
Year = year,
Month = month,
TotalWorkedHours = includedUnits.Sum(item => item.Unit.ManualWorkedHours),
TotalPreviewWorkedHours = previewUnits.Sum(item => item.Unit.ManualWorkedHours),
CountedWorkUnits = includedUnits.Count,
PreviewWorkUnits = previewUnits.Count,
OfficeDays = includedUnits.Where(item => item.Unit.Location == WorkUnitLocation.Office).Select(item => item.Date).Distinct().Count(),
HomeDays = includedUnits.Where(item => item.Unit.Location == WorkUnitLocation.Home).Select(item => item.Date).Distinct().Count(),
HolidayDays = CountDaysWithEvent(days, CalendarEventType.Holiday),
SickDays = CountDaysWithEvent(days, CalendarEventType.Illness),
DaysOff = CountDaysWithEvent(days, CalendarEventType.DayOff),
ClosureDays = CountDaysWithEvent(days, CalendarEventType.Closure),
TotalHoursOff = days.Sum(day => GetHoursOff(day, includePreview)),
TotalGrossIncome = includedUnits.Sum(item => item.Unit.GrossIncome),
TotalNetIncome = includedUnits.Sum(item => item.Unit.NetIncome),
TotalWorkingDays = includedUnits.Select(item => item.Date).Distinct().Count()
};
cancellationToken.ThrowIfCancellationRequested();
summaries.Add(await BuildMonthlySummaryAsync(new DateOnly(year, month, 1), includePreview, cancellationToken));
}
return summaries;
}
public async Task<MonthlyTimesheetModel> GetMonthlyTimesheetAsync(int year, int month, bool includePreview, CancellationToken cancellationToken = default)
@ -480,6 +464,40 @@ public sealed class CouchbaseLiteWorkDayService : IWorkDayService
return days.Count(day => day.CalendarEvents.Any(calendarEvent => calendarEvent.EventType == eventType));
}
private async Task<MonthlySummaryModel> BuildMonthlySummaryAsync(DateOnly from, bool includePreview, CancellationToken cancellationToken)
{
var to = from.AddMonths(1).AddDays(-1);
var days = await GetRangeAsync(from, to, cancellationToken);
var includedUnits = days
.SelectMany(day => day.WorkUnits.Where(unit => includePreview || !unit.IsPreview).Select(unit => new { day.Date, Unit = unit }))
.ToList();
var previewUnits = days
.SelectMany(day => day.WorkUnits.Where(unit => unit.IsPreview).Select(unit => new { day.Date, Unit = unit }))
.ToList();
return new MonthlySummaryModel
{
Year = from.Year,
Month = from.Month,
TotalWorkedHours = includedUnits.Sum(item => item.Unit.ManualWorkedHours),
TotalPreviewWorkedHours = previewUnits.Sum(item => item.Unit.ManualWorkedHours),
CountedWorkUnits = includedUnits.Count,
PreviewWorkUnits = previewUnits.Count,
OfficeDays = includedUnits.Where(item => item.Unit.Location == WorkUnitLocation.Office).Select(item => item.Date).Distinct().Count(),
HomeDays = includedUnits.Where(item => item.Unit.Location == WorkUnitLocation.Home).Select(item => item.Date).Distinct().Count(),
HolidayDays = CountDaysWithEvent(days, CalendarEventType.Holiday),
SickDays = CountDaysWithEvent(days, CalendarEventType.Illness),
DaysOff = CountDaysWithEvent(days, CalendarEventType.DayOff),
ClosureDays = CountDaysWithEvent(days, CalendarEventType.Closure),
TotalHoursOff = days.Sum(day => GetHoursOff(day, includePreview)),
TotalGrossIncome = includedUnits.Sum(item => item.Unit.GrossIncome),
TotalNetIncome = includedUnits.Sum(item => item.Unit.NetIncome),
TotalWorkingDays = includedUnits.Select(item => item.Date).Distinct().Count()
};
}
private static MonthlyTimesheetDaySummary CreateTimesheetDaySummary(WorkDayDocument? day, DateOnly date, bool includePreview, decimal defaultStandardHours)
{
var includedUnits = day?.WorkUnits.Where(unit => includePreview || !unit.IsPreview).ToList() ?? [];
@ -585,9 +603,7 @@ public sealed class CouchbaseLiteWorkDayService : IWorkDayService
private static string FormatCompactHours(decimal value)
{
return value == decimal.Truncate(value)
? value.ToString("0")
: value.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture);
return Formatting.DurationFormatter.FormatHours(value);
}
private static decimal GetNightHours(WorkUnitDocument unit)

View file

@ -22,6 +22,8 @@ public interface IWorkDayService
Task<MonthlySummaryModel> GetMonthlySummaryAsync(int year, int month, bool includePreview, CancellationToken cancellationToken = default);
Task<IReadOnlyList<MonthlySummaryModel>> GetYearlySummaryAsync(int year, bool includePreview, CancellationToken cancellationToken = default);
Task<MonthlyTimesheetModel> GetMonthlyTimesheetAsync(int year, int month, bool includePreview, CancellationToken cancellationToken = default);
Task<int> GenerateMonthlyPreviewWorkUnitsAsync(int year, int month, CancellationToken cancellationToken = default);