From 3adc3ae3a182d8cc961ff87f6ee49d81486d7cd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20A=2EP?= <53834183+Jossec101@users.noreply.github.com> Date: Thu, 19 Feb 2026 21:29:59 +0100 Subject: [PATCH] [GEN-1863] Wallets pagination + filtering Add GetPaginatedAsync method to WalletRepository and IWalletRepository interface with comprehensive filtering options including name, status, wallet type, date range, and various boolean filters. Update Wallets page UI to support paginated display with filter controls for improved wallet management and navigation. stack-info: PR: https://github.com/Elenpay/NodeGuard/pull/484, branch: Jossec101/stack/17 --- .../Interfaces/IWalletRepository.cs | 13 ++ src/Data/Repositories/WalletRepository.cs | 84 +++++++++ src/Pages/Wallets.razor | 177 ++++++++++++++---- 3 files changed, 240 insertions(+), 34 deletions(-) diff --git a/src/Data/Repositories/Interfaces/IWalletRepository.cs b/src/Data/Repositories/Interfaces/IWalletRepository.cs index b552700f..dd60dfee 100644 --- a/src/Data/Repositories/Interfaces/IWalletRepository.cs +++ b/src/Data/Repositories/Interfaces/IWalletRepository.cs @@ -27,6 +27,19 @@ public interface IWalletRepository Task GetById(int id); Task> GetAll(); + + Task<(List wallets, int totalCount)> GetPaginatedAsync( + int pageNumber, + int pageSize, + string? nameFilter = null, + string? statusFilter = null, + bool archivedFilter = false, + bool compromisedFilter = false, + bool hotWalletFilter = false, + bool coldWalletFilter = false, + bool finalisedFilter = false, + DateTimeOffset? fromDate = null, + DateTimeOffset? toDate = null); Task> GetAvailableByType(WALLET_TYPE type); diff --git a/src/Data/Repositories/WalletRepository.cs b/src/Data/Repositories/WalletRepository.cs index 2144ef93..7b2d4460 100644 --- a/src/Data/Repositories/WalletRepository.cs +++ b/src/Data/Repositories/WalletRepository.cs @@ -70,6 +70,90 @@ public async Task> GetAll() return await applicationDbContext.Wallets.Include(x => x.InternalWallet).Include(x => x.Keys).ToListAsync(); } + public async Task<(List wallets, int totalCount)> GetPaginatedAsync( + int pageNumber, + int pageSize, + string? nameFilter = null, + string? statusFilter = null, + bool archivedFilter = false, + bool compromisedFilter = false, + bool hotWalletFilter = false, + bool coldWalletFilter = false, + bool finalisedFilter = false, + DateTimeOffset? fromDate = null, + DateTimeOffset? toDate = null) + { + await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync(); + + var query = applicationDbContext.Wallets + .Include(x => x.InternalWallet) + .Include(x => x.Keys) + .AsQueryable(); + + if (!string.IsNullOrWhiteSpace(nameFilter)) + { + query = query.Where(w => w.Name.Contains(nameFilter)); + } + + switch (statusFilter) + { + case "Finalised": + query = query.Where(w => w.IsFinalised); + break; + case "Not Finalised": + query = query.Where(w => !w.IsFinalised); + break; + case "Archived": + query = query.Where(w => w.IsArchived); + break; + case "Not Archived": + query = query.Where(w => !w.IsArchived); + break; + } + + if (fromDate.HasValue) + { + query = query.Where(w => w.CreationDatetime >= fromDate.Value); + } + + if (toDate.HasValue) + { + query = query.Where(w => w.CreationDatetime <= toDate.Value); + } + + if (!archivedFilter && statusFilter != "Archived") + { + query = query.Where(w => !w.IsArchived); + } + + if (!compromisedFilter) + { + query = query.Where(w => !w.IsCompromised); + } + + var anyPropertyFilter = archivedFilter || compromisedFilter || hotWalletFilter || coldWalletFilter || finalisedFilter; + if (anyPropertyFilter) + { + query = query.Where(w => + (archivedFilter && w.IsArchived) || + (compromisedFilter && w.IsCompromised) || + (hotWalletFilter && w.IsHotWallet) || + (coldWalletFilter && !w.IsHotWallet) || + (finalisedFilter && w.IsFinalised)); + } + + query = query.OrderByDescending(w => w.CreationDatetime); + + var totalCount = await query.CountAsync(); + + var wallets = await query + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + return (wallets, totalCount); + } + public async Task> GetAvailableByType(WALLET_TYPE type) { await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync(); diff --git a/src/Pages/Wallets.razor b/src/Pages/Wallets.razor index a4febb82..1fd22826 100644 --- a/src/Pages/Wallets.razor +++ b/src/Pages/Wallets.razor @@ -2,6 +2,7 @@ @using System.Security.Claims @using System.Linq; @using Blazorise.Extensions +@using Blazorise.Components @using NodeGuard.Jobs @using Humanizer @using NBitcoin @@ -31,10 +32,64 @@

Treasury Wallets

+ + + + Name + + + + + + Properties + + Select + +
+ 🔥 Hot Wallet + ❄️ Cold Wallet + 🏁 Finalised + 📂 Archived + ⛔️ Compromised +
+
+
+
+
+ + + From + + + + + + To + + + + + + +
+ >

@(context.EditState) wallet

@@ -130,21 +185,7 @@ - - - - Select - -
- 🔥 Hot Wallet - ❄️ Cold Wallet - 🏁 finalised - 📂 Archived - ⛔️ Compromised -
-
-
-
+ Wallet Properties @@ -828,7 +869,14 @@ OnSubmit="TransferFundsHotWallet"/> private bool _finalisedWalletFilter = false; private bool _archivedWalletFilter = false; private bool _compromisedWalletFilter = false; + private string _nameFilter = string.Empty; + private List _walletNameOptions = new(); + private string _walletStatusFilter = "All"; + private DateTime? _fromDateFilter; + private DateTime? _toDateFilter; + private int _filtersResetKey; private DataGrid _walletsDataGrid; + private int _totalItems; private ColumnLayout WalletsColumnLayout; private Dictionary WalletsColumns = new(); @@ -888,16 +936,72 @@ OnSubmit="TransferFundsHotWallet"/> private async Task GetData() { - _wallets = await WalletRepository.GetAll(); var financeManagers = (await ApplicationUserRepository.GetUsersInRole(ApplicationUserRole.FinanceManager)); _financeManagers = financeManagers.Where(x => x.Keys.Any()).ToList(); + if (_walletNameOptions.Count == 0) + { + var walletNames = await WalletRepository.GetAll(); + _walletNameOptions = walletNames + .Select(wallet => wallet.Name) + .Where(name => !string.IsNullOrWhiteSpace(name)) + .Distinct() + .OrderBy(name => name) + .ToList(); + } + if (_financeManagers.Any()) _selectedFinanceManager = financeManagers.FirstOrDefault(); if (_selectedFinanceManager?.Keys != null) _selectedFinanceManagerAvailableKeys = await FilterKeys(_selectedFinanceManager.Keys); } + private async Task OnReadData(DataGridReadDataEventArgs e) + { + var fromDate = _fromDateFilter.HasValue + ? new DateTimeOffset(DateTime.SpecifyKind(_fromDateFilter.Value.Date, DateTimeKind.Local)).ToUniversalTime() + : (DateTimeOffset?)null; + var toDate = _toDateFilter.HasValue + ? new DateTimeOffset(DateTime.SpecifyKind(_toDateFilter.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Local)).ToUniversalTime() + : (DateTimeOffset?)null; + + var (wallets, totalCount) = await WalletRepository.GetPaginatedAsync( + e.Page, + e.PageSize, + nameFilter: _nameFilter, + statusFilter: _walletStatusFilter, + archivedFilter: _archivedWalletFilter, + compromisedFilter: _compromisedWalletFilter, + hotWalletFilter: _hotWalletFilter, + coldWalletFilter: _coldWalletFilter, + finalisedFilter: _finalisedWalletFilter, + fromDate: fromDate, + toDate: toDate); + + _wallets = wallets; + _totalItems = totalCount; + } + + private async Task OnFiltersChanged() + { + await _walletsDataGrid.Reload(); + } + + private async Task ClearAllFilters() + { + _hotWalletFilter = false; + _coldWalletFilter = false; + _finalisedWalletFilter = false; + _archivedWalletFilter = false; + _compromisedWalletFilter = false; + _nameFilter = string.Empty; + _walletStatusFilter = "All"; + _fromDateFilter = null; + _toDateFilter = null; + _filtersResetKey++; + await _walletsDataGrid.Reload(); + } + private async Task OnRowInserted(SavedRowItem> arg) { @@ -917,7 +1021,8 @@ OnSubmit="TransferFundsHotWallet"/> AuditObjectType.Wallet, arg.Item.Id.ToString(), new { Name = arg.Item.Name, IsHotWallet = arg.Item.IsHotWallet, MofN = arg.Item.MofN }); - await GetData(); + AddWalletNameOption(arg.Item.Name); + await _walletsDataGrid.Reload(); } else { @@ -963,7 +1068,7 @@ OnSubmit="TransferFundsHotWallet"/> AuditObjectType.Wallet, arg.Item.Id.ToString(), new { Name = arg.Item.Name }); - await GetData(); + await _walletsDataGrid.Reload(); } } } @@ -1000,6 +1105,7 @@ OnSubmit="TransferFundsHotWallet"/> AuditObjectType.Wallet, arg.Item.Id.ToString(), new { Name = arg.Item.Name, IsHotWallet = arg.Item.IsHotWallet }); + AddWalletNameOption(arg.Item.Name); } else { @@ -1012,7 +1118,7 @@ OnSubmit="TransferFundsHotWallet"/> new { Name = arg.Item.Name, Error = updateResult.Item2 }); } - await GetData(); + await _walletsDataGrid.Reload(); } private async Task CloseModal() @@ -1091,7 +1197,7 @@ OnSubmit="TransferFundsHotWallet"/> CleanModal(); - await GetData(); + await _walletsDataGrid.Reload(); await _modalRef.Close(CloseReason.UserClosing); } @@ -1320,7 +1426,7 @@ OnSubmit="TransferFundsHotWallet"/> await _finaliseModalRef.Close(CloseReason.UserClosing); - await GetData(); + await _walletsDataGrid.Reload(); } private async Task CloseAndCleanFinaliseModal() @@ -1403,7 +1509,7 @@ OnSubmit="TransferFundsHotWallet"/> } //Load data - await GetData(); + await _walletsDataGrid.Reload(); //Success ToastService.ShowSuccess("Wallet imported successfully"); @@ -1725,17 +1831,20 @@ OnSubmit="TransferFundsHotWallet"/> await CloseTextModal(); } - private bool OnWalletCustomFilter(Wallet wallet) + private void AddWalletNameOption(string? name) { - bool anyCheck = _archivedWalletFilter || _compromisedWalletFilter || _hotWalletFilter || _coldWalletFilter || _finalisedWalletFilter; - if (!anyCheck) - return true; + if (string.IsNullOrWhiteSpace(name)) + { + return; + } - return _archivedWalletFilter && wallet.IsArchived - || _compromisedWalletFilter && wallet.IsCompromised - || _hotWalletFilter && wallet.IsHotWallet - || _coldWalletFilter && !wallet.IsHotWallet - || _finalisedWalletFilter && wallet.IsFinalised; + if (_walletNameOptions.Contains(name)) + { + return; + } + + _walletNameOptions.Add(name); + _walletNameOptions = _walletNameOptions.OrderBy(option => option).ToList(); } private void OnColumnLayoutUpdate() @@ -1754,7 +1863,7 @@ OnSubmit="TransferFundsHotWallet"/> private Task OnCheckedChanged(bool value) { - return _walletsDataGrid.Reload(); + return Task.CompletedTask; } private async Task ValidateOutputDescriptor(ValidatorEventArgs arg1, CancellationToken arg2)