From ed911a13bad642b0d1c5732d903b46341a633d93 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 1 Feb 2026 23:56:28 -0500 Subject: [PATCH] feat: Redesign Results page for multi-material output Updates Results page to display packing results grouped by material, showing in-stock vs. to-be-purchased breakdown with order summaries. Co-Authored-By: Claude Opus 4.5 --- .../Components/Pages/Projects/Results.razor | 163 +++++++++++++++--- .../Components/Shared/CutListReport.razor | 4 +- 2 files changed, 141 insertions(+), 26 deletions(-) diff --git a/CutList.Web/Components/Pages/Projects/Results.razor b/CutList.Web/Components/Pages/Projects/Results.razor index f9842f4..8e0724e 100644 --- a/CutList.Web/Components/Pages/Projects/Results.razor +++ b/CutList.Web/Components/Pages/Projects/Results.razor @@ -1,9 +1,9 @@ @page "/projects/{Id:int}/results" @inject ProjectService ProjectService @inject CutListPackingService PackingService -@inject ReportService ReportService @inject NavigationManager Navigation @inject IJSRuntime JS +@using CutList.Core @using CutList.Core.Nesting @using CutList.Core.Formatting @@ -22,7 +22,10 @@ else

@project.Name

-

Optimization Results

+ @if (!string.IsNullOrWhiteSpace(project.Customer)) + { +

Customer: @project.Customer

+ }
Edit Project @@ -39,10 +42,6 @@ else {
  • No parts defined. Add parts to the project.
  • } - @if (project.StockBins.Count == 0) - { -
  • No stock bins defined. Add stock bins to the project.
  • - } @if (project.CuttingToolId == null) {
  • No cutting tool selected. Select a cutting tool.
  • @@ -52,27 +51,21 @@ else } else if (packResult != null) { - @if (packResult.ItemsNotUsed.Count > 0) + @if (summary!.TotalItemsNotPlaced > 0) {
    Items Not Placed
    -

    The following @packResult.ItemsNotUsed.Count item(s) could not be placed (probably too long for available stock):

    -
      - @foreach (var item in packResult.ItemsNotUsed.GroupBy(i => new { i.Name, i.Length })) - { -
    • @item.Count() x @item.Key.Name (@ArchUnits.FormatFromInches(item.Key.Length))
    • - } -
    +

    Some items could not be placed. This usually means no stock lengths are configured for the material, or parts are too long.

    } - +
    -

    @summary!.TotalBins

    -

    Stock Bars

    +

    @(summary.TotalInStockBins + summary.TotalToBePurchasedBins)

    +

    Total Stock Bars

    @@ -102,8 +95,94 @@ else
    - - + +
    +
    +
    +
    +
    In Stock
    +
    +
    +

    @summary.TotalInStockBins bars

    +

    Ready to cut from existing inventory

    +
    +
    +
    +
    +
    +
    +
    To Be Purchased
    +
    +
    +

    @summary.TotalToBePurchasedBins bars

    +

    Need to order from supplier

    +
    +
    +
    +
    + + + @foreach (var materialResult in packResult.MaterialResults) + { + var materialSummary = summary.MaterialSummaries.First(s => s.Material.Id == materialResult.Material.Id); + +
    +
    +

    @materialResult.Material.DisplayName

    +
    +
    + +
    +
    + @(materialSummary.InStockBins + materialSummary.ToBePurchasedBins) bars +
    +
    + @materialSummary.TotalPieces pieces +
    +
    + @materialSummary.Efficiency.ToString("F1")% efficiency +
    +
    + @materialSummary.InStockBins in stock +
    +
    + @materialSummary.ToBePurchasedBins to purchase +
    +
    + + @if (materialResult.PackResult.ItemsNotUsed.Count > 0) + { +
    + @materialResult.PackResult.ItemsNotUsed.Count items not placed - + No stock lengths available or parts too long. +
    + } + + @if (materialResult.InStockBins.Count > 0) + { +
    In Stock (@materialResult.InStockBins.Count bars)
    + @RenderBinList(materialResult.InStockBins) + } + + @if (materialResult.ToBePurchasedBins.Count > 0) + { +
    To Be Purchased (@materialResult.ToBePurchasedBins.Count bars)
    + @RenderBinList(materialResult.ToBePurchasedBins) + + +
    + Order Summary: +
      + @foreach (var group in materialResult.ToBePurchasedBins.GroupBy(b => b.Length).OrderByDescending(g => g.Key)) + { +
    • @group.Count() x @ArchUnits.FormatFromInches(group.Key)
    • + } +
    +
    + } +
    +
    + } } } @@ -112,13 +191,12 @@ else public int Id { get; set; } private Project? project; - private PackResult? packResult; - private PackingSummary? summary; + private MultiMaterialPackResult? packResult; + private MultiMaterialPackingSummary? summary; private bool loading = true; private bool CanOptimize => project != null && project.Parts.Count > 0 && - project.StockBins.Count > 0 && project.CuttingToolId != null; protected override async Task OnInitializedAsync() @@ -128,15 +206,52 @@ else if (project != null && CanOptimize) { var kerf = project.CuttingTool?.KerfInches ?? 0.125m; - packResult = PackingService.Pack(project.Parts, project.StockBins, kerf); + packResult = await PackingService.PackAsync(project.Parts, kerf); summary = PackingService.GetSummary(packResult); } loading = false; } + private RenderFragment RenderBinList(List bins) => __builder => + { +
    + + + + + + + + + + + @{ var binNumber = 1; } + @foreach (var bin in bins) + { + + + + + + + binNumber++; + } + +
    #Stock LengthCutsWaste
    @binNumber@ArchUnits.FormatFromInches(bin.Length) + @foreach (var item in bin.Items) + { + + @(string.IsNullOrWhiteSpace(item.Name) ? ArchUnits.FormatFromInches(item.Length) : $"{item.Name} ({ArchUnits.FormatFromInches(item.Length)})") + + } + @ArchUnits.FormatFromInches(bin.RemainingLength)
    +
    + }; + private async Task PrintReport() { - await JS.InvokeVoidAsync("window.print"); + var filename = $"CutList - {project!.Name} - {DateTime.Now:yyyy-MM-dd}"; + await JS.InvokeVoidAsync("printWithTitle", filename); } } diff --git a/CutList.Web/Components/Shared/CutListReport.razor b/CutList.Web/Components/Shared/CutListReport.razor index b179fbc..256db9b 100644 --- a/CutList.Web/Components/Shared/CutListReport.razor +++ b/CutList.Web/Components/Shared/CutListReport.razor @@ -9,9 +9,9 @@
    Date: @DateTime.Now.ToString("g")
    Project: @Project.Name
    - @if (Project.Material != null) + @if (!string.IsNullOrWhiteSpace(Project.Customer)) { -
    Material: @Project.Material.Shape - @Project.Material.Size
    +
    Customer: @Project.Customer
    } @if (Project.CuttingTool != null) {