feat: Update Project pages for multi-material parts
Redesigns Project Edit with a tabbed interface and adds material selection (shape -> size) when adding parts. Updates Index to show customer instead of material. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
@page "/projects/{Id:int}"
|
@page "/projects/{Id:int}"
|
||||||
@inject ProjectService ProjectService
|
@inject ProjectService ProjectService
|
||||||
@inject MaterialService MaterialService
|
@inject MaterialService MaterialService
|
||||||
@inject SupplierService SupplierService
|
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@using CutList.Core.Formatting
|
@using CutList.Core.Formatting
|
||||||
|
|
||||||
@@ -20,11 +19,114 @@
|
|||||||
{
|
{
|
||||||
<p><em>Loading...</em></p>
|
<p><em>Loading...</em></p>
|
||||||
}
|
}
|
||||||
|
else if (IsNew)
|
||||||
|
{
|
||||||
|
<!-- New Project: Simple form -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
@RenderDetailsForm()
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<!-- Existing Project: Tabbed interface -->
|
||||||
|
<ul class="nav nav-tabs mb-3" role="tablist">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link @(activeTab == Tab.Details ? "active" : "")"
|
||||||
|
@onclick="() => SetTab(Tab.Details)" type="button">
|
||||||
|
Details
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link @(activeTab == Tab.Parts ? "active" : "")"
|
||||||
|
@onclick="() => SetTab(Tab.Parts)" type="button">
|
||||||
|
Parts
|
||||||
|
@if (project.Parts.Count > 0)
|
||||||
|
{
|
||||||
|
<span class="badge bg-secondary ms-1">@project.Parts.Sum(p => p.Quantity)</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
@if (activeTab == Tab.Details)
|
||||||
|
{
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Project Details -->
|
<div class="col-lg-6">
|
||||||
<div class="col-lg-4 mb-4">
|
@RenderDetailsForm()
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (activeTab == Tab.Parts)
|
||||||
|
{
|
||||||
|
@RenderPartsTab()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private enum Tab { Details, Parts }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int? Id { get; set; }
|
||||||
|
|
||||||
|
private Project project = new();
|
||||||
|
private List<Material> materials = new();
|
||||||
|
private List<CuttingTool> 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<string> DistinctShapes => materials.Select(m => m.Shape).Distinct().OrderBy(s => s);
|
||||||
|
private IEnumerable<Material> FilteredMaterials => string.IsNullOrEmpty(selectedShape)
|
||||||
|
? Enumerable.Empty<Material>()
|
||||||
|
: 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 =>
|
||||||
|
{
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0">Project Details</h5>
|
<h5 class="mb-0">Project Details</h5>
|
||||||
@@ -37,14 +139,8 @@ else
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label">Material</label>
|
<label class="form-label">Customer</label>
|
||||||
<InputSelect class="form-select" @bind-Value="project.MaterialId">
|
<InputText class="form-control" @bind-Value="project.Customer" placeholder="Customer name" />
|
||||||
<option value="">-- Select Material --</option>
|
|
||||||
@foreach (var material in materials)
|
|
||||||
{
|
|
||||||
<option value="@material.Id">@material.DisplayName</option>
|
|
||||||
}
|
|
||||||
</InputSelect>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -81,239 +177,109 @@ else
|
|||||||
</EditForm>
|
</EditForm>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
};
|
||||||
|
|
||||||
@if (!IsNew)
|
private RenderFragment RenderPartsTab() => __builder =>
|
||||||
{
|
{
|
||||||
<!-- Parts -->
|
|
||||||
<div class="col-lg-4 mb-4">
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">Parts</h5>
|
<h5 class="mb-0">Parts to Cut</h5>
|
||||||
<button class="btn btn-sm btn-primary" @onclick="ShowAddPartForm">Add Part</button>
|
<button class="btn btn-primary" @onclick="ShowAddPartForm">Add Part</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@if (showPartForm)
|
@if (showPartForm)
|
||||||
{
|
{
|
||||||
<div class="border rounded p-3 mb-3 bg-light">
|
<div class="border rounded p-3 mb-3 bg-light">
|
||||||
<div class="row g-2">
|
<h6>@(editingPart == null ? "Add Part" : "Edit Part")</h6>
|
||||||
<div class="col-12">
|
<div class="row g-3">
|
||||||
<label class="form-label">Name</label>
|
<div class="col-md-2">
|
||||||
<input type="text" class="form-control" @bind="newPart.Name" placeholder="Part name" />
|
<label class="form-label">Shape</label>
|
||||||
|
<select class="form-select" @bind="selectedShape" @bind:after="OnShapeChanged">
|
||||||
|
<option value="">-- Select --</option>
|
||||||
|
@foreach (var shape in DistinctShapes)
|
||||||
|
{
|
||||||
|
<option value="@shape">@shape</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-md-2">
|
||||||
|
<label class="form-label">Size</label>
|
||||||
|
<select class="form-select" @bind="newPart.MaterialId" disabled="@string.IsNullOrEmpty(selectedShape)">
|
||||||
|
<option value="0">-- Select --</option>
|
||||||
|
@foreach (var material in FilteredMaterials)
|
||||||
|
{
|
||||||
|
<option value="@material.Id">@material.Size</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
<label class="form-label">Length</label>
|
<label class="form-label">Length</label>
|
||||||
<LengthInput @bind-Value="newPart.LengthInches" />
|
<LengthInput @bind-Value="newPart.LengthInches" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4">
|
<div class="col-md-2">
|
||||||
<label class="form-label">Qty</label>
|
<label class="form-label">Qty</label>
|
||||||
<input type="number" class="form-control" @bind="newPart.Quantity" min="1" />
|
<input type="number" class="form-control" @bind="newPart.Quantity" min="1" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Name <span class="text-muted fw-normal">(optional)</span></label>
|
||||||
|
<input type="text" class="form-control" @bind="newPart.Name" placeholder="Part name" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (!string.IsNullOrEmpty(partErrorMessage))
|
@if (!string.IsNullOrEmpty(partErrorMessage))
|
||||||
{
|
{
|
||||||
<div class="alert alert-danger mt-2 mb-0 py-1">@partErrorMessage</div>
|
<div class="alert alert-danger mt-3 mb-0">@partErrorMessage</div>
|
||||||
}
|
}
|
||||||
<div class="mt-2 d-flex gap-2">
|
<div class="mt-3 d-flex gap-2">
|
||||||
<button class="btn btn-primary btn-sm" @onclick="SavePartAsync">@(editingPart == null ? "Add" : "Save")</button>
|
<button class="btn btn-primary" @onclick="SavePartAsync">@(editingPart == null ? "Add Part" : "Save Changes")</button>
|
||||||
<button class="btn btn-outline-secondary btn-sm" @onclick="CancelPartForm">Cancel</button>
|
<button class="btn btn-outline-secondary" @onclick="CancelPartForm">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (project.Parts.Count == 0)
|
@if (project.Parts.Count == 0)
|
||||||
{
|
{
|
||||||
<p class="text-muted mb-0">No parts added yet.</p>
|
<div class="text-center py-4 text-muted">
|
||||||
|
<p class="mb-2">No parts added yet.</p>
|
||||||
|
<p class="small">Add the parts you need to cut, selecting the material for each.</p>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<table class="table table-sm mb-0">
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Material</th>
|
||||||
<th>Length</th>
|
<th>Length</th>
|
||||||
<th>Qty</th>
|
<th>Qty</th>
|
||||||
<th style="width: 80px;"></th>
|
<th>Name</th>
|
||||||
|
<th style="width: 120px;">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach (var part in project.Parts)
|
@foreach (var part in project.Parts)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@part.Name</td>
|
<td>@part.Material.DisplayName</td>
|
||||||
<td>@ArchUnits.FormatFromInches((double)part.LengthInches)</td>
|
<td>@ArchUnits.FormatFromInches((double)part.LengthInches)</td>
|
||||||
<td>@part.Quantity</td>
|
<td>@part.Quantity</td>
|
||||||
|
<td>@(string.IsNullOrWhiteSpace(part.Name) ? "-" : part.Name)</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn btn-sm btn-link p-0 me-2" @onclick="() => EditPart(part)">Edit</button>
|
<button class="btn btn-sm btn-outline-primary me-1" @onclick="() => EditPart(part)">Edit</button>
|
||||||
<button class="btn btn-sm btn-link p-0 text-danger" @onclick="() => DeletePart(part)">Del</button>
|
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeletePart(part)">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="mt-2 text-muted small">
|
</div>
|
||||||
|
<div class="mt-3 text-muted">
|
||||||
Total: @project.Parts.Sum(p => p.Quantity) pieces
|
Total: @project.Parts.Sum(p => p.Quantity) pieces
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
};
|
||||||
|
|
||||||
<!-- Stock Bins -->
|
|
||||||
<div class="col-lg-4 mb-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<h5 class="mb-0">Stock Bins</h5>
|
|
||||||
<div>
|
|
||||||
<button class="btn btn-sm btn-outline-secondary me-1" @onclick="ShowImportDialog">Import</button>
|
|
||||||
<button class="btn btn-sm btn-primary" @onclick="ShowAddBinForm">Add</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
@if (showImportDialog)
|
|
||||||
{
|
|
||||||
<div class="border rounded p-3 mb-3 bg-light">
|
|
||||||
<h6>Import from Supplier</h6>
|
|
||||||
<select class="form-select mb-2" @bind="selectedSupplierId">
|
|
||||||
<option value="0">-- Select Supplier --</option>
|
|
||||||
@foreach (var supplier in suppliers)
|
|
||||||
{
|
|
||||||
<option value="@supplier.Id">@supplier.Name</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<button class="btn btn-primary btn-sm" @onclick="ImportFromSupplier" disabled="@(selectedSupplierId == 0)">Import</button>
|
|
||||||
<button class="btn btn-outline-secondary btn-sm" @onclick="() => showImportDialog = false">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (showBinForm)
|
|
||||||
{
|
|
||||||
<div class="border rounded p-3 mb-3 bg-light">
|
|
||||||
<div class="row g-2">
|
|
||||||
<div class="col-6">
|
|
||||||
<label class="form-label">Length</label>
|
|
||||||
<LengthInput @bind-Value="newBin.LengthInches" />
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-label">Qty</label>
|
|
||||||
<input type="number" class="form-control" @bind="newBin.Quantity" min="-1" />
|
|
||||||
<div class="form-text">-1 = unlimited</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<label class="form-label">Priority</label>
|
|
||||||
<input type="number" class="form-control" @bind="newBin.Priority" min="0" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (!string.IsNullOrEmpty(binErrorMessage))
|
|
||||||
{
|
|
||||||
<div class="alert alert-danger mt-2 mb-0 py-1">@binErrorMessage</div>
|
|
||||||
}
|
|
||||||
<div class="mt-2 d-flex gap-2">
|
|
||||||
<button class="btn btn-primary btn-sm" @onclick="SaveBinAsync">@(editingBin == null ? "Add" : "Save")</button>
|
|
||||||
<button class="btn btn-outline-secondary btn-sm" @onclick="CancelBinForm">Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (project.StockBins.Count == 0)
|
|
||||||
{
|
|
||||||
<p class="text-muted mb-0">No stock bins added yet.</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<table class="table table-sm mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Length</th>
|
|
||||||
<th>Qty</th>
|
|
||||||
<th>Priority</th>
|
|
||||||
<th style="width: 80px;"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach (var bin in project.StockBins.OrderBy(b => b.Priority).ThenBy(b => b.LengthInches))
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>@ArchUnits.FormatFromInches((double)bin.LengthInches)</td>
|
|
||||||
<td>@(bin.Quantity == -1 ? "Unlimited" : bin.Quantity.ToString())</td>
|
|
||||||
<td>@bin.Priority</td>
|
|
||||||
<td>
|
|
||||||
<button class="btn btn-sm btn-link p-0 me-2" @onclick="() => EditBin(bin)">Edit</button>
|
|
||||||
<button class="btn btn-sm btn-link p-0 text-danger" @onclick="() => DeleteBin(bin)">Del</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter]
|
|
||||||
public int? Id { get; set; }
|
|
||||||
|
|
||||||
private Project project = new();
|
|
||||||
private List<Material> materials = new();
|
|
||||||
private List<CuttingTool> cuttingTools = new();
|
|
||||||
private List<Supplier> suppliers = new();
|
|
||||||
|
|
||||||
private bool loading = true;
|
|
||||||
private bool savingProject;
|
|
||||||
private string? projectErrorMessage;
|
|
||||||
|
|
||||||
// Parts form
|
|
||||||
private bool showPartForm;
|
|
||||||
private ProjectPart newPart = new();
|
|
||||||
private ProjectPart? editingPart;
|
|
||||||
private string? partErrorMessage;
|
|
||||||
|
|
||||||
// Bins form
|
|
||||||
private bool showBinForm;
|
|
||||||
private ProjectStockBin newBin = new();
|
|
||||||
private ProjectStockBin? editingBin;
|
|
||||||
private string? binErrorMessage;
|
|
||||||
|
|
||||||
// Import dialog
|
|
||||||
private bool showImportDialog;
|
|
||||||
private int selectedSupplierId;
|
|
||||||
|
|
||||||
private bool IsNew => !Id.HasValue;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
materials = await MaterialService.GetAllAsync();
|
|
||||||
cuttingTools = await ProjectService.GetCuttingToolsAsync();
|
|
||||||
suppliers = await SupplierService.GetAllAsync();
|
|
||||||
|
|
||||||
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 async Task SaveProjectAsync()
|
private async Task SaveProjectAsync()
|
||||||
{
|
{
|
||||||
@@ -349,10 +315,16 @@ else
|
|||||||
{
|
{
|
||||||
editingPart = null;
|
editingPart = null;
|
||||||
newPart = new ProjectPart { ProjectId = Id!.Value, Quantity = 1 };
|
newPart = new ProjectPart { ProjectId = Id!.Value, Quantity = 1 };
|
||||||
|
selectedShape = string.Empty;
|
||||||
showPartForm = true;
|
showPartForm = true;
|
||||||
partErrorMessage = null;
|
partErrorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnShapeChanged()
|
||||||
|
{
|
||||||
|
newPart.MaterialId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
private void EditPart(ProjectPart part)
|
private void EditPart(ProjectPart part)
|
||||||
{
|
{
|
||||||
editingPart = part;
|
editingPart = part;
|
||||||
@@ -360,11 +332,13 @@ else
|
|||||||
{
|
{
|
||||||
Id = part.Id,
|
Id = part.Id,
|
||||||
ProjectId = part.ProjectId,
|
ProjectId = part.ProjectId,
|
||||||
|
MaterialId = part.MaterialId,
|
||||||
Name = part.Name,
|
Name = part.Name,
|
||||||
LengthInches = part.LengthInches,
|
LengthInches = part.LengthInches,
|
||||||
Quantity = part.Quantity,
|
Quantity = part.Quantity,
|
||||||
SortOrder = part.SortOrder
|
SortOrder = part.SortOrder
|
||||||
};
|
};
|
||||||
|
selectedShape = part.Material?.Shape ?? string.Empty;
|
||||||
showPartForm = true;
|
showPartForm = true;
|
||||||
partErrorMessage = null;
|
partErrorMessage = null;
|
||||||
}
|
}
|
||||||
@@ -379,9 +353,15 @@ else
|
|||||||
{
|
{
|
||||||
partErrorMessage = null;
|
partErrorMessage = null;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(newPart.Name))
|
if (string.IsNullOrEmpty(selectedShape))
|
||||||
{
|
{
|
||||||
partErrorMessage = "Name is required";
|
partErrorMessage = "Please select a shape";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPart.MaterialId == 0)
|
||||||
|
{
|
||||||
|
partErrorMessage = "Please select a size";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,88 +396,4 @@ else
|
|||||||
await ProjectService.DeletePartAsync(part.Id);
|
await ProjectService.DeletePartAsync(part.Id);
|
||||||
project = (await ProjectService.GetByIdAsync(Id!.Value))!;
|
project = (await ProjectService.GetByIdAsync(Id!.Value))!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bins methods
|
|
||||||
private void ShowAddBinForm()
|
|
||||||
{
|
|
||||||
editingBin = null;
|
|
||||||
newBin = new ProjectStockBin { ProjectId = Id!.Value, Quantity = -1, Priority = 25 };
|
|
||||||
showBinForm = true;
|
|
||||||
binErrorMessage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EditBin(ProjectStockBin bin)
|
|
||||||
{
|
|
||||||
editingBin = bin;
|
|
||||||
newBin = new ProjectStockBin
|
|
||||||
{
|
|
||||||
Id = bin.Id,
|
|
||||||
ProjectId = bin.ProjectId,
|
|
||||||
LengthInches = bin.LengthInches,
|
|
||||||
Quantity = bin.Quantity,
|
|
||||||
Priority = bin.Priority,
|
|
||||||
SortOrder = bin.SortOrder
|
|
||||||
};
|
|
||||||
showBinForm = true;
|
|
||||||
binErrorMessage = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CancelBinForm()
|
|
||||||
{
|
|
||||||
showBinForm = false;
|
|
||||||
editingBin = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveBinAsync()
|
|
||||||
{
|
|
||||||
binErrorMessage = null;
|
|
||||||
|
|
||||||
if (newBin.LengthInches <= 0)
|
|
||||||
{
|
|
||||||
binErrorMessage = "Length must be greater than zero";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newBin.Quantity < -1 || newBin.Quantity == 0)
|
|
||||||
{
|
|
||||||
binErrorMessage = "Quantity must be positive or -1 for unlimited";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editingBin == null)
|
|
||||||
{
|
|
||||||
await ProjectService.AddStockBinAsync(newBin);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await ProjectService.UpdateStockBinAsync(newBin);
|
|
||||||
}
|
|
||||||
|
|
||||||
project = (await ProjectService.GetByIdAsync(Id!.Value))!;
|
|
||||||
showBinForm = false;
|
|
||||||
editingBin = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task DeleteBin(ProjectStockBin bin)
|
|
||||||
{
|
|
||||||
await ProjectService.DeleteStockBinAsync(bin.Id);
|
|
||||||
project = (await ProjectService.GetByIdAsync(Id!.Value))!;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import methods
|
|
||||||
private void ShowImportDialog()
|
|
||||||
{
|
|
||||||
selectedSupplierId = 0;
|
|
||||||
showImportDialog = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ImportFromSupplier()
|
|
||||||
{
|
|
||||||
if (selectedSupplierId > 0)
|
|
||||||
{
|
|
||||||
await ProjectService.ImportStockFromSupplierAsync(Id!.Value, selectedSupplierId, project.MaterialId);
|
|
||||||
project = (await ProjectService.GetByIdAsync(Id!.Value))!;
|
|
||||||
showImportDialog = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ else
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Material</th>
|
<th>Customer</th>
|
||||||
<th>Cutting Tool</th>
|
<th>Cutting Tool</th>
|
||||||
<th>Last Modified</th>
|
<th>Last Modified</th>
|
||||||
<th style="width: 200px;">Actions</th>
|
<th style="width: 200px;">Actions</th>
|
||||||
@@ -36,7 +36,7 @@ else
|
|||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="projects/@project.Id">@project.Name</a></td>
|
<td><a href="projects/@project.Id">@project.Name</a></td>
|
||||||
<td>@(project.Material?.DisplayName ?? "-")</td>
|
<td>@(project.Customer ?? "-")</td>
|
||||||
<td>@(project.CuttingTool?.Name ?? "-")</td>
|
<td>@(project.CuttingTool?.Name ?? "-")</td>
|
||||||
<td>@((project.UpdatedAt ?? project.CreatedAt).ToLocalTime().ToString("g"))</td>
|
<td>@((project.UpdatedAt ?? project.CreatedAt).ToLocalTime().ToString("g"))</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
Reference in New Issue
Block a user