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
@@ -0,0 +1,281 @@
@page "/profile"
@rendermode InteractiveWebAssembly
@attribute [Authorize]
@using System.Security.Claims
@inject IAuthService AuthService
@inject ITimetrackerService TrackerService
@inject ISnackbar Snackbar
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager Nav
<PageTitle>Benutzerprofil Timetracker</PageTitle>
@if (_loading)
{
<MudStack AlignItems="AlignItems.Center" Class="mt-16">
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
<MudText Color="Color.Secondary">Lade Profil…</MudText>
</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.AccountCircle" Style="color:white; font-size:2.5rem" />
<MudStack Spacing="0">
<MudText Typo="Typo.h5" Style="color:white; font-weight:700">Mein Profil</MudText>
<MudText Typo="Typo.caption" Style="color:rgba(255,255,255,0.72)">
Persönliche Details verwalten und Account sichern
</MudText>
</MudStack>
</MudStack>
</MudPaper>
<MudGrid Spacing="4">
@* ── Übersicht & Details ── *@
<MudItem xs="12" md="4">
<MudCard Elevation="3" Class="rounded-xl h-100">
<MudCardContent>
<MudStack AlignItems="AlignItems.Center" Class="py-6" Spacing="3">
<MudAvatar Style="width:96px; height:96px; font-size:2rem; font-weight:700; background: linear-gradient(135deg, #0EA5E9 0%, #0284C7 100%); color: white;">
@GetInitials(_username)
</MudAvatar>
<MudStack AlignItems="AlignItems.Center" Spacing="0">
<MudText Typo="Typo.h6" Style="font-weight:700">@_username</MudText>
<MudText Typo="Typo.caption" Color="Color.Secondary">@(_isAdmin ? "Administrator" : "Standardbenutzer")</MudText>
</MudStack>
</MudStack>
<MudDivider Class="my-3" />
<MudStack Spacing="2" Class="pa-2">
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Color="Color.Secondary">Benutzer-ID</MudText>
<MudText Typo="Typo.body2"><b>#@_userId</b></MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Color="Color.Secondary">Gleitzeit-Saldo</MudText>
<MudText Typo="Typo.body2" Color="@(_overtime >= TimeSpan.Zero ? Color.Success : Color.Warning)">
<b>@FormatTs(_overtime, sign: true)</b>
</MudText>
</MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween">
<MudText Typo="Typo.body2" Color="Color.Secondary">Sollstunden / Tag</MudText>
<MudText Typo="Typo.body2"><b>@_settings.DailyTargetHours h</b></MudText>
</MudStack>
</MudStack>
</MudCardContent>
</MudCard>
</MudItem>
@* ── Einstellungen verwalten ── *@
<MudItem xs="12" md="8">
<MudStack Spacing="4">
@* Benutzername ändern *@
<MudCard Elevation="3" Class="rounded-xl">
<MudCardHeader>
<CardHeaderContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Badge" Color="Color.Primary" />
<MudText Typo="Typo.h6" Style="font-weight:600">Benutzername ändern</MudText>
</MudStack>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudForm @ref="_renameForm" @bind-IsValid="_renameIsValid">
<MudStack Spacing="3">
<MudTextField @bind-Value="_newUsername"
Label="Neuer Benutzername"
Variant="Variant.Outlined"
Required="true"
RequiredError="Bitte gib einen Benutzernamen ein."
Validation="@(new Func<string, string?>(ValidateUsername))" />
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="HandleRename"
Disabled="!_renameIsValid || _newUsername == _username"
Class="align-self-start rounded-lg py-2 px-4"
StartIcon="@Icons.Material.Filled.Save">
Speichern
</MudButton>
</MudStack>
</MudForm>
</MudCardContent>
</MudCard>
@* Passwort ändern *@
<MudCard Elevation="3" Class="rounded-xl">
<MudCardHeader>
<CardHeaderContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@Icons.Material.Filled.Lock" Color="Color.Primary" />
<MudText Typo="Typo.h6" Style="font-weight:600">Passwort ändern</MudText>
</MudStack>
</CardHeaderContent>
</MudCardHeader>
<MudCardContent>
<MudForm @ref="_passwordForm" @bind-IsValid="_passwordIsValid">
<MudStack Spacing="3">
<MudTextField @bind-Value="_currentPassword"
Label="Aktuelles Passwort"
InputType="InputType.Password"
Variant="Variant.Outlined"
Required="true"
RequiredError="Bitte gib dein aktuelles Passwort ein." />
<MudTextField @bind-Value="_newPassword"
Label="Neues Passwort"
InputType="InputType.Password"
Variant="Variant.Outlined"
Required="true"
RequiredError="Bitte gib dein neues Passwort ein."
Validation="@(new Func<string, string?>(ValidatePassword))" />
<MudTextField @bind-Value="_confirmPassword"
Label="Neues Passwort bestätigen"
InputType="InputType.Password"
Variant="Variant.Outlined"
Required="true"
RequiredError="Bitte bestätige dein neues Passwort."
Validation="@(new Func<string, string?>(ValidateConfirmPassword))" />
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
OnClick="HandleChangePassword"
Disabled="!_passwordIsValid"
Class="align-self-start rounded-lg py-2 px-4"
StartIcon="@Icons.Material.Filled.VpnKey">
Passwort ändern
</MudButton>
</MudStack>
</MudForm>
</MudCardContent>
</MudCard>
</MudStack>
</MudItem>
</MudGrid>
</MudStack>
}
@code {
private bool _loading = true;
private int _userId;
private string _username = "";
private string _newUsername = "";
private bool _isAdmin;
private AppSettings _settings = new();
private TimeSpan _overtime;
// Formulare
private MudForm? _renameForm;
private bool _renameIsValid;
private MudForm? _passwordForm;
private bool _passwordIsValid;
// Passwörter
private string _currentPassword = "";
private string _newPassword = "";
private string _confirmPassword = "";
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);
_username = authState.User.Identity?.Name ?? "";
_newUsername = _username;
_isAdmin = _username.Equals("marc", StringComparison.OrdinalIgnoreCase);
_settings = await TrackerService.GetSettingsAsync(_userId);
_overtime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
_loading = false;
}
private string GetInitials(string? name)
{
if (string.IsNullOrWhiteSpace(name)) return "U";
var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 1) return parts[0][..Math.Min(2, parts[0].Length)].ToUpper();
return $"{parts[0][0]}{parts[1][0]}".ToUpper();
}
private static string FormatTs(TimeSpan ts, bool sign = false)
{
if (ts == TimeSpan.Zero && sign) return "±0:00";
var prefix = sign ? (ts >= TimeSpan.Zero ? "+" : "") : (ts < TimeSpan.Zero ? "" : "");
var abs = ts.Duration();
return $"{prefix}{(int)abs.TotalHours}:{abs.Minutes:D2}";
}
// Validierungen
private string? ValidateUsername(string val)
{
if (string.IsNullOrWhiteSpace(val) || val.Length < 3)
return "Benutzername muss mindestens 3 Zeichen lang sein.";
return null;
}
private string? ValidatePassword(string val)
{
if (string.IsNullOrWhiteSpace(val) || val.Length < 6)
return "Passwort muss mindestens 6 Zeichen lang sein.";
return null;
}
private string? ValidateConfirmPassword(string val)
{
if (val != _newPassword)
return "Die Passwörter stimmen nicht überein.";
return null;
}
// Aktionen
private async Task HandleRename()
{
if (_renameForm == null) return;
await _renameForm.ValidateAsync();
if (!_renameForm.IsValid) return;
var error = await AuthService.RenameUserAsync(_userId, _newUsername);
if (error != null)
{
Snackbar.Add(error, Severity.Error);
}
else
{
Snackbar.Add("Benutzername erfolgreich geändert. Bitte melde dich ggf. neu an, um alle Anzeigen zu aktualisieren.", Severity.Success);
_username = _newUsername;
// Umleitung zur Startseite, um die neue Identity zu laden
Nav.NavigateTo("/", forceLoad: true);
}
}
private async Task HandleChangePassword()
{
if (_passwordForm == null) return;
await _passwordForm.ValidateAsync();
if (!_passwordForm.IsValid) return;
var error = await AuthService.ChangePasswordAsync(_userId, _currentPassword, _newPassword);
if (error != null)
{
Snackbar.Add(error, Severity.Error);
}
else
{
Snackbar.Add("Passwort erfolgreich geändert.", Severity.Success);
_currentPassword = "";
_newPassword = "";
_confirmPassword = "";
if (_passwordForm != null) await _passwordForm.ResetAsync();
}
}
}