From 24f3df1bbcc220bb1299d3840666c0f61a5844b3 Mon Sep 17 00:00:00 2001
From: Scott Pouliot
Date: Tue, 19 May 2026 20:11:33 -0400
Subject: [PATCH] Jobs list defaults to On Floor; add Completed filter pill;
fix encoding bugs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- /Jobs now redirects to ?statusGroup=active so completed jobs don't clutter the default view
- Add Completed pill (filters Completed + ReadyForPickup + Delivered)
- Pill badge counts are now global DB counts, not page-local item counts
- Ready pill badge now shows ReadyForPickup-only count
- All pill links to ?statusGroup=all to bypass the redirect
- Fix double-encoded & in Completed filter alert label
- Fix corrupted em dash (â€") in Customers/Details billing email fallback text
Co-Authored-By: Claude Sonnet 4.6
---
.../Controllers/JobsController.cs | 33 +++++++++++++++++
.../Views/Customers/Details.cshtml | 2 +-
src/PowderCoating.Web/Views/Jobs/Index.cshtml | 36 +++++++++++--------
3 files changed, 55 insertions(+), 16 deletions(-)
diff --git a/src/PowderCoating.Web/Controllers/JobsController.cs b/src/PowderCoating.Web/Controllers/JobsController.cs
index 393ae28..a09a1e0 100644
--- a/src/PowderCoating.Web/Controllers/JobsController.cs
+++ b/src/PowderCoating.Web/Controllers/JobsController.cs
@@ -110,6 +110,11 @@ public class JobsController : Controller
{
try
{
+ // Default landing view: On Floor — redirect bare /Jobs to ?statusGroup=active
+ // so completed/cancelled jobs don't clutter the first screen.
+ if (string.IsNullOrEmpty(statusGroup) && string.IsNullOrEmpty(searchTerm) && string.IsNullOrEmpty(tagFilter))
+ return RedirectToAction("Index", new { statusGroup = "active" });
+
// Create and validate grid request
var gridRequest = new GridRequest
{
@@ -141,6 +146,13 @@ public class JobsController : Controller
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Delivered
&& j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Cancelled;
}
+ else if (statusGroup == "completed")
+ {
+ filter = j => j.JobStatus.StatusCode == AppConstants.StatusCodes.Job.Completed
+ || j.JobStatus.StatusCode == AppConstants.StatusCodes.Job.ReadyForPickup
+ || j.JobStatus.StatusCode == AppConstants.StatusCodes.Job.Delivered;
+ }
+ // "all" or unknown group: no filter applied (show every status)
}
else if (!string.IsNullOrWhiteSpace(searchTerm))
{
@@ -195,6 +207,27 @@ public class JobsController : Controller
gridRequest, jobDtos,
string.IsNullOrWhiteSpace(tagFilter) ? totalCount : jobDtos.Count);
+ // Pill badge counts — always global (not scoped to current filter/page)
+ var today = DateTime.Today;
+ ViewBag.AllJobCount = await _unitOfWork.Jobs.CountAsync();
+ ViewBag.ActiveCount = await _unitOfWork.Jobs.CountAsync(j =>
+ j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Completed
+ && j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.ReadyForPickup
+ && j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Delivered
+ && j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Cancelled);
+ ViewBag.OverdueCount = await _unitOfWork.Jobs.CountAsync(j =>
+ j.DueDate < today
+ && j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Completed
+ && j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.ReadyForPickup
+ && j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Delivered
+ && j.JobStatus.StatusCode != AppConstants.StatusCodes.Job.Cancelled);
+ ViewBag.CompletedCount = await _unitOfWork.Jobs.CountAsync(j =>
+ j.JobStatus.StatusCode == AppConstants.StatusCodes.Job.Completed
+ || j.JobStatus.StatusCode == AppConstants.StatusCodes.Job.ReadyForPickup
+ || j.JobStatus.StatusCode == AppConstants.StatusCodes.Job.Delivered);
+ ViewBag.ReadyCount = await _unitOfWork.Jobs.CountAsync(j =>
+ j.JobStatus.StatusCode == AppConstants.StatusCodes.Job.ReadyForPickup);
+
// Set ViewBag for sorting
ViewBag.SearchTerm = searchTerm;
ViewBag.StatusGroup = statusGroup;
diff --git a/src/PowderCoating.Web/Views/Customers/Details.cshtml b/src/PowderCoating.Web/Views/Customers/Details.cshtml
index 0c641e4..8ee1522 100644
--- a/src/PowderCoating.Web/Views/Customers/Details.cshtml
+++ b/src/PowderCoating.Web/Views/Customers/Details.cshtml
@@ -123,7 +123,7 @@
}
else
{
- Not set — invoices go to contact email
+ Not set — invoices go to contact email
}
diff --git a/src/PowderCoating.Web/Views/Jobs/Index.cshtml b/src/PowderCoating.Web/Views/Jobs/Index.cshtml
index 6d3e6d5..6be0d17 100644
--- a/src/PowderCoating.Web/Views/Jobs/Index.cshtml
+++ b/src/PowderCoating.Web/Views/Jobs/Index.cshtml
@@ -8,10 +8,12 @@
}
@{
- var _wip = Model.Items.Count(j => j.StatusIsWIP);
- var _done = Model.Items.Count(j => j.StatusCode == "COMPLETED" || j.StatusCode == "READYFORPICKUP" || j.StatusCode == "DELIVERED");
- var _overdue = Model.Items.Count(j => j.DueDate.HasValue && j.DueDate.Value < DateTime.Now && j.StatusCode != "COMPLETED" && j.StatusCode != "READYFORPICKUP" && j.StatusCode != "DELIVERED" && j.StatusCode != "CANCELLED");
- var _value = Model.Items.Sum(j => j.FinalPrice);
+ var _allCount = (int)(ViewBag.AllJobCount ?? 0);
+ var _wip = (int)(ViewBag.ActiveCount ?? 0);
+ var _done = (int)(ViewBag.CompletedCount ?? 0);
+ var _ready = (int)(ViewBag.ReadyCount ?? 0);
+ var _overdue = (int)(ViewBag.OverdueCount ?? 0);
+ var _value = Model.Items.Sum(j => j.FinalPrice);
}
@@ -39,23 +41,23 @@
Showing @Model.TotalCount job(s) matching "@ViewBag.SearchTerm"
(searches job number, description, customer, PO, instructions, status, priority)
-
+
Clear Filter
}
-@if (!string.IsNullOrEmpty(ViewBag.StatusGroup as string))
+@if (!string.IsNullOrEmpty(ViewBag.StatusGroup as string) && ViewBag.StatusGroup != "active" && ViewBag.StatusGroup != "all")
{
- var groupLabel = ViewBag.StatusGroup == "active" ? "Active Jobs (excluding completed & cancelled)"
- : ViewBag.StatusGroup == "overdue" ? "Overdue Jobs (past due date)"
- : ViewBag.StatusGroup;
+ var groupLabel = ViewBag.StatusGroup == "overdue" ? "Overdue Jobs (past due date)"
+ : ViewBag.StatusGroup == "completed" ? "Completed Jobs (completed, ready for pickup & delivered)"
+ : (string)ViewBag.StatusGroup;
Showing: @groupLabel — @Model.TotalCount result@(Model.TotalCount == 1 ? "" : "s")
-
- Show All
+
+ Back to On Floor
}
@@ -63,7 +65,8 @@
@{
var _activeGroup = ViewBag.StatusGroup as string;
var _activeSearch = ViewBag.SearchTerm as string;
- var _noFilter = string.IsNullOrEmpty(_activeGroup) && string.IsNullOrEmpty(_activeSearch) && string.IsNullOrEmpty(ViewBag.TagFilter as string);
+ // "all" is the explicit show-everything group (bare URL now redirects to "active")
+ var _noFilter = _activeGroup == "all" && string.IsNullOrEmpty(_activeSearch) && string.IsNullOrEmpty(ViewBag.TagFilter as string);
}
@@ -135,8 +138,8 @@