diff --git a/src/Data/Repositories/Interfaces/ISwapOutRepository.cs b/src/Data/Repositories/Interfaces/ISwapOutRepository.cs index 0e3780af..6ee61f21 100644 --- a/src/Data/Repositories/Interfaces/ISwapOutRepository.cs +++ b/src/Data/Repositories/Interfaces/ISwapOutRepository.cs @@ -26,7 +26,17 @@ public interface ISwapOutRepository { Task GetById(int id); Task> GetByIds(List ids); - Task<(List swaps, int totalCount)> GetPaginatedAsync(int pageNumber, int pageSize); + Task<(List swaps, int totalCount)> GetPaginatedAsync( + int pageNumber, + int pageSize, + SwapOutStatus? status = null, + SwapProvider? provider = null, + int? nodeId = null, + int? walletId = null, + string? userId = null, + bool? isManual = null, + DateTimeOffset? fromDate = null, + DateTimeOffset? toDate = null); Task> GetAllPending(); Task<(bool, string?)> AddAsync(SwapOut swap); Task<(bool, string?)> AddRangeAsync(List swaps); diff --git a/src/Data/Repositories/SwapOutRepository.cs b/src/Data/Repositories/SwapOutRepository.cs index 939324bf..feb9a0dd 100644 --- a/src/Data/Repositories/SwapOutRepository.cs +++ b/src/Data/Repositories/SwapOutRepository.cs @@ -60,7 +60,17 @@ public async Task> GetByIds(List ids) return swaps; } - public async Task<(List swaps, int totalCount)> GetPaginatedAsync(int pageNumber, int pageSize) + public async Task<(List swaps, int totalCount)> GetPaginatedAsync( + int pageNumber, + int pageSize, + SwapOutStatus? status = null, + SwapProvider? provider = null, + int? nodeId = null, + int? walletId = null, + string? userId = null, + bool? isManual = null, + DateTimeOffset? fromDate = null, + DateTimeOffset? toDate = null) { await using var context = await _dbContextFactory.CreateDbContextAsync(); @@ -69,7 +79,33 @@ public async Task> GetByIds(List ids) .ThenInclude(w => w!.Keys) .Include(s => s.UserRequestor) .Include(s => s.Node) - .OrderByDescending(s => s.CreationDatetime); + .AsQueryable(); + + if (status.HasValue) + query = query.Where(s => s.Status == status.Value); + + if (provider.HasValue) + query = query.Where(s => s.Provider == provider.Value); + + if (nodeId.HasValue) + query = query.Where(s => s.NodeId == nodeId.Value); + + if (walletId.HasValue) + query = query.Where(s => s.DestinationWalletId == walletId.Value); + + if (!string.IsNullOrEmpty(userId)) + query = query.Where(s => s.UserRequestorId == userId); + + if (isManual.HasValue) + query = query.Where(s => s.IsManual == isManual.Value); + + if (fromDate.HasValue) + query = query.Where(s => s.CreationDatetime >= fromDate.Value); + + if (toDate.HasValue) + query = query.Where(s => s.CreationDatetime <= toDate.Value); + + query = query.OrderByDescending(s => s.CreationDatetime); var totalCount = await query.CountAsync(); diff --git a/src/Pages/Swaps.razor b/src/Pages/Swaps.razor index d89c1709..5ce22333 100644 --- a/src/Pages/Swaps.razor +++ b/src/Pages/Swaps.razor @@ -2,18 +2,118 @@ @using System.Security.Claims @using Humanizer @using Blazorise +@using Blazorise.Components @attribute [Authorize(Roles = "Superadmin,NodeManager,FinanceManager")] +

Swaps

+ + + + + Provider + + + + + + Status + + + + + + Source Node + + + + + + Requestor + + + + + + Is Manual + + + + + + From Date + + + + + + To Date + + + + + + + -

Swaps

- + @(context.Node?.Name ?? "Unknown") - + @($"{context.DestinationWallet?.Name} ({context.DestinationWallet?.MofN}-of-{context.DestinationWallet?.Keys.Count})") - + @(context.IsManual ? (context.UserRequestor?.UserName ?? "Unknown") : "Autoswap") - + @($"{context.Amount:f8} BTC ({Math.Round(PriceConversionService.BtcToUsdConversion(context.Amount, _btcPrice), 2)} USD)") - + @context.TotalFees.ToUnit(NBitcoin.MoneyUnit.BTC).ToString("f8") BTC - + @context.Status.Humanize() - + @context.CreationDatetime.Humanize() - + @context.UpdateDatetime.Humanize() - + @if (Constants.MEMPOOL_ENDPOINT != null && !string.IsNullOrEmpty(context.TxId)) { @@ -94,11 +194,6 @@ } - - - - -
@@ -118,9 +213,9 @@ @inject ILocalStorageService LocalStorageService @inject IPriceConversionService PriceConversionService -@inject IWalletRepository WalletRepository @inject INodeRepository NodeRepository -@inject ISwapOutRepository SwapOutRepository +@inject ISwapOutRepository SwapOutRepository +@inject IApplicationUserRepository ApplicationUserRepository @inject IToastService ToastService @code { [CascadingParameter] private ApplicationUser? LoggedUser { get; set; } @@ -129,27 +224,38 @@ private DataGrid _swapsDatagrid = new(); private List _swapRequests = new(); - private List _availableWallets = new(); private List _availableNodes = new(); + private List _availableUsers = new(); private decimal _btcPrice; - private Dictionary _swapsColumns = new(); - private bool _columnsLoaded; - private ColumnLayout _swapsColumnLayout = new(); public required NewSwapModal _newSwapModal; private int _totalItems; private int _currentPage = 1; private int _pageSize = 10; - public abstract class SwapsColumnName + // Filter state + private SwapProvider? _providerFilter; + private SwapOutStatus? _statusFilter; + private int? _nodeFilter; + private string? _userFilter; + private bool? _isManualFilter; + private int _filtersResetKey; + private DateTime? _fromDate; + private DateTime? _toDate; + + // Filter options + private List _providerOptions = new() { null }; + private List _statusOptions = new() { null }; + private List _isManualOptions = new() + { + new BoolOption { Label = "All", Value = null }, + new BoolOption { Label = "Yes", Value = true }, + new BoolOption { Label = "No", Value = false } + }; + + public class BoolOption { - public static readonly ColumnDefault DestinationWallet = new("Destination Wallet"); - public static readonly ColumnDefault Requestor = new("Requestor"); - public static readonly ColumnDefault Amount = new("Amount (BTC)"); - public static readonly ColumnDefault TotalFee = new("Total Fee (BTC)"); - 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 string Label { get; set; } = ""; + public bool? Value { get; set; } } protected override async Task OnInitializedAsync() @@ -161,49 +267,58 @@ ToastService.ShowError("Bitcoin price in USD could not be retrieved."); } _availableNodes = await NodeRepository.GetAllManagedByUser(LoggedUser.Id); + _availableUsers = await ApplicationUserRepository.GetAll(); + + // Initialize enum options + _providerOptions.AddRange(Enum.GetValues().Cast()); + _statusOptions.AddRange(Enum.GetValues().Cast()); } private async Task OnReadData(DataGridReadDataEventArgs e) { if (!e.CancellationToken.IsCancellationRequested) { - var (swaps, totalCount) = await SwapOutRepository.GetPaginatedAsync(e.Page, e.PageSize); + var fromDate = _fromDate.HasValue + ? new DateTimeOffset(DateTime.SpecifyKind(_fromDate.Value.Date, DateTimeKind.Local)).ToUniversalTime() + : (DateTimeOffset?)null; + var toDate = _toDate.HasValue + ? new DateTimeOffset(DateTime.SpecifyKind(_toDate.Value.Date.AddDays(1).AddTicks(-1), DateTimeKind.Local)).ToUniversalTime() + : (DateTimeOffset?)null; + + var (swaps, totalCount) = await SwapOutRepository.GetPaginatedAsync( + e.Page, + e.PageSize, + status: _statusFilter, + provider: _providerFilter, + nodeId: _nodeFilter, + userId: _userFilter, + isManual: _isManualFilter, + fromDate: fromDate, + toDate: toDate); _swapRequests = swaps; _totalItems = totalCount; } } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender && !_columnsLoaded) - { - await LoadColumnLayout(); - } - } - - private bool IsColumnVisible(ColumnDefault column) - { - if (_swapsColumnLayout == null) - { - return true; - } - - return _swapsColumnLayout.IsColumnVisible(column); - } + private async Task OnFiltersChanged() + { + await _swapsDatagrid.Reload(); + } - private void OnColumnLayoutUpdate() + private async Task ClearAllFilters() { - StateHasChanged(); + _providerFilter = null; + _statusFilter = null; + _nodeFilter = null; + _userFilter = null; + _isManualFilter = null; + _fromDate = null; + _toDate = null; + _filtersResetKey++; + await _swapsDatagrid.Reload(); } - private async Task LoadColumnLayout() - { - _swapsColumns = await LocalStorageService.LoadStorage(nameof(SwapsColumnName), ColumnHelpers.GetColumnsDictionary()); - _columnsLoaded = true; - StateHasChanged(); - } - private async Task NewSwap() { _newSwapModal.Clear(); StateHasChanged();