Files
ReifenSpaeth/Lagerbestand/Components/Shared/StorageLocationPickerModal.razor
T

191 lines
9.0 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@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";
}
}