Add mobile card view to Bills/Expenses list page

Wraps the desktop table in table-responsive to fix horizontal scrolling,
and adds a mobile-card-view section matching the pattern used on Invoices,
PurchaseOrders, and other list pages. Cards show type, number, vendor,
status, date, due date, amount, balance due, and memo/account.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 18:48:48 -04:00
parent deb248b2a6
commit 2c179bc892
+165 -78
View File
@@ -92,94 +92,181 @@
{
<div class="card shadow-sm">
<div class="card-body p-0">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th style="width:90px">Type</th>
<th>Number</th>
<th>Vendor</th>
<th>Memo / Account</th>
<th>Date</th>
<th>Due Date</th>
<th>Status</th>
<th class="text-end">Amount</th>
<th class="text-end">Balance Due</th>
<th></th>
</tr>
</thead>
<tbody>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th style="width:90px">Type</th>
<th>Number</th>
<th>Vendor</th>
<th>Memo / Account</th>
<th>Date</th>
<th>Due Date</th>
<th>Status</th>
<th class="text-end">Amount</th>
<th class="text-end">Balance Due</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var entry in Model)
{
<tr class="@(entry.IsOverdue ? "table-warning" : "")">
<td>
@if (entry.EntryType == "Bill")
{
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">
<i class="bi bi-file-text me-1"></i>Bill
</span>
}
else
{
<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle">
<i class="bi bi-receipt me-1"></i>Expense
</span>
}
</td>
<td>
@if (entry.EntryType == "Bill")
{
<a asp-controller="Bills" asp-action="Details" asp-route-id="@entry.Id"
class="fw-medium text-decoration-none">@entry.Number</a>
}
else
{
<a asp-controller="Expenses" asp-action="Details" asp-route-id="@entry.Id"
class="fw-medium text-decoration-none">@entry.Number</a>
}
</td>
<td>@entry.VendorName</td>
<td class="text-muted small">
@(entry.EntryType == "Bill" ? entry.Memo : entry.AccountName)
@if (entry.HasReceipt)
{
<i class="bi bi-paperclip ms-1" title="Has receipt"></i>
}
</td>
<td>@entry.Date.ToString("MMM d, yyyy")</td>
<td>
@if (entry.DueDate.HasValue)
{
<span class="@(entry.IsOverdue ? "text-danger fw-medium" : "")">
@entry.DueDate.Value.ToString("MMM d, yyyy")
@if (entry.IsOverdue) { <i class="bi bi-exclamation-circle ms-1"></i> }
</span>
}
else if (entry.EntryType == "Expense")
{
<span class="text-muted">&mdash;</span>
}
</td>
<td><span class="badge bg-@entry.StatusColor">@entry.StatusLabel</span></td>
<td class="text-end">@entry.Total.ToString("C")</td>
<td class="text-end fw-medium @(entry.BalanceDue > 0 ? "text-danger" : "text-muted")">
@Html.Raw(entry.EntryType == "Bill" ? entry.BalanceDue.ToString("C") : "&mdash;")
</td>
<td>
@if (entry.EntryType == "Bill")
{
<a asp-controller="Bills" asp-action="Details" asp-route-id="@entry.Id"
class="btn btn-sm btn-outline-primary"><i class="bi bi-eye"></i></a>
}
else
{
<a asp-controller="Expenses" asp-action="Details" asp-route-id="@entry.Id"
class="btn btn-sm btn-outline-secondary"><i class="bi bi-eye"></i></a>
}
</td>
</tr>
}
</tbody>
</table>
</div>
<div class="mobile-card-view">
<div class="mobile-card-list">
@foreach (var entry in Model)
{
<tr class="@(entry.IsOverdue ? "table-warning" : "")">
<td>
@if (entry.EntryType == "Bill")
{
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">
<i class="bi bi-file-text me-1"></i>Bill
</span>
}
else
{
<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle">
<i class="bi bi-receipt me-1"></i>Expense
</span>
}
</td>
<td>
@if (entry.EntryType == "Bill")
{
<a asp-controller="Bills" asp-action="Details" asp-route-id="@entry.Id"
class="fw-medium text-decoration-none">@entry.Number</a>
}
else
{
<a asp-controller="Expenses" asp-action="Details" asp-route-id="@entry.Id"
class="fw-medium text-decoration-none">@entry.Number</a>
}
</td>
<td>@entry.VendorName</td>
<td class="text-muted small">
@(entry.EntryType == "Bill" ? entry.Memo : entry.AccountName)
@if (entry.HasReceipt)
{
<i class="bi bi-paperclip ms-1" title="Has receipt"></i>
}
</td>
<td>@entry.Date.ToString("MMM d, yyyy")</td>
<td>
var isBill = entry.EntryType == "Bill";
var detailUrl = isBill
? Url.Action("Details", "Bills", new { id = entry.Id })
: Url.Action("Details", "Expenses", new { id = entry.Id });
<div class="mobile-data-card" onclick="window.location='@detailUrl'"
style="@(entry.IsOverdue ? "border-left: 3px solid #f59e0b;" : "")">
<div class="mobile-card-header">
<div class="mobile-card-icon" style="background: linear-gradient(135deg, @(isBill ? "#3b82f6 0%, #2563eb" : "#6b7280 0%, #4b5563") 100%);">
<i class="bi @(isBill ? "bi-file-text" : "bi-receipt")"></i>
</div>
<div class="mobile-card-title">
<h6>@entry.Number</h6>
<small>@entry.VendorName</small>
</div>
<div class="ms-auto">
@if (isBill)
{
<span class="badge bg-primary-subtle text-primary border border-primary-subtle">Bill</span>
}
else
{
<span class="badge bg-secondary-subtle text-secondary border border-secondary-subtle">Expense</span>
}
</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-@entry.StatusColor">@entry.StatusLabel</span></span>
</div>
<div class="mobile-card-row">
<span class="mobile-card-label">Date</span>
<span class="mobile-card-value">@entry.Date.ToString("MMM d, yyyy")</span>
</div>
@if (entry.DueDate.HasValue)
{
<span class="@(entry.IsOverdue ? "text-danger fw-medium" : "")">
@entry.DueDate.Value.ToString("MMM d, yyyy")
@if (entry.IsOverdue) { <i class="bi bi-exclamation-circle ms-1"></i> }
</span>
<div class="mobile-card-row">
<span class="mobile-card-label">Due</span>
<span class="mobile-card-value @(entry.IsOverdue ? "text-danger fw-medium" : "")">
@entry.DueDate.Value.ToString("MMM d, yyyy")
@if (entry.IsOverdue) { <i class="bi bi-exclamation-circle ms-1"></i> }
</span>
</div>
}
else if (entry.EntryType == "Expense")
<div class="mobile-card-row">
<span class="mobile-card-label">Amount</span>
<span class="mobile-card-value fw-semibold">@entry.Total.ToString("C")</span>
</div>
@if (isBill)
{
<span class="text-muted">&mdash;</span>
<div class="mobile-card-row">
<span class="mobile-card-label">Balance Due</span>
<span class="mobile-card-value @(entry.BalanceDue > 0 ? "fw-semibold text-danger" : "text-muted")">
@entry.BalanceDue.ToString("C")
</span>
</div>
}
</td>
<td><span class="badge bg-@entry.StatusColor">@entry.StatusLabel</span></td>
<td class="text-end">@entry.Total.ToString("C")</td>
<td class="text-end fw-medium @(entry.BalanceDue > 0 ? "text-danger" : "text-muted")">
@Html.Raw(entry.EntryType == "Bill" ? entry.BalanceDue.ToString("C") : "&mdash;")
</td>
<td>
@if (entry.EntryType == "Bill")
@{
var memoText = isBill ? entry.Memo : entry.AccountName;
}
@if (!string.IsNullOrEmpty(memoText))
{
<a asp-controller="Bills" asp-action="Details" asp-route-id="@entry.Id"
class="btn btn-sm btn-outline-primary"><i class="bi bi-eye"></i></a>
<div class="mobile-card-row">
<span class="mobile-card-label">@(isBill ? "Memo" : "Account")</span>
<span class="mobile-card-value text-muted small">
@memoText
@if (entry.HasReceipt) { <i class="bi bi-paperclip ms-1" title="Has receipt"></i> }
</span>
</div>
}
else
{
<a asp-controller="Expenses" asp-action="Details" asp-route-id="@entry.Id"
class="btn btn-sm btn-outline-secondary"><i class="bi bi-eye"></i></a>
}
</td>
</tr>
</div>
<div class="mobile-card-footer">
<a href="@detailUrl" class="btn btn-sm @(isBill ? "btn-outline-primary" : "btn-outline-secondary")"
onclick="event.stopPropagation()">
<i class="bi bi-eye me-1"></i>View
</a>
</div>
</div>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
}