From aff2e3a778575816e89c3ba82c285ea27d4a1305 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:28:11 +0100 Subject: [PATCH] [GEN-1863] add filtering parameters to wallet withdrawal request pagination Added optional filtering parameters (status, walletId, userId, fromDate, toDate) to GetPaginatedAsync method in IWalletWithdrawalRequestRepository interface and implementation to enable filtered retrieval of wallet withdrawal requests. stack-info: PR: https://github.com/Elenpay/NodeGuard/pull/481, branch: Jossec101/stack/14 --- .../IWalletWithdrawalRequestRepository.cs | 10 +- .../WalletWithdrawalRequestRepository.cs | 25 +- src/Pages/Withdrawals.razor | 246 ++++++++++-------- 3 files changed, 167 insertions(+), 114 deletions(-) diff --git a/src/Data/Repositories/Interfaces/IWalletWithdrawalRequestRepository.cs b/src/Data/Repositories/Interfaces/IWalletWithdrawalRequestRepository.cs index ca065bfc..e744b116 100644 --- a/src/Data/Repositories/Interfaces/IWalletWithdrawalRequestRepository.cs +++ b/src/Data/Repositories/Interfaces/IWalletWithdrawalRequestRepository.cs @@ -30,7 +30,15 @@ public interface IWalletWithdrawalRequestRepository : IBitcoinRequestRepository Task> GetAll(); - Task<(List Requests, int TotalCount)> GetPaginatedAsync(int pageNumber, int pageSize, IEnumerable? excludedRequestIds = null); + Task<(List Requests, int TotalCount)> GetPaginatedAsync( + int pageNumber, + int pageSize, + IEnumerable? excludedRequestIds = null, + WalletWithdrawalRequestStatus? status = null, + int? walletId = null, + string? userId = null, + DateTimeOffset? fromDate = null, + DateTimeOffset? toDate = null); Task> GetUnsignedPendingRequestsByUser(string userId); diff --git a/src/Data/Repositories/WalletWithdrawalRequestRepository.cs b/src/Data/Repositories/WalletWithdrawalRequestRepository.cs index e1afcb4d..bc81b8ed 100644 --- a/src/Data/Repositories/WalletWithdrawalRequestRepository.cs +++ b/src/Data/Repositories/WalletWithdrawalRequestRepository.cs @@ -100,7 +100,15 @@ public async Task> GetAll() .ToListAsync(); } - public async Task<(List Requests, int TotalCount)> GetPaginatedAsync(int pageNumber, int pageSize, IEnumerable? excludedRequestIds = null) + public async Task<(List Requests, int TotalCount)> GetPaginatedAsync( + int pageNumber, + int pageSize, + IEnumerable? excludedRequestIds = null, + WalletWithdrawalRequestStatus? status = null, + int? walletId = null, + string? userId = null, + DateTimeOffset? fromDate = null, + DateTimeOffset? toDate = null) { await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync(); @@ -132,6 +140,21 @@ public async Task> GetAll() } } + if (status.HasValue) + query = query.Where(x => x.Status == status.Value); + + if (walletId.HasValue) + query = query.Where(x => x.WalletId == walletId.Value); + + if (!string.IsNullOrEmpty(userId)) + query = query.Where(x => x.UserRequestorId == userId); + + if (fromDate.HasValue) + query = query.Where(x => x.CreationDatetime >= fromDate.Value); + + if (toDate.HasValue) + query = query.Where(x => x.CreationDatetime <= toDate.Value); + query = query.OrderByDescending(x => x.CreationDatetime); var totalCount = await query.CountAsync(); diff --git a/src/Pages/Withdrawals.razor b/src/Pages/Withdrawals.razor index 11dfa49e..cd3b0171 100644 --- a/src/Pages/Withdrawals.razor +++ b/src/Pages/Withdrawals.razor @@ -2,6 +2,7 @@ @using System.Security.Claims @using Blazorise.Extensions @using Blazorise.DataGrid +@using Blazorise.Components @using Quartz @using Humanizer @using NBitcoin @@ -15,12 +16,14 @@ @using NodeGuard.Services; @using Polly @attribute [Authorize(Roles = "Superadmin,NodeManager,FinanceManager")] +

Withdrawals

@if (_isFinanceManager) {

Withdrawal requests awaiting my signature

+ @* TODO: Convert this grid to paginated ReadData to avoid loading all records in memory. *@ @@ -78,7 +80,7 @@ } - + @context.Description.Truncate(40) @@ -94,7 +96,7 @@ - + @if (context.Wallet != null) { @@ -134,12 +136,12 @@ - + @ShowRequestor(context) - + @(context.WalletWithdrawalRequestDestinations?.FirstOrDefault()?.Address ?? "No address provided") @@ -166,7 +168,7 @@ } - + @{ @($"{context.TotalAmount:f8} BTC ({Math.Round(PriceConversionService.BtcToUsdConversion(context.TotalAmount, _btcPrice), 2)} USD)") @@ -203,7 +205,7 @@ } - + @(context.MempoolRecommendedFeesType == MempoolRecommendedFeesType.CustomFee ? $"Custom fee ({context.CustomFeeRate} sat/vb)" : $"{context.MempoolRecommendedFeesType.Humanize()} ({context.CustomFeeRate} sat/vb)") @@ -227,7 +229,7 @@ @(_selectedMempoolRecommendedFeesType != MempoolRecommendedFeesType.CustomFee ? "Fees may change by the time the request is first signed" : "") - + @{ if (context.Wallet != null) @@ -239,26 +241,21 @@ } - + @context?.Status.Humanize() - + @context.CreationDatetime.Humanize() - + @context.UpdateDatetime.Humanize() - - - - - @@ -278,13 +275,78 @@

Withdrawal requests

+ + + + Status + + + + + + Wallet + + + + + + Requestor + + + + + + From Date + + + + + + To Date + + + + + + + - + @context.Description.Truncate(40) - + @if (context.Wallet != null) { @@ -331,18 +393,18 @@ } - + @ShowRequestor(context) - + @context.WalletWithdrawalRequestDestinations?.FirstOrDefault()?.Address @* TODO Copy and explorer buttons *@ - + @{ @($"{context.TotalAmount:f8} BTC ({Math.Round(PriceConversionService.BtcToUsdConversion(context.TotalAmount, _btcPrice), 2)} USD)") @@ -350,12 +412,12 @@ - + @(context.MempoolRecommendedFeesType == MempoolRecommendedFeesType.CustomFee ? $"Custom fee ({context.CustomFeeRate} sat/vb)" : $"{context.MempoolRecommendedFeesType.Humanize()} ({context.CustomFeeRate} sat/vb)") - + @{ if (context.Wallet != null) @@ -367,24 +429,24 @@ } - + @context?.Status.Humanize() - + @context.CreationDatetime.Humanize() - + @context.UpdateDatetime.Humanize() - + @if (mempoolUrl != null && !string.IsNullOrEmpty(context.TxId)) { @@ -392,7 +454,7 @@ } - + @if (_isFinanceManager && context.Status == WalletWithdrawalRequestStatus.OnChainConfirmationPending) { @@ -406,12 +468,6 @@ } - - - - - -
@@ -476,6 +532,7 @@ @inject INBXplorerService NBXplorerService @inject IFMUTXORepository FMUTXORepository @inject IWalletWithdrawalRequestDestinationRepository WalletWithdrawalRequestDestinationRepository +@inject IApplicationUserRepository ApplicationUserRepository @inject ILogger Logger @code { @@ -484,9 +541,9 @@ [CascadingParameter] private ClaimsPrincipal? ClaimsPrincipal { get; set; } - private ColumnLayout _pendingRequestsColumnLayout; private List _userPendingRequests = new(); private List _availableWallets = new(); + private List _availableUsers = new(); private List _withdrawalRequests = new(); private int _totalWithdrawalRequests; private int _currentPage = 1; @@ -513,43 +570,20 @@ private WalletWithdrawalRequestStatus? _rejectCancelStatus = WalletWithdrawalRequestStatus.Rejected; private decimal? _selectedRequestWalletBalance; private decimal _btcPrice; - private ColumnLayout _allRequestsColumnLayout; - private Dictionary _pendingRequestsColumns = new(); - private Dictionary _allRequestsColumns = new(); - private bool _columnsLoaded; private bool _isCheckedAllFunds; private MempoolRecommendedFeesType _selectedMempoolRecommendedFeesType; private long _customSatPerVbAmount = 1; - public abstract class PendingWithdrawalsColumnName - { - public static readonly ColumnDefault Description = new("Description"); - public static readonly ColumnDefault Wallet = new("Wallet"); - public static readonly ColumnDefault Requestor = new("Requestor"); - public static readonly ColumnDefault DestinationAddress = new("Destination Address"); - public static readonly ColumnDefault Amount = new("Amount (BTC)"); - public static readonly ColumnDefault FeeRate = new("Fee Rate"); - public static readonly ColumnDefault SignaturesCollected = new("Signatures Collected"); - public static readonly ColumnDefault Status = new("Status"); - public static readonly ColumnDefault CreationDate = new("Creation Date"); - public static readonly ColumnDefault UpdateDate = new("Update Date"); - } + // Filter state + private WalletWithdrawalRequestStatus? _statusFilter; + private int? _walletFilter; + private string? _userFilter; + private int _filtersResetKey; + private DateTime? _fromDate; + private DateTime? _toDate; - public abstract class AllWithdrawalsColumnName - { - public static readonly ColumnDefault Description = new("Description"); - public static readonly ColumnDefault Wallet = new("Wallet"); - public static readonly ColumnDefault Requestor = new("Requestor"); - public static readonly ColumnDefault DestinationAddress = new("Destination Address"); - public static readonly ColumnDefault Amount = new("Amount (BTC)"); - public static readonly ColumnDefault FeeRate = new("Fee Rate"); - public static readonly ColumnDefault SignaturesCollected = new("Signatures Collected"); - public static readonly ColumnDefault Status = new("Status"); - public static readonly ColumnDefault CreationDate = new("Creation Date"); - public static readonly ColumnDefault UpdateDate = new("Update Date"); - public static readonly ColumnDefault Links = new("Links"); - public static readonly ColumnDefault Actions = new("Actions"); - } + // Filter options + private List _statusOptions = new() { null }; protected override async Task OnInitializedAsync() { @@ -562,16 +596,11 @@ } _isFinanceManager = ClaimsPrincipal != null && ClaimsPrincipal.IsInRole(ApplicationUserRole.FinanceManager.ToString()); - await GetData(); - await LoadColumnLayout(); - } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender && !_columnsLoaded) - { - await LoadColumnLayout(); - } + // Initialize enum options + _statusOptions.AddRange(Enum.GetValues().Cast()); + + await GetData(); } private decimal SelectedUTXOsValue() @@ -579,14 +608,6 @@ return _selectedUTXOs.Sum(x => ((Money) x.Value).ToUnit(MoneyUnit.BTC)); } - private async Task LoadColumnLayout() - { - _allRequestsColumns = await LocalStorageService.LoadStorage(nameof(AllWithdrawalsColumnName), ColumnHelpers.GetColumnsDictionary()); - _pendingRequestsColumns = await LocalStorageService.LoadStorage(nameof(PendingWithdrawalsColumnName), ColumnHelpers.GetColumnsDictionary()); - _columnsLoaded = true; - StateHasChanged(); - } - private async Task GetData() { if (LoggedUser?.Id != null && _isFinanceManager) @@ -602,6 +623,8 @@ //TODO Fix BIP39 withdrawals, until then, manual hack to filter them out _availableWallets = _availableWallets.Where(x => !x.IsBIP39Imported).ToList(); + _availableUsers = await ApplicationUserRepository.GetAll(); + await ReloadAllWithdrawalsAsync(); } @@ -625,7 +648,15 @@ } var excludedIds = _userPendingRequests.Select(request => request.Id); - var (requests, totalCount) = await WalletWithdrawalRequestRepository.GetPaginatedAsync(pageNumber, pageSize, excludedIds); + var (requests, totalCount) = await WalletWithdrawalRequestRepository.GetPaginatedAsync( + pageNumber, + pageSize, + excludedIds, + status: _statusFilter, + walletId: _walletFilter, + userId: _userFilter, + fromDate: _fromDate.HasValue ? new DateTimeOffset(_fromDate.Value, TimeSpan.Zero) : null, + toDate: _toDate.HasValue ? new DateTimeOffset(_toDate.Value.AddDays(1).AddTicks(-1), TimeSpan.Zero) : null); if (cancellationToken.IsCancellationRequested) { @@ -643,6 +674,22 @@ await LoadAllWithdrawalRequestsAsync(e.Page, e.PageSize, e.CancellationToken); } + private async Task OnFiltersChanged() + { + await ReloadAllWithdrawalsAsync(); + } + + private async Task ClearAllFilters() + { + _statusFilter = null; + _walletFilter = null; + _userFilter = null; + _fromDate = null; + _toDate = null; + _filtersResetKey++; + await ReloadAllWithdrawalsAsync(); + } + private Task LogWithdrawalAuditAsync( AuditActionType actionType, AuditEventType eventType, @@ -1540,31 +1587,6 @@ return false; } - private void OnColumnLayoutUpdate() - { - StateHasChanged(); - } - - private bool IsPendingRequestsColumnVisible(ColumnDefault column) - { - if (_pendingRequestsColumnLayout == null) - { - return true; - } - - return _pendingRequestsColumnLayout.IsColumnVisible(column); - } - - private bool IsAllRequestsColumnVisible(ColumnDefault column) - { - if (_allRequestsColumnLayout == null) - { - return true; - } - - return _allRequestsColumnLayout.IsColumnVisible(column); - } - private async Task OnShowNewWalletWithdrawalModal() { _selectedMempoolRecommendedFeesType = MempoolRecommendedFeesType.FastestFee;