This commit is contained in:
rossmill76 committed 2026-03-02 18:07:08 +00:00
1 parent 6f63488c89
commit 64c3eaa473
4 files changed
+86 -39

No files matched your search

+80 -39
View File
@@ -107,31 +107,55 @@ else if (transactions.Count == 0)
}
else
{
<div class="card">
<div class="card-header d-flex align-items-center gap-2 flex-wrap">
<label class="form-label mb-0 text-nowrap">Account:</label>
<select class="form-select form-select-sm" style="max-width: 200px;" title="Filter transactions by account" @onchange="OnAccountFilterChanged">
<option value="">All Accounts</option>
@if (accountsList is not null)
{
@foreach (var a in accountsList)
{
<option value="@a.Id" selected="@(filterAccountId == a.Id)">@a.Name</option>
}
}
</select>
<input type="search" class="form-control form-control-sm" style="max-width: 220px;"
title="Search by description, account, category or amount" placeholder="Search..." @bind="searchQuery" @bind:event="oninput" @bind:after="OnSearchChanged" />
<div class="ms-auto d-flex align-items-center gap-2">
<label class="form-label mb-0 text-nowrap">Show:</label>
<select class="form-select form-select-sm" style="max-width: 90px;" @onchange="OnPageSizeChanged">
@foreach (var size in pageSizeOptions)
{
<option value="@size" selected="@(pageSize == size)">@size</option>
}
</select>
<div class="card mb-3">
<div class="card-header d-flex flex-wrap align-items-center justify-content-between gap-2">
<h5 class="mb-0">Filters</h5>
<div class="d-flex flex-wrap align-items-center gap-2">
<div class="btn-group btn-group-sm">
<button class="btn @(windowPreset == "7d" ? "btn-primary" : "btn-outline-secondary")" title="Show next 7 days" @onclick="() => SetWindow(7)">7d</button>
<button class="btn @(windowPreset == "30d" ? "btn-primary" : "btn-outline-secondary")" title="Show next 30 days" @onclick="() => SetWindow(30)">30d</button>
<button class="btn @(windowPreset == "90d" ? "btn-primary" : "btn-outline-secondary")" title="Show next 90 days" @onclick="() => SetWindow(90)">90d</button>
<button class="btn @(windowPreset == "1y" ? "btn-primary" : "btn-outline-secondary")" title="Show next year" @onclick="() => SetWindow(365)">1y</button>
</div>
<input type="date" class="form-control form-control-sm" style="width:auto;"
title="Custom start date" @bind="windowStart" @bind:after="OnWindowDateChanged" />
<span class="text-muted">to</span>
<input type="date" class="form-control form-control-sm" style="width:auto;"
title="Custom end date" @bind="windowEnd" @bind:after="OnWindowDateChanged" />
</div>
</div>
<div class="card-body py-2">
<div class="row g-2 align-items-center">
<div class="col-12 col-sm-auto">
<select class="form-select form-select-sm" title="Filter transactions by account" @onchange="OnAccountFilterChanged">
<option value="">All Accounts</option>
@if (accountsList is not null)
{
@foreach (var a in accountsList)
{
<option value="@a.Id" selected="@(filterAccountId == a.Id)">@a.Name</option>
}
}
</select>
</div>
<div class="col-12 col-sm">
<input type="search" class="form-control form-control-sm"
title="Search by description, account, category or amount" placeholder="Search..." @bind="searchQuery" @bind:event="oninput" @bind:after="OnSearchChanged" />
</div>
<div class="col-auto d-flex align-items-center gap-2">
<label class="form-label mb-0 text-nowrap">Show:</label>
<select class="form-select form-select-sm" style="width:auto;" @onchange="OnPageSizeChanged">
@foreach (var size in pageSizeOptions)
{
<option value="@size" selected="@(pageSize == size)">@size</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="card">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
@@ -142,8 +166,8 @@ else
<th role="button" class="user-select-none" title="Sort by date" @onclick='() => SetSort("date")'>Date @SortIndicator("date")</th>
<th role="button" class="user-select-none" title="Sort by description" @onclick='() => SetSort("description")'>Description @SortIndicator("description")</th>
<th>Account</th>
<th>Category</th>
<th>Recurrence</th>
<th class="d-none d-md-table-cell">Category</th>
<th class="d-none d-md-table-cell">Recurrence</th>
<th role="button" class="user-select-none text-end" title="Sort by amount" @onclick='() => SetSort("amount")'>Amount @SortIndicator("amount")</th>
<th></th>
</tr>
@@ -155,14 +179,14 @@ else
<td>
<input type="checkbox" class="form-check-input" checked="@selectedIds.Contains(t.Id)" @onchange="() => ToggleSelect(t.Id)" />
</td>
<td>@t.Date.ToString("MMM dd, yyyy")</td>
<td class="text-nowrap">@t.Date.ToString("MMM dd, yyyy")</td>
<td>@t.Description</td>
<td>
<span class="badge" style="background-color: @t.Account.Color;">
@t.Account.Name
</span>
</td>
<td>
<td class="d-none d-md-table-cell">
@if (t.Category is not null)
{
@if (!string.IsNullOrEmpty(t.Category.Icon))
@@ -176,7 +200,7 @@ else
<span>—</span>
}
</td>
<td>
<td class="d-none d-md-table-cell">
@if (t.RecurrenceType != RecurrenceType.None)
{
<span class="badge bg-info">🔁 @t.RecurrenceType</span>
@@ -186,16 +210,14 @@ else
<span class="badge bg-info">🔁 @(t.ParentTransaction?.RecurrenceType.ToString() ?? "Recurring")</span>
}
</td>
<td class="text-end @(t.Type == TransactionType.Income ? "text-success" : "text-danger") fw-semibold">
<td class="text-end text-nowrap @(t.Type == TransactionType.Income ? "text-success" : "text-danger") fw-semibold">
@(t.Type == TransactionType.Income ? "+" : "-")@t.Account.FormatAmount(t.Amount)
</td>
<td class="text-end">
<button class="btn btn-outline-primary btn-sm" @onclick="() => OpenEditModal(t)">
Edit
</button>
<button class="btn btn-outline-danger btn-sm" @onclick="() => ConfirmDelete(t)">
Delete
</button>
<td class="text-end text-nowrap">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" @onclick="() => OpenEditModal(t)">Edit</button>
<button class="btn btn-outline-danger" @onclick="() => ConfirmDelete(t)">Delete</button>
</div>
</td>
</tr>
}
@@ -204,7 +226,7 @@ else
</div>
@if (totalPages > 1)
{
<div class="card-footer d-flex justify-content-between align-items-center">
<div class="card-footer d-flex flex-wrap justify-content-between align-items-center gap-2">
<button class="btn btn-sm btn-outline-secondary" disabled="@(currentPage <= 1 ? true : (bool?)null)" @onclick="() => currentPage--">← Prev</button>
<div class="d-flex align-items-center gap-3">
@if (selectedIds.Count > 0)
@@ -220,7 +242,7 @@ else
}
else
{
<div class="card-footer d-flex justify-content-between align-items-center">
<div class="card-footer d-flex flex-wrap justify-content-between align-items-center gap-2">
<small class="text-muted">@filteredTransactions.Count transactions</small>
@if (selectedIds.Count > 0)
{
@@ -364,6 +386,11 @@ else
private int? filterAccountId;
private string searchQuery = string.Empty;
// Time window state
private DateTime windowStart = DateTime.Today;
private DateTime windowEnd = DateTime.Today.AddDays(30);
private string? windowPreset = "30d";
// Pagination state
private int currentPage = 1;
private int pageSize = 25;
@@ -382,7 +409,7 @@ else
{
get
{
var list = (transactions ?? []).Where(t => t.Date.Date >= DateTime.Today);
var list = (transactions ?? []).Where(t => t.Date.Date >= windowStart.Date && t.Date.Date <= windowEnd.Date);
if (filterAccountId is not null)
list = list.Where(t => t.AccountId == filterAccountId);
@@ -421,6 +448,20 @@ else
private void OnSearchChanged() => currentPage = 1;
private void SetWindow(int days)
{
windowPreset = days switch { 7 => "7d", 30 => "30d", 90 => "90d", 365 => "1y", _ => null };
windowStart = DateTime.Today;
windowEnd = DateTime.Today.AddDays(days);
currentPage = 1;
}
private void OnWindowDateChanged()
{
windowPreset = null;
currentPage = 1;
}
private void OnPageSizeChanged(ChangeEventArgs e)
{
if (int.TryParse(e.Value?.ToString(), out var size))
@@ -0,0 +1,6 @@
@media (max-width: 767.98px) {
::deep .table > :not(caption) > * > * {
padding: 0.3rem 0.4rem;
font-size: 0.8125rem;
}
}
Binary file not shown.
Binary file not shown.