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