WIP
This commit is contained in:
1 parent
08eefe2973
commit
2cf6128a85
10 files changed
+130
-12
No files matched your search
@@ -2,6 +2,7 @@
|
||||
@attribute [Authorize]
|
||||
@inject AccountService AccountService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject AppState AppState
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Accounts</PageTitle>
|
||||
@@ -200,6 +201,7 @@ else
|
||||
await AccountService.CreateAccountAsync(account);
|
||||
formModel = new AccountForm();
|
||||
await LoadAccounts();
|
||||
AppState.NotifyAccountsChanged();
|
||||
}
|
||||
|
||||
private void ConfirmDeleteAccount(Account account)
|
||||
@@ -216,6 +218,7 @@ else
|
||||
await AccountService.DeleteAccountAsync(pendingDeleteAccount.Id, userId);
|
||||
pendingDeleteAccount = null;
|
||||
await LoadAccounts();
|
||||
AppState.NotifyAccountsChanged();
|
||||
}
|
||||
|
||||
private void CancelDeleteAccount()
|
||||
@@ -242,6 +245,7 @@ else
|
||||
await AccountService.UpdateBalanceAsync(accountId, userId, editingBalanceValue);
|
||||
editingBalanceAccountId = null;
|
||||
await LoadAccounts();
|
||||
AppState.NotifyAccountsChanged();
|
||||
}
|
||||
|
||||
private void StartAccountEdit(Account account)
|
||||
@@ -264,6 +268,7 @@ else
|
||||
await AccountService.UpdateAccountDetailsAsync(accountId, userId, editName, editColor, editCurrencyCode);
|
||||
editingAccountId = null;
|
||||
await LoadAccounts();
|
||||
AppState.NotifyAccountsChanged();
|
||||
}
|
||||
|
||||
private sealed class AccountForm
|
||||
|
||||
@@ -140,29 +140,43 @@ else
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<ul class="nav nav-tabs card-header-tabs">
|
||||
<li class="nav-item">
|
||||
<button class="nav-link @(activeTab == "recent" ? "active" : "")" @onclick='() => activeTab = "recent"'>
|
||||
Recent <span class="badge bg-secondary ms-1">@data.RecentTransactions.Count</span>
|
||||
<button class="nav-link @(activeTab == "recent" ? "active" : "")" @onclick='() => { activeTab = "recent"; recentPage = 1; }'>
|
||||
Recent <span class="badge bg-secondary ms-1">@filteredRecent.Count</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button class="nav-link @(activeTab == "upcoming" ? "active" : "")" @onclick='() => activeTab = "upcoming"'>
|
||||
Upcoming <span class="badge bg-secondary ms-1">@data.UpcomingTransactions.Count</span>
|
||||
<button class="nav-link @(activeTab == "upcoming" ? "active" : "")" @onclick='() => { activeTab = "upcoming"; upcomingPage = 1; }'>
|
||||
Upcoming <span class="badge bg-secondary ms-1">@filteredUpcoming.Count</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="btn btn-sm btn-primary" @onclick="OpenAddModal">+ Add</button>
|
||||
</div>
|
||||
@if (data.Accounts.Count > 1)
|
||||
{
|
||||
<div class="px-3 py-2 border-bottom d-flex flex-wrap align-items-center gap-2">
|
||||
<small class="text-muted me-1">Filter:</small>
|
||||
@foreach (var a in data.Accounts)
|
||||
{
|
||||
<button class="btn btn-sm px-2 py-0 @(hiddenAccountIds.Contains(a.Id) ? "btn-outline-secondary" : "")"
|
||||
style="font-size: 0.75rem; @(hiddenAccountIds.Contains(a.Id) ? "opacity: 0.5;" : $"background-color: {a.Color}; color: white;")"
|
||||
@onclick="() => ToggleAccountFilter(a.Id)">
|
||||
@a.Name
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="card-body p-0" style="max-height: 500px; overflow-y: auto;">
|
||||
@if (activeTab == "recent")
|
||||
{
|
||||
@if (data.RecentTransactions.Count == 0)
|
||||
@if (filteredRecent.Count == 0)
|
||||
{
|
||||
<p class="p-3 text-muted mb-0">No recent transactions.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list-group list-group-flush">
|
||||
@foreach (var t in data.RecentTransactions.Skip((recentPage - 1) * PageSize).Take(PageSize))
|
||||
@foreach (var t in filteredRecent.Skip((recentPage - 1) * PageSize).Take(PageSize))
|
||||
{
|
||||
<div class="list-group-item list-group-item-action txn-row @(selectedTransactionId == t.Id ? "txn-row-selected" : "")"
|
||||
style="cursor: pointer; border-left: 3px solid @(t.Type == TransactionType.Income ? "#198754" : "#dc3545");"
|
||||
@@ -214,14 +228,14 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (data.UpcomingTransactions.Count == 0)
|
||||
@if (filteredUpcoming.Count == 0)
|
||||
{
|
||||
<p class="p-3 text-muted mb-0">No upcoming transactions.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="list-group list-group-flush">
|
||||
@foreach (var t in data.UpcomingTransactions.Skip((upcomingPage - 1) * PageSize).Take(PageSize))
|
||||
@foreach (var t in filteredUpcoming.Skip((upcomingPage - 1) * PageSize).Take(PageSize))
|
||||
{
|
||||
<div class="list-group-item list-group-item-action txn-row @(selectedTransactionId == t.Id ? "txn-row-selected" : "")"
|
||||
style="cursor: pointer; border-left: 3px solid @(t.Type == TransactionType.Income ? "#198754" : "#dc3545");"
|
||||
@@ -331,12 +345,29 @@ else
|
||||
private DateTime windowEnd = DateTime.Today.AddDays(30);
|
||||
private string windowPreset = "30d";
|
||||
|
||||
// Account filter
|
||||
private HashSet<int> hiddenAccountIds = [];
|
||||
|
||||
private List<Transaction> filteredRecent => data is null ? []
|
||||
: data.RecentTransactions.Where(t => !hiddenAccountIds.Contains(t.AccountId)).ToList();
|
||||
|
||||
private List<Transaction> filteredUpcoming => data is null ? []
|
||||
: data.UpcomingTransactions.Where(t => !hiddenAccountIds.Contains(t.AccountId)).ToList();
|
||||
|
||||
private void ToggleAccountFilter(int accountId)
|
||||
{
|
||||
if (!hiddenAccountIds.Remove(accountId))
|
||||
hiddenAccountIds.Add(accountId);
|
||||
recentPage = 1;
|
||||
upcomingPage = 1;
|
||||
}
|
||||
|
||||
// Pagination
|
||||
private int recentPage = 1;
|
||||
private int upcomingPage = 1;
|
||||
private const int PageSize = 10;
|
||||
private int RecentTotalPages => data is null ? 0 : (int)Math.Ceiling(data.RecentTransactions.Count / (double)PageSize);
|
||||
private int UpcomingTotalPages => data is null ? 0 : (int)Math.Ceiling(data.UpcomingTransactions.Count / (double)PageSize);
|
||||
private int RecentTotalPages => (int)Math.Ceiling(filteredRecent.Count / (double)PageSize);
|
||||
private int UpcomingTotalPages => (int)Math.Ceiling(filteredUpcoming.Count / (double)PageSize);
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
||||
@@ -107,7 +107,7 @@ else if (transactions.Count == 0)
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center gap-2">
|
||||
<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;" @onchange="OnAccountFilterChanged">
|
||||
<option value="">All Accounts</option>
|
||||
@@ -119,6 +119,15 @@ else
|
||||
}
|
||||
}
|
||||
</select>
|
||||
<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>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
@@ -134,7 +143,7 @@ else
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var t in filteredTransactions)
|
||||
@foreach (var t in pagedTransactions)
|
||||
{
|
||||
<tr>
|
||||
<td>@t.Date.ToString("MMM dd, yyyy")</td>
|
||||
@@ -184,6 +193,20 @@ else
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if (totalPages > 1)
|
||||
{
|
||||
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||
<button class="btn btn-sm btn-outline-secondary" disabled="@(currentPage <= 1 ? true : (bool?)null)" @onclick="() => currentPage--">← Prev</button>
|
||||
<small class="text-muted">Page @currentPage of @totalPages (@filteredTransactions.Count transactions)</small>
|
||||
<button class="btn btn-sm btn-outline-secondary" disabled="@(currentPage >= totalPages ? true : (bool?)null)" @onclick="() => currentPage++">Next →</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card-footer">
|
||||
<small class="text-muted">@filteredTransactions.Count transactions</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (showDeleteModal && pendingDeleteTransaction is not null)
|
||||
@@ -293,6 +316,16 @@ else
|
||||
private string? userId;
|
||||
private int? filterAccountId;
|
||||
|
||||
// Pagination state
|
||||
private int currentPage = 1;
|
||||
private int pageSize = 25;
|
||||
private static readonly int[] pageSizeOptions = [10, 25, 50, 100];
|
||||
private int totalPages => (int)Math.Ceiling(filteredTransactions.Count / (double)pageSize);
|
||||
private List<Transaction> pagedTransactions => filteredTransactions
|
||||
.Skip((currentPage - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
// Sort state
|
||||
private string sortColumn = "date";
|
||||
private bool sortAscending = true;
|
||||
@@ -325,6 +358,16 @@ else
|
||||
private void OnAccountFilterChanged(ChangeEventArgs e)
|
||||
{
|
||||
filterAccountId = int.TryParse(e.Value?.ToString(), out var id) ? id : null;
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
private void OnPageSizeChanged(ChangeEventArgs e)
|
||||
{
|
||||
if (int.TryParse(e.Value?.ToString(), out var size))
|
||||
{
|
||||
pageSize = size;
|
||||
currentPage = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSort(string column)
|
||||
@@ -336,6 +379,7 @@ else
|
||||
sortColumn = column;
|
||||
sortAscending = true;
|
||||
}
|
||||
currentPage = 1;
|
||||
}
|
||||
|
||||
private MarkupString SortIndicator(string column)
|
||||
|
||||
Reference in new issue
Block a user