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