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;