feat: Add job locking UI to Edit and Index pages

Locked jobs show a warning banner with unlock button on the Edit page.
All form fields, part/stock add/edit/delete buttons are disabled via
fieldset when locked. Jobs Index shows a lock icon badge next to locked
job numbers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-07 23:02:59 -05:00
parent ed705625e9
commit 5f4e36c688
2 changed files with 99 additions and 57 deletions

View File

@@ -17,6 +17,19 @@
}
</div>
@if (!IsNew && job.IsLocked)
{
<div class="alert alert-warning d-flex justify-content-between align-items-center mb-3">
<div>
<i class="bi bi-lock-fill me-2"></i>
<strong>This job is locked</strong> — materials ordered on @job.LockedAt!.Value.ToLocalTime().ToString("g"). Unlock to make changes.
</div>
<button class="btn btn-outline-warning btn-sm" @onclick="UnlockJob">
<i class="bi bi-unlock"></i> Unlock Job
</button>
</div>
}
@if (loading)
{
<p><em>Loading...</em></p>
@@ -286,6 +299,15 @@ else
private bool IsNew => !Id.HasValue;
private async Task UnlockJob()
{
if (Id.HasValue)
{
await JobService.UnlockAsync(Id.Value);
job = (await JobService.GetByIdAsync(Id.Value))!;
}
}
protected override async Task OnInitializedAsync()
{
materials = await MaterialService.GetAllAsync();
@@ -322,6 +344,7 @@ else
</div>
<div class="card-body">
<EditForm Model="job" OnValidSubmit="SaveJobAsync">
<fieldset disabled="@job.IsLocked">
@if (!IsNew)
{
<div class="mb-3">
@@ -371,6 +394,7 @@ else
</button>
<a href="jobs" class="btn btn-outline-secondary">Back</a>
</div>
</fieldset>
</EditForm>
</div>
</div>
@@ -381,7 +405,10 @@ else
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Parts to Cut</h5>
@if (!job.IsLocked)
{
<button class="btn btn-primary" @onclick="ShowAddPartForm">Add Part</button>
}
</div>
<div class="card-body">
@if (job.Parts.Count == 0)
@@ -413,8 +440,11 @@ else
<td>@part.Quantity</td>
<td>@(string.IsNullOrWhiteSpace(part.Name) ? "-" : part.Name)</td>
<td>
@if (!job.IsLocked)
{
<button class="btn btn-sm btn-outline-primary me-1" @onclick="() => EditPart(part)" title="Edit"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeletePart(part)" title="Delete"><i class="bi bi-trash"></i></button>
}
</td>
</tr>
}
@@ -545,6 +575,8 @@ else
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Stock for This Job</h5>
@if (!job.IsLocked)
{
<div class="d-flex gap-2">
<button class="btn btn-success" @onclick="ShowImportModal" disabled="@(job.Parts.Count == 0)"
title="@(job.Parts.Count == 0 ? "Add parts first to match against inventory" : "Find and import stock matching your parts")">
@@ -555,6 +587,7 @@ else
<button class="btn btn-outline-primary" @onclick="ShowAddCustomStock">Add Custom Length</button>
</div>
</div>
}
</div>
<div class="card-body">
@if (showStockForm)
@@ -731,8 +764,11 @@ else
}
</td>
<td>
@if (!job.IsLocked)
{
<button class="btn btn-sm btn-outline-primary me-1" @onclick="() => EditStock(stock)" title="Edit"><i class="bi bi-pencil"></i></button>
<button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteStock(stock)" title="Delete"><i class="bi bi-trash"></i></button>
}
</td>
</tr>
}

View File

@@ -50,7 +50,13 @@ else
@foreach (var job in pagedJobs)
{
<tr>
<td><a href="jobs/@job.Id">@job.JobNumber</a></td>
<td>
<a href="jobs/@job.Id">@job.JobNumber</a>
@if (job.IsLocked)
{
<i class="bi bi-lock-fill text-warning ms-1" title="Locked — materials ordered"></i>
}
</td>
<td>@(job.Name ?? "-")</td>
<td>@(job.Customer ?? "-")</td>
<td>@(job.CuttingTool?.Name ?? "-")</td>