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;