From 3ccaddef0c9573629e8cf9e94f0213d7b5cbcce7 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 3 Aug 2026 01:31:09 +0000 Subject: [PATCH] [verified] feat: add configurable results length units Default cut results to total-inch mixed fractions, allow switching to feet and inches, and separate part names from lengths visually. --- CutList.Core.Tests/CutList.Core.Tests.csproj | 21 +++++++++++++++ .../ResultsLengthDisplayTests.cs | 27 +++++++++++++++++++ CutList.Core/Formatting/ArchUnits.cs | 8 ++++++ CutList.Web/Components/Pages/Jobs/Edit.razor | 19 +++++++++---- CutList.Web/wwwroot/css/app.css | 1 + CutList.sln | 14 ++++++++++ 6 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 CutList.Core.Tests/CutList.Core.Tests.csproj create mode 100644 CutList.Core.Tests/ResultsLengthDisplayTests.cs diff --git a/CutList.Core.Tests/CutList.Core.Tests.csproj b/CutList.Core.Tests/CutList.Core.Tests.csproj new file mode 100644 index 0000000..499a631 --- /dev/null +++ b/CutList.Core.Tests/CutList.Core.Tests.csproj @@ -0,0 +1,21 @@ + + + net10.0 + enable + enable + false + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/CutList.Core.Tests/ResultsLengthDisplayTests.cs b/CutList.Core.Tests/ResultsLengthDisplayTests.cs new file mode 100644 index 0000000..5dacd4a --- /dev/null +++ b/CutList.Core.Tests/ResultsLengthDisplayTests.cs @@ -0,0 +1,27 @@ +using CutList.Core.Formatting; +using Xunit; + +namespace CutList.Core.Tests; + +public class ResultsLengthDisplayTests +{ + [Theory] + [InlineData(150, "150\"")] + [InlineData(150.375, "150-3/8\"")] + public void FormatInches_uses_total_inches_without_converting_to_feet(double inches, string expected) + { + Assert.Equal(expected, ArchUnits.FormatInches(inches)); + } + + [Fact] + public void Results_markup_includes_unit_toggle_and_divider_between_part_and_length() + { + var sourcePath = Path.GetFullPath( + Path.Combine(AppContext.BaseDirectory, "../../../../CutList.Web/Components/Pages/Jobs/Edit.razor")); + var markup = File.ReadAllText(sourcePath); + + Assert.Contains("FormatResultLength", markup); + Assert.Contains("cut-part-badge-divider", markup); + Assert.Contains("Show feet + inches", markup); + } +} diff --git a/CutList.Core/Formatting/ArchUnits.cs b/CutList.Core/Formatting/ArchUnits.cs index d0dce37..3696a5f 100644 --- a/CutList.Core/Formatting/ArchUnits.cs +++ b/CutList.Core/Formatting/ArchUnits.cs @@ -80,5 +80,13 @@ namespace CutList.Core.Formatting return $"{inches}\""; } } + + /// + /// Formats a measurement as a mixed fraction of total inches without converting to feet. + /// + public static string FormatInches(double totalInches) + { + return $"{FormatHelper.ConvertToMixedFraction(totalInches)}\""; + } } } diff --git a/CutList.Web/Components/Pages/Jobs/Edit.razor b/CutList.Web/Components/Pages/Jobs/Edit.razor index a944880..eb8d270 100644 --- a/CutList.Web/Components/Pages/Jobs/Edit.razor +++ b/CutList.Web/Components/Pages/Jobs/Edit.razor @@ -503,6 +503,7 @@ else private MultiMaterialPackingSummary? summary; private bool optimizing; private bool lockingJob; + private bool showLengthsInInches = true; private IEnumerable DistinctShapes => materials.Select(m => m.Shape).Distinct().OrderBy(s => s); private IEnumerable FilteredMaterials => !selectedShape.HasValue @@ -511,6 +512,9 @@ else private bool IsNew => !Id.HasValue; private bool CanOptimize => job.Parts.Count > 0 && job.CuttingToolId != null; + private string FormatResultLength(double inches) => showLengthsInInches + ? ArchUnits.FormatInches(inches) + : ArchUnits.FormatFromInches(inches); private async Task UnlockJob() { @@ -987,6 +991,10 @@ else + @if (!job.IsLocked) {