Auth integration
This commit is contained in:
@@ -6,5 +6,15 @@
|
||||
<MudNavLink Href="feiertage" Icon="@Icons.Material.Filled.Celebration">Feiertage</MudNavLink>
|
||||
<MudNavLink Href="urlaub-maximizer" Icon="@Icons.Material.Filled.AutoAwesome">Urlaubs-Maximizer</MudNavLink>
|
||||
<MudNavLink Href="settings" Icon="@Icons.Material.Filled.Settings">Einstellungen</MudNavLink>
|
||||
<MudDivider Class="mt-2 mb-2" />
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Class="px-4 py-1" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccountCircle" Color="Color.Primary" Size="Size.Small" />
|
||||
<MudText Typo="Typo.body2" Style="font-weight:600">@context.User.Identity?.Name</MudText>
|
||||
</MudStack>
|
||||
<MudNavLink Href="/auth/logout" Icon="@Icons.Material.Filled.Logout">Abmelden</MudNavLink>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</MudNavMenu>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/feiertage"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject HolidayService HolidayService
|
||||
|
||||
<PageTitle>Feiertage – Timetracker</PageTitle>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@page "/"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject TimetrackerService TrackerService
|
||||
@inject HolidayService HolidayService
|
||||
@inject ISnackbar Snackbar
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>KW @_kw – Wochenübersicht – Timetracker</PageTitle>
|
||||
|
||||
@@ -332,6 +334,7 @@ else
|
||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||
|
||||
private bool _loading = true;
|
||||
private int _userId;
|
||||
private DateOnly _monday;
|
||||
private List<DayVm> _days = [];
|
||||
private AppSettings _settings = new();
|
||||
@@ -349,10 +352,14 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (claim == null) return; // Prerender-Pass – Circuit noch nicht authentifiziert
|
||||
_userId = int.Parse(claim.Value);
|
||||
_monday = GetMonday(DateOnly.FromDateTime(DateTime.Today));
|
||||
_settings = await TrackerService.GetSettingsAsync();
|
||||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||
await LoadWeek();
|
||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_settings);
|
||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
@@ -364,11 +371,11 @@ else
|
||||
_holidays = list.ToDictionary(h => h.Date, h => h.Name);
|
||||
_holidayYear = _monday.Year;
|
||||
}
|
||||
var dbDays = await TrackerService.GetWeekAsync(_monday);
|
||||
var dbDays = await TrackerService.GetWeekAsync(_userId, _monday);
|
||||
_days = Enumerable.Range(0, 7).Select(i =>
|
||||
{
|
||||
var date = _monday.AddDays(i);
|
||||
return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date);
|
||||
return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date, _userId);
|
||||
}).ToList();
|
||||
BuildWeekLabels();
|
||||
}
|
||||
@@ -421,7 +428,7 @@ else
|
||||
private async Task SaveDay(DayVm day)
|
||||
{
|
||||
await TrackerService.UpsertWorkDayAsync(day.ToWorkDay());
|
||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_settings);
|
||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
|
||||
BuildWeekLabels();
|
||||
}
|
||||
|
||||
@@ -486,6 +493,7 @@ else
|
||||
private sealed class DayVm
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public DateOnly Date { get; set; }
|
||||
public TimeSpan? Start { get; set; }
|
||||
public TimeSpan? End { get; set; }
|
||||
@@ -500,9 +508,10 @@ else
|
||||
|
||||
public TimeSpan? NetWork => GrossWork.HasValue ? GrossWork.Value - TotalBreakTime : null;
|
||||
|
||||
public static DayVm From(WorkDay? wd, DateOnly date) => new()
|
||||
public static DayVm From(WorkDay? wd, DateOnly date, int userId) => new()
|
||||
{
|
||||
Id = wd?.Id ?? 0,
|
||||
UserId = wd?.UserId ?? userId,
|
||||
Date = date,
|
||||
Start = wd?.StartTime?.ToTimeSpan(),
|
||||
End = wd?.EndTime?.ToTimeSpan(),
|
||||
@@ -517,6 +526,7 @@ else
|
||||
public WorkDay ToWorkDay() => new()
|
||||
{
|
||||
Id = Id,
|
||||
UserId = UserId,
|
||||
Date = Date,
|
||||
StartTime = Start.HasValue ? TimeOnly.FromTimeSpan(Start.Value) : null,
|
||||
EndTime = End.HasValue ? TimeOnly.FromTimeSpan(End.Value) : null,
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
@page "/login"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [AllowAnonymous]
|
||||
@inject NavigationManager Nav
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>Anmelden – Timetracker</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="mt-16">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||||
|
||||
@* ── Logo / Header ── *@
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccessTime"
|
||||
Style="font-size:4rem; color:#1565C0" />
|
||||
<MudText Typo="Typo.h4" Style="font-weight:700; color:#1565C0">Timetracker</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudPaper Elevation="4" Class="pa-6 rounded-xl" Style="width:100%">
|
||||
<MudTabs @bind-ActivePanelIndex="_activeTab" Rounded="true" Centered="true" Color="Color.Primary">
|
||||
|
||||
@* ── Login ── *@
|
||||
<MudTabPanel Text="Anmelden">
|
||||
<MudStack Spacing="3" Class="mt-4">
|
||||
@if (_error != null && _activeTab == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||
}
|
||||
<form action="/auth/login" method="post">
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_loginUsername"
|
||||
Label="Benutzername"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||
name="username"
|
||||
AutoFocus="true" />
|
||||
<MudTextField @bind-Value="_loginPassword"
|
||||
Label="Passwort"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||
InputType="@(_showLoginPw ? InputType.Text : InputType.Password)"
|
||||
AdornmentAriaLabel="Passwort anzeigen"
|
||||
name="password"
|
||||
OnAdornmentClick="@(() => _showLoginPw = !_showLoginPw)" />
|
||||
<MudButton ButtonType="ButtonType.Submit"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
FullWidth="true"
|
||||
Size="Size.Large"
|
||||
StartIcon="@Icons.Material.Filled.Login">
|
||||
Anmelden
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</form>
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
@* ── Registrieren ── *@
|
||||
<MudTabPanel Text="Registrieren">
|
||||
<MudStack Spacing="3" Class="mt-4">
|
||||
@if (_error != null && _activeTab == 1)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||
}
|
||||
<form action="/auth/register" method="post">
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_regUsername"
|
||||
Label="Benutzername"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||
name="username"
|
||||
HelperText="Mindestens 3 Zeichen" />
|
||||
<MudTextField @bind-Value="_regPassword"
|
||||
Label="Passwort"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||
InputType="@(_showRegPw ? InputType.Text : InputType.Password)"
|
||||
AdornmentAriaLabel="Passwort anzeigen"
|
||||
name="password"
|
||||
HelperText="Mindestens 6 Zeichen"
|
||||
OnAdornmentClick="@(() => _showRegPw = !_showRegPw)" />
|
||||
<MudButton ButtonType="ButtonType.Submit"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Secondary"
|
||||
FullWidth="true"
|
||||
Size="Size.Large"
|
||||
StartIcon="@Icons.Material.Filled.PersonAdd">
|
||||
Konto erstellen
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</form>
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
</MudTabs>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private int _activeTab = 0;
|
||||
private string? _error;
|
||||
private string _loginUsername = "";
|
||||
private string _loginPassword = "";
|
||||
private string _regUsername = "";
|
||||
private string _regPassword = "";
|
||||
private bool _showLoginPw;
|
||||
private bool _showRegPw;
|
||||
|
||||
[SupplyParameterFromQuery(Name = "error")]
|
||||
public string? ErrorParam { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "tab")]
|
||||
public string? TabParam { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
_error = ErrorParam switch
|
||||
{
|
||||
"invalid" => "Benutzername oder Passwort falsch.",
|
||||
not null => Uri.UnescapeDataString(ErrorParam),
|
||||
_ => null
|
||||
};
|
||||
_activeTab = TabParam == "register" ? 1 : 0;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
@page "/month"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject TimetrackerService TrackerService
|
||||
@inject HolidayService HolidayService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>@_deCulture.DateTimeFormat.GetMonthName(_month) @_year – Monatsübersicht – Timetracker</PageTitle>
|
||||
|
||||
@@ -150,6 +152,7 @@ else
|
||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||
|
||||
private bool _loading = true;
|
||||
private int _userId;
|
||||
private int _year = DateTime.Today.Year;
|
||||
private int _month = DateTime.Today.Month;
|
||||
private List<MonthDayVm> _days = [];
|
||||
@@ -167,16 +170,20 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_settings = await TrackerService.GetSettingsAsync();
|
||||
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);
|
||||
await LoadMonth();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private async Task LoadMonth()
|
||||
{
|
||||
var workDays = await TrackerService.GetMonthAsync(_year, _month);
|
||||
var workDays = await TrackerService.GetMonthAsync(_userId, _year, _month);
|
||||
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||
var vacations = await TrackerService.GetVacationDaysAsync(_year);
|
||||
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
||||
|
||||
var holidayMap = holidays.ToDictionary(h => h.Date, h => h.Name);
|
||||
var vacationSet = vacations.Select(v => v.Date).ToHashSet();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@page "/settings"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject TimetrackerService TrackerService
|
||||
@inject HolidayService HolidayService
|
||||
@inject ISnackbar Snackbar
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>Einstellungen – Timetracker</PageTitle>
|
||||
|
||||
@@ -375,6 +377,7 @@ else
|
||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||
|
||||
private AppSettings? _settings;
|
||||
private int _userId;
|
||||
private int _vacYear = DateTime.Today.Year;
|
||||
private List<VacationDay> _vacationDays = [];
|
||||
private DateTime? _newVacDateFrom;
|
||||
@@ -402,14 +405,18 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_settings = await TrackerService.GetSettingsAsync();
|
||||
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);
|
||||
await LoadVacations();
|
||||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
|
||||
}
|
||||
|
||||
private async Task LoadVacations()
|
||||
{
|
||||
_vacationDays = await TrackerService.GetVacationDaysAsync(_vacYear);
|
||||
_vacationDays = await TrackerService.GetVacationDaysAsync(_userId, _vacYear);
|
||||
}
|
||||
|
||||
private async Task ChangeYear(int delta)
|
||||
@@ -438,7 +445,7 @@ else
|
||||
{
|
||||
if (_settings!.IsWorkDay(current.DayOfWeek))
|
||||
{
|
||||
await TrackerService.AddVacationDayAsync(new VacationDay { Date = current, Note = note });
|
||||
await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = current, Note = note });
|
||||
added++;
|
||||
}
|
||||
current = current.AddDays(1);
|
||||
@@ -452,7 +459,7 @@ else
|
||||
|
||||
private async Task RemoveVacation(int id)
|
||||
{
|
||||
await TrackerService.RemoveVacationDayAsync(id);
|
||||
await TrackerService.RemoveVacationDayAsync(_userId, id);
|
||||
await LoadVacations();
|
||||
Snackbar.Add("Urlaubstag entfernt", Severity.Info);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@page "/urlaub-maximizer"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject TimetrackerService TrackerService
|
||||
@inject HolidayService HolidayService
|
||||
@inject ISnackbar Snackbar
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>Urlaubs-Maximizer – Timetracker</PageTitle>
|
||||
|
||||
@@ -232,10 +234,15 @@ else
|
||||
private int _remainingDays;
|
||||
private List<Suggestion> _suggestions = [];
|
||||
private string _subLabel = "";
|
||||
private int _userId;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_settings = await TrackerService.GetSettingsAsync();
|
||||
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);
|
||||
await LoadYear();
|
||||
_loading = false;
|
||||
}
|
||||
@@ -243,7 +250,7 @@ else
|
||||
private async Task LoadYear()
|
||||
{
|
||||
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||
var vacations = await TrackerService.GetVacationDaysAsync(_year);
|
||||
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
||||
_holidays = holidays.ToDictionary(h => h.Date, h => h.Name);
|
||||
_vacationSet = vacations.Select(v => v.Date).ToHashSet();
|
||||
_remainingDays = Math.Max(0, _settings.VacationDaysPerYear - vacations.Count);
|
||||
@@ -262,7 +269,7 @@ else
|
||||
private async Task TakeSuggestion(Suggestion s)
|
||||
{
|
||||
foreach (var d in s.VacationDaysToTake.Where(d => !_vacationSet.Contains(d)))
|
||||
await TrackerService.AddVacationDayAsync(new VacationDay { Date = d, Note = "Urlaubs-Maximizer" });
|
||||
await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = d, Note = "Urlaubs-Maximizer" });
|
||||
await LoadYear();
|
||||
var word = s.VacationDaysNeeded == 1 ? "Urlaubstag" : "Urlaubstage";
|
||||
Snackbar.Add($"{s.VacationDaysNeeded} {word} eingetragen – {s.TotalFreeDays} Tage frei!", Severity.Success);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
@inject NavigationManager Nav
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var returnUrl = Uri.EscapeDataString(Nav.Uri);
|
||||
Nav.NavigateTo($"/login?returnUrl={returnUrl}", forceLoad: true);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
|
||||
<NotAuthorized>
|
||||
<RedirectToLogin />
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using System.Security.Claims
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
|
||||
Reference in New Issue
Block a user