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:
@@ -1,4 +1,4 @@
|
||||
@using PowderCoating.Application.DTOs.Invoice
|
||||
@using PowderCoating.Application.DTOs.Invoice
|
||||
@using PowderCoating.Core.Enums
|
||||
@using PowderCoating.Web.Controllers
|
||||
@model InvoiceDto
|
||||
@@ -66,17 +66,17 @@
|
||||
|
||||
@if (!hasEmail)
|
||||
{
|
||||
<div class="alert alert-warning d-flex align-items-center gap-2 mb-4">
|
||||
<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>
|
||||
}
|
||||
else if (emailOptedOut)
|
||||
{
|
||||
<div class="alert alert-warning d-flex align-items-center gap-2 mb-4">
|
||||
<div class="alert alert-warning alert-permanent d-flex align-items-center gap-2 mb-4">
|
||||
<i class="bi bi-envelope-x fs-5"></i>
|
||||
<span>
|
||||
<strong>@Model.CustomerName</strong> has email notifications turned off.
|
||||
@@ -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"
|
||||
@@ -784,7 +784,7 @@
|
||||
}
|
||||
else if (linkExpired)
|
||||
{
|
||||
<div class="alert alert-warning py-2 small mb-2">
|
||||
<div class="alert alert-warning alert-permanent py-2 small mb-2">
|
||||
<i class="bi bi-clock me-1"></i>Payment link expired.
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm w-100"
|
||||
@@ -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>
|
||||
@@ -1112,13 +1112,13 @@
|
||||
<form asp-action="IssueRefund" asp-route-invoiceId="@Model.Id" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="modal-body">
|
||||
<div id="refundAlertCash" class="alert alert-info small mb-3">
|
||||
<div id="refundAlertCash" class="alert alert-info alert-permanent small mb-3">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
This records the refund intent. You still need to issue the actual refund (cash, check, etc.) manually.
|
||||
</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>
|
||||
@@ -1188,7 +1188,7 @@
|
||||
<form asp-action="IssueCreditMemo" asp-route-invoiceId="@Model.Id" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info small mb-3">
|
||||
<div class="alert alert-info alert-permanent small mb-3">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
A credit memo adds store credit to the customer's account that can be applied against future invoices.
|
||||
</div>
|
||||
@@ -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">
|
||||
@@ -1328,7 +1328,7 @@
|
||||
<form asp-action="WriteOff" asp-route-id="@Model.Id" method="post">
|
||||
@Html.AntiForgeryToken()
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning py-2 mb-3">
|
||||
<div class="alert alert-warning alert-permanent py-2 mb-3">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
This will write off the remaining balance of <strong>@Model.BalanceDue.ToString("C")</strong>
|
||||
as bad debt. A GL journal entry will be posted. This action cannot be undone.
|
||||
@@ -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('');
|
||||
@@ -1555,7 +1555,7 @@
|
||||
const data = await resp.json();
|
||||
if (data.success) {
|
||||
if (msg) msg.innerHTML = `
|
||||
<div class="alert alert-success py-2 small">
|
||||
<div class="alert alert-success alert-permanent py-2 small">
|
||||
<i class="bi bi-check-circle me-1"></i>New link generated!
|
||||
<a href="${data.paymentUrl}" target="_blank" class="alert-link ms-1">Open</a>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@model PowderCoating.Application.DTOs.Invoice.UpdateInvoiceDto
|
||||
@model PowderCoating.Application.DTOs.Invoice.UpdateInvoiceDto
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Edit Invoice";
|
||||
@@ -38,7 +38,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Invoice Details"
|
||||
data-bs-content="Invoice Date is the date of issue and the reference for payment terms. Due Date drives overdue status and A/R aging. Payment Terms prints on the invoice — changing it here only affects this invoice. Draft, Sent, and Overdue invoices can be edited; Paid and Partially Paid invoices are locked.">
|
||||
data-bs-content="Invoice Date is the date of issue and the reference for payment terms. Due Date drives overdue status and A/R aging. Payment Terms prints on the invoice — changing it here only affects this invoice. Draft, Sent, and Overdue invoices can be edited; Paid and Partially Paid invoices are locked.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -79,7 +79,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Line Items"
|
||||
data-bs-content="Each row is a billable line on the invoice. Qty × Unit Price = Total per line; you can also override Total directly. Color is optional and appears under the description when printed. Add manual lines for charges not in the original job (e.g., rush fee, pickup charge).">
|
||||
data-bs-content="Each row is a billable line on the invoice. Qty × Unit Price = Total per line; you can also override Total directly. Color is optional and appears under the description when printed. Add manual lines for charges not in the original job (e.g., rush fee, pickup charge).">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -163,7 +163,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Notes"
|
||||
data-bs-content="Customer Notes appear on the printed and emailed invoice — use these for payment instructions, thank-you messages, or job-specific reminders. Internal Notes are only visible to staff in the app and are never sent to the customer.">
|
||||
data-bs-content="Customer Notes appear on the printed and emailed invoice — use these for payment instructions, thank-you messages, or job-specific reminders. Internal Notes are only visible to staff in the app and are never sent to the customer.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -196,7 +196,7 @@
|
||||
<a tabindex="0" class="help-icon" role="button"
|
||||
data-bs-toggle="popover" data-bs-placement="right" data-bs-trigger="focus"
|
||||
data-bs-title="Totals"
|
||||
data-bs-content="Subtotal = sum of all line item totals. Discount is a flat dollar amount deducted before tax. Tax % is applied to (Subtotal − Discount). Both default from the company settings but can be overridden for this invoice.">
|
||||
data-bs-content="Subtotal = sum of all line item totals. Discount is a flat dollar amount deducted before tax. Tax % is applied to (Subtotal − Discount). Both default from the company settings but can be overridden for this invoice.">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</a>
|
||||
</div>
|
||||
@@ -252,7 +252,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-footer border-0 pt-0">
|
||||
<div class="alert alert-info mb-0 small py-2">
|
||||
<div class="alert alert-info alert-permanent mb-0 small py-2">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
<strong>Draft, Sent,</strong> and <strong>Overdue</strong> invoices can be edited.
|
||||
Paid invoices are locked.
|
||||
|
||||
Reference in New Issue
Block a user