Design consistency audit fixes: alerts, cards, dark mode, utilities

Alert sweep (113 alerts, 79 files):
  All persistent static banners now carry alert-permanent so the
  layout's 5-second auto-dismiss cannot swallow guidance, warnings,
  or validation errors. Transient dismissible toasts left untouched.

CSS fixes (site.css):
  .card.shadow-sm      — strips rogue border from ~40 drifted cards
  .card-header.bg-white — rebinds to var(--bs-body-bg) so card
                          headers follow dark/light theme correctly
  Typography utilities  — .text-2xs (.68rem), .text-xs (.73rem)
  Token color classes   — .text-ember, .text-ok, .text-bad,
                          .text-warn, .text-cool, .bg-paper-2
  Layout utilities      — .mw-xs/sm/md/lg replace inline max-width
  Comment              — documents text-ember vs text-primary intent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 18:05:29 -04:00
parent f6d457fe0e
commit 328b195127
80 changed files with 603 additions and 561 deletions
@@ -1,4 +1,4 @@
@using PowderCoating.Web.Controllers
@using PowderCoating.Web.Controllers
@model List<EntityPurgeStat>
@{
ViewData["Title"] = "Data Purge & Cleanup";
@@ -56,10 +56,10 @@
}
@* Warning banner *@
<div class="alert alert-warning d-flex gap-3 align-items-start mb-3">
<div class="alert alert-warning alert-permanent d-flex gap-3 align-items-start mb-3">
<i class="bi bi-exclamation-triangle-fill fs-4 flex-shrink-0 mt-1"></i>
<div>
<strong>Destructive operation this cannot be undone.</strong>
<strong>Destructive operation — this cannot be undone.</strong>
Purging permanently deletes records from the database. Soft-deleted records are hidden from users but still occupy database space. Use this tool periodically to reclaim space and keep the database clean.
Job photo blobs in Azure Storage are also deleted when purging job photo records.
</div>
@@ -83,8 +83,8 @@
<th style="width:36px"></th>
<th>Entity</th>
<th class="text-end" style="width:90px">Total</th>
<th class="text-end" style="width:100px">030d</th>
<th class="text-end" style="width:100px">3090d</th>
<th class="text-end" style="width:100px">0–30d</th>
<th class="text-end" style="width:100px">30–90d</th>
<th class="text-end" style="width:100px">&gt;90d</th>
<th style="width:130px">Oldest</th>
<th style="width:42px">
@@ -109,7 +109,7 @@
}
else
{
<span class="text-muted"></span>
<span class="text-muted">—</span>
}
</td>
<td class="text-end">
@@ -117,24 +117,24 @@
{
<span class="badge bg-success-subtle text-success">@s.DeletedLast30Days</span>
}
else { <span class="text-muted"></span> }
else { <span class="text-muted">—</span> }
</td>
<td class="text-end">
@if (s.Deleted30To90Days > 0)
{
<span class="badge bg-warning-subtle text-warning">@s.Deleted30To90Days</span>
}
else { <span class="text-muted"></span> }
else { <span class="text-muted">—</span> }
</td>
<td class="text-end">
@if (s.DeletedOlderThan90Days > 0)
{
<span class="badge bg-danger-subtle text-danger">@s.DeletedOlderThan90Days</span>
}
else { <span class="text-muted"></span> }
else { <span class="text-muted">—</span> }
</td>
<td class="text-muted">
@(s.OldestDeletion.HasValue ? s.OldestDeletion.Value.ToString("MM/dd/yyyy") : "")
@(s.OldestDeletion.HasValue ? s.OldestDeletion.Value.ToString("MM/dd/yyyy") : "—")
</td>
<td class="text-center">
<input type="checkbox" class="form-check-input entity-select"
@@ -149,7 +149,7 @@
</table>
</div>
<!-- Mobile card view for this group shown on screens < 992px -->
<!-- Mobile card view for this group — shown on screens < 992px -->
<div class="mobile-card-view">
<div class="px-3 pt-2 pb-1">
<span class="text-muted text-uppercase fw-semibold" style="font-size:0.7rem;letter-spacing:.05em">@group.Key</span>
@@ -164,7 +164,7 @@
</div>
<div class="mobile-card-title">
<h6>@s.Label</h6>
<small>Oldest: @(s.OldestDeletion.HasValue ? s.OldestDeletion.Value.ToString("MM/dd/yyyy") : "")</small>
<small>Oldest: @(s.OldestDeletion.HasValue ? s.OldestDeletion.Value.ToString("MM/dd/yyyy") : "—")</small>
</div>
</div>
<div class="mobile-card-body">
@@ -175,11 +175,11 @@
{
<span class="badge bg-secondary">@s.Total</span>
}
else { <span class="text-muted"></span> }
else { <span class="text-muted">—</span> }
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">030d / 3090d / &gt;90d</span>
<span class="mobile-card-label">0–30d / 30–90d / &gt;90d</span>
<span class="mobile-card-value">@s.DeletedLast30Days / @s.Deleted30To90Days / @s.DeletedOlderThan90Days</span>
</div>
</div>
@@ -300,7 +300,7 @@
const confirmModal = new bootstrap.Modal(document.getElementById('confirmModal'));
const confirmSummary= document.getElementById('confirmSummary');
// ── Select all ──────────────────────────────────────────────────────────
// ── Select all ──────────────────────────────────────────────────────────
selectAll.addEventListener('change', () => {
document.querySelectorAll('.entity-select:not(:disabled)').forEach(cb => {
cb.checked = selectAll.checked;
@@ -308,7 +308,7 @@
updatePurgeBtn();
});
// ── Group select all ────────────────────────────────────────────────────
// ── Group select all ────────────────────────────────────────────────────
document.querySelectorAll('.group-select-all').forEach(ga => {
ga.addEventListener('change', () => {
document.querySelectorAll(`.entity-select[data-group="${ga.dataset.group}"]:not(:disabled)`)
@@ -335,7 +335,7 @@
previewRes.classList.add('d-none');
}
// ── Preview ─────────────────────────────────────────────────────────────
// ── Preview ─────────────────────────────────────────────────────────────
previewBtn.addEventListener('click', async () => {
const entities = getSelectedEntities();
if (!entities.length) {
@@ -344,7 +344,7 @@
}
previewBtn.disabled = true;
previewBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Loading';
previewBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Loading…';
const days = document.getElementById('olderThanDays').value;
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
@@ -379,7 +379,7 @@
}
});
// ── Purge button modal ────────────────────────────────────────────────
// ── Purge button → modal ────────────────────────────────────────────────
purgeBtn.addEventListener('click', () => {
const entities = getSelectedEntities();
const days = document.getElementById('olderThanDays').value;
@@ -390,7 +390,7 @@
confirmModal.show();
});
// ── Confirm submit form ───────────────────────────────────────────────
// ── Confirm → submit form ───────────────────────────────────────────────
document.getElementById('confirmPurgeBtn').addEventListener('click', () => {
const entities = getSelectedEntities();
const days = document.getElementById('olderThanDays').value;