Admin Bereich implementiert & Vorbereitung auf API

This commit is contained in:
Marc Wieland 2025-11-05 10:34:49 +01:00
parent 195e0a7b45
commit efda608c4f
12 changed files with 601 additions and 3 deletions

View File

@ -0,0 +1,51 @@
@inherits LayoutComponentBase
@inject NavigationManager Nav
<div class="d-flex flex-column min-vh-100 bg-light">
<!-- 🔹 Admin Navbar -->
<nav class="navbar navbar-expand-lg navbar-dark" style="background-color:#003d66;">
<div class="container-fluid">
<a class="navbar-brand fw-semibold" href="/admin">
<i class="bi bi-gear-fill me-2"></i> Adminbereich
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#adminNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="adminNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<NavLink class="nav-link text-white" href="/admin/customers" Match="NavLinkMatch.All">
<i class="bi bi-buildings me-1"></i> Kunden
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link text-white" href="/admin/filters">
<i class="bi bi-funnel me-1"></i> Filter
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link text-white" href="/admin/tasks">
<i class="bi bi-list-check me-1"></i> Aufgaben
</NavLink>
</li>
</ul>
<button class="btn btn-outline-light btn-sm" @onclick="@(() => Nav.NavigateTo("/"))">
<i class="bi bi-arrow-left me-1"></i> Zurück
</button>
</div>
</div>
</nav>
<!-- 🔹 Body -->
<main class="flex-fill container py-4">
@Body
</main>
<footer class="text-center text-secondary small py-2 mt-auto border-top">
@DateTime.Now.Year FilterCair • Adminbereich
</footer>
</div>

View File

@ -1,6 +1,7 @@
@inherits LayoutComponentBase
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject FilterCair.Client.Services.AppState State
<div class="d-flex flex-column min-vh-100">
@ -32,6 +33,11 @@
<i class="bi bi-funnel me-1"></i> Filter-Form
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link text-white" href="/tasks">
<i class="bi bi-list-check me-1"></i> Tasklist
</NavLink>
</li>
</ul>
<!-- 🔐 Login/Logout Bereich -->
@ -65,6 +71,27 @@
<!-- 🔹 FOOTER -->
<footer class="footer text-center small text-secondary py-2 mt-auto border-top">
© @DateTime.Now.Year FilterCair FPM Service GmbH
@if (!string.IsNullOrEmpty(State.SelectedCustomer))
{
<div class="text-muted small mb-1">
<i class="bi bi-geo-alt me-1 text-primary"></i>
Aktiver Kunde: <strong>@State.SelectedCustomer</strong>
</div>
}
@DateTime.Now.Year FilterCair - FPM Service GmbH
</footer>
</div>
@code{
protected override async Task OnInitializedAsync()
{
await State.LoadFromSessionAsync();
State.OnChange += StateHasChanged;
}
public void Dispose()
{
State.OnChange -= StateHasChanged;
}
}

View File

@ -0,0 +1,34 @@
@page "/admin"
@layout AdminLayout
<PageTitle>Adminbereich</PageTitle>
<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
</h4>
<div class="row g-4 justify-content-center">
<div class="col-12 col-md-4">
<div class="card shadow-sm border-0 admin-tile" @onclick="@(() => Nav.NavigateTo("/admin/customers"))">
<div class="card-body text-center">
<i class="bi bi-buildings display-6 text-primary mb-3"></i>
<h5 class="fw-semibold">Kunden verwalten</h5>
</div>
</div>
</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-body text-center">
<i class="bi bi-funnel display-6 text-primary mb-3"></i>
<h5 class="fw-semibold">Filter verwalten</h5>
</div>
</div>
</div>
</div>
</div>
@code {
[Inject] NavigationManager Nav { get; set; } = default!;
}

View File

@ -0,0 +1,152 @@
@page "/admin/customers"
@layout AdminLayout
@using FilterCair.Shared.Models
<PageTitle>Kunden verwalten</PageTitle>
<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-buildings me-2"></i> Kundenverwaltung
</h4>
<button class="btn btn-success rounded-pill" @onclick="ShowCreateModal">
<i class="bi bi-plus-lg me-1"></i> Neuer Kunde
</button>
</div>
<!-- Kundenliste -->
@if (customers.Count == 0)
{
<div class="alert alert-info text-center">
Keine Kunden vorhanden. Lege den ersten Kunden an!
</div>
}
else
{
<div class="table-responsive">
<table class="table table-striped align-middle shadow-sm">
<thead class="table-primary">
<tr>
<th>Name</th>
<th>Standort</th>
<th>Ansprechpartner</th>
<th>Telefon</th>
<th>Email</th>
<th style="width:120px;"></th>
</tr>
</thead>
<tbody>
@foreach (var c in customers)
{
<tr>
<td>@c.Name</td>
<td>@c.Standort</td>
<td>@c.Ansprechpartner</td>
<td>@c.Telefon</td>
<td>@c.Email</td>
<td class="text-end">
<button class="btn btn-outline-danger btn-sm rounded-pill" @onclick="() => DeleteCustomer(c)">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
</div>
}
<!-- Modal -->
@if (showModal)
{
<div class="modal fade show d-block" tabindex="-1" style="background-color:rgba(0,0,0,0.5);">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow-lg rounded-4">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title"><i class="bi bi-person-plus me-2"></i> Neuer Kunde</h5>
<button type="button" class="btn-close btn-close-white" @onclick="CloseModal"></button>
</div>
<div class="modal-body">
<EditForm Model="@newCustomer" OnValidSubmit="SaveCustomer">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="mb-3">
<label class="form-label">Name</label>
<InputText class="form-control" @bind-Value="newCustomer.Name" required />
</div>
<div class="mb-3">
<label class="form-label">Standort</label>
<InputText class="form-control" @bind-Value="newCustomer.Standort" required />
</div>
<div class="mb-3">
<label class="form-label">Ansprechpartner</label>
<InputText class="form-control" @bind-Value="newCustomer.Ansprechpartner" />
</div>
<div class="mb-3">
<label class="form-label">Telefon</label>
<InputText class="form-control" @bind-Value="newCustomer.Telefon" />
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<InputText class="form-control" @bind-Value="newCustomer.Email" />
</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">
Speichern
</button>
</div>
</EditForm>
</div>
</div>
</div>
</div>
}
</div>
@code {
private List<CustomerModel> customers = new();
private CustomerModel newCustomer = new();
private bool showModal = false;
protected override void OnInitialized()
{
// TODO: später über API laden
customers = new()
{
new() { Id = 1, Name = "Freudenberg Weinheim", Standort = "Weinheim", Ansprechpartner = "Marc Wieland", Telefon = "+49 6201 80 1234", Email = "marc.wieland@freudenberg-pm.com" },
new() { Id = 2, Name = "CleanAir Solutions", Standort = "Mannheim", Ansprechpartner = "Tom Fischer", Telefon = "+49 621 555 123" , Email = "FischersTom@cleanair.de"}
};
}
private void ShowCreateModal()
{
newCustomer = new CustomerModel();
showModal = true;
}
private void CloseModal()
{
showModal = false;
}
private void SaveCustomer()
{
newCustomer.Id = customers.Count + 1;
customers.Add(newCustomer);
showModal = false;
}
private void DeleteCustomer(CustomerModel customer)
{
customers.Remove(customer);
}
}

View File

@ -0,0 +1,84 @@
@page "/customers"
@using FilterCair.Shared.Models
@inject IJSRuntime JS
@inject NavigationManager Nav
@inject FilterCair.Client.Services.AppState State
<h3>Kundenübersicht</h3>
<div class="container py-4 fade-in">
<h4 class="mb-4 text-center text-primary">
<i class="bi bi-buildings me-2"></i> Kunden & Fabriken
</h4>
<!-- 🔍 Suchfeld -->
<div class="mb-4 text-center">
<input type="text" class="form-control w-50 mx-auto shadow-sm"
placeholder="🔍 Kunde oder Standort suchen..."
@bind="searchText"
@bind:event="oninput" />
</div>
<!-- 🏭 Kundenliste -->
<div class="row g-4 justify-content-center">
@foreach (var c in FilteredCustomers)
{
<div class="col-12 col-sm-6 col-lg-4">
<div class="card customer-card border-0 shadow-sm h-100"
@onclick="@(() => SelectCustomer(c))">
<div class="card-body">
<h5 class="fw-semibold mb-2 text-primary">
<i class="bi bi-building me-1"></i> @c.Name
</h5>
<p class="text-muted mb-1">
<i class="bi bi-geo-alt me-1"></i> @c.Standort
</p>
<p class="text-muted small mb-0">
<i class="bi bi-person me-1"></i> @c.Ansprechpartner
</p>
</div>
</div>
</div>
}
</div>
@if (FilteredCustomers.Count() == 0)
{
<div class="text-center text-muted mt-5">
<i class="bi bi-search display-5 d-block mb-2"></i>
Keine passenden Kunden gefunden.
</div>
}
</div>
@code {
private List<CustomerModel> customers = new();
private string searchText = "";
private IEnumerable<CustomerModel> FilteredCustomers =>
string.IsNullOrWhiteSpace(searchText)
? customers
: customers.Where(c =>
c.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
c.Standort.Contains(searchText, StringComparison.OrdinalIgnoreCase));
protected override void OnInitialized()
{
// später aus API laden
customers = new()
{
new() { Id = 1, Name = "Freudenberg Weinheim", Standort = "Weinheim", Ansprechpartner = "Marc Wieland", Telefon = "+49 6201 80 1234" },
new() { Id = 2, Name = "FilterTech GmbH", Standort = "Kaiserslautern", Ansprechpartner = "Lena Becker", Telefon = "+49 631 987654" },
new() { Id = 3, Name = "CleanAir Solutions", Standort = "Mannheim", Ansprechpartner = "Tom Fischer", Telefon = "+49 621 555 123" }
};
}
private async Task SelectCustomer(CustomerModel c)
{
await State.SetCustomerAsync(c.Name);
Nav.NavigateTo("/qrscanner");
}
}

View File

@ -48,6 +48,30 @@
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<div class="card dashboard-tile h-100" @onclick="@(() => Nav.NavigateTo("/tasks"))">
<div class="card-body d-flex flex-column justify-content-center align-items-center text-center">
<i class="bi bi-list-check display-5 mb-3 text-primary"></i>
<h5 class="fw-semibold mb-2">Meine Aufgaben</h5>
<p class="text-muted small mb-0">Übersicht aller offenen Wartungen und Filterprüfungen.</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<div class="card dashboard-tile h-100" @onclick="@(() => Nav.NavigateTo("/customers"))">
<div class="card-body d-flex flex-column justify-content-center align-items-center text-center">
<i class="bi bi-buildings display-5 mb-3 text-primary"></i>
<h5 class="fw-semibold mb-2">Kundenübersicht</h5>
<p class="text-muted small mb-0">Wähle den Kundenstandort aus, um Filterdaten zu erfassen.</p>
</div>
</div>
</div>
</div>
<!-- 📍 Standortanzeige -->

View File

@ -0,0 +1,81 @@
@page "/tasks"
@using FilterCair.Shared.Models
<div class="container py-4 fade-in">
<h4 class="mb-4 text-primary text-center">
<i class="bi bi-list-check me-2"></i> Meine Aufgaben
</h4>
<div class="row g-3">
@if (tasks.Count == 0)
{
<div class="text-center text-muted py-5">
<i class="bi bi-emoji-neutral display-4 d-block mb-3"></i>
Keine Aufgaben vorhanden.
</div>
}
else
{
@foreach (var t in tasks)
{
<div class="col-12 col-md-6 col-lg-4">
<div class="card task-card shadow-sm border-0 rounded-4">
<div class="card-body">
<h5 class="fw-semibold">@t.Title</h5>
<p class="text-muted small mb-2">@t.Beschreibung</p>
<span class="badge bg-@(GetStatusColor(t.Status)) me-2">@t.Status</span>
<span class="badge bg-secondary">@t.Prio</span>
@if (t.FaelligBis.HasValue)
{
<div class="text-muted small mt-2">
<i class="bi bi-calendar-event me-1"></i>
Fällig: @t.FaelligBis.Value.ToShortDateString()
</div>
}
<div class="d-flex justify-content-end mt-3">
<button class="btn btn-sm btn-outline-primary" @onclick="() => ToggleStatus(t)">
<i class="bi bi-check2-circle me-1"></i> Status ändern
</button>
</div>
</div>
</div>
</div>
}
}
</div>
</div>
@code {
private List<TaskModel> tasks = new();
protected override void OnInitialized()
{
tasks = new()
{
new() { Id = 1, Title = "Filter prüfen Halle A", Beschreibung = "Sichtprüfung durchführen", ZugewiesenAn = "Marc Wieland", Status = "Offen", Prio = "Normal", FaelligBis = DateTime.Today.AddDays(1) },
new() { Id = 2, Title = "Wartung Filter B2", Beschreibung = "Druck prüfen & Dokumentation", ZugewiesenAn = "Marc Wieland", Status = "In Arbeit", Prio = "Hoch" },
new() { Id = 3, Title = "Filter austauschen", Beschreibung = "Alten Filter entfernen & neuen einsetzen", ZugewiesenAn = "Marc Wieland", Status = "Erledigt", Prio = "Normal", FaelligBis = DateTime.Today.AddDays(-2) }
};
}
private void ToggleStatus(TaskModel t)
{
t.Status = t.Status switch
{
"Offen" => "In Arbeit",
"In Arbeit" => "Erledigt",
_ => "Offen"
};
}
private string GetStatusColor(string status) => status switch
{
"Erledigt" => "success",
"In Arbeit" => "warning",
_ => "secondary"
};
}

View File

@ -2,6 +2,7 @@ using FilterCair.Client;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using FilterCair.Client.Services;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
@ -21,4 +22,7 @@ builder.Services.AddMsalAuthentication(options =>
"api://54b010c7-4a9a-4f2d-bea3-9faba3f12495/API.Access");
});
//Services
builder.Services.AddScoped<AppState>();
await builder.Build().RunAsync();

View File

@ -0,0 +1,51 @@
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
namespace FilterCair.Client.Services
{
public class AppState
{
private readonly IJSRuntime _js;
public AppState(IJSRuntime js)
{
_js = js;
}
public string? SelectedCustomer { get; private set; }
public bool IsOnline { get; private set; } = true;
public event Action? OnChange;
private void NotifyStateChanged() => OnChange?.Invoke();
public async Task SetCustomerAsync(string customer)
{
SelectedCustomer = customer;
await _js.InvokeVoidAsync("sessionStorage.setItem", "selectedCustomer", customer);
NotifyStateChanged();
}
public async Task LoadFromSessionAsync()
{
SelectedCustomer = await _js.InvokeAsync<string?>("sessionStorage.getItem", "selectedCustomer");
NotifyStateChanged();
}
public void SetOnlineStatus(bool isOnline)
{
IsOnline = isOnline;
NotifyStateChanged();
}
public async Task ClearAsync()
{
SelectedCustomer = null;
await _js.InvokeVoidAsync("sessionStorage.removeItem", "selectedCustomer");
NotifyStateChanged();
}
}
}

View File

@ -220,6 +220,57 @@ code {
transition: transform 0.2s ease;
}
.image-preview img:hover {
transform: scale(1.05);
.image-preview img:hover {
transform: scale(1.05);
}
.task-card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.task-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 16px rgba(0, 91, 153, 0.15);
}
.customer-card {
border-radius: 1rem;
transition: all 0.25s ease;
cursor: pointer;
}
.customer-card:hover {
transform: translateY(-3px);
box-shadow: 0 8px 18px rgba(0, 91, 153, 0.15);
}
.customer-card:active {
transform: translateY(1px);
}
.admin-tile {
border-radius: 1rem;
transition: all 0.2s ease;
cursor: pointer;
}
.admin-tile:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 91, 153, 0.15);
}
.modal-dialog {
max-width: 500px;
margin: 1rem auto;
}
@media (max-width: 576px) {
.modal-dialog {
max-width: 90%;
margin: 0.5rem auto;
}
}

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FilterCair.Shared.Models
{
public class CustomerModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Standort { get; set; } = string.Empty;
public string Ansprechpartner { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Telefon { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FilterCair.Shared.Models
{
public class TaskModel
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Beschreibung { get; set; } = string.Empty;
public string ZugewiesenAn { get; set; } = string.Empty;
public string Status { get; set; } = "Offen";
public string Prio { get; set; } = "Mittel";
public DateTime? FaelligBis { get; set; }
}
}