632 lines
34 KiB
Plaintext
632 lines
34 KiB
Plaintext
@page "/settings"
|
||
@rendermode InteractiveWebAssembly
|
||
@attribute [Authorize]
|
||
@inject ITimetrackerService TrackerService
|
||
@inject IHolidayService HolidayService
|
||
@inject ISnackbar Snackbar
|
||
@inject AuthenticationStateProvider AuthStateProvider
|
||
@inject IJSRuntime JSRuntime
|
||
@inject NavigationManager Nav
|
||
|
||
|
||
<PageTitle>Einstellungen – Timetracker</PageTitle>
|
||
|
||
@if (_settings == null)
|
||
{
|
||
<MudStack AlignItems="AlignItems.Center" Class="mt-16">
|
||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||
</MudStack>
|
||
}
|
||
else
|
||
{
|
||
<MudStack Spacing="4">
|
||
|
||
@* ── Header ── *@
|
||
<MudPaper Elevation="4" Class="pa-5 rounded-xl"
|
||
Style="background: #1E293B; color:white;">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
|
||
<MudIcon Icon="@Icons.Material.Filled.Settings" Style="color:white; font-size:2rem" />
|
||
<MudStack Spacing="0">
|
||
<MudText Typo="Typo.h5" Style="color:white; font-weight:700">Einstellungen</MudText>
|
||
<MudText Typo="Typo.caption" Style="color:rgba(255,255,255,0.72)">
|
||
Arbeitszeit, Arbeitstage und Urlaub konfigurieren
|
||
</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
</MudPaper>
|
||
|
||
<MudGrid Spacing="4">
|
||
|
||
@* ── Arbeitszeit ── *@
|
||
<MudItem xs="12" md="6">
|
||
<MudCard Elevation="3" Class="rounded-xl h-100">
|
||
<MudCardHeader>
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.Schedule" Color="Color.Primary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Arbeitszeit</MudText>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
<MudStack Spacing="4">
|
||
<MudNumericField @bind-Value="_settings.DailyTargetHours"
|
||
Label="Sollstunden pro Tag (h)"
|
||
Variant="Variant.Outlined"
|
||
Min="0.5" Max="24.0" Step="0.25"
|
||
Format="0.##"
|
||
HelperText="Vertraglich vereinbarte Nettoarbeitszeit" />
|
||
|
||
<MudNumericField @bind-Value="_settings.MinimumBreakMinutes"
|
||
Label="Gesetzliche Mindestpause (min)"
|
||
Variant="Variant.Outlined"
|
||
Min="0" Max="120" Step="5"
|
||
HelperText="Pflichtpause laut Arbeitszeitgesetz" />
|
||
|
||
<MudPaper Elevation="0" Class="pa-3 rounded-lg"
|
||
Style="background: var(--mud-palette-background-grey);">
|
||
<MudText Typo="Typo.subtitle2" Color="Color.Secondary" Class="mb-2">
|
||
Tagesberechnung
|
||
</MudText>
|
||
<MudStack Spacing="1">
|
||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary">Netto (Soll)</MudText>
|
||
<MudText Typo="Typo.body2"><b>@FormatHours(_settings.DailyTargetHours)</b></MudText>
|
||
</MudStack>
|
||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary">+ Mindestpause</MudText>
|
||
<MudText Typo="Typo.body2"><b>@_settings.MinimumBreakMinutes min</b></MudText>
|
||
</MudStack>
|
||
<MudDivider />
|
||
<MudStack Row="true" Justify="Justify.SpaceBetween">
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary">= Brutto-Anwesenheit</MudText>
|
||
<MudText Typo="Typo.body2" Color="Color.Primary">
|
||
<b>@FormatHours(_settings.DailyTargetHours + _settings.MinimumBreakMinutes / 60.0)</b>
|
||
</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
</MudPaper>
|
||
</MudStack>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
</MudItem>
|
||
|
||
@* ── Arbeitstage ── *@
|
||
<MudItem xs="12" md="6">
|
||
<MudCard Elevation="3" Class="rounded-xl h-100">
|
||
<MudCardHeader>
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.CalendarToday" Color="Color.Primary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Arbeitstage</MudText>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary" Class="mb-3">
|
||
Wähle die Wochentage, an denen du arbeitest.
|
||
</MudText>
|
||
<MudStack Spacing="2">
|
||
@foreach (var (label, getter, setter) in WorkDayToggles)
|
||
{
|
||
var isChecked = getter(_settings);
|
||
<MudPaper Elevation="0" Class="pa-2 rounded-lg"
|
||
Style="@($"background: {(isChecked ? "rgba(63,81,181,0.08)" : "var(--mud-palette-background-grey)")};")">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.Circle"
|
||
Style="@($"font-size:10px; color:{(isChecked ? "#3F51B5" : "#CFD8DC")}")" />
|
||
<MudText Typo="Typo.body1" Style="@(isChecked ? "font-weight:600" : "")">
|
||
@label
|
||
</MudText>
|
||
</MudStack>
|
||
<MudSwitch Value="@isChecked"
|
||
ValueChanged="@((bool v) => setter(_settings, v))"
|
||
Color="Color.Primary" />
|
||
</MudStack>
|
||
</MudPaper>
|
||
}
|
||
</MudStack>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
</MudItem>
|
||
|
||
@* ── Region & Feiertage ── *@
|
||
<MudItem xs="12" md="6">
|
||
<MudCard Elevation="3" Class="rounded-xl h-100">
|
||
<MudCardHeader>
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.Public" Color="Color.Primary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Region & Feiertage</MudText>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
<MudStack Spacing="4">
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||
Wähle dein Bundesland aus, um Bundesland-spezifische Feiertage zu berücksichtigen.
|
||
</MudText>
|
||
<MudSelect T="string" Label="Bundesland" Variant="Variant.Outlined" @bind-Value="_settings.GermanState" Clearable="true" Placeholder="Nur bundesweite Feiertage">
|
||
@foreach (var state in GermanStates)
|
||
{
|
||
<MudSelectItem Value="@state.Key">@state.Value</MudSelectItem>
|
||
}
|
||
</MudSelect>
|
||
</MudStack>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
</MudItem>
|
||
|
||
@* ── Gleitzeitkonto ── *@
|
||
<MudItem xs="12" md="6">
|
||
<MudCard Elevation="3" Class="rounded-xl h-100">
|
||
<MudCardHeader>
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.AccountBalance" Color="Color.Primary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Gleitzeitkonto-Start</MudText>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
<MudStack Spacing="4">
|
||
<MudDatePicker Label="Berechnungsstart"
|
||
@bind-Date="_flexStartDate"
|
||
HelperText="Gleitzeitberechnung läuft ab diesem Datum. Wenn leer, ab dem ersten Arbeitseintrag."
|
||
Variant="Variant.Outlined"
|
||
Clearable="true"
|
||
DateFormat="dd.MM.yyyy"
|
||
PickerVariant="PickerVariant.Inline" />
|
||
|
||
<MudNumericField @bind-Value="_settings.FlexTimeStartingBalanceHours"
|
||
Label="Anfangsüberstunden (h)"
|
||
Variant="Variant.Outlined"
|
||
Step="0.5"
|
||
Format="0.##"
|
||
HelperText="Stufensaldo (Guthaben/Schulden) zum Berechnungsstart" />
|
||
</MudStack>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
</MudItem>
|
||
|
||
@* ── Hilfe & Einführung ── *@
|
||
<MudItem xs="12" md="6">
|
||
<MudCard Elevation="3" Class="rounded-xl h-100">
|
||
<MudCardHeader>
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.HelpOutline" Color="Color.Primary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Hilfe & Einführung</MudText>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
<MudStack Spacing="4">
|
||
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||
Wenn du die interaktive Einführung (Onboarding Tour) noch einmal sehen möchtest, kannst du sie hier zurücksetzen und neu starten.
|
||
</MudText>
|
||
<MudButton Variant="Variant.Outlined" Color="Color.Primary"
|
||
StartIcon="@Icons.Material.Filled.PlayCircleOutline"
|
||
OnClick="RepeatOnboarding"
|
||
Style="max-width:250px;">
|
||
Onboarding wiederholen
|
||
</MudButton>
|
||
</MudStack>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
</MudItem>
|
||
|
||
</MudGrid>
|
||
|
||
@* ── Speichern-Button ── *@
|
||
<MudButton Variant="Variant.Filled" Color="Color.Primary"
|
||
OnClick="Save" StartIcon="@Icons.Material.Filled.Save"
|
||
Size="Size.Large" Style="max-width:300px">
|
||
Einstellungen speichern
|
||
</MudButton>
|
||
|
||
<MudDivider />
|
||
|
||
@* ── Urlaubsverwaltung ── *@
|
||
<MudCard Elevation="3" Class="rounded-xl">
|
||
<MudCardHeader Style="background: linear-gradient(90deg, rgba(0,150,136,0.1) 0%, transparent 100%);">
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.BeachAccess" Color="Color.Secondary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Urlaubsverwaltung</MudText>
|
||
</MudStack>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIconButton Icon="@Icons.Material.Filled.ChevronLeft"
|
||
Size="Size.Small" OnClick="@(() => ChangeYear(-1))" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:700; min-width:50px; text-align:center">
|
||
@_vacYear
|
||
</MudText>
|
||
<MudIconButton Icon="@Icons.Material.Filled.ChevronRight"
|
||
Size="Size.Small" OnClick="@(() => ChangeYear(1))" />
|
||
</MudStack>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
<MudGrid Spacing="4">
|
||
|
||
@* ── Urlaubskontingent ── *@
|
||
<MudItem xs="12" md="5">
|
||
<MudStack Spacing="3">
|
||
<MudNumericField @bind-Value="_settings.VacationDaysPerYear"
|
||
Label="Urlaubstage pro Jahr"
|
||
Variant="Variant.Outlined"
|
||
Min="1" Max="365" Step="1"
|
||
HelperText="Dein jährliches Urlaubskontingent" />
|
||
|
||
@* ── Statistik-Chips ── *@
|
||
<MudGrid Spacing="2">
|
||
<MudItem xs="4">
|
||
<MudPaper Elevation="0" Class="pa-3 rounded-lg text-center"
|
||
Style="background: rgba(63,81,181,0.08);">
|
||
<MudText Typo="Typo.h5" Color="Color.Primary" Style="font-weight:700">
|
||
@_settings.VacationDaysPerYear
|
||
</MudText>
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary">Gesamt</MudText>
|
||
</MudPaper>
|
||
</MudItem>
|
||
<MudItem xs="4">
|
||
<MudPaper Elevation="0" Class="pa-3 rounded-lg text-center"
|
||
Style="background: rgba(244,67,54,0.08);">
|
||
<MudText Typo="Typo.h5" Color="Color.Error" Style="font-weight:700">
|
||
@_vacationDays.Count
|
||
</MudText>
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary">Genommen</MudText>
|
||
</MudPaper>
|
||
</MudItem>
|
||
<MudItem xs="4">
|
||
<MudPaper Elevation="0" Class="pa-3 rounded-lg text-center"
|
||
Style="@($"background: rgba({(_vacRemaining >= 0 ? "76,175,80" : "255,152,0")},0.08);")">
|
||
<MudText Typo="Typo.h5"
|
||
Color="@(_vacRemaining >= 0 ? Color.Success : Color.Warning)"
|
||
Style="font-weight:700">
|
||
@_vacRemaining
|
||
</MudText>
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary">Verbleibend</MudText>
|
||
</MudPaper>
|
||
</MudItem>
|
||
</MudGrid>
|
||
|
||
@* ── Fortschrittsbalken ── *@
|
||
<MudTooltip Text="@($"{_vacationDays.Count} von {_settings.VacationDaysPerYear} Tagen genommen")">
|
||
<MudProgressLinear Value="@Math.Min(VacationUsedPercent, 100)"
|
||
Color="@(VacationUsedPercent > 100 ? Color.Error : VacationUsedPercent > 80 ? Color.Warning : Color.Success)"
|
||
Rounded="true" Style="height:10px" />
|
||
</MudTooltip>
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary" Align="Align.Center">
|
||
@VacationUsedPercent % des Jahresurlaubs @_vacYear verbraucht
|
||
</MudText>
|
||
</MudStack>
|
||
</MudItem>
|
||
|
||
@* ── Urlaub hinzufügen ── *@
|
||
<MudItem xs="12" md="7">
|
||
<MudStack Spacing="3">
|
||
<MudText Typo="Typo.subtitle2" Style="font-weight:600">Urlaub eintragen</MudText>
|
||
<MudStack Row="true" AlignItems="AlignItems.End" Spacing="2" Wrap="Wrap.Wrap">
|
||
<MudDatePicker @bind-Date="_newVacDateFrom"
|
||
Label="Von"
|
||
Variant="Variant.Outlined"
|
||
DateFormat="dd.MM.yyyy"
|
||
Style="width:300px"
|
||
PickerVariant="PickerVariant.Inline" />
|
||
<MudDatePicker @bind-Date="_newVacDateTo"
|
||
Label="Bis"
|
||
Variant="Variant.Outlined"
|
||
DateFormat="dd.MM.yyyy"
|
||
Style="width:300px"
|
||
MinDate="@_newVacDateFrom"
|
||
PickerVariant="PickerVariant.Inline" />
|
||
<MudTextField @bind-Value="_newVacNote"
|
||
Label="Notiz (optional)"
|
||
Variant="Variant.Outlined"
|
||
Style="width:300px" />
|
||
<MudButton Variant="Variant.Filled" Color="Color.Secondary"
|
||
StartIcon="@Icons.Material.Filled.Add"
|
||
OnClick="AddVacation" Disabled="@(_newVacDateFrom == null)">
|
||
Hinzufügen
|
||
</MudButton>
|
||
</MudStack>
|
||
|
||
@* ── Liste der Urlaubstage ── *@
|
||
@if (_vacationDays.Count == 0)
|
||
{
|
||
<MudPaper Elevation="0" Class="pa-4 rounded-lg text-center"
|
||
Style="background: var(--mud-palette-background-grey);">
|
||
<MudIcon Icon="@Icons.Material.Filled.BeachAccess"
|
||
Style="font-size:2.5rem; color:#CFD8DC" />
|
||
<MudText Color="Color.Secondary" Class="mt-1">
|
||
Noch keine Urlaubstage für @_vacYear eingetragen.
|
||
</MudText>
|
||
</MudPaper>
|
||
}
|
||
else
|
||
{
|
||
<MudList T="VacationDay" Dense="true">
|
||
@foreach (var v in _vacationDays)
|
||
{
|
||
<MudListItem>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center"
|
||
Justify="Justify.SpaceBetween">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.BeachAccess"
|
||
Size="Size.Small" Color="Color.Secondary" />
|
||
<MudStack Spacing="0">
|
||
<MudText Typo="Typo.body2" Style="font-weight:600">
|
||
@v.Date.ToString("dddd, dd. MMMM yyyy", _deCulture)
|
||
</MudText>
|
||
@if (!string.IsNullOrWhiteSpace(v.Note))
|
||
{
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary">
|
||
@v.Note
|
||
</MudText>
|
||
}
|
||
</MudStack>
|
||
</MudStack>
|
||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
|
||
Size="Size.Small" Color="Color.Error"
|
||
OnClick="@(async () => await RemoveVacation(v.Id))" />
|
||
</MudStack>
|
||
</MudListItem>
|
||
<MudDivider />
|
||
}
|
||
</MudList>
|
||
}
|
||
</MudStack>
|
||
</MudItem>
|
||
</MudGrid>
|
||
</MudCardContent>
|
||
</MudCard>
|
||
|
||
@* ── Feiertagsverwaltung ── *@
|
||
<MudCard Elevation="3" Class="rounded-xl">
|
||
<MudCardHeader Style="background: linear-gradient(90deg, rgba(0,188,212,0.1) 0%, transparent 100%);">
|
||
<CardHeaderContent>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Wrap="Wrap.Wrap">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.Celebration" Color="Color.Tertiary" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:600">Feiertage (Deutschland)</MudText>
|
||
</MudStack>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIconButton Icon="@Icons.Material.Filled.ChevronLeft"
|
||
Size="Size.Small" OnClick="@(() => ChangeHolYear(-1))" />
|
||
<MudText Typo="Typo.h6" Style="font-weight:700; min-width:50px; text-align:center">
|
||
@_holYear
|
||
</MudText>
|
||
<MudIconButton Icon="@Icons.Material.Filled.ChevronRight"
|
||
Size="Size.Small" OnClick="@(() => ChangeHolYear(1))" />
|
||
<MudButton Variant="Variant.Filled" Color="Color.Tertiary"
|
||
StartIcon="@Icons.Material.Filled.CloudDownload"
|
||
OnClick="FetchHolidays"
|
||
Disabled="@_fetchingHolidays"
|
||
Size="Size.Small">
|
||
@if (_fetchingHolidays)
|
||
{
|
||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||
}
|
||
Von API laden
|
||
</MudButton>
|
||
</MudStack>
|
||
</MudStack>
|
||
</CardHeaderContent>
|
||
</MudCardHeader>
|
||
<MudCardContent>
|
||
@if (_holHolidays.Count == 0)
|
||
{
|
||
<MudPaper Elevation="0" Class="pa-4 rounded-lg text-center"
|
||
Style="background: var(--mud-palette-background-grey);">
|
||
<MudIcon Icon="@Icons.Material.Filled.Celebration"
|
||
Style="font-size:2.5rem; color:#CFD8DC" />
|
||
<MudText Color="Color.Secondary" Class="mt-1">
|
||
Keine Feiertage für @_holYear gespeichert. Klicke "Von API laden".
|
||
</MudText>
|
||
</MudPaper>
|
||
}
|
||
else
|
||
{
|
||
<MudList T="PublicHoliday" Dense="true">
|
||
@foreach (var h in _holHolidays)
|
||
{
|
||
<MudListItem>
|
||
<MudStack Row="true" AlignItems="AlignItems.Center"
|
||
Justify="Justify.SpaceBetween">
|
||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||
<MudIcon Icon="@Icons.Material.Filled.Celebration"
|
||
Size="Size.Small" Color="Color.Tertiary" />
|
||
<MudStack Spacing="0">
|
||
<MudText Typo="Typo.body2" Style="font-weight:600">
|
||
@h.Date.ToString("dddd, dd. MMMM yyyy", _deCulture)
|
||
</MudText>
|
||
<MudText Typo="Typo.caption" Color="Color.Secondary">@h.Name</MudText>
|
||
</MudStack>
|
||
</MudStack>
|
||
<MudIconButton Icon="@Icons.Material.Filled.DeleteOutline"
|
||
Size="Size.Small" Color="Color.Error"
|
||
OnClick="@(async () => await DeleteHoliday(h.Id))" />
|
||
</MudStack>
|
||
</MudListItem>
|
||
<MudDivider />
|
||
}
|
||
</MudList>
|
||
}
|
||
</MudCardContent>
|
||
</MudCard>
|
||
|
||
</MudStack>
|
||
}
|
||
|
||
@code {
|
||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||
|
||
private AppSettings? _settings;
|
||
private int _userId;
|
||
private DateTime? _flexStartDate
|
||
{
|
||
get => _settings?.FlexTimeStartDate?.ToDateTime(TimeOnly.MinValue);
|
||
set => _settings!.FlexTimeStartDate = value.HasValue ? DateOnly.FromDateTime(value.Value) : null;
|
||
}
|
||
|
||
private static readonly Dictionary<string, string> GermanStates = new()
|
||
{
|
||
{ "DE-BW", "Baden-Württemberg" },
|
||
{ "DE-BY", "Bayern" },
|
||
{ "DE-BE", "Berlin" },
|
||
{ "DE-BB", "Brandenburg" },
|
||
{ "DE-HB", "Bremen" },
|
||
{ "DE-HH", "Hamburg" },
|
||
{ "DE-HE", "Hessen" },
|
||
{ "DE-MV", "Mecklenburg-Vorpommern" },
|
||
{ "DE-NI", "Niedersachsen" },
|
||
{ "DE-NW", "Nordrhein-Westfalen" },
|
||
{ "DE-RP", "Rheinland-Pfalz" },
|
||
{ "DE-SL", "Saarland" },
|
||
{ "DE-SN", "Sachsen" },
|
||
{ "DE-ST", "Sachsen-Anhalt" },
|
||
{ "DE-SH", "Schleswig-Holstein" },
|
||
{ "DE-TH", "Thüringen" }
|
||
};
|
||
|
||
private int _vacYear = DateTime.Today.Year;
|
||
private List<VacationDay> _vacationDays = [];
|
||
private DateTime? _newVacDateFrom;
|
||
private DateTime? _newVacDateTo;
|
||
private string _newVacNote = "";
|
||
private int _holYear = DateTime.Today.Year;
|
||
private List<PublicHoliday> _holHolidays = [];
|
||
private bool _fetchingHolidays;
|
||
private int _vacRemaining => (_settings?.VacationDaysPerYear ?? 0) - _vacationDays.Count;
|
||
private int VacationUsedPercent => _settings?.VacationDaysPerYear > 0
|
||
? (int)Math.Round(_vacationDays.Count * 100.0 / _settings.VacationDaysPerYear)
|
||
: 0;
|
||
|
||
// Arbeitstage-Konfiguration als Liste von (Label, Getter, Setter)
|
||
private static readonly (string Label, Func<AppSettings, bool> Get, Action<AppSettings, bool> Set)[] WorkDayToggles =
|
||
[
|
||
("Montag", s => s.WorkMonday, (s, v) => s.WorkMonday = v),
|
||
("Dienstag", s => s.WorkTuesday, (s, v) => s.WorkTuesday = v),
|
||
("Mittwoch", s => s.WorkWednesday, (s, v) => s.WorkWednesday = v),
|
||
("Donnerstag", s => s.WorkThursday, (s, v) => s.WorkThursday = v),
|
||
("Freitag", s => s.WorkFriday, (s, v) => s.WorkFriday = v),
|
||
("Samstag", s => s.WorkSaturday, (s, v) => s.WorkSaturday = v),
|
||
("Sonntag", s => s.WorkSunday, (s, v) => s.WorkSunday = v),
|
||
];
|
||
|
||
protected override async Task OnInitializedAsync()
|
||
{
|
||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||
if (claim == null) return;
|
||
_userId = int.Parse(claim.Value);
|
||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||
|
||
var loadVacationsTask = LoadVacations();
|
||
var loadHolidaysTask = HolidayService.GetHolidaysAsync(_holYear, _settings.GermanState);
|
||
|
||
await Task.WhenAll(loadVacationsTask, loadHolidaysTask);
|
||
_holHolidays = await loadHolidaysTask;
|
||
}
|
||
|
||
private async Task LoadVacations()
|
||
{
|
||
_vacationDays = await TrackerService.GetVacationDaysAsync(_userId, _vacYear);
|
||
}
|
||
|
||
private async Task ChangeYear(int delta)
|
||
{
|
||
_vacYear += delta;
|
||
await LoadVacations();
|
||
}
|
||
|
||
private async Task Save()
|
||
{
|
||
if (_settings == null) return;
|
||
await TrackerService.SaveSettingsAsync(_settings);
|
||
// Nach dem Speichern Feiertage neu laden, falls sich das Bundesland geändert hat
|
||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings.GermanState);
|
||
Snackbar.Add("Einstellungen gespeichert", Severity.Success);
|
||
}
|
||
|
||
private async Task AddVacation()
|
||
{
|
||
if (_newVacDateFrom == null) return;
|
||
var from = DateOnly.FromDateTime(_newVacDateFrom.Value);
|
||
var to = _newVacDateTo.HasValue ? DateOnly.FromDateTime(_newVacDateTo.Value) : from;
|
||
if (to < from) to = from;
|
||
var note = string.IsNullOrWhiteSpace(_newVacNote) ? null : _newVacNote.Trim();
|
||
var current = from;
|
||
var added = 0;
|
||
while (current <= to)
|
||
{
|
||
if (_settings!.IsWorkDay(current.DayOfWeek))
|
||
{
|
||
await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = current, Note = note });
|
||
added++;
|
||
}
|
||
current = current.AddDays(1);
|
||
}
|
||
_newVacDateFrom = null;
|
||
_newVacDateTo = null;
|
||
_newVacNote = "";
|
||
await LoadVacations();
|
||
Snackbar.Add(added == 1 ? "Urlaubstag eingetragen" : $"{added} Urlaubstage eingetragen", Severity.Success);
|
||
}
|
||
|
||
private async Task RemoveVacation(int id)
|
||
{
|
||
await TrackerService.RemoveVacationDayAsync(_userId, id);
|
||
await LoadVacations();
|
||
Snackbar.Add("Urlaubstag entfernt", Severity.Info);
|
||
}
|
||
|
||
// ── Feiertage ────────────────────────────────────────────────
|
||
private async Task ChangeHolYear(int delta)
|
||
{
|
||
_holYear += delta;
|
||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings?.GermanState);
|
||
}
|
||
|
||
private async Task FetchHolidays()
|
||
{
|
||
_fetchingHolidays = true;
|
||
var (success, message) = await HolidayService.FetchAndStoreAsync(_holYear);
|
||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings?.GermanState);
|
||
_fetchingHolidays = false;
|
||
Snackbar.Add(message, success ? Severity.Success : Severity.Error);
|
||
}
|
||
|
||
private async Task DeleteHoliday(int id)
|
||
{
|
||
await HolidayService.DeleteAsync(id);
|
||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear, _settings?.GermanState);
|
||
Snackbar.Add("Feiertag entfernt", Severity.Info);
|
||
}
|
||
|
||
private async Task RepeatOnboarding()
|
||
{
|
||
try
|
||
{
|
||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "showOnboarding", "true");
|
||
Snackbar.Add("Onboarding zurückgesetzt. Leite weiter zur Startseite...", Severity.Info);
|
||
Nav.NavigateTo("/");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Snackbar.Add($"Fehler beim Zurücksetzen des Onboardings: {ex.Message}", Severity.Error);
|
||
}
|
||
}
|
||
|
||
private static string FormatHours(double hours)
|
||
{
|
||
var ts = TimeSpan.FromHours(hours);
|
||
return $"{(int)ts.TotalHours}:{ts.Minutes:D2} h";
|
||
}
|
||
}
|
||
|