Add carried-over jobs section to Daily Board and fix tip visibility

Non-terminal jobs scheduled for past dates now appear in a red 'Carried
Over' section at the top of today's board so they can't silently disappear.
Also added alert-permanent to the board tip so the layout doesn't auto-dismiss it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-09 15:34:39 -04:00
parent bc9de38da3
commit 61866e1d1e
4 changed files with 168 additions and 1 deletions
@@ -107,6 +107,29 @@ public class JobsPriorityController : Controller
.ThenBy(m => m.ScheduledDate)
.ToList();
// Load overdue jobs only when viewing today — past-date navigation shows that day as-is
if (today == DateTime.Today)
{
var overdueJobs = await _unitOfWork.Jobs.GetOverdueScheduledJobsAsync();
ViewBag.OverdueJobs = overdueJobs.Select(j => new JobDailyPriorityDto
{
Id = 0,
JobId = j.Id,
JobNumber = j.JobNumber,
CustomerName = j.Customer.CompanyName ?? $"{j.Customer.ContactFirstName} {j.Customer.ContactLastName}".Trim(),
StatusDisplayName = j.JobStatus.DisplayName,
StatusColorClass = j.JobStatus.ColorClass,
JobPriorityId = j.JobPriorityId,
PriorityDisplayName = j.JobPriority.DisplayName,
PriorityColorClass = j.JobPriority.ColorClass,
AssignedUserId = j.AssignedUserId,
AssignedWorkerName = j.AssignedUser?.FullName,
ScheduledDate = j.ScheduledDate,
DueDate = j.DueDate,
DisplayOrder = int.MaxValue
}).ToList();
}
ViewBag.ScheduledDate = today;
ViewBag.MaintenanceItems = maintenanceItems;
ViewBag.PrioritiesJson = priorities.OrderBy(p => p.DisplayOrder)
@@ -1,4 +1,5 @@
@model IEnumerable<PowderCoating.Application.DTOs.Job.JobDailyPriorityDto>
@using PowderCoating.Application.DTOs.Job
@using PowderCoating.Core.Entities
@using PowderCoating.Core.Enums
@@ -9,6 +10,7 @@
ViewData["PageHelpContent"] = "Day-by-day view of jobs scheduled for shop work. Drag rows to reorder processing order. Click any Priority badge or Worker cell to quick-edit inline without leaving the page. Navigate between days with Previous/Next. Overdue due dates show in red. The Scheduled Maintenance section at the bottom shows equipment tasks due on the same day.";
var scheduledDate = ViewBag.ScheduledDate as DateTime? ?? DateTime.Today;
var maintenanceItems = ViewBag.MaintenanceItems as IEnumerable<MaintenanceRecord> ?? Enumerable.Empty<MaintenanceRecord>();
var overdueJobs = ViewBag.OverdueJobs as List<JobDailyPriorityDto> ?? new List<JobDailyPriorityDto>();
}
<div class="container-fluid">
@@ -49,6 +51,123 @@
</div>
</div>
@* ── Carried-Over (Overdue) Jobs ──────────────────────────────────────── *@
@if (overdueJobs.Any())
{
<div class="card border-danger mb-4">
<div class="card-header bg-danger text-white">
<div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center gap-2">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<strong>Carried Over — Not Yet Completed</strong>
<a tabindex="0" class="help-icon text-white opacity-75" role="button"
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
data-bs-title="Carried Over Jobs"
data-bs-content="These jobs were scheduled for a past date but have not reached a terminal status. Reschedule them using the Scheduled Date cell, or complete them to remove them from this list.">
<i class="bi bi-question-circle"></i>
</a>
</div>
<span class="badge bg-white text-danger">@overdueJobs.Count job@(overdueJobs.Count == 1 ? "" : "s")</span>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Job Number</th>
<th>Customer</th>
<th>Status</th>
<th>Priority</th>
<th>Assigned Worker</th>
<th>Scheduled Date</th>
<th>Due Date</th>
</tr>
</thead>
<tbody>
@foreach (var job in overdueJobs)
{
<tr>
<td class="clickable-cell" onclick="window.location='@Url.Action("Details", "Jobs", new { id = job.JobId })'">
<strong>@job.JobNumber</strong>
</td>
<td class="clickable-cell" onclick="window.location='@Url.Action("Details", "Jobs", new { id = job.JobId })'">
@job.CustomerName
</td>
<td class="clickable-cell" onclick="window.location='@Url.Action("Details", "Jobs", new { id = job.JobId })'">
<span class="badge bg-@job.StatusColorClass">@job.StatusDisplayName</span>
</td>
<td class="editable-cell"
onclick="event.stopPropagation(); openPriorityModal(@job.JobId, @job.JobPriorityId, '@job.JobNumber');"
style="cursor: pointer;" title="Click to change priority">
<span class="badge bg-@job.PriorityColorClass priority-badge-@job.JobId">
@job.PriorityDisplayName
<i class="bi bi-pencil-fill ms-1" style="font-size: 0.75rem;"></i>
</span>
</td>
<td class="editable-cell"
onclick="event.stopPropagation(); openWorkerModal(@job.JobId, '@(job.AssignedUserId ?? "")', '@job.JobNumber');"
style="cursor: pointer;" title="Click to assign worker">
<span class="worker-display-@job.JobId">
@if (!string.IsNullOrEmpty(job.AssignedWorkerName))
{
<span class="badge bg-info">
<i class="bi bi-person me-1"></i>@job.AssignedWorkerName
<i class="bi bi-pencil-fill ms-1" style="font-size: 0.75rem;"></i>
</span>
}
else
{
<span class="text-muted">Unassigned <i class="bi bi-pencil-fill ms-1" style="font-size: 0.75rem;"></i></span>
}
</span>
</td>
<td class="editable-cell"
onclick="event.stopPropagation(); openScheduledDateModal(@job.JobId, '@(job.ScheduledDate?.ToString("yyyy-MM-dd") ?? "")', '@job.JobNumber');"
style="cursor: pointer;" title="Click to reschedule">
<span class="scheduled-date-display-@job.JobId">
@if (job.ScheduledDate.HasValue)
{
<span class="text-danger fw-bold">
<i class="bi bi-calendar-x me-1"></i>
@job.ScheduledDate.Value.ToString("MMM dd, yyyy")
<i class="bi bi-pencil-fill ms-1" style="font-size: 0.75rem;"></i>
</span>
}
</span>
</td>
<td class="editable-cell"
onclick="event.stopPropagation(); openDueDateModal(@job.JobId, '@(job.DueDate?.ToString("yyyy-MM-dd") ?? "")', '@job.JobNumber');"
style="cursor: pointer;" title="Click to change due date">
<span class="due-date-display-@job.JobId">
@if (job.DueDate.HasValue)
{
var isOverdue = job.DueDate.Value.Date < DateTime.Today;
<span class="@(isOverdue ? "text-danger fw-bold" : "")">
<i class="bi bi-calendar-x me-1"></i>
@job.DueDate.Value.ToString("MMM dd, yyyy")
@if (isOverdue)
{
<i class="bi bi-exclamation-triangle-fill ms-1"></i>
}
<i class="bi bi-pencil-fill ms-1" style="font-size: 0.75rem;"></i>
</span>
}
else
{
<span class="text-muted">Not set <i class="bi bi-pencil-fill ms-1" style="font-size: 0.75rem;"></i></span>
}
</span>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
}
@if (!Model.Any())
{
<div class="alert alert-info">
@@ -194,7 +313,7 @@
</div>
<div class="mt-3">
<div class="alert alert-info border-info shadow-sm" style="position: sticky; bottom: 0; z-index: 1020; margin-bottom: 0;">
<div class="alert alert-info alert-permanent border-info shadow-sm" style="position: sticky; bottom: 0; z-index: 1020; margin-bottom: 0;">
<i class="bi bi-lightbulb me-2"></i>
<strong>Tip:</strong> Drag rows to reorder. Click priority or worker to quick edit. Click other cells to view job details.
</div>