Userverwaltung improved

This commit is contained in:
Wieland, Marc
2026-06-08 15:45:27 +02:00
parent ee4d6cb5b1
commit dd2d47e57d
8 changed files with 164 additions and 2 deletions
+25
View File
@@ -1,4 +1,9 @@
@inherits LayoutComponentBase
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager Nav
@inject timetracker.Data.UserNotificationService UserNotificationService
@implements IDisposable
@using System.Security.Claims
<MudThemeProvider Theme="_theme" />
<MudPopoverProvider />
@@ -29,6 +34,26 @@
@code {
private bool _drawerOpen = true;
protected override void OnInitialized()
{
UserNotificationService.OnUserDeleted += HandleUserDeleted;
}
private async Task HandleUserDeleted(int deletedUserId)
{
var state = await AuthStateProvider.GetAuthenticationStateAsync();
var idClaim = state.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (idClaim != null && int.TryParse(idClaim, out var myId) && myId == deletedUserId)
{
await InvokeAsync(() => Nav.NavigateTo("/auth/logout", forceLoad: true));
}
}
public void Dispose()
{
UserNotificationService.OnUserDeleted -= HandleUserDeleted;
}
private readonly MudTheme _theme = new()
{
PaletteLight = new PaletteLight
+8 -1
View File
@@ -25,5 +25,12 @@
</MudNavMenu>
</Authorized>
</AuthorizeView>
<MudText Typo="Typo.caption" Class="px-4 py-2" Style="color: var(--mud-palette-text-disabled); text-align:center;">Version 1.1</MudText>
<MudTooltip Text="Changelog anzeigen" Placement="Placement.Top">
<MudNavMenu>
<MudNavLink Href="/changelog" Icon="@Icons.Material.Filled.NewReleases"
Style="color: var(--mud-palette-text-disabled); font-size:0.75rem;">
Version 1.1
</MudNavLink>
</MudNavMenu>
</MudTooltip>
</div>
+14
View File
@@ -4,6 +4,8 @@
@inject AuthService AuthService
@inject ISnackbar Snackbar
@inject AuthenticationStateProvider AuthStateProvider
@inject timetracker.Data.UserNotificationService UserNotificationService
@implements IDisposable
<PageTitle>Benutzerverwaltung Timetracker</PageTitle>
@@ -122,10 +124,22 @@ else
.User.FindFirst(ClaimTypes.NameIdentifier);
if (claim == null) return;
UserNotificationService.OnUsersChanged += RefreshUsers;
_users = await AuthService.GetAllUsersAsync();
_loading = false;
}
private async Task RefreshUsers()
{
_users = await AuthService.GetAllUsersAsync();
await InvokeAsync(StateHasChanged);
}
public void Dispose()
{
UserNotificationService.OnUsersChanged -= RefreshUsers;
}
private void StartEdit(User user)
{
_editUserId = user.Id;
+92
View File
@@ -0,0 +1,92 @@
@page "/changelog"
@rendermode InteractiveServer
@attribute [Authorize]
<PageTitle>Changelog Timetracker</PageTitle>
<MudStack Spacing="4">
@* ── Header ── *@
<MudPaper Elevation="4" Class="pa-5 rounded-xl"
Style="background: linear-gradient(135deg, #3F51B5 0%, #1A237E 100%); color:white;">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="3">
<MudIcon Icon="@Icons.Material.Filled.NewReleases" Style="color:white; font-size:2rem" />
<MudStack Spacing="0">
<MudText Typo="Typo.h5" Style="color:white; font-weight:700">Changelog</MudText>
<MudText Typo="Typo.caption" Style="color:rgba(255,255,255,0.72)">Versionshistorie &amp; Änderungen</MudText>
</MudStack>
</MudStack>
</MudPaper>
@foreach (var release in _releases)
{
<MudCard Elevation="2" Class="rounded-xl">
<MudCardContent>
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="mb-3">
<MudChip T="string" Color="@(release.IsLatest ? Color.Primary : Color.Default)"
Variant="Variant.Filled" Size="Size.Medium" Style="font-weight:700">
@release.Version
</MudChip>
@if (release.IsLatest)
{
<MudChip T="string" Color="Color.Success" Variant="Variant.Outlined" Size="Size.Small">
Aktuell
</MudChip>
}
<MudText Typo="Typo.body2" Color="Color.Secondary">@release.Date</MudText>
</MudStack>
<MudDivider Class="mb-3" />
<MudStack Spacing="1">
@foreach (var entry in release.Entries)
{
<MudStack Row="true" AlignItems="AlignItems.Start" Spacing="2">
<MudChip T="string" Color="@GetTagColor(entry.Tag)" Variant="Variant.Outlined"
Size="Size.Small" Style="min-width:72px; justify-content:center; font-size:0.7rem;">
@entry.Tag
</MudChip>
<MudText Typo="Typo.body2" Style="padding-top:2px">@entry.Text</MudText>
</MudStack>
}
</MudStack>
</MudCardContent>
</MudCard>
}
</MudStack>
@code {
private record ChangeEntry(string Tag, string Text);
private record Release(string Version, string Date, bool IsLatest, List<ChangeEntry> Entries);
private static Color GetTagColor(string tag) => tag switch
{
"Neu" => Color.Success,
"Fix" => Color.Error,
"Upgrade" => Color.Info,
_ => Color.Default
};
private readonly List<Release> _releases =
[
new("1.1", "08.06.2026", true,
[
new("Neu", "Versionsnummer in der Navbar mit Link zum Changelog"),
new("Neu", "Changelog-Seite"),
new("Neu", "Live-Aktualisierung der Benutzerliste bei neuer Registrierung"),
new("Neu", "Automatisches Abmelden gelöschter Benutzer"),
new("Neu", "Benutzernamen in der Benutzerverwaltung umbenennen"),
new("Upgrade", "Navbar: Benutzer und Abmelden-Button unten fixiert"),
]),
new("1.0", "20.05.2026", false,
[
new("Neu", "Erste Version des Timetrackers"),
new("Neu", "Wochenübersicht mit Arbeitszeiten und Pausen"),
new("Neu", "Monatsübersicht"),
new("Neu", "Feiertage-Verwaltung"),
new("Neu", "Urlaubs-Maximizer"),
new("Neu", "Einstellungen"),
new("Neu", "Benutzerverwaltung für Admins"),
new("Neu", "Registrierung und Login"),
]),
];
}
+5 -1
View File
@@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
namespace timetracker.Data;
public class AuthService(IDbContextFactory<TimetrackerDbContext> factory)
public class AuthService(IDbContextFactory<TimetrackerDbContext> factory, UserNotificationService notifier)
{
public async Task<User?> LoginAsync(string username, string password)
{
@@ -31,6 +31,8 @@ public class AuthService(IDbContextFactory<TimetrackerDbContext> factory)
{
db.Users.Remove(user);
await db.SaveChangesAsync();
await notifier.NotifyUserDeletedAsync(userId);
await notifier.NotifyUsersChangedAsync();
}
}
@@ -48,6 +50,7 @@ public class AuthService(IDbContextFactory<TimetrackerDbContext> factory)
user.Username = newUsername;
await db.SaveChangesAsync();
await notifier.NotifyUsersChangedAsync();
return null;
}
@@ -65,6 +68,7 @@ public class AuthService(IDbContextFactory<TimetrackerDbContext> factory)
var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt };
db.Users.Add(user);
await db.SaveChangesAsync();
await notifier.NotifyUsersChangedAsync();
return (user, null);
}
+19
View File
@@ -0,0 +1,19 @@
namespace timetracker.Data;
public class UserNotificationService
{
public event Func<Task>? OnUsersChanged;
public event Func<int, Task>? OnUserDeleted;
public async Task NotifyUsersChangedAsync()
{
if (OnUsersChanged != null)
await OnUsersChanged.Invoke();
}
public async Task NotifyUserDeletedAsync(int userId)
{
if (OnUserDeleted != null)
await OnUserDeleted.Invoke(userId);
}
}
+1
View File
@@ -24,6 +24,7 @@ builder.Services.AddAuthorization(options =>
});
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<timetracker.Data.UserNotificationService>();
builder.Services.AddScoped<AuthService>();
// Add services to the container.
BIN
View File
Binary file not shown.