@using Blazored.Modal @using Blazored.Modal.Services @using Domain.Dtos.Stock @using Microsoft.AspNetCore.Components.Web @inject IToastService toastService @inject IStockScanService stockScanService @inject IModalService Modal @inherits LayoutComponentBase @code { [CascadingParameter] BlazoredModalInstance ModalInstance { get; set; } = default!; [Parameter] public int? ProductId { get; set; } [Parameter] public int? LocationId { get; set; } [Parameter] public List? SetItems { get; set; } private string InputCode { get; set; } = string.Empty; private ElementReference scanInput; private readonly List SelectedItems = new(); private List StockList = new(); protected override async Task OnInitializedAsync() { await LoadMockStock(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) await scanInput.FocusAsync(); } private async Task OnKeyDown(KeyboardEventArgs e) { if (e.Key == "Enter") await HandleScan(); } private async Task HandleKeyDown(KeyboardEventArgs e) { if (e.Key is "Enter" or "NumpadEnter") { await HandleScan(); await scanInput.FocusAsync(); } } private async Task HandleScan() { var code = (InputCode ?? string.Empty).Trim(); // limpia CR/LF del lector if (string.IsNullOrWhiteSpace(code)) { toastService.ShowWarning("Ingrese un código válido."); await Refocus(); return; } try { var matchedItem = await stockScanService.ParseAndMatchAsync(code, LocationId ?? 1); if (matchedItem is null) { toastService.ShowWarning("No se encontró el producto en stock."); await Refocus(); return; } if (StockList.Any(s => s.StockItemId == matchedItem.StockItemId)) { toastService.ShowInfo("Este ítem ya está listado."); await Refocus(); return; } StockList.Insert(0, new StockDisplayRow { StockItemId = matchedItem.StockItemId, ProductId = matchedItem.ProductId, ProductName = matchedItem.ProductName, Batch = matchedItem.Batch, Expiration = matchedItem.Expiration, Available = matchedItem.Quantity, Selected = 1, LocationId = matchedItem.LocationId }); } catch (Exception ex) { toastService.ShowError($"Error en escaneo: {ex.Message}"); } finally { InputCode = string.Empty; await Refocus(); StateHasChanged(); } } private async Task Refocus() { await Task.Yield(); // asegura que el DOM está listo await scanInput.FocusAsync(); // deja el cursor listo para el próximo escaneo } private Task Cancel() => ModalInstance.CancelAsync(); private async Task Confirm() { var selected = StockList .Where(x => x.Selected > 0) .Select(x => new StockItemSelectionDto { StockItemId = x.StockItemId, ProductId = x.ProductId, ProductName = x.ProductName, Batch = x.Batch, Expiration = x.Expiration, Quantity = x.Selected, LocationId = x.LocationId }) .ToList(); if (!selected.Any()) { toastService.ShowWarning("No se seleccionó ningún producto."); return; } await ModalInstance.CloseAsync(ModalResult.Ok(selected)); } private async Task LoadMockStock() { StockList = new List { new StockDisplayRow { StockItemId = 101, ProductId = 1, ProductName = "Tornillo 4mm x 20mm", Batch = "LOTE001", Expiration = DateTime.Today.AddMonths(12), Available = 5, Selected = 0, LocationId = LocationId ?? 1 }, new StockDisplayRow { StockItemId = 102, ProductId = 1, ProductName = "Tornillo 4mm x 20mm", Batch = "LOTE002", Expiration = DateTime.Today.AddMonths(18), Available = 10, Selected = 0, LocationId = LocationId ?? 1 }, new StockDisplayRow { StockItemId = 103, ProductId = 2, ProductName = "Placa LCP 6 orificios", Batch = "PL001-A", Expiration = DateTime.Today.AddYears(2), Available = 3, Selected = 0, LocationId = LocationId ?? 1 } }; await Task.CompletedTask; } public class StockDisplayRow { public int StockItemId { get; set; } public int ProductId { get; set; } public string ProductName { get; set; } = string.Empty; public string Batch { get; set; } = string.Empty; public DateTime? Expiration { get; set; } public decimal Available { get; set; } public decimal Selected { get; set; } public int LocationId { get; set; } } }