diff --git a/CutList.Web/Components/Pages/Projects/Edit.razor b/CutList.Web/Components/Pages/Projects/Edit.razor deleted file mode 100644 index aace452..0000000 --- a/CutList.Web/Components/Pages/Projects/Edit.razor +++ /dev/null @@ -1,399 +0,0 @@ -@page "/projects/new" -@page "/projects/{Id:int}" -@inject ProjectService ProjectService -@inject MaterialService MaterialService -@inject NavigationManager Navigation -@using CutList.Core.Formatting - -@(IsNew ? "New Project" : project.Name) - -
-

@(IsNew ? "New Project" : project.Name)

- @if (!IsNew) - { - Run Optimization - } -
- -@if (loading) -{ -

Loading...

-} -else if (IsNew) -{ - -
-
- @RenderDetailsForm() -
-
-} -else -{ - - - -
- @if (activeTab == Tab.Details) - { -
-
- @RenderDetailsForm() -
-
- } - else if (activeTab == Tab.Parts) - { - @RenderPartsTab() - } -
-} - -@code { - private enum Tab { Details, Parts } - - [Parameter] - public int? Id { get; set; } - - private Project project = new(); - private List materials = new(); - private List cuttingTools = new(); - - private bool loading = true; - private bool savingProject; - private string? projectErrorMessage; - private Tab activeTab = Tab.Details; - - private void SetTab(Tab tab) => activeTab = tab; - - // Parts form - private bool showPartForm; - private ProjectPart newPart = new(); - private ProjectPart? editingPart; - private string? partErrorMessage; - private string selectedShape = string.Empty; - - private IEnumerable DistinctShapes => materials.Select(m => m.Shape).Distinct().OrderBy(s => s); - private IEnumerable FilteredMaterials => string.IsNullOrEmpty(selectedShape) - ? Enumerable.Empty() - : materials.Where(m => m.Shape == selectedShape).OrderBy(m => m.Size); - - private bool IsNew => !Id.HasValue; - - protected override async Task OnInitializedAsync() - { - materials = await MaterialService.GetAllAsync(); - cuttingTools = await ProjectService.GetCuttingToolsAsync(); - - if (Id.HasValue) - { - var existing = await ProjectService.GetByIdAsync(Id.Value); - if (existing == null) - { - Navigation.NavigateTo("projects"); - return; - } - project = existing; - } - else - { - // Set default cutting tool for new projects - var defaultTool = await ProjectService.GetDefaultCuttingToolAsync(); - if (defaultTool != null) - { - project.CuttingToolId = defaultTool.Id; - } - } - - loading = false; - } - - private RenderFragment RenderDetailsForm() => __builder => - { -
-
-
Project Details
-
-
- -
- - -
- -
- - -
- -
- - - - @foreach (var tool in cuttingTools) - { - - } - -
- -
- - -
- - @if (!string.IsNullOrEmpty(projectErrorMessage)) - { -
@projectErrorMessage
- } - -
- - Back -
-
-
-
- }; - - private RenderFragment RenderPartsTab() => __builder => - { -
-
-
Parts to Cut
- -
-
- @if (showPartForm) - { -
-
@(editingPart == null ? "Add Part" : "Edit Part")
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- @if (!string.IsNullOrEmpty(partErrorMessage)) - { -
@partErrorMessage
- } -
- - -
-
- } - - @if (project.Parts.Count == 0) - { -
-

No parts added yet.

-

Add the parts you need to cut, selecting the material for each.

-
- } - else - { -
- - - - - - - - - - - - @foreach (var part in project.Parts) - { - - - - - - - - } - -
MaterialLengthQtyNameActions
@part.Material.DisplayName@ArchUnits.FormatFromInches((double)part.LengthInches)@part.Quantity@(string.IsNullOrWhiteSpace(part.Name) ? "-" : part.Name) - - -
-
-
- Total: @project.Parts.Sum(p => p.Quantity) pieces -
- } -
-
- }; - - private async Task SaveProjectAsync() - { - projectErrorMessage = null; - savingProject = true; - - try - { - if (string.IsNullOrWhiteSpace(project.Name)) - { - projectErrorMessage = "Project name is required"; - return; - } - - if (IsNew) - { - var created = await ProjectService.CreateAsync(project); - Navigation.NavigateTo($"projects/{created.Id}"); - } - else - { - await ProjectService.UpdateAsync(project); - } - } - finally - { - savingProject = false; - } - } - - // Parts methods - private void ShowAddPartForm() - { - editingPart = null; - newPart = new ProjectPart { ProjectId = Id!.Value, Quantity = 1 }; - selectedShape = string.Empty; - showPartForm = true; - partErrorMessage = null; - } - - private void OnShapeChanged() - { - newPart.MaterialId = 0; - } - - private void EditPart(ProjectPart part) - { - editingPart = part; - newPart = new ProjectPart - { - Id = part.Id, - ProjectId = part.ProjectId, - MaterialId = part.MaterialId, - Name = part.Name, - LengthInches = part.LengthInches, - Quantity = part.Quantity, - SortOrder = part.SortOrder - }; - selectedShape = part.Material?.Shape ?? string.Empty; - showPartForm = true; - partErrorMessage = null; - } - - private void CancelPartForm() - { - showPartForm = false; - editingPart = null; - } - - private async Task SavePartAsync() - { - partErrorMessage = null; - - if (string.IsNullOrEmpty(selectedShape)) - { - partErrorMessage = "Please select a shape"; - return; - } - - if (newPart.MaterialId == 0) - { - partErrorMessage = "Please select a size"; - return; - } - - if (newPart.LengthInches <= 0) - { - partErrorMessage = "Length must be greater than zero"; - return; - } - - if (newPart.Quantity < 1) - { - partErrorMessage = "Quantity must be at least 1"; - return; - } - - if (editingPart == null) - { - await ProjectService.AddPartAsync(newPart); - } - else - { - await ProjectService.UpdatePartAsync(newPart); - } - - project = (await ProjectService.GetByIdAsync(Id!.Value))!; - showPartForm = false; - editingPart = null; - } - - private async Task DeletePart(ProjectPart part) - { - await ProjectService.DeletePartAsync(part.Id); - project = (await ProjectService.GetByIdAsync(Id!.Value))!; - } -} diff --git a/CutList.Web/Components/Pages/Projects/Index.razor b/CutList.Web/Components/Pages/Projects/Index.razor deleted file mode 100644 index 6c17513..0000000 --- a/CutList.Web/Components/Pages/Projects/Index.razor +++ /dev/null @@ -1,94 +0,0 @@ -@page "/projects" -@inject ProjectService ProjectService -@inject NavigationManager Navigation - -Projects - -
-

Projects

- New Project -
- -@if (loading) -{ -

Loading...

-} -else if (projects.Count == 0) -{ -
- No projects found. Create your first project. -
-} -else -{ - - - - - - - - - - - - @foreach (var project in projects) - { - - - - - - - - } - -
NameCustomerCutting ToolLast ModifiedActions
@project.Name@(project.Customer ?? "-")@(project.CuttingTool?.Name ?? "-")@((project.UpdatedAt ?? project.CreatedAt).ToLocalTime().ToString("g")) - Edit - Optimize - - -
-} - - - -@code { - private List projects = new(); - private bool loading = true; - private ConfirmDialog deleteDialog = null!; - private Project? projectToDelete; - private string deleteMessage = ""; - - protected override async Task OnInitializedAsync() - { - projects = await ProjectService.GetAllAsync(); - loading = false; - } - - private void ConfirmDelete(Project project) - { - projectToDelete = project; - deleteMessage = $"Are you sure you want to delete \"{project.Name}\"? This will also delete all parts and stock bins."; - deleteDialog.Show(); - } - - private async Task DeleteConfirmed() - { - if (projectToDelete != null) - { - await ProjectService.DeleteAsync(projectToDelete.Id); - projects = await ProjectService.GetAllAsync(); - } - } - - private async Task DuplicateProject(Project project) - { - var duplicate = await ProjectService.DuplicateAsync(project.Id); - Navigation.NavigateTo($"projects/{duplicate.Id}"); - } -} diff --git a/CutList.Web/Components/Pages/Projects/Results.razor b/CutList.Web/Components/Pages/Projects/Results.razor deleted file mode 100644 index 8e0724e..0000000 --- a/CutList.Web/Components/Pages/Projects/Results.razor +++ /dev/null @@ -1,257 +0,0 @@ -@page "/projects/{Id:int}/results" -@inject ProjectService ProjectService -@inject CutListPackingService PackingService -@inject NavigationManager Navigation -@inject IJSRuntime JS -@using CutList.Core -@using CutList.Core.Nesting -@using CutList.Core.Formatting - -Results - @(project?.Name ?? "Project") - -@if (loading) -{ -

Loading...

-} -else if (project == null) -{ -
Project not found.
-} -else -{ -
-
-

@project.Name

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

Customer: @project.Customer

- } -
-
- Edit Project - -
-
- - @if (!CanOptimize) - { -
-

Cannot Optimize

- -
- } - else if (packResult != null) - { - @if (summary!.TotalItemsNotPlaced > 0) - { -
-
Items Not Placed
-

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

-
- } - - -
-
-
-
-

@(summary.TotalInStockBins + summary.TotalToBePurchasedBins)

-

Total Stock Bars

-
-
-
-
-
-
-

@summary.TotalPieces

-

Total Pieces

-
-
-
-
-
-
-

@ArchUnits.FormatFromInches(summary.TotalWaste)

-

Total Waste

-
-
-
-
-
-
-

@summary.Efficiency.ToString("F1")%

-

Efficiency

-
-
-
-
- - -
-
-
-
-
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)
  • - } -
-
- } -
-
- } - } -} - -@code { - [Parameter] - public int Id { get; set; } - - private Project? project; - private MultiMaterialPackResult? packResult; - private MultiMaterialPackingSummary? summary; - private bool loading = true; - - private bool CanOptimize => project != null && - project.Parts.Count > 0 && - project.CuttingToolId != null; - - protected override async Task OnInitializedAsync() - { - project = await ProjectService.GetByIdAsync(Id); - - if (project != null && CanOptimize) - { - var kerf = project.CuttingTool?.KerfInches ?? 0.125m; - 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() - { - var filename = $"CutList - {project!.Name} - {DateTime.Now:yyyy-MM-dd}"; - await JS.InvokeVoidAsync("printWithTitle", filename); - } -} diff --git a/CutList.Web/Data/Entities/MaterialStockLength.cs b/CutList.Web/Data/Entities/MaterialStockLength.cs deleted file mode 100644 index 7f73daf..0000000 --- a/CutList.Web/Data/Entities/MaterialStockLength.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace CutList.Web.Data.Entities; - -public class MaterialStockLength -{ - public int Id { get; set; } - public int MaterialId { get; set; } - public decimal LengthInches { get; set; } - public int Quantity { get; set; } = 0; - public string? Notes { get; set; } - public bool IsActive { get; set; } = true; - - public Material Material { get; set; } = null!; -} diff --git a/CutList.Web/Data/Entities/Project.cs b/CutList.Web/Data/Entities/Project.cs deleted file mode 100644 index 3d0e807..0000000 --- a/CutList.Web/Data/Entities/Project.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CutList.Web.Data.Entities; - -public class Project -{ - public int Id { get; set; } - public string Name { get; set; } = string.Empty; - public string? Customer { get; set; } - public int? CuttingToolId { get; set; } - public string? Notes { get; set; } - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; - public DateTime? UpdatedAt { get; set; } - - public CuttingTool? CuttingTool { get; set; } - public ICollection Parts { get; set; } = new List(); -} diff --git a/CutList.Web/Data/Entities/ProjectPart.cs b/CutList.Web/Data/Entities/ProjectPart.cs deleted file mode 100644 index 662b5d0..0000000 --- a/CutList.Web/Data/Entities/ProjectPart.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace CutList.Web.Data.Entities; - -public class ProjectPart -{ - public int Id { get; set; } - public int ProjectId { get; set; } - public int MaterialId { get; set; } - public string Name { get; set; } = string.Empty; - public decimal LengthInches { get; set; } - public int Quantity { get; set; } = 1; - public int SortOrder { get; set; } - - public Project Project { get; set; } = null!; - public Material Material { get; set; } = null!; -} diff --git a/CutList.Web/Services/ProjectService.cs b/CutList.Web/Services/ProjectService.cs deleted file mode 100644 index 8762263..0000000 --- a/CutList.Web/Services/ProjectService.cs +++ /dev/null @@ -1,213 +0,0 @@ -using CutList.Web.Data; -using CutList.Web.Data.Entities; -using Microsoft.EntityFrameworkCore; - -namespace CutList.Web.Services; - -public class ProjectService -{ - private readonly ApplicationDbContext _context; - - public ProjectService(ApplicationDbContext context) - { - _context = context; - } - - public async Task> GetAllAsync() - { - return await _context.Projects - .Include(p => p.CuttingTool) - .Include(p => p.Parts) - .ThenInclude(pt => pt.Material) - .OrderByDescending(p => p.UpdatedAt ?? p.CreatedAt) - .ToListAsync(); - } - - public async Task GetByIdAsync(int id) - { - return await _context.Projects - .Include(p => p.CuttingTool) - .Include(p => p.Parts.OrderBy(pt => pt.SortOrder)) - .ThenInclude(pt => pt.Material) - .FirstOrDefaultAsync(p => p.Id == id); - } - - public async Task CreateAsync(Project project) - { - project.CreatedAt = DateTime.UtcNow; - _context.Projects.Add(project); - await _context.SaveChangesAsync(); - return project; - } - - public async Task UpdateAsync(Project project) - { - project.UpdatedAt = DateTime.UtcNow; - _context.Projects.Update(project); - await _context.SaveChangesAsync(); - } - - public async Task DeleteAsync(int id) - { - var project = await _context.Projects.FindAsync(id); - if (project != null) - { - _context.Projects.Remove(project); - await _context.SaveChangesAsync(); - } - } - - public async Task DuplicateAsync(int id) - { - var original = await GetByIdAsync(id); - if (original == null) - { - throw new ArgumentException("Project not found", nameof(id)); - } - - var duplicate = new Project - { - Name = $"{original.Name} (Copy)", - Customer = original.Customer, - CuttingToolId = original.CuttingToolId, - Notes = original.Notes, - CreatedAt = DateTime.UtcNow - }; - - _context.Projects.Add(duplicate); - await _context.SaveChangesAsync(); - - // Copy parts - foreach (var part in original.Parts) - { - _context.ProjectParts.Add(new ProjectPart - { - ProjectId = duplicate.Id, - MaterialId = part.MaterialId, - Name = part.Name, - LengthInches = part.LengthInches, - Quantity = part.Quantity, - SortOrder = part.SortOrder - }); - } - - await _context.SaveChangesAsync(); - return duplicate; - } - - // Parts management - public async Task AddPartAsync(ProjectPart part) - { - var maxOrder = await _context.ProjectParts - .Where(p => p.ProjectId == part.ProjectId) - .MaxAsync(p => (int?)p.SortOrder) ?? -1; - part.SortOrder = maxOrder + 1; - - _context.ProjectParts.Add(part); - await _context.SaveChangesAsync(); - - // Update project timestamp - var project = await _context.Projects.FindAsync(part.ProjectId); - if (project != null) - { - project.UpdatedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(); - } - - return part; - } - - public async Task UpdatePartAsync(ProjectPart part) - { - _context.ProjectParts.Update(part); - await _context.SaveChangesAsync(); - - var project = await _context.Projects.FindAsync(part.ProjectId); - if (project != null) - { - project.UpdatedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(); - } - } - - public async Task DeletePartAsync(int id) - { - var part = await _context.ProjectParts.FindAsync(id); - if (part != null) - { - var projectId = part.ProjectId; - _context.ProjectParts.Remove(part); - await _context.SaveChangesAsync(); - - var project = await _context.Projects.FindAsync(projectId); - if (project != null) - { - project.UpdatedAt = DateTime.UtcNow; - await _context.SaveChangesAsync(); - } - } - } - - // Cutting tools - public async Task> GetCuttingToolsAsync(bool includeInactive = false) - { - var query = _context.CuttingTools.AsQueryable(); - if (!includeInactive) - { - query = query.Where(t => t.IsActive); - } - return await query.OrderBy(t => t.Name).ToListAsync(); - } - - public async Task GetCuttingToolByIdAsync(int id) - { - return await _context.CuttingTools.FindAsync(id); - } - - public async Task GetDefaultCuttingToolAsync() - { - return await _context.CuttingTools.FirstOrDefaultAsync(t => t.IsDefault && t.IsActive); - } - - public async Task CreateCuttingToolAsync(CuttingTool tool) - { - if (tool.IsDefault) - { - // Clear other defaults - var others = await _context.CuttingTools.Where(t => t.IsDefault).ToListAsync(); - foreach (var other in others) - { - other.IsDefault = false; - } - } - - _context.CuttingTools.Add(tool); - await _context.SaveChangesAsync(); - return tool; - } - - public async Task UpdateCuttingToolAsync(CuttingTool tool) - { - if (tool.IsDefault) - { - var others = await _context.CuttingTools.Where(t => t.IsDefault && t.Id != tool.Id).ToListAsync(); - foreach (var other in others) - { - other.IsDefault = false; - } - } - - _context.CuttingTools.Update(tool); - await _context.SaveChangesAsync(); - } - - public async Task DeleteCuttingToolAsync(int id) - { - var tool = await _context.CuttingTools.FindAsync(id); - if (tool != null) - { - tool.IsActive = false; - await _context.SaveChangesAsync(); - } - } -}