From dd2d47e57d959447ce9615bae34b9bdc4b35914d Mon Sep 17 00:00:00 2001 From: "Wieland, Marc" Date: Mon, 8 Jun 2026 15:45:27 +0200 Subject: [PATCH] Userverwaltung improved --- Components/Layout/MainLayout.razor | 25 ++++++++ Components/Layout/NavMenu.razor | 9 ++- Components/Pages/AdminUsers.razor | 14 +++++ Components/Pages/Changelog.razor | 92 +++++++++++++++++++++++++++++ Data/AuthService.cs | 6 +- Data/UserNotificationService.cs | 19 ++++++ Program.cs | 1 + timetracker.db | Bin 49152 -> 49152 bytes 8 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 Components/Pages/Changelog.razor create mode 100644 Data/UserNotificationService.cs diff --git a/Components/Layout/MainLayout.razor b/Components/Layout/MainLayout.razor index 8ae796f..e08a347 100644 --- a/Components/Layout/MainLayout.razor +++ b/Components/Layout/MainLayout.razor @@ -1,4 +1,9 @@ @inherits LayoutComponentBase +@inject AuthenticationStateProvider AuthStateProvider +@inject NavigationManager Nav +@inject timetracker.Data.UserNotificationService UserNotificationService +@implements IDisposable +@using System.Security.Claims @@ -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 diff --git a/Components/Layout/NavMenu.razor b/Components/Layout/NavMenu.razor index ef15087..f10b3dd 100644 --- a/Components/Layout/NavMenu.razor +++ b/Components/Layout/NavMenu.razor @@ -25,5 +25,12 @@ - Version 1.1 + + + + Version 1.1 + + + \ No newline at end of file diff --git a/Components/Pages/AdminUsers.razor b/Components/Pages/AdminUsers.razor index 3c48166..cef5c86 100644 --- a/Components/Pages/AdminUsers.razor +++ b/Components/Pages/AdminUsers.razor @@ -4,6 +4,8 @@ @inject AuthService AuthService @inject ISnackbar Snackbar @inject AuthenticationStateProvider AuthStateProvider +@inject timetracker.Data.UserNotificationService UserNotificationService +@implements IDisposable Benutzerverwaltung – Timetracker @@ -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; diff --git a/Components/Pages/Changelog.razor b/Components/Pages/Changelog.razor new file mode 100644 index 0000000..d806a7e --- /dev/null +++ b/Components/Pages/Changelog.razor @@ -0,0 +1,92 @@ +@page "/changelog" +@rendermode InteractiveServer +@attribute [Authorize] + +Changelog – Timetracker + + + + @* ── Header ── *@ + + + + + Changelog + Versionshistorie & Änderungen + + + + + @foreach (var release in _releases) + { + + + + + @release.Version + + @if (release.IsLatest) + { + + Aktuell + + } + @release.Date + + + + @foreach (var entry in release.Entries) + { + + + @entry.Tag + + @entry.Text + + } + + + + } + + + +@code { + private record ChangeEntry(string Tag, string Text); + private record Release(string Version, string Date, bool IsLatest, List Entries); + + private static Color GetTagColor(string tag) => tag switch + { + "Neu" => Color.Success, + "Fix" => Color.Error, + "Upgrade" => Color.Info, + _ => Color.Default + }; + + private readonly List _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"), + ]), + ]; +} diff --git a/Data/AuthService.cs b/Data/AuthService.cs index 67b94ed..7b91746 100644 --- a/Data/AuthService.cs +++ b/Data/AuthService.cs @@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore; namespace timetracker.Data; -public class AuthService(IDbContextFactory factory) +public class AuthService(IDbContextFactory factory, UserNotificationService notifier) { public async Task LoginAsync(string username, string password) { @@ -31,6 +31,8 @@ public class AuthService(IDbContextFactory factory) { db.Users.Remove(user); await db.SaveChangesAsync(); + await notifier.NotifyUserDeletedAsync(userId); + await notifier.NotifyUsersChangedAsync(); } } @@ -48,6 +50,7 @@ public class AuthService(IDbContextFactory factory) user.Username = newUsername; await db.SaveChangesAsync(); + await notifier.NotifyUsersChangedAsync(); return null; } @@ -65,6 +68,7 @@ public class AuthService(IDbContextFactory factory) var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt }; db.Users.Add(user); await db.SaveChangesAsync(); + await notifier.NotifyUsersChangedAsync(); return (user, null); } diff --git a/Data/UserNotificationService.cs b/Data/UserNotificationService.cs new file mode 100644 index 0000000..683222d --- /dev/null +++ b/Data/UserNotificationService.cs @@ -0,0 +1,19 @@ +namespace timetracker.Data; + +public class UserNotificationService +{ + public event Func? OnUsersChanged; + public event Func? OnUserDeleted; + + public async Task NotifyUsersChangedAsync() + { + if (OnUsersChanged != null) + await OnUsersChanged.Invoke(); + } + + public async Task NotifyUserDeletedAsync(int userId) + { + if (OnUserDeleted != null) + await OnUserDeleted.Invoke(userId); + } +} diff --git a/Program.cs b/Program.cs index 32713e2..41fe168 100644 --- a/Program.cs +++ b/Program.cs @@ -24,6 +24,7 @@ builder.Services.AddAuthorization(options => }); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddHttpContextAccessor(); +builder.Services.AddSingleton(); builder.Services.AddScoped(); // Add services to the container. diff --git a/timetracker.db b/timetracker.db index de90888712b6b2ebd566a1d6fd138c42668db224..720ffe928108f81a6cc01a7b966d65bc7efb0baf 100644 GIT binary patch delta 276 zcmZo@U~Xt&-tbnQ*T~Gu$W+hR!q~*v%w+Q~`StdUteaQG-;(1ovobZbGB7YSwK6o_ zJfW_{kc)}mh=G4DzY*W%&4L0)`RY?xSQ(^KQ=L-t@={AOiX)O-E&Vcrva>R>^aH%j zy*)DwBc1$;QWFDH-A#PTii%SLg54bh3$)X1i^Gz9{T$OXG7^iOEZxdY(hamNTq;97 z{EEUo3(O7t4V;0@OjcF~)zs8%pz)Q(8Hvf+sYT@`MS&@q?*8Vk`fh$+t`WxGX6cqW zc||F?;RQ(+>FH5MA*sP8jw!~L#wNBVJ|$5>F1cQA+S*~3r8&js5uRZY&W`5AEN XfvH}8QOO1Vo<(7$LD`e%>`wszzWi94 delta 171 zcmV;c095~gfCGSl1F-EM4KXk}H90OYI5RReF*mdNAFn?F0<)@L+8zurIx{gkFfcGR zIx{e{kbZPA2mueB01urHIkOQEEe~U20tEmUWo2t+Zf<#cSaD5ma8WNeQ&)LeHA+xu zLq$?_Fl$wBNntBzcyl>3XJSQ1G;lR$dUHKPIWkyvP)Bh%FE~jvc5rq_YgtK9Y%^{) ZI7voLO+sT;NmX+-O;&eTP*szhzhsOqHVFU#