From d646cfd28dcf1c8bf09472b6fc3e3ca777da22df Mon Sep 17 00:00:00 2001 From: MarcWieland Date: Wed, 24 Jun 2026 22:39:22 +0200 Subject: [PATCH] feat: add vacation day management to home view and persist changelog version in local storage --- .../Components/Layout/NavMenu.razor | 10 ++- .../Components/Pages/Home.razor | 88 ++++++++++++++++--- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/timetracker.Client/Components/Layout/NavMenu.razor b/timetracker.Client/Components/Layout/NavMenu.razor index 3fd041b..1b89023 100644 --- a/timetracker.Client/Components/Layout/NavMenu.razor +++ b/timetracker.Client/Components/Layout/NavMenu.razor @@ -114,9 +114,17 @@ } } - private void ClearBadge() + private async Task ClearBadge() { _showVersionBadge = false; + try + { + await JSRuntime.InvokeVoidAsync("localStorage.setItem", "lastSeenVersion", timetracker.Client.Components.Pages.Changelog.LatestVersion); + } + catch + { + // Ignored + } } private string GetInitials(string? name) diff --git a/timetracker.Client/Components/Pages/Home.razor b/timetracker.Client/Components/Pages/Home.razor index be6bebe..5dd8e16 100644 --- a/timetracker.Client/Components/Pages/Home.razor +++ b/timetracker.Client/Components/Pages/Home.razor @@ -139,9 +139,30 @@ else } else if (!hasData) { - - Nicht erfasst - + + @if (day.IsVacation) + { + + URLAUB + + + Urlaub entfernen + + } + else + { + + Nicht erfasst + + + Als Urlaub eintragen + + } + } @@ -190,7 +211,7 @@ else Soll - @FormatTs(TimeSpan.FromHours(_settings.DailyTargetHours)) + @FormatTs(TimeSpan.FromHours(GetDailyTargetHours(day))) @@ -224,7 +245,7 @@ else @* ── Fortschrittsbalken ── *@ @if (progressPct >= 0) { - + @@ -378,6 +399,7 @@ else private Dictionary _holidays = []; private int _holidayYear = -1; private bool _showOnboarding; + private List _vacations = []; private bool IsCurrentWeek => _monday == GetMonday(DateOnly.FromDateTime(DateTime.Today)); @@ -428,8 +450,9 @@ else } var dbDaysTask = TrackerService.GetWeekAsync(_userId, _monday); + var vacationsTask = TrackerService.GetVacationDaysAsync(_userId, _monday.Year); - await Task.WhenAll(holidaysTask, dbDaysTask); + await Task.WhenAll(holidaysTask, dbDaysTask, vacationsTask); if (_monday.Year != _holidayYear) { @@ -438,11 +461,14 @@ else _holidayYear = _monday.Year; } + _vacations = await vacationsTask; var dbDays = await dbDaysTask; _days = Enumerable.Range(0, 7).Select(i => { var date = _monday.AddDays(i); - return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date, _userId); + var dayVm = DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date, _userId); + dayVm.IsVacation = _vacations.Any(v => v.Date == date); + return dayVm; }).ToList(); BuildWeekLabels(); } @@ -523,23 +549,36 @@ else return day.GrossWork.Value - GetEffectiveBreakTime(day); } + private double GetDailyTargetHours(DayVm day) + { + var isHoliday = _holidays.ContainsKey(day.Date); + var isWorkDay = _settings.IsWorkDay(day.Date.DayOfWeek); + if (!isWorkDay || isHoliday || day.IsVacation) + { + return 0.0; + } + return _settings.DailyTargetHours; + } + private TimeSpan? GetOvertime(DayVm day) { var net = GetEffectiveNetWork(day); - return net.HasValue ? net.Value - TimeSpan.FromHours(_settings.DailyTargetHours) : null; + return net.HasValue ? net.Value - TimeSpan.FromHours(GetDailyTargetHours(day)) : null; } private int GetProgressPercent(DayVm day) { var net = GetEffectiveNetWork(day); - if (!net.HasValue || _settings.DailyTargetHours <= 0) return -1; - return (int)(net.Value.TotalHours / _settings.DailyTargetHours * 100); + var target = GetDailyTargetHours(day); + if (!net.HasValue || target <= 0) return -1; + return (int)(net.Value.TotalHours / target * 100); } private string GetBorderColor(DayVm day, bool isWorkDay, List errors) { if (!isWorkDay) return "#CFD8DC"; if (errors.Count > 0) return "#EF5350"; + if (day.IsVacation) return "#0EA5E9"; // Cool blue for vacation if (!day.Start.HasValue && !day.End.HasValue) return "#B0BEC5"; if (!GetEffectiveNetWork(day).HasValue) return "#B0BEC5"; return "#4CAF50"; @@ -610,6 +649,34 @@ else return day.Start.Value + target + finalBreak; } + private async Task MarkAsVacation(DayVm day) + { + var vacation = new VacationDay + { + UserId = _userId, + Date = day.Date, + Note = "Über Wochenübersicht eingetragen" + }; + await TrackerService.AddVacationDayAsync(vacation); + + await LoadWeek(); + _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings); + Snackbar.Add($"{day.Date:dd.MM.yyyy} als Urlaub eingetragen.", Severity.Success); + } + + private async Task RemoveVacation(DayVm day) + { + var item = _vacations.FirstOrDefault(v => v.Date == day.Date); + if (item != null) + { + await TrackerService.RemoveVacationDayAsync(_userId, item.Id); + + await LoadWeek(); + _totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings); + Snackbar.Add($"Urlaub für {day.Date:dd.MM.yyyy} entfernt.", Severity.Info); + } + } + private static string FormatTimeSpan(TimeSpan? ts) { if (!ts.HasValue) return ""; @@ -626,6 +693,7 @@ else public TimeSpan? Start { get; set; } public TimeSpan? End { get; set; } public List Breaks { get; set; } = []; + public bool IsVacation { get; set; } public TimeSpan? GrossWork => Start.HasValue && End.HasValue && End > Start ? End.Value - Start.Value : null;