Huge updates
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user