Button consistency sweep + mobile responsiveness patches

- Standardize modal dismiss/cancel buttons to btn-outline-secondary across 70+ views
- Remove btn-sm from page-level Create and Back buttons (Index + Detail pages)
- Fix Edit buttons on Details pages: btn-secondary -> btn-warning
- Fix form Cancel/Back links: btn-secondary -> btn-outline-secondary
- Add 10 CSS patches to site.css for mobile/tablet responsiveness:
  top-navbar overflow prevention, page-header flex-wrap at 575px,
  table action button min-height override, notification dropdown width cap,
  tablet content padding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 19:04:10 -04:00
parent 328b195127
commit e2f9e9ae4f
71 changed files with 553 additions and 422 deletions
@@ -1,4 +1,4 @@
@using PowderCoating.Application.DTOs.Invoice
@using PowderCoating.Application.DTOs.Invoice
@using PowderCoating.Core.Enums
@using PowderCoating.Web.Controllers
@model InvoiceDto
@@ -69,7 +69,7 @@
<div class="alert alert-warning alert-permanent d-flex align-items-center gap-2 mb-4">
<i class="bi bi-envelope-slash fs-5"></i>
<span>
<strong>@Model.CustomerName</strong> has no email address on file — you'll be prompted to enter one when sending.
<strong>@Model.CustomerName</strong> has no email address on file you'll be prompted to enter one when sending.
<a asp-controller="Customers" asp-action="Edit" asp-route-id="@Model.CustomerId" class="alert-link">Add one in customer settings</a>.
</span>
</div>
@@ -168,12 +168,12 @@
<div class="col-md-4">
<label class="text-muted small mb-1">Due Date</label>
<p class="mb-0 @(Model.Status == InvoiceStatus.Overdue ? "text-danger fw-bold" : "")">
@(Model.DueDate.HasValue ? Model.DueDate.Value.ToString("MMMM d, yyyy") : "—")
@(Model.DueDate.HasValue ? Model.DueDate.Value.ToString("MMMM d, yyyy") : "")
</p>
</div>
<div class="col-md-4">
<label class="text-muted small mb-1">Sent Date</label>
<p class="mb-0">@(Model.SentDate.HasValue ? Model.SentDate.Value.ToString("MMMM d, yyyy") : "—")</p>
<p class="mb-0">@(Model.SentDate.HasValue ? Model.SentDate.Value.ToString("MMMM d, yyyy") : "")</p>
</div>
@if (!string.IsNullOrWhiteSpace(Model.CustomerPO))
{
@@ -339,7 +339,7 @@
</span>
</td>
<td class="text-muted">
@(gcItem.Description.Contains("for ") ? gcItem.Description.Substring(gcItem.Description.IndexOf("for ") + 4).TrimEnd(')') : "—")
@(gcItem.Description.Contains("for ") ? gcItem.Description.Substring(gcItem.Description.IndexOf("for ") + 4).TrimEnd(')') : "")
</td>
<td class="text-end fw-semibold">@gcItem.TotalPrice.ToString("C")</td>
<td>
@@ -385,7 +385,7 @@
<tr>
<td>@p.PaymentDate.ToString("MM/dd/yyyy")</td>
<td>@p.PaymentMethodDisplay</td>
<td>@(p.Reference ?? "—")</td>
<td>@(p.Reference ?? "")</td>
<td>
@if (!string.IsNullOrEmpty(p.DepositAccountName))
{
@@ -393,10 +393,10 @@
}
else
{
<span class="text-muted">—</span>
<span class="text-muted"></span>
}
</td>
<td>@(p.RecordedByName ?? "—")</td>
<td>@(p.RecordedByName ?? "")</td>
<td class="text-end fw-semibold text-success">@p.Amount.ToString("C")</td>
<td class="text-end">
@if (!isVoided)
@@ -452,7 +452,7 @@
<td>@r.RefundDate.ToString("MM/dd/yyyy")</td>
<td>@r.RefundMethodDisplay</td>
<td>@r.Reason</td>
<td>@(r.Reference ?? "—")</td>
<td>@(r.Reference ?? "")</td>
<td><span class="badge bg-@refundStatusColor">@r.Status</span></td>
<td class="text-end fw-semibold text-danger">(@r.Amount.ToString("C"))</td>
<td class="text-nowrap">
@@ -564,7 +564,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="left" data-bs-trigger="focus"
data-bs-title="Invoice Actions"
data-bs-content="Workflow: Edit (Draft only) → Send Invoice (locks it, emails customer) → Record Payment. Partial payments are supported — record multiple payments until fully paid. Void cancels the invoice and reverses the customer balance without deleting history. Delete is only available for Drafts.">
data-bs-content="Workflow: Edit (Draft only) Send Invoice (locks it, emails customer) Record Payment. Partial payments are supported record multiple payments until fully paid. Void cancels the invoice and reverses the customer balance without deleting history. Delete is only available for Drafts.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -761,7 +761,7 @@
{
<div class="mb-2">
<span class="badge bg-success-subtle text-success mb-2">
<i class="bi bi-check-circle me-1"></i>Active — expires @Model.PaymentLinkExpiresAt!.Value.ToString("MMM d")
<i class="bi bi-check-circle me-1"></i>Active expires @Model.PaymentLinkExpiresAt!.Value.ToString("MMM d")
</span>
<div class="input-group input-group-sm">
<input type="text" id="paymentLinkInput" class="form-control font-monospace"
@@ -899,7 +899,7 @@
<a tabindex="0" class="help-icon" role="button"
data-bs-toggle="popover" data-bs-placement="left" data-bs-trigger="focus"
data-bs-title="Payment Reference"
data-bs-content="Optional identifier for reconciliation — e.g., the check number, last 4 digits of the card, ACH transaction ID, or Venmo/PayPal confirmation code. Appears in payment history so you can match payments to your bank statement.">
data-bs-content="Optional identifier for reconciliation e.g., the check number, last 4 digits of the card, ACH transaction ID, or Venmo/PayPal confirmation code. Appears in payment history so you can match payments to your bank statement.">
<i class="bi bi-question-circle"></i>
</a>
</div>
@@ -1054,7 +1054,7 @@
</div>
</div>
<div class="modal-footer d-none" id="resendInvoiceFooter">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
@@ -1095,7 +1095,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
@@ -1118,7 +1118,7 @@
</div>
<div id="refundAlertCredit" class="alert alert-success small mb-3 d-none">
<i class="bi bi-piggy-bank me-1"></i>
The refund amount will be added to the customer's store credit balance immediately — no manual action needed.
The refund amount will be added to the customer's store credit balance immediately no manual action needed.
</div>
<div class="mb-3">
<label class="form-label fw-semibold">Amount <span class="text-danger">*</span></label>
@@ -1240,7 +1240,7 @@
<div class="mb-3">
<label class="form-label fw-semibold">Select Credit Memo <span class="text-danger">*</span></label>
<select name="CreditMemoId" class="form-select" required>
<option value="">— Select —</option>
<option value=""> Select </option>
@foreach (var item in (IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.AvailableCreditMemos)
{
<option value="@item.Value">@item.Text</option>
@@ -1254,7 +1254,7 @@
<input type="number" name="Amount" class="form-control" step="0.01" min="0.01"
max="@Model.BalanceDue.ToString("F2")" value="@Model.BalanceDue.ToString("F2")" required />
</div>
<div class="form-text">Balance due: @Model.BalanceDue.ToString("C") — the system will cap at the memo's remaining balance.</div>
<div class="form-text">Balance due: @Model.BalanceDue.ToString("C") the system will cap at the memo's remaining balance.</div>
</div>
</div>
<div class="modal-footer">
@@ -1336,7 +1336,7 @@
<div class="mb-3">
<label class="form-label">Bad Debt Expense Account</label>
<select name="expenseAccountId" class="form-select">
<option value="">— Use default bad debt account —</option>
<option value=""> Use default bad debt account </option>
@if (ViewBag.ExpenseAccounts != null)
{
@foreach (var item in (IEnumerable<Microsoft.AspNetCore.Mvc.Rendering.SelectListItem>)ViewBag.ExpenseAccounts)
@@ -1397,8 +1397,8 @@
const max = Math.min(data.remainingBalance, @Model.BalanceDue.ToString("F2", System.Globalization.CultureInfo.InvariantCulture));
document.getElementById('gcAmountInput').value = max.toFixed(2);
document.getElementById('gcAmountInput').max = max;
const expiry = data.expiryDate ? ` · Expires ${data.expiryDate}` : '';
result.innerHTML = `<div class="alert alert-success py-1 mb-0 small"><i class="bi bi-check-circle me-1"></i><strong>${data.certificateCode}</strong> — $${data.remainingBalance.toFixed(2)} remaining${expiry}</div>`;
const expiry = data.expiryDate ? ` · Expires ${data.expiryDate}` : '';
result.innerHTML = `<div class="alert alert-success py-1 mb-0 small"><i class="bi bi-check-circle me-1"></i><strong>${data.certificateCode}</strong> $${data.remainingBalance.toFixed(2)} remaining${expiry}</div>`;
}
} catch { result.innerHTML = '<div class="alert alert-danger py-1 mb-0 small">Lookup failed.</div>'; }
document.getElementById('gcLookupSpinner').style.display = 'none';
@@ -1511,7 +1511,7 @@
<td class="small">${escHtml(n.type.replace(/([A-Z])/g, ' $1').trim())}</td>
<td class="small"><i class="bi ${channelIcon} me-1"></i>${escHtml(n.channel)}</td>
<td class="small">${escHtml(n.recipientName)}<br><span class="text-muted">${escHtml(n.recipient)}</span></td>
<td class="small">${n.subject ? escHtml(n.subject) : '<span class="text-muted">—</span>'}</td>
<td class="small">${n.subject ? escHtml(n.subject) : '<span class="text-muted"></span>'}</td>
<td><span class="badge bg-${statusClass}">${escHtml(n.status)}</span>${expandBtn}</td>
</tr>${errorRow}`;
}).join('');