feat: add vacation day management to home view and persist changelog version in local storage
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -139,9 +139,30 @@ else
|
||||
}
|
||||
else if (!hasData)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Default" Variant="Variant.Outlined" Size="Size.Small">
|
||||
Nicht erfasst
|
||||
</MudChip>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
|
||||
@if (day.IsVacation)
|
||||
{
|
||||
<MudChip T="string" Color="Color.Info" Variant="Variant.Filled" Size="Size.Small" Style="font-weight:700">
|
||||
URLAUB
|
||||
</MudChip>
|
||||
<MudButton Size="Size.Small" Variant="Variant.Text" Color="Color.Error"
|
||||
OnClick="@(async () => await RemoveVacation(day))"
|
||||
Style="text-transform:none; font-size: 0.75rem;">
|
||||
Urlaub entfernen
|
||||
</MudButton>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip T="string" Color="Color.Default" Variant="Variant.Outlined" Size="Size.Small">
|
||||
Nicht erfasst
|
||||
</MudChip>
|
||||
<MudButton Size="Size.Small" Variant="Variant.Text" Color="Color.Info"
|
||||
OnClick="@(async () => await MarkAsVacation(day))"
|
||||
Style="text-transform:none; font-size: 0.75rem;">
|
||||
Als Urlaub eintragen
|
||||
</MudButton>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</MudStack>
|
||||
</CardHeaderContent>
|
||||
@@ -190,7 +211,7 @@ else
|
||||
<MudDivider Vertical="true" FlexItem="true" Style="height:36px" />
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.overline" Color="Color.Secondary">Soll</MudText>
|
||||
<MudText Typo="Typo.body1">@FormatTs(TimeSpan.FromHours(_settings.DailyTargetHours))</MudText>
|
||||
<MudText Typo="Typo.body1">@FormatTs(TimeSpan.FromHours(GetDailyTargetHours(day)))</MudText>
|
||||
</MudStack>
|
||||
<MudDivider Vertical="true" FlexItem="true" Style="height:36px" />
|
||||
<MudStack Spacing="0">
|
||||
@@ -224,7 +245,7 @@ else
|
||||
@* ── Fortschrittsbalken ── *@
|
||||
@if (progressPct >= 0)
|
||||
{
|
||||
<MudTooltip Text="@($"Netto {FormatTs(GetEffectiveNetWork(day) ?? TimeSpan.Zero)} von {FormatTs(TimeSpan.FromHours(_settings.DailyTargetHours))} Soll")">
|
||||
<MudTooltip Text="@($"Netto {FormatTs(GetEffectiveNetWork(day) ?? TimeSpan.Zero)} von {FormatTs(TimeSpan.FromHours(GetDailyTargetHours(day)))} Soll")">
|
||||
<MudProgressLinear Value="@Math.Min(progressPct, 100)"
|
||||
Color="@(progressPct >= 100 ? Color.Success : Color.Primary)"
|
||||
Rounded="true" Class="mt-3" Style="height:6px" />
|
||||
@@ -378,6 +399,7 @@ else
|
||||
private Dictionary<DateOnly, string> _holidays = [];
|
||||
private int _holidayYear = -1;
|
||||
private bool _showOnboarding;
|
||||
private List<VacationDay> _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<string> 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<BreakVm> Breaks { get; set; } = [];
|
||||
public bool IsVacation { get; set; }
|
||||
|
||||
public TimeSpan? GrossWork =>
|
||||
Start.HasValue && End.HasValue && End > Start ? End.Value - Start.Value : null;
|
||||
|
||||
Reference in New Issue
Block a user