Neue Benutzerübersicht

This commit is contained in:
MarcWieland 2025-11-18 10:08:01 +01:00
parent 2927131f9e
commit 0c2a83a31f
2 changed files with 312 additions and 4 deletions

View File

@ -5,7 +5,7 @@
<div class="container py-4 text-center fade-in">
<h4 class="mb-4 text-primary">
<i class="bi bi-shield-lock me-2"></i> Willkommen im Adminbereich
<i class="bi bi-shield-lock me-2"></i> Adminbereich
</h4>
<div class="row g-4 justify-content-center">
@ -19,10 +19,10 @@
</div>
<div class="col-12 col-md-4">
<div class="card shadow-sm border-0 admin-tile" @onclick="@(() => Nav.NavigateTo("/admin/filters"))">
<div class="card shadow-sm border-0 admin-tile" @onclick="@(() => Nav.NavigateTo("/admin/users"))">
<div class="card-body text-center">
<i class="bi bi-funnel display-6 text-primary mb-3"></i>
<h5 class="fw-semibold">Filter verwalten</h5>
<i class="bi bi-person display-6 text-primary mb-3"></i>
<h5 class="fw-semibold">Benutzer verwalten</h5>
</div>
</div>
</div>

View File

@ -0,0 +1,308 @@
@page "/admin/users"
@layout AdminLayout
@inject IJSRuntime JS
<PageTitle>Benutzerverwaltung</PageTitle>
<style>
/* Responsives Verhalten optimieren */
.users-table-wrapper {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.users-table {
min-width: 900px; /* verhindert hässliches Zusammenstauchen auf kleinen Bildschirmen */
}
@@media (max-width: 992px) {
.users-table th:nth-child(2),
.users-table td:nth-child(2),
.users-table th:nth-child(3),
.users-table td:nth-child(3) {
min-width: 120px;
}
}
</style>
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4 flex-wrap gap-3">
<h4 class="text-primary mb-0">
<i class="bi bi-people me-2"></i> Benutzerverwaltung
</h4>
<button class="btn btn-success rounded-pill" @onclick="ShowCreateModal">
<i class="bi bi-plus-lg me-1"></i> Neuer Benutzer
</button>
</div>
@if (isLoading)
{
<div class="text-center py-5 text-muted">
<div class="spinner-border text-primary" role="status"></div>
<p class="mt-3">Lade Benutzer...</p>
</div>
}
else if (users.Count == 0)
{
<div class="alert alert-info text-center">
Keine Benutzer vorhanden. Lege den ersten Benutzer an!
</div>
}
else
{
<div class="users-table-wrapper">
<table class="table table-striped align-middle shadow-sm users-table">
<thead class="table-primary">
<tr>
<th>Benutzername</th>
<th>Vorname</th>
<th>Nachname</th>
<th>E-Mail</th>
<th>Rolle</th>
<th style="width: 80px;">Aktiv</th>
<th style="width: 140px; text-align: end;">Aktionen</th>
</tr>
</thead>
<tbody>
@foreach (var u in users)
{
<tr>
<td><strong>@u.UserName</strong></td>
<td>@u.FirstName</td>
<td>@u.LastName</td>
<td>@u.Email</td>
<td>
<span class="badge @(u.Role == "Administrator" ? "bg-danger" : "bg-secondary")">
@u.Role
</span>
</td>
<td class="text-center">
<i class="bi @(u.IsActive ? "bi-check-circle-fill text-success fs-5" : "bi-x-circle-fill text-danger fs-5")"></i>
</td>
<td class="text-end">
<button class="btn btn-outline-primary btn-sm rounded-pill me-1"
@onclick="() => ShowEditModal(u)"
title="Bearbeiten">
<i class="bi bi-pencil"></i>
</button>
<button class="btn btn-outline-danger btn-sm rounded-pill"
@onclick="() => DeleteUser(u)"
title="Löschen">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
}
<!-- Modal mit Backdrop-Click & ESC-Support -->
@if (showModal)
{
<div class="modal fade show d-block" tabindex="-1"
style="background-color: rgba(0,0,0,0.5);"
@onkeydown="OnModalKeyDown"
@onkeydown:preventDefault="true"
@onkeydown:stopPropagation="true"
@onclick="CloseModal"
>
<!-- Backdrop-Click schließt Modal -->
<div class="modal-dialog modal-dialog-centered" @onclick:stopPropagation>
<div class="modal-content border-0 shadow-lg rounded-4">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title">
<i class="me-2 @(isEditMode ? "bi bi-pencil" : "bi bi-person-plus")"></i>
@(isEditMode ? "Benutzer bearbeiten" : "Neuer Benutzer")
</h5>
<button type="button" class="btn-close btn-close-white" @onclick="CloseModal" aria-label="Schließen"></button>
</div>
<div class="modal-body">
<EditForm Model="@currentUser" OnValidSubmit="SaveUser">
<DataAnnotationsValidator />
ge
<ValidationSummary class="text-danger mb-3" />
<div class="mb-3">
<label class="form-label">Benutzername *</label>
<InputText class="form-control" @bind-Value="currentUser.UserName" required />
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Vorname</label>
<InputText class="form-control" @bind-Value="currentUser.FirstName" />
</div>
<div class="col-md-6">
<label class="form-label">Nachname</label>
<InputText class="form-control" @bind-Value="currentUser.LastName" />
</div>
</div>
<div class="mb-3 mt-3">
<label class="form-label">E-Mail *</label>
<InputText type="email" class="form-control" @bind-Value="currentUser.Email" required />
</div>
<div class="mb-3">
<label class="form-label">
Passwort @(isEditMode ? "(leer = unverändert)" : "*")
</label>
<InputText type="password"
class="form-control"
@bind-Value="currentUser.Password"
placeholder="@(isEditMode ? "Leer lassen = Passwort bleibt gleich" : "Pflichtfeld beim Anlegen")"
required="@(!isEditMode)" />
</div>
<div class="mb-3">
<label class="form-label">Rolle</label>
<InputSelect class="form-select" @bind-Value="currentUser.Role">
<option value="User">User</option>
<option value="Administrator">Administrator</option>
</InputSelect>
</div>
<div class="form-check mb-4">
<InputCheckbox class="form-check-input" @bind-Value="currentUser.IsActive" />
<label class="form-check-label">Account aktiv</label>
</div>
<div class="text-end">
<button type="button" class="btn btn-outline-secondary me-2 rounded-pill" @onclick="CloseModal">
Abbrechen
</button>
<button type="submit" class="btn btn-success rounded-pill">
@(isEditMode ? "Aktualisieren" : "Anlegen")
</button>
</div>
</EditForm>
</div>
</div>
</div>
</div>
}
</div>
@code {
private List<AppUser> users = new();
private AppUser currentUser = new();
private bool showModal = false;
private bool isEditMode = false;
private bool isLoading = true;
protected override async Task OnInitializedAsync()
{
// Demo-Daten direkt beim Start
users = new List<AppUser>
{
new AppUser { Id = 1, UserName = "admin", FirstName = "Max", LastName = "Mustermann", Email = "admin@filtercair.de", Role = "Administrator", IsActive = true },
new AppUser { Id = 2, UserName = "peter", FirstName = "Peter", LastName = "Parker", Email = "peter@company.com", Role = "User", IsActive = true },
new AppUser { Id = 3, UserName = "susi", FirstName = "Susanne", LastName = "Müller", Email = "susi@company.com", Role = "User", IsActive = false },
new AppUser { Id = 4, UserName = "tom", FirstName = "Tom", LastName = "Tester", Email = "tom@test.de", Role = "User", IsActive = true }
};
isLoading = false;
}
private void ShowCreateModal()
{
currentUser = new AppUser { IsActive = true, Role = "User" };
isEditMode = false;
showModal = true;
}
private void ShowEditModal(AppUser user)
{
currentUser = new AppUser
{
Id = user.Id,
UserName = user.UserName,
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
Role = user.Role,
IsActive = user.IsActive,
Password = "" // bleibt leer → wird beim Edit nicht überschrieben
};
isEditMode = true;
showModal = true;
}
private void CloseModal()
{
showModal = false;
StateHasChanged();
}
private void OnBackdropClick(MouseEventArgs args)
{
CloseModal();
}
private void OnModalKeyDown(KeyboardEventArgs args)
{
if (args.Key == "Escape")
{
CloseModal();
}
}
private async Task SaveUser()
{
if (string.IsNullOrWhiteSpace(currentUser.UserName) || string.IsNullOrWhiteSpace(currentUser.Email) ||
(!isEditMode && string.IsNullOrWhiteSpace(currentUser.Password)))
{
await JS.InvokeVoidAsync("showToast", "Bitte alle Pflichtfelder ausfüllen!", "error");
return;
}
if (isEditMode)
{
var existing = users.First(x => x.Id == currentUser.Id);
existing.UserName = currentUser.UserName;
existing.FirstName = currentUser.FirstName;
existing.LastName = currentUser.LastName;
existing.Email = currentUser.Email;
existing.Role = currentUser.Role;
existing.IsActive = currentUser.IsActive;
// Passwort wird nur geändert, wenn was eingetragen ist
if (!string.IsNullOrWhiteSpace(currentUser.Password))
existing.PasswordHash = currentUser.Password; // hier einfach als Klartext für Demozwecke
}
else
{
currentUser.Id = users.Max(x => x.Id) + 1;
currentUser.PasswordHash = currentUser.Password; // Dummy-Hash
users.Add(currentUser);
}
CloseModal();
await JS.InvokeVoidAsync("showToast", isEditMode ? "✅ Benutzer aktualisiert!" : "✅ Benutzer angelegt!");
}
private async Task DeleteUser(AppUser user)
{
var confirmed = await JS.InvokeAsync<bool>("confirm", $"Soll der Benutzer '{user.UserName}' wirklich gelöscht werden?");
if (confirmed)
{
users.Remove(user);
await JS.InvokeVoidAsync("showToast", "🗑️ Benutzer gelöscht");
}
}
// Dummy-User-Klasse nur für diese Seite
public class AppUser
{
public int Id { get; set; }
public string UserName { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty; // nur im Modal
public string? PasswordHash { get; set; } // wird später mal richtig
public string Role { get; set; } = "User";
public bool IsActive { get; set; } = true;
}
}