717 lines
29 KiB
Plaintext
717 lines
29 KiB
Plaintext
@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}";
|
|
}
|
|
}
|
|
}
|