Files
PowderCoatingLogix/src/PowderCoating.Web/Views/BugReport/Index.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

365 lines
20 KiB
Plaintext

@model List<PowderCoating.Application.DTOs.BugReport.BugReportDto>
@using PowderCoating.Core.Enums
@{
ViewData["Title"] = "Bug Reports";
ViewData["PageIcon"] = "bi-bug";
var sortCol = ViewBag.SortColumn as string ?? "CreatedAt";
var sortDir = ViewBag.SortDirection as string ?? "desc";
int totalCount = ViewBag.TotalCount ?? 0;
int pageNumber = ViewBag.PageNumber ?? 1;
int pageSize = ViewBag.PageSize ?? 25;
int totalPages = ViewBag.TotalPages ?? 1;
string NextDir(string col) => sortCol == col && sortDir == "asc" ? "desc" : "asc";
string SortIcon(string col) => sortCol == col
? (sortDir == "asc" ? "bi-sort-up" : "bi-sort-down")
: "bi-arrow-down-up text-muted";
}
@section Styles {
<style>
[data-bs-theme="dark"] .table-light th,
[data-bs-theme="dark"] .table-light td {
background-color: var(--bs-secondary-bg);
color: var(--bs-body-color);
}
[data-bs-theme="dark"] .card {
border-color: var(--bs-border-color) !important;
}
[data-bs-theme="dark"] .sort-link {
color: var(--bs-body-color) !important;
}
[data-bs-theme="dark"] .border-top {
border-color: var(--bs-border-color) !important;
}
[data-bs-theme="dark"] .pagination .page-link {
background-color: var(--bs-body-bg);
border-color: var(--bs-border-color);
color: var(--bs-body-color);
}
[data-bs-theme="dark"] .pagination .page-item.active .page-link {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
color: #fff;
}
</style>
}
<div class="container-fluid">
<div class="mb-4"></div>
@if (TempData["SuccessMessage"] != null)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="bi bi-check-circle"></i> @TempData["SuccessMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
@if (TempData["ErrorMessage"] != null)
{
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<i class="bi bi-exclamation-triangle"></i> @TempData["ErrorMessage"]
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
}
<!-- Filters -->
<div class="card mb-3">
<div class="card-body py-2">
<form method="get" class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label small mb-1">Search</label>
<input type="text" name="searchTerm" value="@ViewBag.SearchTerm" class="form-control form-control-sm"
placeholder="Title, description, or submitter..." />
</div>
<div class="col-md-2">
<label class="form-label small mb-1">Status</label>
<select name="statusFilter" class="form-select form-select-sm">
<option value="">All Statuses</option>
@foreach (var s in Enum.GetValues<BugReportStatus>())
{
<option value="@s" selected="@(ViewBag.StatusFilter == s.ToString())">@s.ToString()</option>
}
</select>
</div>
<div class="col-md-2">
<label class="form-label small mb-1">Priority</label>
<select name="priorityFilter" class="form-select form-select-sm">
<option value="">All Priorities</option>
@foreach (var p in Enum.GetValues<BugReportPriority>())
{
<option value="@p" selected="@(ViewBag.PriorityFilter == p.ToString())">@p.ToString()</option>
}
</select>
</div>
<div class="col-md-2">
<label class="form-label small mb-1">Per page</label>
<select name="pageSize" class="form-select form-select-sm">
@foreach (var n in new[] { 10, 25, 50, 100 })
{
<option value="@n" selected="@(pageSize == n)">@n</option>
}
</select>
</div>
<input type="hidden" name="sortColumn" value="@sortCol" />
<input type="hidden" name="sortDirection" value="@sortDir" />
<div class="col-md-2">
<button type="submit" class="btn btn-primary btn-sm w-100">
<i class="bi bi-search"></i> Filter
</button>
</div>
</form>
</div>
</div>
<!-- Table -->
<div class="card">
<div class="card-body p-0">
@if (!Model.Any())
{
<div class="text-center py-5 text-muted">
<i class="bi bi-check-circle display-4 d-block mb-2"></i>
<p>No bug reports found.</p>
</div>
}
else
{
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="Title"
asp-route-sortDirection="@NextDir("Title")"
class="text-decoration-none sort-link">
Title <i class="bi @SortIcon("Title")"></i>
</a>
</th>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="Priority"
asp-route-sortDirection="@NextDir("Priority")"
class="text-decoration-none sort-link">
Priority <i class="bi @SortIcon("Priority")"></i>
</a>
</th>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="Status"
asp-route-sortDirection="@NextDir("Status")"
class="text-decoration-none sort-link">
Status <i class="bi @SortIcon("Status")"></i>
</a>
</th>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="Submitted"
asp-route-sortDirection="@NextDir("Submitted")"
class="text-decoration-none sort-link">
Submitted By <i class="bi @SortIcon("Submitted")"></i>
</a>
</th>
<th>Company</th>
<th>
<a asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-pageSize="@pageSize"
asp-route-sortColumn="CreatedAt"
asp-route-sortDirection="@NextDir("CreatedAt")"
class="text-decoration-none sort-link">
Submitted <i class="bi @SortIcon("CreatedAt")"></i>
</a>
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var report in Model)
{
<tr style="cursor:pointer" onclick="window.location='@Url.Action("Edit", new { id = report.Id })'">
<td>
<div class="fw-semibold">@report.Title</div>
<div class="text-muted small text-truncate" style="max-width:320px;" title="@report.Description">
@report.Description
</div>
</td>
<td>
@{
var priClass = report.Priority switch
{
BugReportPriority.Critical => "bg-danger",
BugReportPriority.High => "bg-warning text-dark",
BugReportPriority.Normal => "bg-primary",
_ => "bg-secondary"
};
}
<span class="badge @priClass">@report.Priority</span>
</td>
<td>
@{
var statusClass = report.Status switch
{
BugReportStatus.New => "bg-info text-dark",
BugReportStatus.InProgress => "bg-warning text-dark",
BugReportStatus.Completed => "bg-success",
BugReportStatus.Cancelled => "bg-secondary",
_ => "bg-secondary"
};
var statusLabel = report.Status switch
{
BugReportStatus.InProgress => "In Progress",
_ => report.Status.ToString()
};
}
<span class="badge @statusClass">@statusLabel</span>
</td>
<td class="small">@report.SubmittedByUserName</td>
<td class="small text-muted">@report.CompanyId</td>
<td class="small text-muted text-nowrap">@report.CreatedAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yyyy h:mm tt")</td>
<td onclick="event.stopPropagation()">
<a asp-action="Edit" asp-route-id="@report.Id" class="btn btn-sm btn-outline-primary">
<i class="bi bi-pencil"></i> Edit
</a>
</td>
</tr>
}
</tbody>
</table>
</div>
<!-- Mobile card view &mdash; shown on screens < 992px -->
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var report in Model)
{
var mPriClass = report.Priority switch
{
BugReportPriority.Critical => "bg-danger",
BugReportPriority.High => "bg-warning text-dark",
BugReportPriority.Normal => "bg-primary",
_ => "bg-secondary"
};
var mStatusClass = report.Status switch
{
BugReportStatus.New => "bg-info text-dark",
BugReportStatus.InProgress => "bg-warning text-dark",
BugReportStatus.Completed => "bg-success",
BugReportStatus.Cancelled => "bg-secondary",
_ => "bg-secondary"
};
var mStatusLabel = report.Status switch
{
BugReportStatus.InProgress => "In Progress",
_ => report.Status.ToString()
};
<a href="@Url.Action("Edit", new { id = report.Id })" class="mobile-data-card text-decoration-none">
<div class="mobile-card-header">
<div class="mobile-card-icon bg-danger"><i class="bi bi-bug"></i></div>
<div class="mobile-card-title">
<h6>@report.Title</h6>
<small>@report.CreatedAt.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yyyy h:mm tt")</small>
</div>
</div>
<div class="mobile-card-body">
<div class="mobile-card-row">
<span class="mobile-card-label">Status</span>
<span class="mobile-card-value"><span class="badge @mStatusClass">@mStatusLabel</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Priority</span>
<span class="mobile-card-value"><span class="badge @mPriClass">@report.Priority</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Reporter</span>
<span class="mobile-card-value">@report.SubmittedByUserName</span>
</div>
@if (report.CompanyId > 0)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Company ID</span>
<span class="mobile-card-value">@report.CompanyId</span>
</div>
}
</div>
<div class="mobile-card-footer">
<span class="btn btn-sm btn-outline-primary">Edit →</span>
</div>
</a>
}
</div>
</div>
<!-- Pagination -->
@if (totalPages > 1)
{
<div class="d-flex justify-content-between align-items-center px-3 py-2 border-top">
<small class="text-muted">
Showing @((pageNumber - 1) * pageSize + 1)&ndash;@(Math.Min(pageNumber * pageSize, totalCount)) of @totalCount
</small>
<nav>
<ul class="pagination pagination-sm mb-0">
<li class="page-item @(pageNumber <= 1 ? "disabled" : "")">
<a class="page-link" asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-sortColumn="@sortCol"
asp-route-sortDirection="@sortDir"
asp-route-pageSize="@pageSize"
asp-route-pageNumber="@(pageNumber - 1)">
<i class="bi bi-chevron-left"></i>
</a>
</li>
@for (int i = Math.Max(1, pageNumber - 2); i <= Math.Min(totalPages, pageNumber + 2); i++)
{
<li class="page-item @(i == pageNumber ? "active" : "")">
<a class="page-link" asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-sortColumn="@sortCol"
asp-route-sortDirection="@sortDir"
asp-route-pageSize="@pageSize"
asp-route-pageNumber="@i">@i</a>
</li>
}
<li class="page-item @(pageNumber >= totalPages ? "disabled" : "")">
<a class="page-link" asp-action="Index"
asp-route-searchTerm="@ViewBag.SearchTerm"
asp-route-statusFilter="@ViewBag.StatusFilter"
asp-route-priorityFilter="@ViewBag.PriorityFilter"
asp-route-sortColumn="@sortCol"
asp-route-sortDirection="@sortDir"
asp-route-pageSize="@pageSize"
asp-route-pageNumber="@(pageNumber + 1)">
<i class="bi bi-chevron-right"></i>
</a>
</li>
</ul>
</nav>
</div>
}
}
</div>
</div>
</div>