191 lines
9.0 KiB
Plaintext
191 lines
9.0 KiB
Plaintext
@using Lagerbestand.Models
|
||
@using Lagerbestand.Services
|
||
@inject IStorageLocationService StorageLocationService
|
||
|
||
@if (IsOpen)
|
||
{
|
||
<div class="picker-overlay" @onclick="Cancel">
|
||
<div class="picker-dialog" @onclick:stopPropagation="true" role="dialog" aria-modal="true" aria-labelledby="picker-title">
|
||
<header class="picker-header">
|
||
<div>
|
||
<h2 id="picker-title">Lagerplatz wählen</h2>
|
||
@if (!string.IsNullOrEmpty(Subtitle))
|
||
{
|
||
<p class="picker-subtitle">@Subtitle</p>
|
||
}
|
||
</div>
|
||
<button type="button" class="picker-close" @onclick="Cancel" aria-label="Schließen">✕</button>
|
||
</header>
|
||
|
||
<div class="picker-body">
|
||
@if (isLoading)
|
||
{
|
||
<div class="picker-loading" role="status">Lade Lagerplätze...</div>
|
||
}
|
||
else
|
||
{
|
||
<div class="rack-view">
|
||
@foreach (var area in new[] { AreaType.Werkstatt, AreaType.Lager })
|
||
{
|
||
<div class="building-section" @key="area">
|
||
<h3 class="building-title">@area.GetBuildingLabel()</h3>
|
||
@for (int row = StorageLocation.MinRow; row <= area.GetMaxRow(); row++)
|
||
{
|
||
var currentRow = row;
|
||
<div class="rack-row" @key="@($"{area}-{currentRow}")">
|
||
<h4 class="row-title">Reihe @currentRow</h4>
|
||
<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 isHighlighted = loc != null && HighlightLocationId == loc.Id;
|
||
var isSelectable = loc != null && (isHighlighted || (loc.IsAvailable && !loc.IsOccupied));
|
||
var cellClass = loc == null ? "missing" : isHighlighted ? "freeing" : !loc.IsAvailable ? "blocked" : loc.IsOccupied ? "occupied" : "free";
|
||
<button type="button"
|
||
class="rack-cell @cellClass @(pendingLocation?.Id == loc?.Id ? "selected" : "")"
|
||
@key="slot"
|
||
disabled="@(!isSelectable)"
|
||
title="@GetTooltip(loc, area, currentRow, pos, slot, isHighlighted)"
|
||
@onclick="() => Select(loc)">
|
||
@slot
|
||
</button>
|
||
}
|
||
</div>
|
||
</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>
|
||
@if (HighlightLocationId.HasValue)
|
||
{
|
||
<span class="legend-item"><span class="legend-swatch freeing"></span> Wird 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>
|
||
}
|
||
</div>
|
||
|
||
<footer class="picker-footer">
|
||
<div class="picker-selection">
|
||
@if (pendingLocation != null)
|
||
{
|
||
<span class="picker-selection-label">Ausgewählt:</span>
|
||
<LocationDisplay Code="@pendingLocation.Code" DisplayMode="LocationDisplayMode.Full" Size="LocationDisplaySize.Small" />
|
||
}
|
||
else
|
||
{
|
||
<span class="picker-selection-hint">Bitte einen freien Platz anklicken</span>
|
||
}
|
||
</div>
|
||
<div class="picker-actions">
|
||
<button type="button" class="btn btn-outline" @onclick="Cancel">Abbrechen</button>
|
||
<button type="button" class="btn btn-primary" @onclick="Confirm" disabled="@(pendingLocation == null)">Übernehmen</button>
|
||
</div>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
@code {
|
||
[Parameter] public bool IsOpen { get; set; }
|
||
[Parameter] public EventCallback<bool> IsOpenChanged { get; set; }
|
||
|
||
/// <summary>Currently selected location (pre-selected when opening).</summary>
|
||
[Parameter] public Guid? SelectedLocationId { get; set; }
|
||
|
||
/// <summary>Location that is about to be freed (e.g. during a swap) – shown as selectable.</summary>
|
||
[Parameter] public Guid? HighlightLocationId { get; set; }
|
||
|
||
[Parameter] public string? Subtitle { get; set; }
|
||
|
||
[Parameter] public EventCallback<StorageLocation> OnSelected { get; set; }
|
||
|
||
private bool isLoading;
|
||
private bool wasOpen;
|
||
private List<StorageLocation> allLocations = new();
|
||
private Dictionary<(AreaType, int, ShelfPosition, int), StorageLocation> lookup = new();
|
||
private StorageLocation? pendingLocation;
|
||
|
||
protected override async Task OnParametersSetAsync()
|
||
{
|
||
if (IsOpen && !wasOpen)
|
||
{
|
||
wasOpen = true;
|
||
await LoadAsync();
|
||
}
|
||
else if (!IsOpen)
|
||
{
|
||
wasOpen = false;
|
||
}
|
||
}
|
||
|
||
private async Task LoadAsync()
|
||
{
|
||
isLoading = true;
|
||
try
|
||
{
|
||
allLocations = await StorageLocationService.GetAllAsync();
|
||
lookup = allLocations.ToDictionary(l => (l.Area, l.Row, l.Position, l.SlotNumber));
|
||
pendingLocation = SelectedLocationId.HasValue
|
||
? allLocations.FirstOrDefault(l => l.Id == SelectedLocationId.Value)
|
||
: null;
|
||
}
|
||
finally
|
||
{
|
||
isLoading = false;
|
||
}
|
||
}
|
||
|
||
private StorageLocation? GetLocation(AreaType area, int row, ShelfPosition pos, int slot)
|
||
=> lookup.TryGetValue((area, row, pos, slot), out var loc) ? loc : null;
|
||
|
||
private void Select(StorageLocation? loc)
|
||
{
|
||
if (loc == null) return;
|
||
pendingLocation = loc;
|
||
}
|
||
|
||
private async Task Confirm()
|
||
{
|
||
if (pendingLocation == null) return;
|
||
await OnSelected.InvokeAsync(pendingLocation);
|
||
await Close();
|
||
}
|
||
|
||
private Task Cancel() => Close();
|
||
|
||
private async Task Close()
|
||
{
|
||
IsOpen = false;
|
||
await IsOpenChanged.InvokeAsync(false);
|
||
}
|
||
|
||
private string GetTooltip(StorageLocation? loc, AreaType area, int row, ShelfPosition pos, int slot, bool isHighlighted)
|
||
{
|
||
var baseText = $"{area.GetDisplayName()}, Reihe {row}, {pos.GetDisplayName()}, Platz {slot}";
|
||
if (loc == null) return $"{baseText} – nicht angelegt";
|
||
if (isHighlighted) return $"{baseText} – wird frei";
|
||
if (!loc.IsAvailable) return $"{baseText} – gesperrt";
|
||
if (loc.IsOccupied) return $"{baseText} – belegt";
|
||
return $"{baseText} – frei";
|
||
}
|
||
}
|