Funktionierende Customers ueber API mit Database Sync
This commit is contained in:
parent
13a0dbb9bc
commit
c4ad142e4f
@ -1,6 +1,8 @@
|
||||
@page "/admin/customers"
|
||||
@layout AdminLayout
|
||||
@using FilterCair.Shared.Models
|
||||
@inject FilterCair.Client.Services.API.CustomerService CustomerService
|
||||
@inject IJSRuntime JS
|
||||
|
||||
<PageTitle>Kunden verwalten</PageTitle>
|
||||
|
||||
@ -14,8 +16,14 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Kundenliste -->
|
||||
@if (customers.Count == 0)
|
||||
@if (isLoading)
|
||||
{
|
||||
<div class="text-center py-5 text-muted">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
<p class="mt-3">Lade Kunden...</p>
|
||||
</div>
|
||||
}
|
||||
else if (customers.Count == 0)
|
||||
{
|
||||
<div class="alert alert-info text-center">
|
||||
Keine Kunden vorhanden. Lege den ersten Kunden an!
|
||||
@ -45,7 +53,8 @@
|
||||
<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)">
|
||||
<button class="btn btn-outline-danger btn-sm rounded-pill"
|
||||
@onclick="() => DeleteCustomer(c)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
@ -75,34 +84,26 @@
|
||||
<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>
|
||||
<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>
|
||||
@ -116,15 +117,18 @@
|
||||
private List<CustomerModel> customers = new();
|
||||
private CustomerModel newCustomer = new();
|
||||
private bool showModal = false;
|
||||
private bool isLoading = true;
|
||||
|
||||
protected override void OnInitialized()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// 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"}
|
||||
};
|
||||
await LoadCustomers();
|
||||
}
|
||||
|
||||
private async Task LoadCustomers()
|
||||
{
|
||||
isLoading = true;
|
||||
customers = await CustomerService.GetCustomersAsync();
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
private void ShowCreateModal()
|
||||
@ -138,15 +142,35 @@
|
||||
showModal = false;
|
||||
}
|
||||
|
||||
private void SaveCustomer()
|
||||
private async Task SaveCustomer()
|
||||
{
|
||||
newCustomer.Id = customers.Count + 1;
|
||||
customers.Add(newCustomer);
|
||||
showModal = false;
|
||||
var success = await CustomerService.AddCustomerAsync(newCustomer);
|
||||
if (success)
|
||||
{
|
||||
await LoadCustomers();
|
||||
await JS.InvokeVoidAsync("alert", "✅ Kunde erfolgreich angelegt!");
|
||||
CloseModal();
|
||||
}
|
||||
else
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "❌ Fehler beim Speichern.");
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteCustomer(CustomerModel customer)
|
||||
private async Task DeleteCustomer(CustomerModel customer)
|
||||
{
|
||||
customers.Remove(customer);
|
||||
bool confirmed = await JS.InvokeAsync<bool>("confirm", $"Soll der Kunde '{customer.Name}' wirklich gelöscht werden?");
|
||||
if (!confirmed) return;
|
||||
|
||||
var success = await CustomerService.DeleteCustomerAsync(customer.Id);
|
||||
if (success)
|
||||
{
|
||||
customers.Remove(customer);
|
||||
await JS.InvokeVoidAsync("alert", "🗑️ Kunde gelöscht.");
|
||||
}
|
||||
else
|
||||
{
|
||||
await JS.InvokeVoidAsync("alert", "❌ Fehler beim Löschen.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
@page "/customers"
|
||||
@using FilterCair.Shared.Models
|
||||
@inject FilterCair.Client.Services.API.CustomerService CustomerService
|
||||
|
||||
@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
|
||||
@ -23,62 +23,62 @@
|
||||
|
||||
<!-- 🏭 Kundenliste -->
|
||||
<div class="row g-4 justify-content-center">
|
||||
@foreach (var c in FilteredCustomers)
|
||||
@if (customers == null)
|
||||
{
|
||||
<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>
|
||||
<p class="text-center text-muted">Lade Kunden...</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@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.Any())
|
||||
{
|
||||
<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>
|
||||
|
||||
@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 List<CustomerModel>? customers;
|
||||
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));
|
||||
? customers ?? new List<CustomerModel>()
|
||||
: customers!.Where(c =>
|
||||
c.Name.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
|
||||
c.Standort.Contains(searchText, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
protected override void OnInitialized()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// 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" }
|
||||
};
|
||||
customers = await CustomerService.GetCustomersAsync();
|
||||
}
|
||||
|
||||
private async Task SelectCustomer(CustomerModel c)
|
||||
{
|
||||
await State.SetCustomerAsync(c.Name);
|
||||
Nav.NavigateTo("/qrscanner");
|
||||
Nav.NavigateTo("/qrscanner");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,4 +25,7 @@ builder.Services.AddMsalAuthentication(options =>
|
||||
//Services
|
||||
builder.Services.AddScoped<AppState>();
|
||||
|
||||
//API Services
|
||||
builder.Services.AddScoped<FilterCair.Client.Services.API.CustomerService>();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
|
||||
61
FilterCair.Client/Services/API/CustomerService.cs
Normal file
61
FilterCair.Client/Services/API/CustomerService.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using FilterCair.Shared.Models;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace FilterCair.Client.Services.API
|
||||
{
|
||||
public class CustomerService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private readonly IConfiguration _config;
|
||||
private string BaseUrl => _config["Api:BaseUrl"] + "/api/Customer";
|
||||
|
||||
|
||||
public CustomerService(HttpClient http, IConfiguration config)
|
||||
{
|
||||
_http = http;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<List<CustomerModel>> GetCustomersAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _http.GetFromJsonAsync<List<CustomerModel>>(BaseUrl);
|
||||
return result ?? new();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Fehler beim Laden der Kunden: {ex.Message}");
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> AddCustomerAsync(CustomerModel customer)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.PostAsJsonAsync(BaseUrl, customer);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Fehler beim Anlegen: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteCustomerAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.DeleteAsync($"{BaseUrl}/{id}");
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Fehler beim Löschen: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,6 @@
|
||||
]
|
||||
},
|
||||
"Api": {
|
||||
"BaseUrl": "https://filtercair-api.azurewebsites.net"
|
||||
"BaseUrl": "https://filtercair-server-gwctc2gbf5f4axgj.westeurope-01.azurewebsites.net"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace FilterCair.Server.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class TestController : ControllerBase
|
||||
{
|
||||
[HttpGet("public")]
|
||||
public IActionResult GetPublic() => Ok("✅ Öffentlich erreichbar – keine Auth nötig");
|
||||
|
||||
[Authorize]
|
||||
[HttpGet("protected")]
|
||||
public IActionResult GetProtected() => Ok("🔒 Du bist authentifiziert über Azure AD!");
|
||||
}
|
||||
}
|
||||
@ -10,7 +10,7 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Variables
|
||||
$projectPath = "C:\DEVQPDC\Masterarbeit\filtercair_dev\Masterarbeit_New\FilterCair.Server"
|
||||
$projectPath = "C:\DEVQPDC\FilterCairStack\FilterCair.Server"
|
||||
$publishFolder = "$projectPath\bin\Release\net9.0\linux-x64\publish"
|
||||
$zipFile = "$projectPath\publish.zip"
|
||||
$resourceGroup = "FilterCair-RG"
|
||||
|
||||
@ -9,14 +9,24 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.10" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.10">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Identity.Web" Version="4.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="9.0.6" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.6" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FilterCair.Shared\FilterCair.Shared.csproj" />
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FilterCair.Shared\FilterCair.Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using FilterCair.Server.Data;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Identity.Web;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@ -6,11 +8,18 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
|
||||
|
||||
|
||||
builder.Services.AddDbContext<FilterCairContext>(options =>
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
|
||||
sql => sql.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), null))
|
||||
);
|
||||
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowClient", policy =>
|
||||
{
|
||||
policy.WithOrigins("https://filtercair-client.azurewebsites.net", "https://localhost:5001")
|
||||
policy.WithOrigins("https://filtercair-client-efava4bfgvamhkfu.westeurope-01.azurewebsites.net", "https://localhost:5001")
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"SignedOutCallbackPath": "/authentication/logout-callback"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=tcp:filtercair-sql.database.windows.net,1433;Initial Catalog=FilterCairDB;User ID=sqladmin;Password=SuperSicher123!;Encrypt=True;"
|
||||
"DefaultConnection": "Server=tcp:filtercair-azure-sql-server.database.windows.net,1433;Initial Catalog=FilterCairDB;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;Authentication=Active Directory Managed Identity;"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user