Files
PowderCoatingLogix/src/PowderCoating.Web/Views/Jobs/StatusBump.cshtml
T
spouliot a0bdd2b5b4 Sweep all .cshtml files for encoding corruption; add pre-commit guard
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>
2026-05-20 21:37:10 -04:00

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;"> &mdash; 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 &mdash; 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 &mdash; 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>