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 de90888..720ffe9 100644
Binary files a/timetracker.db and b/timetracker.db differ