From 4aec4c22755e1b3cf5dbad002ee1bc0b4df443f9 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Thu, 5 Feb 2026 23:17:42 -0500 Subject: [PATCH] feat: Add bulk stock import modal to job editor Allows importing multiple stock items from inventory at once, matching against materials already in the job's parts list. Includes select all/none, quantity, and priority controls. Co-Authored-By: Claude Opus 4.6 --- CutList.Web/Components/Pages/Jobs/Edit.razor | 213 ++++++++++++++++++- 1 file changed, 210 insertions(+), 3 deletions(-) diff --git a/CutList.Web/Components/Pages/Jobs/Edit.razor b/CutList.Web/Components/Pages/Jobs/Edit.razor index f77fab9..b471897 100644 --- a/CutList.Web/Components/Pages/Jobs/Edit.razor +++ b/CutList.Web/Components/Pages/Jobs/Edit.razor @@ -143,6 +143,102 @@ else } +@* Import Stock Modal *@ +@if (showImportModal) +{ + +} + @code { private enum Tab { Details, Parts, Stock } @@ -177,6 +273,12 @@ else private int stockSelectedMaterialId; private List availableStockItems = new(); + // Import modal + private bool showImportModal; + private bool loadingImport; + private List importCandidates = new(); + private string? importErrorMessage; + private IEnumerable DistinctShapes => materials.Select(m => m.Shape).Distinct().OrderBy(s => s); private IEnumerable FilteredMaterials => !selectedShape.HasValue ? Enumerable.Empty() @@ -443,9 +545,15 @@ else
Stock for This Job
-
- - +
+ +
+ + +
@@ -819,4 +927,103 @@ else await JobService.DeleteStockAsync(stock.Id); job = (await JobService.GetByIdAsync(Id!.Value))!; } + + // Import modal methods + private async Task ShowImportModal() + { + importErrorMessage = null; + importCandidates.Clear(); + loadingImport = true; + showImportModal = true; + + try + { + var materialIds = job.Parts.Select(p => p.MaterialId).Distinct().ToList(); + var existingStockItemIds = job.Stock + .Where(s => s.StockItemId.HasValue) + .Select(s => s.StockItemId!.Value) + .ToHashSet(); + + foreach (var materialId in materialIds) + { + var stockItems = await JobService.GetAvailableStockForMaterialAsync(materialId); + foreach (var item in stockItems.Where(s => !existingStockItemIds.Contains(s.Id))) + { + importCandidates.Add(new ImportStockCandidate + { + StockItem = item, + Selected = true, + Quantity = -1, + Priority = 10 + }); + } + } + } + catch (Exception ex) + { + importErrorMessage = $"Error loading stock: {ex.Message}"; + } + finally + { + loadingImport = false; + } + } + + private void CloseImportModal() + { + showImportModal = false; + importCandidates.Clear(); + importErrorMessage = null; + } + + private void ToggleAllImportCandidates(bool selected) + { + foreach (var c in importCandidates) + c.Selected = selected; + } + + private async Task ImportSelectedStockAsync() + { + importErrorMessage = null; + var selected = importCandidates.Where(c => c.Selected).ToList(); + if (selected.Count == 0) + { + importErrorMessage = "No items selected"; + return; + } + + try + { + foreach (var candidate in selected) + { + var jobStock = new JobStock + { + JobId = Id!.Value, + MaterialId = candidate.StockItem.MaterialId, + StockItemId = candidate.StockItem.Id, + LengthInches = candidate.StockItem.LengthInches, + Quantity = candidate.Quantity, + Priority = candidate.Priority, + IsCustomLength = false + }; + await JobService.AddStockAsync(jobStock); + } + + job = (await JobService.GetByIdAsync(Id!.Value))!; + showImportModal = false; + importCandidates.Clear(); + } + catch (Exception ex) + { + importErrorMessage = $"Error importing stock: {ex.Message}"; + } + } + + private class ImportStockCandidate + { + public StockItem StockItem { get; set; } = null!; + public bool Selected { get; set; } = true; + public int Quantity { get; set; } = -1; + public int Priority { get; set; } = 10; + } }