Huge updates

This commit is contained in:
MarcWieland
2026-06-24 22:29:05 +02:00
parent 94c10aebdd
commit 0d59d521dc
12 changed files with 559 additions and 40 deletions
+96 -11
View File
@@ -178,13 +178,13 @@ else
<MudText Color="Color.Secondary" Class="mb-1"></MudText>
<MudStack Spacing="0">
<MudText Typo="Typo.overline" Color="Color.Secondary">Pausen</MudText>
<MudText Typo="Typo.body1">@FormatTs(day.TotalBreakTime)</MudText>
<MudText Typo="Typo.body1">@FormatTs(GetEffectiveBreakTime(day))</MudText>
</MudStack>
<MudText Color="Color.Secondary" Class="mb-1">=</MudText>
<MudStack Spacing="0">
<MudText Typo="Typo.overline" Color="Color.Secondary">Netto</MudText>
<MudText Typo="Typo.body1" Color="Color.Primary" Style="font-weight:700">
@FormatTs(day.NetWork!.Value)
@FormatTs(GetEffectiveNetWork(day)!.Value)
</MudText>
</MudStack>
<MudDivider Vertical="true" FlexItem="true" Style="height:36px" />
@@ -192,6 +192,30 @@ else
<MudText Typo="Typo.overline" Color="Color.Secondary">Soll</MudText>
<MudText Typo="Typo.body1">@FormatTs(TimeSpan.FromHours(_settings.DailyTargetHours))</MudText>
</MudStack>
<MudDivider Vertical="true" FlexItem="true" Style="height:36px" />
<MudStack Spacing="0">
<MudText Typo="Typo.overline" Color="Color.Secondary">Soll-Ende</MudText>
<MudText Typo="Typo.body1">@FormatTimeSpan(CalculateEarliestDeparture(day)) Uhr</MudText>
</MudStack>
</MudStack>
</MudItem>
}
else if (day.Start.HasValue)
{
<MudItem xs="12" md="8">
<MudStack Row="true" Spacing="3" AlignItems="AlignItems.Center" Class="h-100 pl-1">
<MudStack Spacing="0">
<MudText Typo="Typo.overline" Style="color: var(--mud-palette-info);">Früheste Gehenszeit</MudText>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<MudIcon Icon="@Icons.Material.Filled.ExitToApp" Color="Color.Info" Size="Size.Small" />
<MudText Typo="Typo.body1" Color="Color.Info" Style="font-weight:700">
@FormatTimeSpan(CalculateEarliestDeparture(day)) Uhr
</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary" Class="ml-2">
(Bei Soll: @FormatTs(TimeSpan.FromHours(_settings.DailyTargetHours))h + @((int)GetRequiredBreakForTarget(day).TotalMinutes) Min. Pause)
</MudText>
</MudStack>
</MudStack>
</MudStack>
</MudItem>
}
@@ -200,7 +224,7 @@ else
@* ── Fortschrittsbalken ── *@
@if (progressPct >= 0)
{
<MudTooltip Text="@($"Netto {FormatTs(day.NetWork ?? TimeSpan.Zero)} von {FormatTs(TimeSpan.FromHours(_settings.DailyTargetHours))} Soll")">
<MudTooltip Text="@($"Netto {FormatTs(GetEffectiveNetWork(day) ?? TimeSpan.Zero)} von {FormatTs(TimeSpan.FromHours(_settings.DailyTargetHours))} Soll")">
<MudProgressLinear Value="@Math.Min(progressPct, 100)"
Color="@(progressPct >= 100 ? Color.Success : Color.Primary)"
Rounded="true" Class="mt-3" Style="height:6px" />
@@ -476,21 +500,48 @@ else
}
// ── Berechnungen ─────────────────────────────────────────────
private TimeSpan? GetOvertime(DayVm day) =>
day.NetWork.HasValue ? day.NetWork.Value - TimeSpan.FromHours(_settings.DailyTargetHours) : null;
private TimeSpan GetEffectiveBreakTime(DayVm day)
{
var enteredBreak = day.Breaks
.Where(b => b.Duration.HasValue)
.Aggregate(TimeSpan.Zero, (s, b) => s + b.Duration!.Value);
if (!day.Start.HasValue || !day.End.HasValue)
return enteredBreak;
var gross = day.End.Value - day.Start.Value;
int minMinutes = gross.TotalHours >= 9.0 ? 45 : 30;
int finalMin = Math.Max(minMinutes, _settings.MinimumBreakMinutes);
var minBreak = TimeSpan.FromMinutes(finalMin);
return enteredBreak > minBreak ? enteredBreak : minBreak;
}
private TimeSpan? GetEffectiveNetWork(DayVm day)
{
if (!day.GrossWork.HasValue) return null;
return day.GrossWork.Value - GetEffectiveBreakTime(day);
}
private TimeSpan? GetOvertime(DayVm day)
{
var net = GetEffectiveNetWork(day);
return net.HasValue ? net.Value - TimeSpan.FromHours(_settings.DailyTargetHours) : null;
}
private int GetProgressPercent(DayVm day)
{
if (!day.NetWork.HasValue || _settings.DailyTargetHours <= 0) return -1;
return (int)(day.NetWork.Value.TotalHours / _settings.DailyTargetHours * 100);
var net = GetEffectiveNetWork(day);
if (!net.HasValue || _settings.DailyTargetHours <= 0) return -1;
return (int)(net.Value.TotalHours / _settings.DailyTargetHours * 100);
}
private static string GetBorderColor(DayVm day, bool isWorkDay, List<string> errors)
private string GetBorderColor(DayVm day, bool isWorkDay, List<string> errors)
{
if (!isWorkDay) return "#CFD8DC";
if (errors.Count > 0) return "#EF5350";
if (!day.Start.HasValue && !day.End.HasValue) return "#B0BEC5";
if (!day.NetWork.HasValue) return "#B0BEC5";
if (!GetEffectiveNetWork(day).HasValue) return "#B0BEC5";
return "#4CAF50";
}
@@ -517,11 +568,17 @@ else
yield return $"Pause {i + 1} und Pause {j + 1} überschneiden sich.";
}
}
var net = GetEffectiveNetWork(day);
if (net.HasValue && net.Value.TotalHours > 10.0)
{
yield return "Die tägliche Arbeitszeit überschreitet das gesetzliche Maximum von 10 Stunden!";
}
}
private TimeSpan WeekGross => _days.Aggregate(TimeSpan.Zero, (s, d) => s + (d.GrossWork ?? TimeSpan.Zero));
private TimeSpan WeekBreaks => _days.Aggregate(TimeSpan.Zero, (s, d) => s + d.TotalBreakTime);
private TimeSpan WeekNet => _days.Aggregate(TimeSpan.Zero, (s, d) => s + (d.NetWork ?? TimeSpan.Zero));
private TimeSpan WeekBreaks => _days.Aggregate(TimeSpan.Zero, (s, d) => s + GetEffectiveBreakTime(d));
private TimeSpan WeekNet => _days.Aggregate(TimeSpan.Zero, (s, d) => s + (GetEffectiveNetWork(d) ?? TimeSpan.Zero));
private TimeSpan WeekOvertime => _days.Aggregate(TimeSpan.Zero, (s, d) => s + (GetOvertime(d) ?? TimeSpan.Zero));
private static string FormatTs(TimeSpan ts, bool sign = false)
@@ -532,6 +589,34 @@ else
return $"{prefix}{(int)abs.TotalHours}:{abs.Minutes:D2}";
}
private TimeSpan GetRequiredBreakForTarget(DayVm day)
{
var targetNet = _settings.DailyTargetHours;
var minMinutes = targetNet >= 8.5 ? 45 : 30;
var requiredBreak = TimeSpan.FromMinutes(Math.Max(minMinutes, _settings.MinimumBreakMinutes));
var enteredBreak = day.Breaks
.Where(b => b.Duration.HasValue)
.Aggregate(TimeSpan.Zero, (s, b) => s + b.Duration!.Value);
return enteredBreak > requiredBreak ? enteredBreak : requiredBreak;
}
private TimeSpan CalculateEarliestDeparture(DayVm day)
{
if (!day.Start.HasValue) return TimeSpan.Zero;
var target = TimeSpan.FromHours(_settings.DailyTargetHours);
var finalBreak = GetRequiredBreakForTarget(day);
return day.Start.Value + target + finalBreak;
}
private static string FormatTimeSpan(TimeSpan? ts)
{
if (!ts.HasValue) return "";
var hours = ((int)ts.Value.TotalHours) % 24;
return $"{hours:D2}:{ts.Value.Minutes:D2}";
}
// ── ViewModels ────────────────────────────────────────────────
private sealed class DayVm
{