Initial commit: Lagerbestand Blazor Projekt
This commit is contained in:
commit
cba9414e92
105 files changed
+36431
No files matched your search
@@ -0,0 +1,716 @@
|
||||
@page "/customers"
|
||||
@implements IAsyncDisposable
|
||||
@inject ICustomerService CustomerService
|
||||
@inject ITireStackService TireStackService
|
||||
@inject ILexwareSyncService LexwareSyncService
|
||||
@inject ISiteSettingsService SiteSettingsService
|
||||
@inject UserSessionService UserSession
|
||||
@inject IJSRuntime JS
|
||||
@using Lagerbestand.Components.Shared
|
||||
|
||||
<PageTitle>Kundenverwaltung - Reifen Späth Lagerverwaltung</PageTitle>
|
||||
|
||||
<SeoMetaTags
|
||||
Title="Kundenverwaltung - Reifen Späth Lagerverwaltung"
|
||||
Description="Verwaltung der Kundendaten in der Reifen Späth Lagerverwaltung."
|
||||
Robots="noindex, nofollow" />
|
||||
|
||||
@if (!UserSession.IsAdmin)
|
||||
{
|
||||
<section class="access-denied" role="alert" aria-labelledby="access-denied-title">
|
||||
<div class="access-denied-icon" aria-hidden="true">🔒</div>
|
||||
<h1 id="access-denied-title">Zugriff verweigert</h1>
|
||||
<p>Diese Seite ist nur für Administratoren zugänglich.</p>
|
||||
<a href="/tire-storage" class="btn btn-primary">Zur Lagerübersicht</a>
|
||||
</section>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="customers-page">
|
||||
<header class="page-header">
|
||||
<h1>Kundenverwaltung</h1>
|
||||
<div class="header-actions">
|
||||
@if (lexwareEnabled)
|
||||
{
|
||||
<button class="btn btn-secondary" @onclick="SyncFromLexware" disabled="@isSyncing" aria-label="Kunden von Lexware synchronisieren">
|
||||
@if (isSyncing)
|
||||
{
|
||||
<span class="spinner-inline" aria-hidden="true"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M23 4v6h-6"></path>
|
||||
<path d="M1 20v-6h6"></path>
|
||||
<path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>
|
||||
</svg>
|
||||
}
|
||||
Lexware Sync
|
||||
</button>
|
||||
}
|
||||
<button class="btn btn-primary" @onclick="ShowAddCustomer" aria-label="Neuen Kunden anlegen">
|
||||
<span aria-hidden="true">+</span> Neuer Kunde
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@if (!string.IsNullOrEmpty(syncMessage))
|
||||
{
|
||||
<div class="sync-message @syncMessageType" role="status">@syncMessage</div>
|
||||
}
|
||||
|
||||
@* Search *@
|
||||
<section class="search-bar" aria-label="Kundensuche">
|
||||
<label for="customer-search" class="visually-hidden">Kunden suchen</label>
|
||||
<input type="search"
|
||||
id="customer-search"
|
||||
class="search-input"
|
||||
placeholder="Name, Fahrzeug oder Kundennummer..."
|
||||
@bind="searchTerm"
|
||||
@bind:event="oninput"
|
||||
@onkeyup="SearchCustomers" />
|
||||
</section>
|
||||
|
||||
@* Customer List *@
|
||||
@if (isLoading)
|
||||
{
|
||||
<div class="loading-state" role="status" aria-live="polite">
|
||||
<div class="spinner" aria-hidden="true"></div>
|
||||
<span>Lade Kunden...</span>
|
||||
</div>
|
||||
}
|
||||
else if (filteredCustomers.Count == 0)
|
||||
{
|
||||
<section class="empty-state" aria-labelledby="empty-title">
|
||||
<div class="empty-icon" aria-hidden="true">👥</div>
|
||||
<h2 id="empty-title">Keine Kunden</h2>
|
||||
<p>@(string.IsNullOrEmpty(searchTerm) ? "Noch keine Kunden angelegt." : "Keine Ergebnisse für Ihre Suche.")</p>
|
||||
</section>
|
||||
}
|
||||
else
|
||||
{
|
||||
<section class="customer-list" aria-label="Kundenliste" role="list">
|
||||
@foreach (var customer in filteredCustomers)
|
||||
{
|
||||
<article @key="customer.Id" class="customer-card @(expandedId == customer.Id ? "expanded" : "")"
|
||||
@onclick="() => ToggleCustomer(customer.Id)"
|
||||
role="listitem"
|
||||
aria-expanded="@(expandedId == customer.Id)">
|
||||
<div class="customer-main">
|
||||
<div class="customer-avatar" aria-hidden="true">
|
||||
@(customer.FirstName?.Length > 0 ? customer.FirstName[0] : '?')@(customer.LastName.Length > 0 ? customer.LastName[0] : "")
|
||||
</div>
|
||||
<div class="customer-info">
|
||||
<span class="customer-name">@customer.FullName</span>
|
||||
@if (!string.IsNullOrEmpty(customer.Company))
|
||||
{
|
||||
<span class="customer-company">@customer.Company</span>
|
||||
}
|
||||
<span class="customer-number">@customer.CustomerNumber</span>
|
||||
</div>
|
||||
<div class="customer-meta">
|
||||
@{
|
||||
var activeVehicles = customer.Vehicles?.Where(v => v.IsActive).ToList() ?? new List<Vehicle>();
|
||||
}
|
||||
@if (activeVehicles.Any())
|
||||
{
|
||||
<span class="vehicle-make">@string.Join(", ", activeVehicles.Select(v => v.ShortName))</span>
|
||||
}
|
||||
<span class="tire-count">
|
||||
@(customer.TireStacks?.Count(ts => ts.Status == TireStackStatus.Stored) ?? 0) Stapel
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (expandedId == customer.Id)
|
||||
{
|
||||
<div class="customer-details" @onclick:stopPropagation="true">
|
||||
<dl class="detail-grid">
|
||||
@if (!string.IsNullOrEmpty(customer.Phone))
|
||||
{
|
||||
<div class="detail-item">
|
||||
<dt class="label">Telefon</dt>
|
||||
<dd><a href="tel:@customer.Phone" class="value link">@customer.Phone</a></dd>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(customer.Email))
|
||||
{
|
||||
<div class="detail-item">
|
||||
<dt class="label">E-Mail</dt>
|
||||
<dd><a href="mailto:@customer.Email" class="value link">@customer.Email</a></dd>
|
||||
</div>
|
||||
}
|
||||
@if (customer.PrimaryVehicle is { } primaryVehicle)
|
||||
{
|
||||
<div class="detail-item">
|
||||
<dt class="label">Fahrzeug</dt>
|
||||
<dd class="value">@primaryVehicle.DisplayName</dd>
|
||||
</div>
|
||||
}
|
||||
<div class="detail-item">
|
||||
<dt class="label">Kunde seit</dt>
|
||||
<dd class="value"><time datetime="@customer.CreatedAt.ToString("yyyy-MM-dd")">@customer.CreatedAt.ToLocalTime().ToString("dd.MM.yyyy")</time></dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
@if (!string.IsNullOrEmpty(customer.Notes))
|
||||
{
|
||||
<div class="notes-section">
|
||||
<span class="label">Notizen</span>
|
||||
<span class="notes-text">@customer.Notes</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Customer's Tire Stacks *@
|
||||
@if (customerTireStacks.TryGetValue(customer.Id, out var tireStacks) && tireStacks.Count > 0)
|
||||
{
|
||||
<section class="tireset-section" aria-labelledby="tireset-heading-@customer.Id">
|
||||
<h3 id="tireset-heading-@customer.Id">Eingelagerte Reifenstapel</h3>
|
||||
<ul>
|
||||
@foreach (var ts in tireStacks.Where(t => t.Status == TireStackStatus.Stored))
|
||||
{
|
||||
<li @key="ts.Id" class="mini-tireset">
|
||||
<span class="season" aria-label="@(ts.Season == TireSeason.Sommer ? "Sommerreifen" : ts.Season == TireSeason.Winter ? "Winterreifen" : "Ganzjahresreifen")">
|
||||
@(ts.Season == TireSeason.Sommer ? "☀️" : ts.Season == TireSeason.Winter ? "❄️" : "🔄")
|
||||
</span>
|
||||
<span class="info">@ts.TireSize (@TireStack.TiresPerStack Reifen)</span>
|
||||
<span class="location"><span aria-hidden="true">📍</span> @(ts.StorageLocation?.Code ?? "-")</span>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</section>
|
||||
}
|
||||
|
||||
<nav class="customer-actions" aria-label="Kundenaktionen">
|
||||
<button class="btn btn-sm btn-outline" @onclick="() => EditCustomer(customer)">
|
||||
Bearbeiten
|
||||
</button>
|
||||
<a href="/tire-checkin" class="btn btn-sm btn-primary">
|
||||
Reifenstapel einlagern
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
</article>
|
||||
}
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
|
||||
@* Add/Edit Customer Modal *@
|
||||
@if (showModal)
|
||||
{
|
||||
<div class="modal-backdrop" @onclick="CloseModal"></div>
|
||||
<div class="app-modal" @onclick="CloseModal" role="dialog" aria-modal="true">
|
||||
<div class="modal-dialog" @onclick:stopPropagation="true">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(isEditing ? "Kunde bearbeiten" : "Neuer Kunde")</h5>
|
||||
<button type="button" class="btn-close" @onclick="CloseModal" aria-label="Dialog schließen"></button>
|
||||
</div>
|
||||
<form @onsubmit="SaveCustomer" @onsubmit:preventDefault>
|
||||
<div class="modal-body">
|
||||
<div class="form-row-2col">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Vorname</label>
|
||||
<input type="text" class="form-control" @bind="editCustomer.FirstName" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Nachname <span class="required">*</span></label>
|
||||
<input type="text" class="form-control" @bind="editCustomer.LastName" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Firma</label>
|
||||
<input type="text" class="form-control" @bind="editCustomer.Company" />
|
||||
</div>
|
||||
<div class="form-row-2col">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Telefon</label>
|
||||
<input type="tel" class="form-control" @bind="editCustomer.Phone" autocomplete="tel" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">E-Mail</label>
|
||||
<input type="email" class="form-control" @bind="editCustomer.Email" autocomplete="email" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Notizen</label>
|
||||
<textarea class="form-control" rows="2" @bind="editCustomer.Notes"></textarea>
|
||||
</div>
|
||||
|
||||
@* Vehicle Management Section *@
|
||||
<div class="vehicles-section">
|
||||
<div class="vehicles-header">
|
||||
<h6>Fahrzeuge</h6>
|
||||
<button type="button" class="btn btn-sm btn-outline" @onclick="AddEditVehicle">
|
||||
+ Fahrzeug hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (editVehicles.Count == 0)
|
||||
{
|
||||
<p class="text-muted small">Keine Fahrzeuge hinterlegt</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="vehicle-edit-list">
|
||||
@foreach (var vehicle in editVehicles)
|
||||
{
|
||||
<div class="vehicle-edit-item @(vehicle.IsPrimary ? "primary" : "")">
|
||||
<div class="vehicle-edit-info">
|
||||
<span class="vehicle-make">@vehicle.Make @vehicle.Model</span>
|
||||
@if (!string.IsNullOrEmpty(vehicle.TireSize))
|
||||
{
|
||||
<span class="vehicle-tiresize">@vehicle.TireSize</span>
|
||||
}
|
||||
@if (vehicle.IsPrimary)
|
||||
{
|
||||
<span class="primary-badge">Hauptfahrzeug</span>
|
||||
}
|
||||
</div>
|
||||
<div class="vehicle-edit-actions">
|
||||
@if (!vehicle.IsPrimary && editVehicles.Count > 1)
|
||||
{
|
||||
<button type="button" class="btn-icon" title="Als Hauptfahrzeug" @onclick="() => SetPrimaryVehicle(vehicle)">⭐</button>
|
||||
}
|
||||
<button type="button" class="btn-icon" title="Bearbeiten" @onclick="() => EditVehicle(vehicle)">✏️</button>
|
||||
<button type="button" class="btn-icon danger" title="Entfernen" @onclick="() => RemoveVehicle(vehicle)">🗑️</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (showVehicleForm)
|
||||
{
|
||||
<div class="vehicle-form">
|
||||
<div class="form-row-2col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Marke *</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="editVehicle.Make" placeholder="z.B. BMW" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Modell</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="editVehicle.Model" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row-2col">
|
||||
<div class="mb-2">
|
||||
<label class="form-label small">Reifengröße *</label>
|
||||
<input type="text" class="form-control form-control-sm" @bind="editVehicle.TireSize" placeholder="z.B. 225/45 R17" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="vehicle-form-actions">
|
||||
<button type="button" class="btn btn-sm btn-outline" @onclick="CancelVehicleForm">Abbrechen</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" @onclick="SaveVehicle" disabled="@(string.IsNullOrWhiteSpace(editVehicle.Make) || string.IsNullOrWhiteSpace(editVehicle.TireSize))">
|
||||
@(isEditingVehicle ? "Speichern" : "Hinzufügen")
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<div class="alert alert-danger">@errorMessage</div>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" @onclick="CloseModal">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-primary" disabled="@(!CanSave)">
|
||||
@(isEditing ? "Speichern" : "Anlegen")
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Customer> allCustomers = new();
|
||||
private List<Customer> filteredCustomers = new();
|
||||
private Dictionary<Guid, List<TireStack>> customerTireStacks = new();
|
||||
|
||||
private string searchTerm = "";
|
||||
private Guid? expandedId;
|
||||
private bool isLoading = true;
|
||||
|
||||
// Modal state
|
||||
private bool showModal;
|
||||
private bool isEditing;
|
||||
private Customer editCustomer = new();
|
||||
private string? errorMessage;
|
||||
|
||||
// Vehicle editing state
|
||||
private List<Vehicle> editVehicles = new();
|
||||
private Vehicle editVehicle = new();
|
||||
private bool showVehicleForm;
|
||||
private bool isEditingVehicle;
|
||||
private List<Guid> vehiclesToDelete = new();
|
||||
|
||||
// Lexware sync state
|
||||
private bool lexwareEnabled;
|
||||
private bool isSyncing;
|
||||
private string? syncMessage;
|
||||
private string syncMessageType = "success";
|
||||
|
||||
private bool CanSave =>
|
||||
!string.IsNullOrWhiteSpace(editCustomer.LastName);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (UserSession.IsAdmin)
|
||||
{
|
||||
await LoadCustomers();
|
||||
await CheckLexwareEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
private DotNetObjectReference<Customers>? _visibilityRef;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender && UserSession.IsAdmin)
|
||||
{
|
||||
_visibilityRef = DotNetObjectReference.Create(this);
|
||||
await VisibilityRefresh.RegisterAsync(JS, _visibilityRef, nameof(OnVisibilityReturn), 120);
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task OnVisibilityReturn(int hiddenSeconds)
|
||||
{
|
||||
if (!UserSession.IsAdmin) return;
|
||||
await LoadCustomers();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await VisibilityRefresh.UnregisterAsync(JS);
|
||||
_visibilityRef?.Dispose();
|
||||
}
|
||||
|
||||
private async Task CheckLexwareEnabled()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SiteSettingsService.GetLexwareApiSettingsAsync();
|
||||
lexwareEnabled = settings.IsEnabled && settings.IsConfigured;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task SyncFromLexware()
|
||||
{
|
||||
isSyncing = true;
|
||||
syncMessage = null;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await LexwareSyncService.SyncCustomersAsync();
|
||||
syncMessage = result.Message;
|
||||
syncMessageType = result.Success ? "success" : "error";
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
await LoadCustomers();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
syncMessage = $"Fehler: {ex.Message}";
|
||||
syncMessageType = "error";
|
||||
}
|
||||
finally
|
||||
{
|
||||
isSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadCustomers()
|
||||
{
|
||||
isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
allCustomers = await CustomerService.GetAllAsync();
|
||||
filteredCustomers = allCustomers;
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchCustomers()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchTerm))
|
||||
{
|
||||
filteredCustomers = allCustomers;
|
||||
return;
|
||||
}
|
||||
|
||||
var term = searchTerm.ToLower();
|
||||
filteredCustomers = allCustomers
|
||||
.Where(c =>
|
||||
c.FullName.ToLower().Contains(term) ||
|
||||
(c.Company?.ToLower().Contains(term) ?? false) ||
|
||||
c.Vehicles.Any(v => v.Make.ToLower().Contains(term) || (v.Model?.ToLower().Contains(term) ?? false)) ||
|
||||
c.CustomerNumber.ToLower().Contains(term) ||
|
||||
(c.Phone?.Contains(term) ?? false))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task ToggleCustomer(Guid id)
|
||||
{
|
||||
if (expandedId == id)
|
||||
{
|
||||
expandedId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
expandedId = id;
|
||||
|
||||
// Load tire stacks for this customer if not already loaded
|
||||
if (!customerTireStacks.ContainsKey(id))
|
||||
{
|
||||
var tireStacks = await TireStackService.GetByCustomerAsync(id);
|
||||
customerTireStacks[id] = tireStacks;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowAddCustomer()
|
||||
{
|
||||
editCustomer = new Customer();
|
||||
editVehicles = new List<Vehicle>();
|
||||
vehiclesToDelete = new List<Guid>();
|
||||
isEditing = false;
|
||||
errorMessage = null;
|
||||
showVehicleForm = false;
|
||||
showModal = true;
|
||||
}
|
||||
|
||||
private async Task EditCustomer(Customer customer)
|
||||
{
|
||||
editCustomer = new Customer
|
||||
{
|
||||
Id = customer.Id,
|
||||
CustomerNumber = customer.CustomerNumber,
|
||||
FirstName = customer.FirstName,
|
||||
LastName = customer.LastName,
|
||||
Company = customer.Company,
|
||||
Phone = customer.Phone,
|
||||
Email = customer.Email,
|
||||
Notes = customer.Notes
|
||||
};
|
||||
|
||||
// Load vehicles for editing
|
||||
var vehicles = await CustomerService.GetVehiclesAsync(customer.Id);
|
||||
editVehicles = vehicles.Select(v => new Vehicle
|
||||
{
|
||||
Id = v.Id,
|
||||
CustomerId = v.CustomerId,
|
||||
Make = v.Make,
|
||||
Model = v.Model,
|
||||
TireSize = v.TireSize,
|
||||
IsPrimary = v.IsPrimary,
|
||||
IsActive = v.IsActive,
|
||||
Notes = v.Notes
|
||||
}).ToList();
|
||||
|
||||
vehiclesToDelete = new List<Guid>();
|
||||
isEditing = true;
|
||||
errorMessage = null;
|
||||
showVehicleForm = false;
|
||||
showModal = true;
|
||||
}
|
||||
|
||||
private void CloseModal()
|
||||
{
|
||||
showModal = false;
|
||||
editCustomer = new Customer();
|
||||
editVehicles = new List<Vehicle>();
|
||||
vehiclesToDelete = new List<Guid>();
|
||||
showVehicleForm = false;
|
||||
errorMessage = null;
|
||||
}
|
||||
|
||||
// Vehicle management methods
|
||||
private void AddEditVehicle()
|
||||
{
|
||||
editVehicle = new Vehicle();
|
||||
isEditingVehicle = false;
|
||||
showVehicleForm = true;
|
||||
}
|
||||
|
||||
private void EditVehicle(Vehicle vehicle)
|
||||
{
|
||||
editVehicle = new Vehicle
|
||||
{
|
||||
Id = vehicle.Id,
|
||||
CustomerId = vehicle.CustomerId,
|
||||
Make = vehicle.Make,
|
||||
Model = vehicle.Model,
|
||||
TireSize = vehicle.TireSize,
|
||||
IsPrimary = vehicle.IsPrimary,
|
||||
IsActive = vehicle.IsActive,
|
||||
Notes = vehicle.Notes
|
||||
};
|
||||
isEditingVehicle = true;
|
||||
showVehicleForm = true;
|
||||
}
|
||||
|
||||
private void CancelVehicleForm()
|
||||
{
|
||||
editVehicle = new Vehicle();
|
||||
showVehicleForm = false;
|
||||
isEditingVehicle = false;
|
||||
}
|
||||
|
||||
private void SaveVehicle()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(editVehicle.Make) || string.IsNullOrWhiteSpace(editVehicle.TireSize)) return;
|
||||
|
||||
if (isEditingVehicle)
|
||||
{
|
||||
var existing = editVehicles.FirstOrDefault(v => v.Id == editVehicle.Id);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Make = editVehicle.Make;
|
||||
existing.Model = editVehicle.Model;
|
||||
existing.TireSize = editVehicle.TireSize;
|
||||
existing.Notes = editVehicle.Notes;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var newVehicle = new Vehicle
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Make = editVehicle.Make,
|
||||
Model = editVehicle.Model,
|
||||
TireSize = editVehicle.TireSize,
|
||||
IsPrimary = editVehicles.Count == 0, // First vehicle is primary
|
||||
IsActive = true
|
||||
};
|
||||
editVehicles.Add(newVehicle);
|
||||
}
|
||||
|
||||
showVehicleForm = false;
|
||||
editVehicle = new Vehicle();
|
||||
isEditingVehicle = false;
|
||||
}
|
||||
|
||||
private void RemoveVehicle(Vehicle vehicle)
|
||||
{
|
||||
editVehicles.Remove(vehicle);
|
||||
|
||||
// Track for deletion if it's an existing vehicle
|
||||
if (vehicle.CustomerId != Guid.Empty)
|
||||
{
|
||||
vehiclesToDelete.Add(vehicle.Id);
|
||||
}
|
||||
|
||||
// Ensure there's still a primary if vehicles remain
|
||||
if (editVehicles.Any() && !editVehicles.Any(v => v.IsPrimary))
|
||||
{
|
||||
editVehicles.First().IsPrimary = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPrimaryVehicle(Vehicle vehicle)
|
||||
{
|
||||
foreach (var v in editVehicles)
|
||||
{
|
||||
v.IsPrimary = v.Id == vehicle.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveCustomer()
|
||||
{
|
||||
if (!CanSave) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (isEditing)
|
||||
{
|
||||
var existing = allCustomers.First(c => c.Id == editCustomer.Id);
|
||||
existing.FirstName = editCustomer.FirstName;
|
||||
existing.LastName = editCustomer.LastName;
|
||||
existing.Company = editCustomer.Company;
|
||||
existing.Phone = editCustomer.Phone;
|
||||
existing.Email = editCustomer.Email;
|
||||
existing.Notes = editCustomer.Notes;
|
||||
|
||||
await CustomerService.UpdateAsync(existing);
|
||||
|
||||
// Delete removed vehicles
|
||||
foreach (var vehicleId in vehiclesToDelete)
|
||||
{
|
||||
await CustomerService.DeleteVehicleAsync(vehicleId);
|
||||
}
|
||||
|
||||
// Update or add vehicles
|
||||
foreach (var vehicle in editVehicles)
|
||||
{
|
||||
if (vehicle.CustomerId == Guid.Empty)
|
||||
{
|
||||
// New vehicle
|
||||
vehicle.CustomerId = existing.Id;
|
||||
await CustomerService.AddVehicleAsync(existing.Id, vehicle);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Existing vehicle
|
||||
await CustomerService.UpdateVehicleAsync(vehicle);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle primary vehicle
|
||||
var primary = editVehicles.FirstOrDefault(v => v.IsPrimary);
|
||||
if (primary != null)
|
||||
{
|
||||
await CustomerService.SetPrimaryVehicleAsync(existing.Id, primary.Id);
|
||||
}
|
||||
|
||||
// Reload to get updated vehicles
|
||||
await LoadCustomers();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new customer
|
||||
if (editVehicles.Any())
|
||||
{
|
||||
var firstVehicle = editVehicles.First();
|
||||
var created = await CustomerService.CreateWithVehicleAsync(editCustomer, firstVehicle);
|
||||
|
||||
// Add remaining vehicles
|
||||
for (int i = 1; i < editVehicles.Count; i++)
|
||||
{
|
||||
await CustomerService.AddVehicleAsync(created.Id, editVehicles[i]);
|
||||
}
|
||||
|
||||
allCustomers.Insert(0, created);
|
||||
}
|
||||
else
|
||||
{
|
||||
var created = await CustomerService.CreateAsync(editCustomer);
|
||||
allCustomers.Insert(0, created);
|
||||
}
|
||||
}
|
||||
|
||||
SearchCustomers();
|
||||
CloseModal();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Fehler: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user