Add mobile card views to 12 high-priority list pages

Pages were blank on phones because mobile-cards.css hides .table-responsive
below 992px. Added .mobile-card-view sections to: GiftCertificates, PurchaseOrders,
CreditMemos, VendorCredits, JournalEntries, Appointments, InAppNotifications,
BankReconciliations, FixedAssets, RecurringTemplates, SmsAgreements, SmsConsentAudit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 23:07:52 -04:00
parent 7ad7d84016
commit f467862877
12 changed files with 771 additions and 0 deletions
@@ -176,6 +176,60 @@
<div class="card-body">
@if (Model.Items.Any())
{
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var appointment in Model.Items)
{
<div class="mobile-data-card">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);">
<i class="bi bi-calendar-event"></i>
</div>
<div class="mobile-card-title">
<h6>@appointment.Title</h6>
<small>@appointment.ScheduledStartTime.ToString("MMM dd, yyyy")<br />@(!appointment.IsAllDay ? $"{appointment.ScheduledStartTime:h:mm tt} &ndash; {appointment.ScheduledEndTime:h:mm tt}" : "All Day")</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 bg-@appointment.StatusColorClass">@appointment.StatusDisplayName</span>
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Type</span>
<span class="mobile-card-value">
<span class="badge bg-@appointment.TypeColorClass">@appointment.TypeDisplayName</span>
</span>
</div>
@if (!string.IsNullOrEmpty(appointment.CustomerName))
{
<div class="mobile-card-row">
<span class="mobile-card-label">Customer</span>
<span class="mobile-card-value">@appointment.CustomerName</span>
</div>
}
@if (!string.IsNullOrEmpty(appointment.AssignedWorkerName))
{
<div class="mobile-card-row">
<span class="mobile-card-label">Worker</span>
<span class="mobile-card-value">@appointment.AssignedWorkerName</span>
</div>
}
</div>
<div class="mobile-card-footer">
<a asp-action="Details" asp-route-id="@appointment.Id" class="btn btn-sm btn-outline-primary">
<i class="bi bi-eye me-1"></i>View
</a>
<a asp-action="Edit" asp-route-id="@appointment.Id" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-pencil me-1"></i>Edit
</a>
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
@@ -21,6 +21,64 @@
<div class="card shadow-sm">
<div class="card-body p-0">
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var br in Model)
{
<div class="mobile-data-card">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #14b8a6 0%, #0f766e 100%);">
<i class="bi bi-bank"></i>
</div>
<div class="mobile-card-title">
<h6>@br.Account?.Name</h6>
<small>Statement: @br.StatementDate.ToString("MMM d, yyyy")</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">
@if (br.Status == BankReconciliationStatus.Completed)
{
<span class="badge bg-success">Completed</span>
}
else
{
<span class="badge bg-warning text-dark">In Progress</span>
}
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Ending Balance</span>
<span class="mobile-card-value fw-semibold">@br.EndingBalance.ToString("C")</span>
</div>
@if (br.CompletedAt.HasValue)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Completed By</span>
<span class="mobile-card-value">@br.CompletedBy</span>
</div>
}
</div>
<div class="mobile-card-footer">
@if (br.Status == BankReconciliationStatus.Completed)
{
<a asp-action="Report" asp-route-id="@br.Id" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-file-earmark-text me-1"></i>Report
</a>
}
else
{
<a asp-action="Reconcile" asp-route-id="@br.Id" class="btn btn-sm btn-outline-primary">
<i class="bi bi-check2-square me-1"></i>Continue
</a>
}
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
@@ -101,6 +101,73 @@
else
{
<div class="card">
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var m in Model)
{
var expired2 = m.ExpiryDate.HasValue && m.ExpiryDate.Value < DateTime.UtcNow
&& m.Status != CreditMemoStatus.FullyApplied
&& m.Status != CreditMemoStatus.Voided;
var (cmBadge, cmLabel) = m.Status switch
{
CreditMemoStatus.Active => ("bg-success-subtle text-success", "Active"),
CreditMemoStatus.PartiallyApplied => ("bg-warning-subtle text-warning", "Partial"),
CreditMemoStatus.FullyApplied => ("bg-secondary-subtle text-secondary", "Applied"),
CreditMemoStatus.Voided => ("bg-danger-subtle text-danger", "Voided"),
_ => ("bg-secondary-subtle text-secondary", m.Status.ToString())
};
var cmCustomer = string.IsNullOrWhiteSpace(m.Customer?.CompanyName)
? $"{m.Customer?.ContactFirstName} {m.Customer?.ContactLastName}".Trim()
: m.Customer!.CompanyName;
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = m.Id })'">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);">
<i class="bi bi-journal-minus"></i>
</div>
<div class="mobile-card-title">
<h6>@m.MemoNumber</h6>
<small>@cmCustomer</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 @cmBadge">@cmLabel</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Amount</span>
<span class="mobile-card-value">@m.Amount.ToString("C")</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Remaining</span>
<span class="mobile-card-value @(m.RemainingBalance > 0 && m.Status != CreditMemoStatus.Voided ? "text-success fw-semibold" : "text-muted")">
@m.RemainingBalance.ToString("C")
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Issued</span>
<span class="mobile-card-value">@m.IssueDate.ToLocalTime().ToString("MM/dd/yy")</span>
</div>
@if (m.ExpiryDate.HasValue)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Expires</span>
<span class="mobile-card-value @(expired2 ? "text-danger fw-semibold" : "")">
@m.ExpiryDate.Value.ToLocalTime().ToString("MM/dd/yy")
@if (expired2) { <small>(Expired)</small> }
</span>
</div>
}
</div>
<div class="mobile-card-footer">
<a asp-action="Details" asp-route-id="@m.Id" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
Details
</a>
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
@@ -118,6 +118,63 @@
}
else
{
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var a in Model)
{
var fd = a.AccumulatedDepreciation >= (a.PurchaseCost - a.SalvageValue);
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = a.Id })'">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);">
<i class="bi bi-building-gear"></i>
</div>
<div class="mobile-card-title">
<h6>@a.Name</h6>
<small>Purchased @a.PurchaseDate.ToLocalTime().ToString("MM/dd/yyyy")</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">
@if (a.IsDisposed)
{
<span class="badge bg-secondary">Disposed</span>
}
else if (fd)
{
<span class="badge bg-light text-dark border">Fully Depreciated</span>
}
else
{
<span class="badge bg-success">Active</span>
}
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Cost</span>
<span class="mobile-card-value">@a.PurchaseCost.ToString("C")</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Book Value</span>
<span class="mobile-card-value @(a.BookValue <= 0 ? "text-muted" : "text-success fw-semibold")">
@a.BookValue.ToString("C")
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Monthly Depr.</span>
<span class="mobile-card-value">@a.MonthlyDepreciation.ToString("C")</span>
</div>
</div>
<div class="mobile-card-footer">
<a asp-action="Details" asp-route-id="@a.Id" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
<i class="bi bi-eye me-1"></i>View
</a>
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
@@ -57,6 +57,73 @@ else
{
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var cert in Model)
{
var (gcBadge, gcLabel) = cert.Status switch
{
GiftCertificateStatus.Active => ("bg-success", "Active"),
GiftCertificateStatus.PartiallyRedeemed => ("bg-info text-dark", "Partial"),
GiftCertificateStatus.FullyRedeemed => ("bg-secondary", "Used"),
GiftCertificateStatus.Expired => ("bg-warning text-dark", "Expired"),
GiftCertificateStatus.Voided => ("bg-danger", "Voided"),
_ => ("bg-secondary", cert.Status.ToString())
};
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = cert.Id })'">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #a855f7 0%, #7c3aed 100%);">
<i class="bi bi-gift"></i>
</div>
<div class="mobile-card-title">
<h6 class="font-monospace">@cert.CertificateCode</h6>
<small>@(cert.RecipientName ?? cert.RecipientEmail ?? "No recipient")</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 @gcBadge">@gcLabel</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Face Value</span>
<span class="mobile-card-value">@cert.OriginalAmount.ToString("C")</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Remaining</span>
<span class="mobile-card-value @(cert.RemainingBalance > 0 ? "text-success fw-semibold" : "text-muted")">
@cert.RemainingBalance.ToString("C")
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Issued</span>
<span class="mobile-card-value">@cert.IssueDate.Tz(ViewBag.CompanyTimeZone as string).ToString("MM/dd/yy")</span>
</div>
@if (cert.ExpiryDate.HasValue)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Expires</span>
<span class="mobile-card-value @(cert.ExpiryDate.Value < DateTime.Now ? "text-danger" : "")">
@cert.ExpiryDate.Value.ToString("MM/dd/yy")
</span>
</div>
}
</div>
<div class="mobile-card-footer">
<a asp-action="Details" asp-route-id="@cert.Id" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
<i class="bi bi-eye me-1"></i>View
</a>
@if (cert.BatchId.HasValue)
{
<a asp-action="BulkResult" asp-route-batchId="@cert.BatchId" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
<i class="bi bi-collection me-1"></i>Batch
</a>
}
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
@@ -29,6 +29,61 @@
else
{
<div class="card border-0 shadow-sm">
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var n in items)
{
bool mIsRead = (bool)n.IsRead;
string mTitle = (string)n.Title;
string mMessage = (string)n.Message;
string? mLink = (string?)n.Link;
string mType = (string)n.NotificationType;
DateTime mCreatedAt = ((DateTime)n.CreatedAt).Tz(ViewBag.CompanyTimeZone as string);
<div class="mobile-data-card notif-history-row @(!mIsRead ? "notif-unread" : "")"
data-id="@n.Id"
data-title="@mTitle"
data-message="@mMessage"
data-link="@(mLink ?? "")"
data-type="@mType"
data-is-read="@(mIsRead ? "1" : "0")"
data-created-at="@mCreatedAt.ToString("MMM d, yyyy h:mm tt")">
<div class="mobile-card-header" style="@(!mIsRead ? "background:rgba(99,102,241,0.08);" : "")">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);">
<i class="bi bi-bell"></i>
</div>
<div class="mobile-card-title">
<h6 class="@(!mIsRead ? "fw-semibold" : "text-muted")">
@if (!mIsRead)
{
<span style="display:inline-block;width:8px;height:8px;background:#6366f1;border-radius:50%;margin-right:6px;"></span>
}
@mTitle
</h6>
<small>@mCreatedAt.ToString("MMM d, yyyy h:mm tt")</small>
</div>
</div>
<div class="mobile-card-body">
<div class="mobile-card-row">
<span class="mobile-card-label">Type</span>
<span class="mobile-card-value"><span class="badge bg-secondary bg-opacity-25 text-body small">@mType</span></span>
</div>
<div class="mobile-card-row" style="align-items:flex-start;">
<span class="mobile-card-label">Message</span>
<span class="mobile-card-value" style="white-space:normal;text-align:right;">@mMessage</span>
</div>
</div>
@if (!string.IsNullOrEmpty(mLink))
{
<div class="mobile-card-footer">
<a href="@mLink" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
<i class="bi bi-arrow-right me-1"></i>Open
</a>
</div>
}
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
@@ -47,6 +47,68 @@
<div class="card shadow-sm">
<div class="card-body p-0">
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var je in Model)
{
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = je.Id })'">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);">
<i class="bi bi-journal-text"></i>
</div>
<div class="mobile-card-title">
<h6>
@je.EntryNumber
@if (je.IsReversal)
{
<span class="badge bg-secondary ms-1">REV</span>
}
</h6>
<small>@je.EntryDate.ToString("MMM d, yyyy")</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">
@if (je.Status == JournalEntryStatus.Draft)
{
<span class="badge bg-warning text-dark">Draft</span>
}
else if (je.Status == JournalEntryStatus.Posted)
{
<span class="badge bg-success">Posted</span>
}
else
{
<span class="badge bg-secondary">Reversed</span>
}
</span>
</div>
@if (!string.IsNullOrWhiteSpace(je.Description))
{
<div class="mobile-card-row">
<span class="mobile-card-label">Description</span>
<span class="mobile-card-value" style="white-space:normal;text-align:right;">@je.Description</span>
</div>
}
@if (!string.IsNullOrWhiteSpace(je.Reference))
{
<div class="mobile-card-row">
<span class="mobile-card-label">Reference</span>
<span class="mobile-card-value">@je.Reference</span>
</div>
}
</div>
<div class="mobile-card-footer">
<a asp-action="Details" asp-route-id="@je.Id" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
View
</a>
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
@@ -164,6 +164,56 @@
<!-- Grid -->
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var po in Model.Items)
{
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = po.Id })'">
<div class="mobile-card-header" style="@(po.IsOverdue ? "background:#fee2e2;" : "")">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);">
<i class="bi bi-cart-check"></i>
</div>
<div class="mobile-card-title">
<h6>@po.PoNumber @(po.IsOverdue ? " — Overdue" : "")</h6>
<small>@po.VendorName</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 bg-@StatusBadge(po.Status)">@po.Status</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Order Date</span>
<span class="mobile-card-value">@po.OrderDate.ToString("MM/dd/yy")</span>
</div>
@if (po.ExpectedDeliveryDate.HasValue)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Expected</span>
<span class="mobile-card-value @(po.IsOverdue ? "text-danger fw-semibold" : "")">
@po.ExpectedDeliveryDate.Value.ToString("MM/dd/yy")
</span>
</div>
}
<div class="mobile-card-row">
<span class="mobile-card-label">Items</span>
<span class="mobile-card-value">@po.ItemCount</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Total</span>
<span class="mobile-card-value fw-semibold">$@po.TotalAmount.ToString("N2")</span>
</div>
</div>
<div class="mobile-card-footer">
<a asp-action="Details" asp-route-id="@po.Id" class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
<i class="bi bi-eye me-1"></i>View
</a>
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
@@ -38,6 +38,96 @@
}
else
{
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var t in Model)
{
var isOverdueRT = t.IsActive && t.NextFireDate.Date < DateTime.Today;
<div class="mobile-data-card">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);">
<i class="bi bi-arrow-repeat"></i>
</div>
<div class="mobile-card-title">
<h6>@t.Name</h6>
<small>
@if (t.TemplateType == RecurringTemplateType.Bill)
{
<span>Bill</span>
}
else
{
<span>Expense</span>
}
&mdash;
@(t.IntervalCount == 1 ? t.Frequency.ToString() : $"Every {t.IntervalCount} &times; {t.Frequency}")
</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">
@if (t.IsActive)
{
<span class="badge bg-success"><i class="bi bi-play-fill me-1"></i>Active</span>
}
else
{
<span class="badge bg-secondary"><i class="bi bi-pause-fill me-1"></i>Paused</span>
}
</span>
</div>
@if (t.IsActive)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Next Fire</span>
<span class="mobile-card-value @(isOverdueRT ? "text-danger fw-semibold" : "")">
@t.NextFireDate.ToString("MM/dd/yyyy")
@if (isOverdueRT) { <i class="bi bi-exclamation-circle ms-1"></i> }
</span>
</div>
}
<div class="mobile-card-row">
<span class="mobile-card-label">Occurrences</span>
<span class="mobile-card-value">
@t.OccurrenceCount
@if (t.MaxOccurrences.HasValue) { <span class="text-muted"> / @t.MaxOccurrences</span> }
</span>
</div>
@if (!string.IsNullOrWhiteSpace(t.LastError))
{
<div class="mobile-card-row">
<span class="mobile-card-label">Error</span>
<span class="mobile-card-value text-danger small">@t.LastError</span>
</div>
}
</div>
<div class="mobile-card-footer">
<a asp-action="Edit" asp-route-id="@t.Id" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-pencil"></i>
</a>
<form asp-action="ToggleActive" asp-route-id="@t.Id" method="post" style="display:inline;">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm @(t.IsActive ? "btn-outline-warning" : "btn-outline-success")">
<i class="bi @(t.IsActive ? "bi-pause" : "bi-play")"></i>
</button>
</form>
@if (t.IsActive)
{
<form asp-action="GenerateNow" asp-route-id="@t.Id" method="post" style="display:inline;"
onsubmit="return confirm('Generate one occurrence now?')">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm btn-outline-primary">
<i class="bi bi-lightning-charge"></i>
</button>
</form>
}
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
@@ -112,6 +112,101 @@
}
else
{
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var row in Model)
{
<div class="mobile-data-card">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);">
<i class="bi bi-building"></i>
</div>
<div class="mobile-card-title">
<h6>
@row.CompanyName
@if (row.IsDeleted) { <span class="badge bg-secondary ms-1">Deleted</span> }
</h6>
<small>
@if (row.SmsDisabledByAdmin)
{
<span class="text-danger"><i class="bi bi-slash-circle me-1"></i>Admin-Disabled</span>
}
else if (row.SmsEnabled)
{
<span class="text-success"><i class="bi bi-chat-dots me-1"></i>SMS Enabled</span>
}
else
{
<span class="text-muted">SMS Off</span>
}
</small>
</div>
</div>
<div class="mobile-card-body">
<div class="mobile-card-row">
<span class="mobile-card-label">Terms</span>
<span class="mobile-card-value">
@{
var dispAgreement = row.CurrentAgreement ?? row.LatestAgreement;
}
@if (row.CurrentAgreement != null)
{
<span class="badge bg-success">v@(row.CurrentAgreement.TermsVersion)</span>
}
else if (row.LatestAgreement != null)
{
<span class="badge bg-warning text-dark">Stale (v@(row.LatestAgreement.TermsVersion))</span>
}
else
{
<span class="badge bg-light text-muted border">Never</span>
}
</span>
</div>
@if (dispAgreement != null)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Accepted By</span>
<span class="mobile-card-value @(row.CurrentAgreement == null ? "text-muted" : "")">
@dispAgreement.AgreedByUserName
</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Accepted At</span>
<span class="mobile-card-value @(row.CurrentAgreement == null ? "text-muted" : "")">
@dispAgreement.AgreedAt.ToString("MM/dd/yy")
</span>
</div>
}
@if (row.AllAgreements.Count > 0)
{
<div class="mobile-card-row">
<span class="mobile-card-label">History</span>
<span class="mobile-card-value">
<button type="button"
class="btn btn-sm btn-outline-secondary"
data-bs-toggle="modal"
data-bs-target="#historyModal"
data-company="@row.CompanyName"
data-history="@System.Text.Json.JsonSerializer.Serialize(row.AllAgreements.Select(a => new {
a.TermsVersion,
a.AgreedByUserName,
a.AgreedByUserId,
AgreedAt = a.AgreedAt.ToString("MMM d, yyyy 'at' h:mm tt") + " UTC",
IpAddress = a.IpAddress ?? "—",
UserAgent = a.UserAgent ?? "—"
}), new System.Text.Json.JsonSerializerOptions { PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase })"
onclick="event.stopPropagation()">
@row.AllAgreements.Count <i class="bi bi-clock-history ms-1"></i>
</button>
</span>
</div>
}
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
@@ -110,6 +110,64 @@
</form>
</div>
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var row in Model.Rows)
{
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", "Customers", new { id = row.CustomerId })'">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #ec4899 0%, #be185d 100%);">
<i class="bi bi-phone-vibrate"></i>
</div>
<div class="mobile-card-title">
<h6>@row.CustomerName</h6>
<small>@(row.MobilePhone ?? row.Phone ?? "No phone")</small>
</div>
</div>
<div class="mobile-card-body">
<div class="mobile-card-row">
<span class="mobile-card-label">SMS Status</span>
<span class="mobile-card-value"><span class="badge @row.StatusBadgeClass">@row.StatusLabel</span></span>
</div>
@if (row.ConsentedAt.HasValue)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Consented</span>
<span class="mobile-card-value">@row.ConsentedAt.Value.ToString("MMM d, yyyy")</span>
</div>
}
@if (!string.IsNullOrWhiteSpace(row.ConsentMethod))
{
<div class="mobile-card-row">
<span class="mobile-card-label">Method</span>
<span class="mobile-card-value">@row.ConsentMethod</span>
</div>
}
@if (row.OptedOutAt.HasValue)
{
<div class="mobile-card-row">
<span class="mobile-card-label">Opted Out</span>
<span class="mobile-card-value text-danger">@row.OptedOutAt.Value.ToString("MMM d, yyyy")</span>
</div>
}
</div>
<div class="mobile-card-footer">
<a asp-controller="Customers" asp-action="Details" asp-route-id="@row.CustomerId"
class="btn btn-sm btn-outline-primary" onclick="event.stopPropagation()">
<i class="bi bi-person me-1"></i>Customer
</a>
</div>
</div>
}
@if (!Model.Rows.Any())
{
<div class="text-center text-muted py-5">
<i class="bi bi-phone-vibrate fs-1 d-block mb-2 opacity-25"></i>
No customers match the current filter.
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<thead class="table-light">
@@ -68,6 +68,64 @@
<div class="card shadow-sm">
<div class="card-body p-0">
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var vc in Model)
{
var (vcBadge, vcLabel) = vc.Status switch
{
VendorCreditStatus.Open => ("bg-success", "Open"),
VendorCreditStatus.PartiallyApplied => ("bg-warning text-dark", "Partial"),
VendorCreditStatus.Applied => ("bg-secondary", "Applied"),
VendorCreditStatus.Voided => ("bg-danger", "Voided"),
_ => ("bg-secondary", vc.Status.ToString())
};
<div class="mobile-data-card" onclick="window.location='@Url.Action("Details", new { id = vc.Id })'">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
<i class="bi bi-credit-card"></i>
</div>
<div class="mobile-card-title">
<h6>@vc.CreditNumber</h6>
<small>@vc.Vendor?.CompanyName</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 @vcBadge">@vcLabel</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Date</span>
<span class="mobile-card-value">@vc.CreditDate.ToString("MM/dd/yy")</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Total</span>
<span class="mobile-card-value">@vc.Total.ToString("C")</span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Remaining</span>
<span class="mobile-card-value @(vc.RemainingAmount > 0 ? "text-success fw-semibold" : "text-muted")">
@(vc.RemainingAmount > 0 ? vc.RemainingAmount.ToString("C") : "&mdash;")
</span>
</div>
@if (!string.IsNullOrWhiteSpace(vc.Memo))
{
<div class="mobile-card-row">
<span class="mobile-card-label">Memo</span>
<span class="mobile-card-value">@vc.Memo</span>
</div>
}
</div>
<div class="mobile-card-footer">
<a asp-action="Details" asp-route-id="@vc.Id" class="btn btn-sm btn-outline-secondary" onclick="event.stopPropagation()">
View
</a>
</div>
</div>
}
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">