a0bdd2b5b4
Replace all corruption variants with HTML entities across 226 view files: - 3-char UTF-8-as-Win1252 sequences (ae-corruption) - Standalone smart/curly quotes that break C# Razor expressions - Partially re-corrupted variants where the 3rd byte was normalised to ASCII tools/Fix-Encoding.ps1: re-runnable sweep; uses [char] code points so the script itself never contains a literal non-ASCII character; supports -DryRun .githooks/pre-commit: blocks commits containing the ae-corruption byte signature (xc3xa2xe2x82xac); git core.hooksPath = .githooks so the hook is repo-committed and active for all future work on this machine. Build clean; 225 unit tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
293 lines
9.5 KiB
Plaintext
293 lines
9.5 KiB
Plaintext
@{
|
|
ViewData["Title"] = "Update Job Status";
|
|
Layout = null;
|
|
var job = ViewBag.Job as PowderCoating.Core.Entities.Job;
|
|
var allStatuses = ViewBag.AllStatuses as List<PowderCoating.Core.Entities.JobStatusLookup>;
|
|
var jobId = (int)ViewBag.JobId;
|
|
|
|
// Determine next/previous status options
|
|
var currentOrder = job!.JobStatus.DisplayOrder;
|
|
var nextStatus = allStatuses!
|
|
.Where(s => s.DisplayOrder > currentOrder && s.StatusCode != "ONHOLD" && s.StatusCode != "CANCELLED")
|
|
.OrderBy(s => s.DisplayOrder)
|
|
.FirstOrDefault();
|
|
var onHoldStatus = allStatuses!.FirstOrDefault(s => s.StatusCode == "ONHOLD");
|
|
var isOnHold = job.JobStatus.StatusCode == "ONHOLD";
|
|
var isTerminal = job.JobStatus.IsTerminalStatus;
|
|
}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
<title>@ViewData["Title"]</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
background: #f0f2f5;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 20px 16px;
|
|
}
|
|
|
|
.card {
|
|
background: white;
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 20px rgba(0,0,0,0.10);
|
|
width: 100%;
|
|
max-width: 420px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.card-header {
|
|
background: #1a56db;
|
|
color: white;
|
|
padding: 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
.card-header .job-number {
|
|
font-size: 28px;
|
|
font-weight: 800;
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
.card-header .customer-name {
|
|
font-size: 14px;
|
|
opacity: 0.85;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 24px 20px;
|
|
}
|
|
|
|
.status-section {
|
|
text-align: center;
|
|
margin-bottom: 28px;
|
|
}
|
|
|
|
.status-label {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
color: #6b7280;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.current-status-badge {
|
|
display: inline-block;
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
padding: 10px 24px;
|
|
border-radius: 50px;
|
|
color: white;
|
|
}
|
|
|
|
.action-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.btn-advance {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 18px 20px;
|
|
background: #059669;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 12px;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
text-align: center;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.btn-advance:active { opacity: 0.85; transform: scale(0.98); }
|
|
|
|
.btn-hold {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 14px 20px;
|
|
background: white;
|
|
color: #d97706;
|
|
border: 2px solid #d97706;
|
|
border-radius: 12px;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
text-align: center;
|
|
}
|
|
|
|
.btn-hold:active { background: #fef3c7; }
|
|
|
|
.btn-resume {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 18px 20px;
|
|
background: #2563eb;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 12px;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
text-align: center;
|
|
}
|
|
|
|
.btn-resume:active { opacity: 0.85; }
|
|
|
|
.terminal-msg {
|
|
text-align: center;
|
|
padding: 16px;
|
|
color: #6b7280;
|
|
font-size: 15px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.success-banner {
|
|
background: #d1fae5;
|
|
border: 2px solid #059669;
|
|
border-radius: 12px;
|
|
padding: 16px;
|
|
text-align: center;
|
|
color: #065f46;
|
|
font-weight: 600;
|
|
font-size: 15px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.divider {
|
|
height: 1px;
|
|
background: #e5e7eb;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.meta-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 13px;
|
|
color: #6b7280;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
/* Bootstrap color helpers inline for the badge */
|
|
.bg-primary { background-color: #0d6efd !important; }
|
|
.bg-secondary { background-color: #6c757d !important; }
|
|
.bg-success { background-color: #198754 !important; }
|
|
.bg-danger { background-color: #dc3545 !important; }
|
|
.bg-warning { background-color: #ffc107 !important; color: #212529 !important; }
|
|
.bg-info { background-color: #0dcaf0 !important; color: #212529 !important; }
|
|
.bg-dark { background-color: #212529 !important; }
|
|
.bg-purple { background-color: #6f42c1 !important; }
|
|
.bg-orange { background-color: #fd7e14 !important; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<div class="job-number">@job.JobNumber</div>
|
|
<div class="customer-name">
|
|
@if (!string.IsNullOrWhiteSpace(job.Customer?.CompanyName))
|
|
{ @job.Customer.CompanyName }
|
|
else
|
|
{ @($"{job.Customer?.ContactFirstName} {job.Customer?.ContactLastName}".Trim()) }
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
|
|
@if (TempData["StatusUpdated"] != null)
|
|
{
|
|
<div class="success-banner">
|
|
✓ Status updated successfully!
|
|
</div>
|
|
}
|
|
|
|
<div class="status-section">
|
|
<div class="status-label">Current Status</div>
|
|
<span class="current-status-badge bg-@job.JobStatus.ColorClass">
|
|
@job.JobStatus.DisplayName
|
|
</span>
|
|
@if (job.DueDate.HasValue)
|
|
{
|
|
<div style="margin-top: 10px; font-size: 13px; color: @(job.DueDate < DateTime.Today ? "#dc2626" : "#6b7280");">
|
|
Due: @job.DueDate.Value.ToString("MMM d, yyyy")
|
|
@if (job.DueDate < DateTime.Today && !isTerminal)
|
|
{
|
|
<strong style="color: #dc2626;"> — OVERDUE</strong>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="action-section">
|
|
@if (isTerminal)
|
|
{
|
|
<div class="terminal-msg">
|
|
This job is <strong>@job.JobStatus.DisplayName</strong> and requires no further updates.
|
|
</div>
|
|
}
|
|
else if (isOnHold)
|
|
{
|
|
@* On hold — offer resume (next logical status after resume by advancing) *@
|
|
@if (nextStatus != null)
|
|
{
|
|
<form method="post" asp-action="StatusBump" asp-route-id="@jobId">
|
|
@Html.AntiForgeryToken()
|
|
<input type="hidden" name="newStatusId" value="@nextStatus.Id" />
|
|
<button type="submit" class="btn-resume">
|
|
▶ Resume — Move to @nextStatus.DisplayName
|
|
</button>
|
|
</form>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
@* Advance to next step *@
|
|
@if (nextStatus != null)
|
|
{
|
|
<form method="post" asp-action="StatusBump" asp-route-id="@jobId">
|
|
@Html.AntiForgeryToken()
|
|
<input type="hidden" name="newStatusId" value="@nextStatus.Id" />
|
|
<button type="submit" class="btn-advance">
|
|
✓ Mark as @nextStatus.DisplayName
|
|
</button>
|
|
</form>
|
|
}
|
|
else
|
|
{
|
|
<div class="terminal-msg">No further stages available.</div>
|
|
}
|
|
|
|
@* On Hold option *@
|
|
@if (onHoldStatus != null)
|
|
{
|
|
<form method="post" asp-action="StatusBump" asp-route-id="@jobId">
|
|
@Html.AntiForgeryToken()
|
|
<input type="hidden" name="newStatusId" value="@onHoldStatus.Id" />
|
|
<button type="submit" class="btn-hold">
|
|
⏸ Put on Hold
|
|
</button>
|
|
</form>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
<div class="divider"></div>
|
|
<div class="meta-row">
|
|
<span>Job created @job.CreatedAt.ToString("MMM d, yyyy")</span>
|
|
<span>@job.JobPriority?.DisplayName Priority</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|