feat: Update UI for Jobs and enhanced Materials
Navigation: - Rename Projects to Jobs in NavMenu - Add new icon for multi-material boxes Home page: - Update references from Projects to Jobs Materials pages: - Add Type and Grade columns to index - Shape-specific dimension editing with typed inputs - Error handling with detailed messages Stock pages: - Show Shape, Type, Grade, Size columns - Display QuantityOnHand with badges Shared components: - LengthInput: Add nullable binding mode for optional dimensions - LengthInput: Format on blur for better UX - CutListReport: Update for Job model references Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,11 @@ else
|
||||
<InputText class="form-control" @bind-Value="stockItem.Name" placeholder="Custom display name" />
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Notes (optional)</label>
|
||||
<InputText class="form-control" @bind-Value="stockItem.Notes" placeholder="Internal notes" />
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<div class="alert alert-danger">@errorMessage</div>
|
||||
@@ -76,7 +81,111 @@ else
|
||||
|
||||
@if (!IsNew)
|
||||
{
|
||||
<div class="col-lg-6">
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
Inventory
|
||||
<span class="badge @(stockItem.QuantityOnHand > 0 ? "bg-success" : "bg-secondary") ms-2">@stockItem.QuantityOnHand on hand</span>
|
||||
</h5>
|
||||
<button class="btn btn-sm btn-primary" @onclick="ShowStockForm">Add/Adjust Stock</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (showStockForm)
|
||||
{
|
||||
<div class="border rounded p-3 mb-3 bg-light">
|
||||
<h6>Stock Transaction</h6>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Type</label>
|
||||
<select class="form-select" @bind="stockTransactionType">
|
||||
<option value="add">Receive Stock</option>
|
||||
<option value="adjust">Set Quantity</option>
|
||||
<option value="scrap">Scrap/Waste</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">@(stockTransactionType == "adjust" ? "New Quantity" : "Quantity")</label>
|
||||
<input type="number" class="form-control" @bind="stockQuantity" min="0" />
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Notes</label>
|
||||
<InputText class="form-control" @bind-Value="stockNotes" />
|
||||
</div>
|
||||
@if (stockTransactionType == "add")
|
||||
{
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Supplier (optional)</label>
|
||||
<select class="form-select" @bind="stockSupplierId">
|
||||
<option value="0">-- Select Supplier --</option>
|
||||
@foreach (var supplier in suppliers)
|
||||
{
|
||||
<option value="@supplier.Id">@supplier.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Unit Price (optional)</label>
|
||||
<input type="number" class="form-control" @bind="stockUnitPrice" step="0.01" min="0" placeholder="0.00" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(stockFormErrorMessage))
|
||||
{
|
||||
<div class="alert alert-danger mt-2 mb-0">@stockFormErrorMessage</div>
|
||||
}
|
||||
<div class="mt-3 d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm" @onclick="SaveStockTransactionAsync" disabled="@savingStockTransaction">
|
||||
@if (savingStockTransaction)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1"></span>
|
||||
}
|
||||
Save
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" @onclick="CancelStockForm">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (transactions.Count == 0)
|
||||
{
|
||||
<p class="text-muted">No transaction history yet.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th>Qty</th>
|
||||
<th>Supplier</th>
|
||||
<th>Price</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var txn in transactions)
|
||||
{
|
||||
<tr>
|
||||
<td>@txn.CreatedAt.ToLocalTime().ToString("MM/dd/yy HH:mm")</td>
|
||||
<td>
|
||||
<span class="badge @GetTransactionBadgeClass(txn.Type)">@txn.Type</span>
|
||||
</td>
|
||||
<td class="@(txn.Quantity >= 0 ? "text-success" : "text-danger")">
|
||||
@(txn.Quantity >= 0 ? "+" : "")@txn.Quantity
|
||||
</td>
|
||||
<td>@(txn.Supplier?.Name ?? "-")</td>
|
||||
<td>@(txn.UnitPrice.HasValue ? txn.UnitPrice.Value.ToString("C") : "-")</td>
|
||||
<td>@(txn.Notes ?? "-")</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Supplier Offerings</h5>
|
||||
@@ -184,6 +293,7 @@ else
|
||||
private List<Material> materials = new();
|
||||
private List<Supplier> suppliers = new();
|
||||
private List<SupplierOffering> offerings = new();
|
||||
private List<StockTransaction> transactions = new();
|
||||
private bool loading = true;
|
||||
private bool saving;
|
||||
private bool savingOffering;
|
||||
@@ -194,6 +304,16 @@ else
|
||||
private SupplierOffering newOffering = new();
|
||||
private SupplierOffering? editingOffering;
|
||||
|
||||
// Stock transaction form
|
||||
private bool showStockForm;
|
||||
private bool savingStockTransaction;
|
||||
private string stockTransactionType = "add";
|
||||
private int stockQuantity;
|
||||
private int stockSupplierId;
|
||||
private decimal? stockUnitPrice;
|
||||
private string? stockNotes;
|
||||
private string? stockFormErrorMessage;
|
||||
|
||||
private ConfirmDialog deleteOfferingDialog = null!;
|
||||
private SupplierOffering? offeringToDelete;
|
||||
private string deleteOfferingMessage = "";
|
||||
@@ -215,10 +335,90 @@ else
|
||||
}
|
||||
stockItem = existing;
|
||||
offerings = existing.SupplierOfferings.Where(o => o.IsActive).ToList();
|
||||
transactions = await StockItemService.GetTransactionHistoryAsync(Id.Value, 20);
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
|
||||
private string GetTransactionBadgeClass(StockTransactionType type) => type switch
|
||||
{
|
||||
StockTransactionType.Received => "bg-success",
|
||||
StockTransactionType.Used => "bg-primary",
|
||||
StockTransactionType.Adjustment => "bg-warning text-dark",
|
||||
StockTransactionType.Scrapped => "bg-danger",
|
||||
StockTransactionType.Returned => "bg-info",
|
||||
_ => "bg-secondary"
|
||||
};
|
||||
|
||||
private void ShowStockForm()
|
||||
{
|
||||
stockTransactionType = "add";
|
||||
stockQuantity = 0;
|
||||
stockSupplierId = 0;
|
||||
stockUnitPrice = null;
|
||||
stockNotes = null;
|
||||
stockFormErrorMessage = null;
|
||||
showStockForm = true;
|
||||
}
|
||||
|
||||
private void CancelStockForm()
|
||||
{
|
||||
showStockForm = false;
|
||||
stockFormErrorMessage = null;
|
||||
}
|
||||
|
||||
private async Task SaveStockTransactionAsync()
|
||||
{
|
||||
stockFormErrorMessage = null;
|
||||
savingStockTransaction = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (stockQuantity <= 0 && stockTransactionType != "adjust")
|
||||
{
|
||||
stockFormErrorMessage = "Quantity must be greater than zero";
|
||||
return;
|
||||
}
|
||||
|
||||
if (stockTransactionType == "adjust" && stockQuantity < 0)
|
||||
{
|
||||
stockFormErrorMessage = "Quantity cannot be negative";
|
||||
return;
|
||||
}
|
||||
|
||||
switch (stockTransactionType)
|
||||
{
|
||||
case "add":
|
||||
await StockItemService.AddStockAsync(
|
||||
Id!.Value,
|
||||
stockQuantity,
|
||||
stockSupplierId > 0 ? stockSupplierId : null,
|
||||
stockUnitPrice,
|
||||
stockNotes);
|
||||
break;
|
||||
case "adjust":
|
||||
await StockItemService.AdjustStockAsync(Id!.Value, stockQuantity, stockNotes);
|
||||
break;
|
||||
case "scrap":
|
||||
await StockItemService.ScrapStockAsync(Id!.Value, stockQuantity, stockNotes);
|
||||
break;
|
||||
}
|
||||
|
||||
// Refresh
|
||||
var updated = await StockItemService.GetByIdAsync(Id!.Value);
|
||||
if (updated != null)
|
||||
{
|
||||
stockItem = updated;
|
||||
}
|
||||
transactions = await StockItemService.GetTransactionHistoryAsync(Id!.Value, 20);
|
||||
showStockForm = false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
savingStockTransaction = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveStockItemAsync()
|
||||
{
|
||||
errorMessage = null;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
@inject StockItemService StockItemService
|
||||
@inject NavigationManager Navigation
|
||||
@using CutList.Core.Formatting
|
||||
@using CutList.Web.Data.Entities
|
||||
|
||||
<PageTitle>Stock Items</PageTitle>
|
||||
|
||||
@@ -25,22 +26,39 @@ else
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Material</th>
|
||||
<th>Shape</th>
|
||||
<th>Type</th>
|
||||
<th>Grade</th>
|
||||
<th>Size</th>
|
||||
<th>Length</th>
|
||||
<th>Name</th>
|
||||
<th style="width: 120px;">Actions</th>
|
||||
<th>On Hand</th>
|
||||
<th style="width: 160px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in stockItems)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Material.DisplayName</td>
|
||||
<td>@item.Material.Shape.GetDisplayName()</td>
|
||||
<td>@item.Material.Type</td>
|
||||
<td>@item.Material.Grade</td>
|
||||
<td>@item.Material.Size</td>
|
||||
<td>@ArchUnits.FormatFromInches((double)item.LengthInches)</td>
|
||||
<td>@(item.Name ?? "-")</td>
|
||||
<td>
|
||||
<a href="stock/@item.Id" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => ConfirmDelete(item)">Delete</button>
|
||||
@if (item.QuantityOnHand > 0)
|
||||
{
|
||||
<span class="badge bg-success">@item.QuantityOnHand</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge bg-secondary">0</span>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<a href="stock/@item.Id" class="btn btn-sm btn-outline-primary">Edit</a>
|
||||
<button class="btn btn-sm btn-outline-danger" @onclick="() => ConfirmDelete(item)">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user