From 61866e1d1ef402da92ffa247afaa7ca7a1c95414 Mon Sep 17 00:00:00 2001 From: Scott Pouliot Date: Sat, 9 May 2026 15:34:39 -0400 Subject: [PATCH] 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 --- .../Interfaces/Repositories/IJobRepository.cs | 7 + .../Repositories/JobRepository.cs | 18 +++ .../Controllers/JobsPriorityController.cs | 23 ++++ .../Views/JobsPriority/Index.cshtml | 121 +++++++++++++++++- 4 files changed, 168 insertions(+), 1 deletion(-) diff --git a/src/PowderCoating.Core/Interfaces/Repositories/IJobRepository.cs b/src/PowderCoating.Core/Interfaces/Repositories/IJobRepository.cs index 4401b74..b941dfd 100644 --- a/src/PowderCoating.Core/Interfaces/Repositories/IJobRepository.cs +++ b/src/PowderCoating.Core/Interfaces/Repositories/IJobRepository.cs @@ -85,4 +85,11 @@ public interface IJobRepository : IRepository /// Returns null if not found or soft-deleted. /// Task LoadForTemplateSnapshotAsync(int jobId); + + /// + /// Returns all non-terminal jobs whose ScheduledDate is before today and not null, + /// ordered by scheduled date then job number. Used by the Daily Board to surface jobs that + /// were never completed and rolled past their scheduled day. + /// + Task> GetOverdueScheduledJobsAsync(); } diff --git a/src/PowderCoating.Infrastructure/Repositories/JobRepository.cs b/src/PowderCoating.Infrastructure/Repositories/JobRepository.cs index 08ffe70..9a41cc8 100644 --- a/src/PowderCoating.Infrastructure/Repositories/JobRepository.cs +++ b/src/PowderCoating.Infrastructure/Repositories/JobRepository.cs @@ -187,4 +187,22 @@ public class JobRepository : Repository, IJobRepository .ThenInclude(i => i.PrepServices.Where(p => !p.IsDeleted)) .FirstOrDefaultAsync(); } + + /// + public async Task> GetOverdueScheduledJobsAsync() + { + var today = DateTime.Today; + return await _context.Jobs + .Include(j => j.Customer) + .Include(j => j.JobStatus) + .Include(j => j.JobPriority) + .Include(j => j.AssignedUser) + .Where(j => j.ScheduledDate.HasValue + && j.ScheduledDate.Value.Date < today + && !j.IsDeleted + && !j.JobStatus.IsTerminalStatus) + .OrderBy(j => j.ScheduledDate) + .ThenBy(j => j.JobNumber) + .ToListAsync(); + } } diff --git a/src/PowderCoating.Web/Controllers/JobsPriorityController.cs b/src/PowderCoating.Web/Controllers/JobsPriorityController.cs index 1756b55..f4b6957 100644 --- a/src/PowderCoating.Web/Controllers/JobsPriorityController.cs +++ b/src/PowderCoating.Web/Controllers/JobsPriorityController.cs @@ -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) diff --git a/src/PowderCoating.Web/Views/JobsPriority/Index.cshtml b/src/PowderCoating.Web/Views/JobsPriority/Index.cshtml index afc3eb2..43d6663 100644 --- a/src/PowderCoating.Web/Views/JobsPriority/Index.cshtml +++ b/src/PowderCoating.Web/Views/JobsPriority/Index.cshtml @@ -1,4 +1,5 @@ @model IEnumerable +@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 ?? Enumerable.Empty(); + var overdueJobs = ViewBag.OverdueJobs as List ?? new List(); }
@@ -49,6 +51,123 @@
+ @* ── Carried-Over (Overdue) Jobs ──────────────────────────────────────── *@ + @if (overdueJobs.Any()) + { +
+
+
+
+ + Carried Over — Not Yet Completed + + + +
+ @overdueJobs.Count job@(overdueJobs.Count == 1 ? "" : "s") +
+
+
+
+ + + + + + + + + + + + + + @foreach (var job in overdueJobs) + { + + + + + + + + + + } + +
Job NumberCustomerStatusPriorityAssigned WorkerScheduled DateDue Date
+ @job.JobNumber + + @job.CustomerName + + @job.StatusDisplayName + + + @job.PriorityDisplayName + + + + + @if (!string.IsNullOrEmpty(job.AssignedWorkerName)) + { + + @job.AssignedWorkerName + + + } + else + { + Unassigned + } + + + + @if (job.ScheduledDate.HasValue) + { + + + @job.ScheduledDate.Value.ToString("MMM dd, yyyy") + + + } + + + + @if (job.DueDate.HasValue) + { + var isOverdue = job.DueDate.Value.Date < DateTime.Today; + + + @job.DueDate.Value.ToString("MMM dd, yyyy") + @if (isOverdue) + { + + } + + + } + else + { + Not set + } + +
+
+
+
+ } + @if (!Model.Any()) {
@@ -194,7 +313,7 @@
-
+
Tip: Drag rows to reorder. Click priority or worker to quick edit. Click other cells to view job details.