565 lines
26 KiB
Plaintext
565 lines
26 KiB
Plaintext
@page "/tire-storage"
|
||
@implements IAsyncDisposable
|
||
@inject ITireStackService TireStackService
|
||
@inject IStorageLocationService StorageLocationService
|
||
@inject ICatalogService CatalogService
|
||
@inject UserSessionService UserSession
|
||
@inject IJSRuntime JS
|
||
@using Lagerbestand.Components.Shared
|
||
|
||
<PageTitle>Lagerübersicht - Reifen Späth Lagerverwaltung</PageTitle>
|
||
|
||
<SeoMetaTags
|
||
Title="Lagerübersicht - Reifen Späth Lagerverwaltung"
|
||
Description="Übersicht aller eingelagerten Reifenstapel in der Reifen Späth Lagerverwaltung."
|
||
Robots="noindex, nofollow" />
|
||
|
||
@if (!UserSession.IsWorkerOrAdmin)
|
||
{
|
||
<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 Mitarbeiter und Administratoren zugänglich.</p>
|
||
<a href="/" class="btn btn-primary">Zur Startseite</a>
|
||
</section>
|
||
}
|
||
else
|
||
{
|
||
<div class="storage-page">
|
||
<header class="page-header">
|
||
<div class="header-content">
|
||
<h1>Lagerübersicht</h1>
|
||
<nav class="header-actions" aria-label="Schnellaktionen">
|
||
<a href="/tire-checkin" class="btn btn-primary">
|
||
<span aria-hidden="true">+</span> Einlagern
|
||
</a>
|
||
<a href="/tire-checkout" class="btn btn-outline">
|
||
Abholen
|
||
</a>
|
||
</nav>
|
||
</div>
|
||
</header>
|
||
|
||
@* Statistics *@
|
||
<section class="stats-grid" aria-label="Lagerstatistik">
|
||
<article class="stat-card">
|
||
<span class="stat-value" aria-label="Anzahl">@stats.TotalStacksStored</span>
|
||
<span class="stat-label">Stapel (@stats.TotalTiresStored Reifen)</span>
|
||
</article>
|
||
<article class="stat-card">
|
||
<span class="stat-value" aria-label="Anzahl">@stats.CustomerStackCount</span>
|
||
<span class="stat-label"><span aria-hidden="true">👤</span> Kundenstapel</span>
|
||
</article>
|
||
<article class="stat-card">
|
||
<span class="stat-value" aria-label="Anzahl">@stats.InventoryStackCount</span>
|
||
<span class="stat-label"><span aria-hidden="true">📦</span> Lagerbestand</span>
|
||
</article>
|
||
<article class="stat-card">
|
||
<span class="stat-value" aria-label="Anzahl">@stats.AvailableLocations</span>
|
||
<span class="stat-label">Freie Plätze</span>
|
||
</article>
|
||
</section>
|
||
|
||
@* Type Filter (Customer vs Inventory) *@
|
||
<nav class="type-filter" role="tablist" aria-label="Filter nach Typ">
|
||
<button class="type-btn @(typeFilter == null ? "active" : "")"
|
||
@onclick="() => SetTypeFilter(null)"
|
||
role="tab"
|
||
aria-selected="@(typeFilter == null)">
|
||
Alle
|
||
</button>
|
||
<button class="type-btn @(typeFilter == false ? "active" : "")"
|
||
@onclick="() => SetTypeFilter(false)"
|
||
role="tab"
|
||
aria-selected="@(typeFilter == false)">
|
||
<span aria-hidden="true">👤</span> Kundenstapel
|
||
</button>
|
||
<button class="type-btn @(typeFilter == true ? "active" : "")"
|
||
@onclick="() => SetTypeFilter(true)"
|
||
role="tab"
|
||
aria-selected="@(typeFilter == true)">
|
||
<span aria-hidden="true">📦</span> Lagerbestand
|
||
</button>
|
||
</nav>
|
||
|
||
@* View Toggle (List / Rack Grid) *@
|
||
<nav class="view-toggle" role="tablist" aria-label="Ansicht wählen">
|
||
<button class="view-btn @(showGridView ? "" : "active")"
|
||
@onclick="() => showGridView = false"
|
||
role="tab"
|
||
aria-selected="@(!showGridView)">
|
||
<span aria-hidden="true">☰</span> Liste
|
||
</button>
|
||
<button class="view-btn @(showGridView ? "active" : "")"
|
||
@onclick="() => showGridView = true"
|
||
role="tab"
|
||
aria-selected="@showGridView">
|
||
<span aria-hidden="true">▦</span> Regalansicht
|
||
</button>
|
||
</nav>
|
||
|
||
@* Search & Filter *@
|
||
<section class="filter-bar" aria-label="Suche und Filter">
|
||
<label for="search-input" class="visually-hidden">Suche</label>
|
||
<input type="search"
|
||
id="search-input"
|
||
class="search-input"
|
||
placeholder="Suchen (Name, Fahrzeug, Kategorie, Ticket-Nr.)..."
|
||
@bind="searchTerm"
|
||
@bind:event="oninput"
|
||
@onkeyup="FilterTireStacks"
|
||
aria-describedby="search-hint" />
|
||
<span id="search-hint" class="visually-hidden">Durchsucht Namen, Fahrzeuge, Kategorien und Ticket-Nummern</span>
|
||
|
||
<fieldset class="filter-buttons">
|
||
<legend class="visually-hidden">Nach Saison filtern</legend>
|
||
<button class="filter-btn @(seasonFilter == null ? "active" : "")"
|
||
@onclick="() => SetSeasonFilter(null)"
|
||
aria-pressed="@(seasonFilter == null)">
|
||
Alle
|
||
</button>
|
||
<button class="filter-btn @(seasonFilter == TireSeason.Sommer ? "active summer" : "")"
|
||
@onclick="() => SetSeasonFilter(TireSeason.Sommer)"
|
||
aria-pressed="@(seasonFilter == TireSeason.Sommer)"
|
||
aria-label="Sommerreifen">
|
||
<span aria-hidden="true">☀️</span>
|
||
</button>
|
||
<button class="filter-btn @(seasonFilter == TireSeason.Winter ? "active winter" : "")"
|
||
@onclick="() => SetSeasonFilter(TireSeason.Winter)"
|
||
aria-pressed="@(seasonFilter == TireSeason.Winter)"
|
||
aria-label="Winterreifen">
|
||
<span aria-hidden="true">❄️</span>
|
||
</button>
|
||
<button class="filter-btn @(seasonFilter == TireSeason.Allrad ? "active allseason" : "")"
|
||
@onclick="() => SetSeasonFilter(TireSeason.Allrad)"
|
||
aria-pressed="@(seasonFilter == TireSeason.Allrad)"
|
||
aria-label="Ganzjahresreifen">
|
||
<span aria-hidden="true">🔄</span>
|
||
</button>
|
||
</fieldset>
|
||
</section>
|
||
|
||
@* Category Filter (only when showing inventory) *@
|
||
@if (typeFilter == true && categories.Count > 0)
|
||
{
|
||
<section class="category-filter">
|
||
<label for="category-select" class="visually-hidden">Kategorie auswählen</label>
|
||
<select id="category-select" class="form-control" @bind="selectedCategoryId" @bind:after="FilterTireStacks">
|
||
<option value="">-- Alle Kategorien --</option>
|
||
@foreach (var cat in categories)
|
||
{
|
||
<option value="@cat.Id">@cat.Name</option>
|
||
}
|
||
</select>
|
||
</section>
|
||
}
|
||
|
||
@* Tire Stacks List *@
|
||
@if (isLoading)
|
||
{
|
||
<div class="loading-state" role="status" aria-live="polite">
|
||
<div class="spinner" aria-hidden="true"></div>
|
||
<span>Lade Daten...</span>
|
||
</div>
|
||
}
|
||
else if (showGridView)
|
||
{
|
||
<section class="rack-view" aria-label="Regalansicht der Lagerplätze">
|
||
@foreach (var area in new[] { AreaType.Werkstatt, AreaType.Lager })
|
||
{
|
||
<div class="building-section" @key="area">
|
||
<h2 class="building-title">@area.GetBuildingLabel()</h2>
|
||
@for (int row = StorageLocation.MinRow; row <= area.GetMaxRow(); row++)
|
||
{
|
||
var currentRow = row;
|
||
<div class="rack-row" @key="@($"{area}-{currentRow}")">
|
||
<h3 class="row-title">Reihe @currentRow</h3>
|
||
<div class="rack-sides">
|
||
@foreach (var side in new[] { "Links", "Rechts" })
|
||
{
|
||
<div class="rack-side" @key="side">
|
||
<span class="side-label">@side</span>
|
||
@foreach (var pos in ShelfPositionExtensions.All.Where(p => p.IsLeft() == (side == "Links")).OrderByDescending(p => p.IsTop()))
|
||
{
|
||
<div class="rack-level" @key="pos">
|
||
<span class="level-label">@(pos.IsTop() ? "Oben" : "Unten")</span>
|
||
<div class="rack-cells">
|
||
@for (int slot = StorageLocation.MinSlot; slot <= StorageLocation.MaxSlot; slot++)
|
||
{
|
||
var loc = GetLocation(area, currentRow, pos, slot);
|
||
var cellClass = loc == null ? "missing" : !loc.IsAvailable ? "blocked" : loc.IsOccupied ? "occupied" : "free";
|
||
<button type="button"
|
||
class="rack-cell @cellClass @(selectedLocation?.Id == loc?.Id ? "selected" : "")"
|
||
@key="slot"
|
||
disabled="@(loc == null)"
|
||
title="@GetCellTooltip(loc, area, currentRow, pos, slot)"
|
||
@onclick="() => SelectLocation(loc)">
|
||
@slot
|
||
</button>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<div class="rack-legend" aria-hidden="true">
|
||
<span class="legend-item"><span class="legend-swatch free"></span> Frei</span>
|
||
<span class="legend-item"><span class="legend-swatch occupied"></span> Belegt</span>
|
||
<span class="legend-item"><span class="legend-swatch blocked"></span> Gesperrt</span>
|
||
</div>
|
||
|
||
@if (selectedLocation != null)
|
||
{
|
||
<aside class="cell-details" aria-label="Details zum gewählten Lagerplatz">
|
||
<header class="cell-details-header">
|
||
<LocationDisplay Code="@selectedLocation.Code" DisplayMode="LocationDisplayMode.Full" IsOccupied="@selectedLocation.IsOccupied" ShowStatus="true" />
|
||
<button type="button" class="btn btn-sm btn-outline" @onclick="() => selectedLocation = null">Schließen</button>
|
||
</header>
|
||
@if (selectedLocation.IsOccupied && selectedLocation.OccupiedTireStack != null)
|
||
{
|
||
var stack = selectedLocation.OccupiedTireStack;
|
||
<dl class="detail-grid">
|
||
<div class="detail-item">
|
||
<dt class="label">Ticket-Nr.</dt>
|
||
<dd class="value">@stack.TicketNumber</dd>
|
||
</div>
|
||
<div class="detail-item">
|
||
<dt class="label">@(stack.IsInventory ? "Kategorie" : "Kunde")</dt>
|
||
<dd class="value">@(stack.IsInventory ? stack.CatalogItem?.Name ?? "Lagerbestand" : stack.Customer?.FullName ?? "-")</dd>
|
||
</div>
|
||
<div class="detail-item">
|
||
<dt class="label">Reifen</dt>
|
||
<dd class="value">@(stack.TireSize ?? "-") @(stack.WithRims ? "| mit Felgen" : "")</dd>
|
||
</div>
|
||
<div class="detail-item">
|
||
<dt class="label">Eingelagert</dt>
|
||
<dd class="value">@stack.StoredAt.ToLocalTime().ToString("dd.MM.yyyy")</dd>
|
||
</div>
|
||
</dl>
|
||
}
|
||
else if (!selectedLocation.IsAvailable)
|
||
{
|
||
<p class="cell-details-text">Dieser Lagerplatz ist gesperrt. @selectedLocation.Notes</p>
|
||
}
|
||
else
|
||
{
|
||
<p class="cell-details-text">Dieser Lagerplatz ist frei.</p>
|
||
<a href="/tire-checkin" class="btn btn-sm btn-primary">Einlagern</a>
|
||
}
|
||
</aside>
|
||
}
|
||
</section>
|
||
}
|
||
else if (filteredTireStacks.Count == 0)
|
||
{
|
||
<section class="empty-state" aria-labelledby="empty-title">
|
||
<div class="empty-icon" aria-hidden="true">📦</div>
|
||
<h2 id="empty-title">Keine Einträge</h2>
|
||
<p>@(string.IsNullOrEmpty(searchTerm) ? "Noch keine Reifenstapel eingelagert." : "Keine Ergebnisse für Ihre Suche.")</p>
|
||
@if (string.IsNullOrEmpty(searchTerm))
|
||
{
|
||
<a href="/tire-checkin" class="btn btn-primary">Reifenstapel einlagern</a>
|
||
}
|
||
</section>
|
||
}
|
||
else
|
||
{
|
||
<section class="tireset-list" aria-label="Liste der Reifenstapel" role="list">
|
||
@foreach (var ts in filteredTireStacks)
|
||
{
|
||
<article class="tireset-card @(ts.IsInventory ? "inventory" : "")"
|
||
@onclick="() => ToggleDetails(ts.Id)"
|
||
role="listitem"
|
||
aria-expanded="@(expandedId == ts.Id)">
|
||
<div class="tireset-main">
|
||
<div class="tireset-left">
|
||
<div class="season-indicator @ts.Season.ToString().ToLower()"
|
||
aria-label="@(ts.Season == TireSeason.Sommer ? "Sommerreifen" : ts.Season == TireSeason.Winter ? "Winterreifen" : "Ganzjahresreifen")">
|
||
@(ts.Season == TireSeason.Sommer ? "☀️" : ts.Season == TireSeason.Winter ? "❄️" : "🔄")
|
||
</div>
|
||
<div class="tireset-info">
|
||
@if (ts.IsInventory)
|
||
{
|
||
<span class="customer-name inventory-name">
|
||
<span aria-hidden="true">📦</span> @(ts.CatalogItem?.Name ?? "Lagerbestand")
|
||
</span>
|
||
}
|
||
else
|
||
{
|
||
<span class="customer-name">@ts.Customer?.FullName</span>
|
||
}
|
||
<span class="tire-details">1 Stapel (@TireStack.TiresPerStack Reifen) @(ts.TireSize != null ? $"| {ts.TireSize}" : "") @(ts.WithRims ? "| mit Felgen" : "")</span>
|
||
</div>
|
||
</div>
|
||
<div class="tireset-right">
|
||
<span class="location-code">@(ts.StorageLocation?.Code ?? "-")</span>
|
||
@if (!ts.IsInventory && ts.Vehicle != null)
|
||
{
|
||
<span class="vehicle-info">@ts.Vehicle.ShortName</span>
|
||
}
|
||
@if (ts.IsInventory)
|
||
{
|
||
<span class="inventory-badge">Lager</span>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
@if (expandedId == ts.Id)
|
||
{
|
||
<div class="tireset-details">
|
||
<dl class="detail-grid">
|
||
<div class="detail-item">
|
||
<dt class="label">Ticket-Nr.</dt>
|
||
<dd class="value">@ts.TicketNumber</dd>
|
||
</div>
|
||
<div class="detail-item">
|
||
<dt class="label">Reifenstapel</dt>
|
||
<dd class="value">1 Stapel (@TireStack.TiresPerStack Reifen)</dd>
|
||
</div>
|
||
@if (ts.IsInventory && ts.CatalogItem != null)
|
||
{
|
||
<div class="detail-item">
|
||
<dt class="label">Kategorie</dt>
|
||
<dd class="value">@ts.CatalogItem.Name</dd>
|
||
</div>
|
||
}
|
||
<div class="detail-item">
|
||
<dt class="label">Marke</dt>
|
||
<dd class="value">@(ts.TireBrand ?? "-")</dd>
|
||
</div>
|
||
<div class="detail-item">
|
||
<dt class="label">Modell</dt>
|
||
<dd class="value">@(ts.TireModel ?? "-")</dd>
|
||
</div>
|
||
<div class="detail-item">
|
||
<dt class="label">Zustand</dt>
|
||
<dd class="value">@ts.ConditionText</dd>
|
||
</div>
|
||
<div class="detail-item">
|
||
<dt class="label">Eingelagert</dt>
|
||
<dd class="value"><time datetime="@ts.StoredAt.ToString("yyyy-MM-dd")">@ts.StoredAt.ToLocalTime().ToString("dd.MM.yyyy")</time></dd>
|
||
</div>
|
||
<div class="detail-item">
|
||
<dt class="label">Dauer</dt>
|
||
<dd class="value">@ts.DaysStored Tage</dd>
|
||
</div>
|
||
</dl>
|
||
@if (!string.IsNullOrEmpty(ts.Notes))
|
||
{
|
||
<div class="notes-section">
|
||
<span class="label">Notizen:</span>
|
||
<span class="notes-text">@ts.Notes</span>
|
||
</div>
|
||
}
|
||
<nav class="detail-actions" aria-label="Aktionen für diesen Stapel">
|
||
@if (!ts.IsInventory)
|
||
{
|
||
<a href="/tire-checkout" class="btn btn-sm btn-primary">Abholen</a>
|
||
}
|
||
else
|
||
{
|
||
<a href="/tire-checkout" class="btn btn-sm btn-primary">Entnehmen</a>
|
||
}
|
||
</nav>
|
||
</div>
|
||
}
|
||
</article>
|
||
}
|
||
</section>
|
||
}
|
||
|
||
@* Expiring Pickups Warning (only for customer stacks) *@
|
||
@if (expiringPickups.Count > 0)
|
||
{
|
||
<aside class="expiring-section" aria-labelledby="expiring-title" role="alert">
|
||
<h2 id="expiring-title"><span aria-hidden="true">⚠️</span> Bevorstehende Abholungen</h2>
|
||
<ul class="expiring-list">
|
||
@foreach (var ts in expiringPickups)
|
||
{
|
||
<li class="expiring-item">
|
||
<span class="customer">@ts.Customer?.FullName</span>
|
||
<span class="date">Erwartet: <time datetime="@ts.ExpectedPickupDate?.ToString("yyyy-MM-dd")">@ts.ExpectedPickupDate?.ToLocalTime().ToString("dd.MM.yyyy")</time></span>
|
||
</li>
|
||
}
|
||
</ul>
|
||
</aside>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@code {
|
||
private List<TireStack> allTireStacks = new();
|
||
private List<TireStack> filteredTireStacks = new();
|
||
private List<TireStack> expiringPickups = new();
|
||
private List<CatalogItem> categories = new();
|
||
private StorageStatistics stats = new();
|
||
|
||
private string searchTerm = "";
|
||
private TireSeason? seasonFilter;
|
||
private bool? typeFilter; // null = all, false = customer, true = inventory
|
||
private string selectedCategoryId = "";
|
||
private Guid? expandedId;
|
||
private bool isLoading = true;
|
||
private bool showGridView;
|
||
private List<StorageLocation> allLocations = new();
|
||
private Dictionary<(AreaType, int, ShelfPosition, int), StorageLocation> locationLookup = new();
|
||
private StorageLocation? selectedLocation;
|
||
|
||
protected override async Task OnInitializedAsync()
|
||
{
|
||
if (UserSession.IsWorkerOrAdmin)
|
||
{
|
||
await LoadData();
|
||
}
|
||
}
|
||
|
||
private DotNetObjectReference<TireStorage>? _visibilityRef;
|
||
|
||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||
{
|
||
if (firstRender && UserSession.IsWorkerOrAdmin)
|
||
{
|
||
_visibilityRef = DotNetObjectReference.Create(this);
|
||
await VisibilityRefresh.RegisterAsync(JS, _visibilityRef, nameof(OnVisibilityReturn), 120);
|
||
}
|
||
}
|
||
|
||
[JSInvokable]
|
||
public async Task OnVisibilityReturn(int hiddenSeconds)
|
||
{
|
||
if (!UserSession.IsWorkerOrAdmin) return;
|
||
await LoadData();
|
||
await InvokeAsync(StateHasChanged);
|
||
}
|
||
|
||
public async ValueTask DisposeAsync()
|
||
{
|
||
await VisibilityRefresh.UnregisterAsync(JS);
|
||
_visibilityRef?.Dispose();
|
||
}
|
||
|
||
private async Task LoadData()
|
||
{
|
||
isLoading = true;
|
||
StateHasChanged();
|
||
|
||
try
|
||
{
|
||
allTireStacks = await TireStackService.GetAllAsync(TireStackStatus.Stored);
|
||
stats = await TireStackService.GetStatisticsAsync();
|
||
expiringPickups = await TireStackService.GetExpiringPickupsAsync();
|
||
categories = await CatalogService.GetAllAsync();
|
||
allLocations = await StorageLocationService.GetAllAsync();
|
||
locationLookup = allLocations.ToDictionary(l => (l.Area, l.Row, l.Position, l.SlotNumber));
|
||
if (selectedLocation != null)
|
||
{
|
||
selectedLocation = allLocations.FirstOrDefault(l => l.Id == selectedLocation.Id);
|
||
}
|
||
ApplyFilters();
|
||
}
|
||
finally
|
||
{
|
||
isLoading = false;
|
||
}
|
||
}
|
||
|
||
private void FilterTireStacks()
|
||
{
|
||
ApplyFilters();
|
||
}
|
||
|
||
private void SetSeasonFilter(TireSeason? season)
|
||
{
|
||
seasonFilter = season;
|
||
ApplyFilters();
|
||
}
|
||
|
||
private void SetTypeFilter(bool? isInventory)
|
||
{
|
||
typeFilter = isInventory;
|
||
selectedCategoryId = ""; // Reset category filter when changing type
|
||
ApplyFilters();
|
||
}
|
||
|
||
private void ApplyFilters()
|
||
{
|
||
var query = allTireStacks.AsEnumerable();
|
||
|
||
// Type filter (customer vs inventory)
|
||
if (typeFilter.HasValue)
|
||
{
|
||
if (typeFilter.Value)
|
||
query = query.Where(ts => ts.CustomerId == null);
|
||
else
|
||
query = query.Where(ts => ts.CustomerId != null);
|
||
}
|
||
|
||
// Season filter
|
||
if (seasonFilter.HasValue)
|
||
{
|
||
query = query.Where(ts => ts.Season == seasonFilter.Value);
|
||
}
|
||
|
||
// Category filter (for inventory)
|
||
if (!string.IsNullOrEmpty(selectedCategoryId) && Guid.TryParse(selectedCategoryId, out var catId))
|
||
{
|
||
query = query.Where(ts => ts.CatalogItemId == catId);
|
||
}
|
||
|
||
// Search term
|
||
if (!string.IsNullOrWhiteSpace(searchTerm))
|
||
{
|
||
var term = searchTerm.ToLower();
|
||
query = query.Where(ts =>
|
||
(ts.Customer?.FullName?.ToLower().Contains(term) ?? false) ||
|
||
(ts.Vehicle?.Make?.ToLower().Contains(term) ?? false) ||
|
||
(ts.Vehicle?.Model?.ToLower().Contains(term) ?? false) ||
|
||
(ts.Customer?.CustomerNumber?.ToLower().Contains(term) ?? false) ||
|
||
(ts.CatalogItem?.Name?.ToLower().Contains(term) ?? false) ||
|
||
(ts.StorageLocation?.Code?.ToLower().Contains(term) ?? false) ||
|
||
ts.TicketNumber.ToLower().Contains(term) ||
|
||
(ts.TireBrand?.ToLower().Contains(term) ?? false) ||
|
||
(ts.TireSize?.ToLower().Contains(term) ?? false)
|
||
);
|
||
}
|
||
|
||
filteredTireStacks = query.OrderBy(ts => ts.StorageLocation?.Code).ToList();
|
||
}
|
||
|
||
private void ToggleDetails(Guid id)
|
||
{
|
||
expandedId = expandedId == id ? null : id;
|
||
}
|
||
|
||
private StorageLocation? GetLocation(AreaType area, int row, ShelfPosition pos, int slot)
|
||
{
|
||
return locationLookup.TryGetValue((area, row, pos, slot), out var loc) ? loc : null;
|
||
}
|
||
|
||
private void SelectLocation(StorageLocation? location)
|
||
{
|
||
if (location == null) return;
|
||
selectedLocation = selectedLocation?.Id == location.Id ? null : location;
|
||
}
|
||
|
||
private string GetCellTooltip(StorageLocation? loc, AreaType area, int row, ShelfPosition pos, int slot)
|
||
{
|
||
var baseText = $"{area.GetDisplayName()}, Reihe {row}, {pos.GetDisplayName()}, Platz {slot}";
|
||
if (loc == null) return $"{baseText} – nicht angelegt";
|
||
if (!loc.IsAvailable) return $"{baseText} – gesperrt";
|
||
if (loc.IsOccupied)
|
||
{
|
||
var stack = loc.OccupiedTireStack;
|
||
var who = stack == null ? "belegt" : stack.IsInventory ? stack.CatalogItem?.Name ?? "Lagerbestand" : stack.Customer?.FullName ?? "belegt";
|
||
return $"{baseText} – {who}";
|
||
}
|
||
return $"{baseText} – frei";
|
||
}
|
||
}
|