Feature: Add pagination to Receipts page

- Add PageNumber and PageSize query parameters (default 25 per page)
- Implement server-side pagination with Skip/Take
- Display "Showing X-Y of Z" count in header
- Add Bootstrap pagination controls with sliding page window

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-05 21:33:05 -05:00
parent 101a340d8c
commit 7b0853b74c
2 changed files with 70 additions and 3 deletions

View File

@@ -142,7 +142,14 @@
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<strong>All Receipts</strong>
<span class="text-muted">- @Model.Receipts.Count total</span>
@if (Model.TotalCount > 0)
{
<span class="text-muted">- Showing @((Model.PageNumber - 1) * Model.PageSize + 1)-@(Math.Min(Model.PageNumber * Model.PageSize, Model.TotalCount)) of @Model.TotalCount</span>
}
else
{
<span class="text-muted">- 0 total</span>
}
</div>
@if (Model.Receipts.Any(r => !r.TransactionId.HasValue && (!string.IsNullOrWhiteSpace(r.Merchant) || r.ReceiptDate.HasValue || r.Total.HasValue)))
{
@@ -273,6 +280,47 @@
</div>
}
</div>
@if (Model.TotalPages > 1)
{
<div class="card-footer">
<nav aria-label="Receipts pagination">
<ul class="pagination mb-0 justify-content-center">
<li class="page-item @(Model.PageNumber <= 1 ? "disabled" : "")">
<a class="page-link" asp-page="/Receipts" asp-route-pageNumber="1" asp-route-pageSize="@Model.PageSize">First</a>
</li>
<li class="page-item @(Model.PageNumber <= 1 ? "disabled" : "")">
<a class="page-link" asp-page="/Receipts" asp-route-pageNumber="@(Model.PageNumber - 1)" asp-route-pageSize="@Model.PageSize">Previous</a>
</li>
@{
var startPage = Math.Max(1, Model.PageNumber - 2);
var endPage = Math.Min(Model.TotalPages, Model.PageNumber + 2);
if (endPage - startPage < 4)
{
if (startPage == 1)
endPage = Math.Min(Model.TotalPages, startPage + 4);
else
startPage = Math.Max(1, endPage - 4);
}
}
@for (var i = startPage; i <= endPage; i++)
{
<li class="page-item @(i == Model.PageNumber ? "active" : "")">
<a class="page-link" asp-page="/Receipts" asp-route-pageNumber="@i" asp-route-pageSize="@Model.PageSize">@i</a>
</li>
}
<li class="page-item @(Model.PageNumber >= Model.TotalPages ? "disabled" : "")">
<a class="page-link" asp-page="/Receipts" asp-route-pageNumber="@(Model.PageNumber + 1)" asp-route-pageSize="@Model.PageSize">Next</a>
</li>
<li class="page-item @(Model.PageNumber >= Model.TotalPages ? "disabled" : "")">
<a class="page-link" asp-page="/Receipts" asp-route-pageNumber="@Model.TotalPages" asp-route-pageSize="@Model.PageSize">Last</a>
</li>
</ul>
</nav>
<div class="text-center mt-2">
<small class="text-muted">Page @Model.PageNumber of @Model.TotalPages</small>
</div>
</div>
}
</div>
<!-- Map to Transaction Modals -->

View File

@@ -24,6 +24,15 @@ namespace MoneyMap.Pages
public List<ReceiptRow> Receipts { get; set; } = new();
public Dictionary<long, List<TransactionOption>> ReceiptTransactionMatches { get; set; } = new();
[BindProperty(SupportsGet = true)]
public int PageNumber { get; set; } = 1;
[BindProperty(SupportsGet = true)]
public int PageSize { get; set; } = 25;
public int TotalCount { get; set; }
public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize);
[BindProperty]
public IFormFile? UploadFile { get; set; }
@@ -238,9 +247,19 @@ namespace MoneyMap.Pages
private async Task LoadReceiptsAsync()
{
var receipts = await _db.Receipts
if (PageNumber < 1) PageNumber = 1;
if (PageSize < 1) PageSize = 25;
if (PageSize > 100) PageSize = 100;
var query = _db.Receipts
.Include(r => r.Transaction)
.OrderByDescending(r => r.UploadedAtUtc)
.OrderByDescending(r => r.UploadedAtUtc);
TotalCount = await query.CountAsync();
var receipts = await query
.Skip((PageNumber - 1) * PageSize)
.Take(PageSize)
.ToListAsync();
Receipts = receipts.Select(r => new ReceiptRow