mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-13 11:35:51 +01:00
Merge pull request #1754 from btcpayserver/feat/prefs-generalization
Cookie Preferences Generalization, applying same style to ListInvoices and Payment Requests
This commit is contained in:
commit
8e2728902a
12 changed files with 278 additions and 221 deletions
|
@ -1357,7 +1357,7 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
var result =
|
||||
(Models.InvoicingModels.InvoicesModel)((ViewResult)acc.GetController<InvoiceController>()
|
||||
.ListInvoices(filter).Result).Model;
|
||||
.ListInvoices(new InvoicesModel { SearchTerm = filter }).Result).Model;
|
||||
Assert.Equal(expected, result.Invoices.Any(i => i.InvoiceId == invoiceId));
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ using Microsoft.EntityFrameworkCore;
|
|||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
|
@ -598,55 +597,23 @@ namespace BTCPayServer.Controllers
|
|||
return Ok("{}");
|
||||
}
|
||||
|
||||
public class InvoicePreference
|
||||
{
|
||||
public int? TimezoneOffset { get; set; }
|
||||
public string SearchTerm { get; set; }
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("invoices")]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50, int? timezoneOffset = null)
|
||||
public async Task<IActionResult> ListInvoices(InvoicesModel model = null)
|
||||
{
|
||||
// If the user enter an empty searchTerm, then the variable will be null and not empty string
|
||||
// but we want searchTerm to be null only if the user is browsing the page via some link
|
||||
// NOT if the user entered some empty search
|
||||
searchTerm = searchTerm is string ? searchTerm :
|
||||
this.Request.Query.ContainsKey(nameof(searchTerm)) ? string.Empty :
|
||||
null;
|
||||
if (searchTerm is null)
|
||||
{
|
||||
if (this.Request.Cookies.TryGetValue("ListInvoicePreferences", out var str))
|
||||
{
|
||||
var preferences = JsonConvert.DeserializeObject<InvoicePreference>(str);
|
||||
searchTerm = preferences.SearchTerm;
|
||||
timezoneOffset = timezoneOffset is int v ? v : preferences.TimezoneOffset;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var preferences = new InvoicePreference();
|
||||
preferences.SearchTerm = searchTerm;
|
||||
preferences.TimezoneOffset = timezoneOffset;
|
||||
this.Response.Cookies.Append("ListInvoicePreferences", JsonConvert.SerializeObject(preferences));
|
||||
}
|
||||
var fs = new SearchString(searchTerm);
|
||||
model = this.ParseListQuery(model ?? new InvoicesModel());
|
||||
|
||||
var fs = new SearchString(model.SearchTerm);
|
||||
var storeIds = fs.GetFilterArray("storeid") != null ? fs.GetFilterArray("storeid") : new List<string>().ToArray();
|
||||
|
||||
var model = new InvoicesModel
|
||||
{
|
||||
SearchTerm = searchTerm,
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
StoreIds = storeIds,
|
||||
TimezoneOffset = timezoneOffset
|
||||
};
|
||||
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset ?? 0);
|
||||
model.StoreIds = storeIds;
|
||||
|
||||
InvoiceQuery invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0);
|
||||
var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery);
|
||||
invoiceQuery.Count = count;
|
||||
invoiceQuery.Skip = skip;
|
||||
invoiceQuery.Count = model.Count;
|
||||
invoiceQuery.Skip = model.Skip;
|
||||
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
||||
|
||||
foreach (var invoice in list)
|
||||
|
|
|
@ -61,23 +61,22 @@ namespace BTCPayServer.Controllers
|
|||
[HttpGet]
|
||||
[Route("")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> GetPaymentRequests(int skip = 0, int count = 50, bool includeArchived = false)
|
||||
public async Task<IActionResult> GetPaymentRequests(ListPaymentRequestsViewModel model = null)
|
||||
{
|
||||
model = this.ParseListQuery(model ?? new ListPaymentRequestsViewModel());
|
||||
|
||||
var includeArchived = new SearchString(model.SearchTerm).GetFilterBool("includearchived") == true;
|
||||
var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery()
|
||||
{
|
||||
UserId = GetUserId(),
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
Skip = model.Skip,
|
||||
Count = model.Count,
|
||||
IncludeArchived = includeArchived
|
||||
});
|
||||
return View(new ListPaymentRequestsViewModel()
|
||||
{
|
||||
IncludeArchived = includeArchived,
|
||||
Skip = skip,
|
||||
Count = count,
|
||||
Total = result.Total,
|
||||
Items = result.Items.Select(data => new ViewPaymentRequestViewModel(data)).ToList()
|
||||
});
|
||||
|
||||
model.Total = result.Total;
|
||||
model.Items = result.Items.Select(data => new ViewPaymentRequestViewModel(data)).ToList();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
|
93
BTCPayServer/Extensions/ControllerBaseExtensions.cs
Normal file
93
BTCPayServer/Extensions/ControllerBaseExtensions.cs
Normal file
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Models.PaymentRequestViewModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
// Classes here remember users preferences on certain pages and store them in unified blob cookie "UserPreferCookie"
|
||||
public static class ControllerBaseExtension
|
||||
{
|
||||
public static T ParseListQuery<T>(this ControllerBase ctrl, T model) where T : BasePagingViewModel
|
||||
{
|
||||
PropertyInfo prop;
|
||||
if (model is InvoicesModel)
|
||||
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.InvoicesQuery));
|
||||
else if (model is ListPaymentRequestsViewModel)
|
||||
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.PaymentRequestsQuery));
|
||||
else
|
||||
throw new Exception("Unsupported BasePagingViewModel for cookie user preferences saving");
|
||||
|
||||
return ProcessParse(ctrl, model, prop);
|
||||
}
|
||||
|
||||
private static T ProcessParse<T>(ControllerBase ctrl, T model, PropertyInfo prop) where T : BasePagingViewModel
|
||||
{
|
||||
var prefCookie = parsePrefCookie(ctrl);
|
||||
|
||||
// If the user enter an empty searchTerm, then the variable will be null and not empty string
|
||||
// but we want searchTerm to be null only if the user is browsing the page via some link
|
||||
// NOT if the user entered some empty search
|
||||
var searchTerm = model.SearchTerm;
|
||||
searchTerm = searchTerm is string ? searchTerm :
|
||||
ctrl.Request.Query.ContainsKey(nameof(searchTerm)) ? string.Empty :
|
||||
null;
|
||||
if (searchTerm is null)
|
||||
{
|
||||
var section = prop.GetValue(prefCookie) as ListQueryDataHolder;
|
||||
if (section != null && !String.IsNullOrEmpty(section.SearchTerm))
|
||||
{
|
||||
model.SearchTerm = section.SearchTerm;
|
||||
model.TimezoneOffset = section.TimezoneOffset ?? 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prop.SetValue(prefCookie, new ListQueryDataHolder(model.SearchTerm, model.TimezoneOffset));
|
||||
ctrl.Response.Cookies.Append(nameof(UserPrefsCookie), JsonConvert.SerializeObject(prefCookie));
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private static UserPrefsCookie parsePrefCookie(ControllerBase ctrl)
|
||||
{
|
||||
var prefCookie = new UserPrefsCookie();
|
||||
ctrl.Request.Cookies.TryGetValue(nameof(UserPrefsCookie), out var strPrefCookie);
|
||||
if (!String.IsNullOrEmpty(strPrefCookie))
|
||||
{
|
||||
try
|
||||
{
|
||||
prefCookie = JsonConvert.DeserializeObject<UserPrefsCookie>(strPrefCookie);
|
||||
}
|
||||
catch { /* ignore cookie deserialization failures */ }
|
||||
}
|
||||
|
||||
return prefCookie;
|
||||
}
|
||||
|
||||
class UserPrefsCookie
|
||||
{
|
||||
public ListQueryDataHolder InvoicesQuery { get; set; }
|
||||
|
||||
public ListQueryDataHolder PaymentRequestsQuery { get; set; }
|
||||
}
|
||||
|
||||
class ListQueryDataHolder
|
||||
{
|
||||
public ListQueryDataHolder() { }
|
||||
|
||||
public ListQueryDataHolder(string searchTerm, int? timezoneOffset)
|
||||
{
|
||||
SearchTerm = searchTerm;
|
||||
TimezoneOffset = timezoneOffset;
|
||||
}
|
||||
|
||||
public int? TimezoneOffset { get; set; }
|
||||
public string SearchTerm { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
14
BTCPayServer/Models/BasePagingViewModel.cs
Normal file
14
BTCPayServer/Models/BasePagingViewModel.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BTCPayServer.Models
|
||||
{
|
||||
public abstract class BasePagingViewModel
|
||||
{
|
||||
public int Skip { get; set; } = 0;
|
||||
public int Count { get; set; } = 50;
|
||||
public int Total { get; set; }
|
||||
[DisplayFormat(ConvertEmptyStringToNull = false)]
|
||||
public string SearchTerm { get; set; }
|
||||
public int? TimezoneOffset { get; set; }
|
||||
}
|
||||
}
|
|
@ -4,13 +4,8 @@ using BTCPayServer.Services.Invoices;
|
|||
|
||||
namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
public class InvoicesModel
|
||||
public class InvoicesModel : BasePagingViewModel
|
||||
{
|
||||
public int Skip { get; set; }
|
||||
public int Count { get; set; }
|
||||
public int Total { get; set; }
|
||||
public string SearchTerm { get; set; }
|
||||
public int? TimezoneOffset { get; set; }
|
||||
public List<InvoiceModel> Invoices { get; set; } = new List<InvoiceModel>();
|
||||
public string[] StoreIds { get; set; }
|
||||
}
|
||||
|
|
|
@ -8,15 +8,10 @@ using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
|
|||
|
||||
namespace BTCPayServer.Models.PaymentRequestViewModels
|
||||
{
|
||||
public class ListPaymentRequestsViewModel
|
||||
public class ListPaymentRequestsViewModel : BasePagingViewModel
|
||||
{
|
||||
public int Skip { get; set; }
|
||||
public int Count { get; set; }
|
||||
|
||||
public List<ViewPaymentRequestViewModel> Items { get; set; }
|
||||
|
||||
public int Total { get; set; }
|
||||
public bool IncludeArchived { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatePaymentRequestViewModel
|
||||
|
|
|
@ -58,6 +58,16 @@ namespace BTCPayServer
|
|||
return null;
|
||||
|
||||
var val = Filters[key].First();
|
||||
|
||||
// handle special string values
|
||||
if (val == "-24h")
|
||||
return DateTimeOffset.UtcNow.AddHours(-24).AddMinutes(timezoneOffset);
|
||||
else if (val == "-3d")
|
||||
return DateTimeOffset.UtcNow.AddDays(-3).AddMinutes(timezoneOffset);
|
||||
else if (val == "-7d")
|
||||
return DateTimeOffset.UtcNow.AddDays(-7).AddMinutes(timezoneOffset);
|
||||
|
||||
// default parsing logic
|
||||
var success = DateTimeOffset.TryParse(val, null as IFormatProvider, DateTimeStyles.AssumeUniversal, out var r);
|
||||
if (success)
|
||||
{
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="ListInvoices" method="get" style="float:right;">
|
||||
<form asp-action="ListInvoices" method="get" class="pull-right">
|
||||
<input type="hidden" asp-for="Count" />
|
||||
<div class="input-group">
|
||||
<input asp-for="TimezoneOffset" type="hidden" />
|
||||
|
@ -74,9 +74,9 @@
|
|||
<a class="dropdown-item" href="/invoices?Count=@Model.Count&SearchTerm=unusual%3Atrue@{@storeIds}">Unusual Invoices</a>
|
||||
<a class="dropdown-item" href="/invoices?Count=@Model.Count&SearchTerm=includearchived%3Atrue@{@storeIds}">Archived Invoices</a>
|
||||
<div role="separator" class="dropdown-divider"></div>
|
||||
<a class="dropdown-item last24" href="/invoices?Count=@Model.Count&timezoneoffset=0&SearchTerm=startDate%3Alast24@{@storeIds}">Last 24 hours</a>
|
||||
<a class="dropdown-item last72" href="/invoices?Count=@Model.Count&timezoneoffset=0&SearchTerm=startDate%3Alast72@{@storeIds}">Last 3 days</a>
|
||||
<a class="dropdown-item last168" href="/invoices?Count=@Model.Count&timezoneoffset=0&SearchTerm=startDate%3Alast168@{@storeIds}">Last 7 days</a>
|
||||
<a class="dropdown-item" href="/invoices?Count=@Model.Count&timezoneoffset=0&SearchTerm=startDate%3A-24h@{@storeIds}">Last 24 hours</a>
|
||||
<a class="dropdown-item" href="/invoices?Count=@Model.Count&timezoneoffset=0&SearchTerm=startDate%3A-3d@{@storeIds}">Last 3 days</a>
|
||||
<a class="dropdown-item" href="/invoices?Count=@Model.Count&timezoneoffset=0&SearchTerm=startDate%3A-7d@{@storeIds}">Last 7 days</a>
|
||||
<button type="button" class="dropdown-item" data-toggle="modal" data-target="#customRangeModal" data-backdrop="static">Custom Range</button>
|
||||
<div role="separator" class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="/invoices?SearchTerm=">Unfiltered</a>
|
||||
|
@ -88,7 +88,7 @@
|
|||
|
||||
<form method="post" id="MassAction" asp-action="MassAction">
|
||||
<div class="row button-row">
|
||||
<div class="col-lg-24">
|
||||
<div class="col-lg-12 pl-0">
|
||||
<a asp-action="CreateInvoice" class="btn btn-primary" role="button" id="CreateNewInvoice"><span class="fa fa-plus"></span> Create a new invoice</a>
|
||||
|
||||
<span>
|
||||
|
@ -313,73 +313,7 @@
|
|||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<nav aria-label="..." class="w-100">
|
||||
@if (Model.Total != 0)
|
||||
{
|
||||
<ul class="pagination float-left">
|
||||
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
|
||||
<a class="page-link" tabindex="-1" href="@ListInvoicesPage(-1, Model.Count)">«</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
@if (Model.Total <= Model.Count)
|
||||
{
|
||||
<span class="page-link">
|
||||
1–@Model.Invoices.Count
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="page-link">
|
||||
@(Model.Skip + 1)–@(Model.Skip + Model.Invoices.Count), Total: @Model.Total
|
||||
</span>
|
||||
}
|
||||
</li>
|
||||
<li class="page-item @(Model.Total > (Model.Skip + Model.Invoices.Count) ? null : "disabled")">
|
||||
<a class="page-link" href="@ListInvoicesPage(1, Model.Count)">»</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
<ul class="pagination float-right">
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Page Size:</span>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 50 ? "active" : null)">
|
||||
<a class="page-link" href="@ListInvoicesPage(0, 50)">50</a>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 100 ? "active" : null)">
|
||||
<a class="page-link" href="@ListInvoicesPage(0, 100)">100</a>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 250 ? "active" : null)">
|
||||
<a class="page-link" href="@ListInvoicesPage(0, 250)">250</a>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 500 ? "active" : null)">
|
||||
<a class="page-link" href="@ListInvoicesPage(0, 500)">500</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@{
|
||||
string ListInvoicesPage(int prevNext, int count)
|
||||
{
|
||||
var skip = Model.Skip;
|
||||
if (prevNext == -1)
|
||||
{
|
||||
skip = Math.Max(0, Model.Skip - Model.Count);
|
||||
}
|
||||
else if (prevNext == 1)
|
||||
{
|
||||
skip = Model.Skip + count;
|
||||
}
|
||||
|
||||
var act = Url.Action("ListInvoices", new
|
||||
{
|
||||
searchTerm = Model.SearchTerm,
|
||||
skip = skip,
|
||||
count = count,
|
||||
});
|
||||
|
||||
return act;
|
||||
}
|
||||
}
|
||||
<partial name="_TableFooterPager" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -387,14 +321,9 @@
|
|||
<script type="text/javascript">
|
||||
$(function () {
|
||||
var timezoneOffset = new Date().getTimezoneOffset();
|
||||
$("#TimezoneOffset").val(timezoneOffset);
|
||||
$(".export-link, a.dropdown-item").each(function () {
|
||||
this.href = this.href.replace("timezoneoffset=0", "timezoneoffset=" + timezoneOffset);
|
||||
});
|
||||
|
||||
$("a.last24").each(function () { this.href = this.href.replace("last24", getDateStringWithOffset(24)); });
|
||||
$("a.last72").each(function () { this.href = this.href.replace("last72", getDateStringWithOffset(72)); });
|
||||
$("a.last168").each(function () { this.href = this.href.replace("last168", getDateStringWithOffset(168)); });
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
{
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<partial name="_StatusMessage"/>
|
||||
<partial name="_StatusMessage" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -18,10 +18,35 @@
|
|||
<div class="col-lg-12 section-heading">
|
||||
<h2>Payment Requests</h2>
|
||||
<hr class="primary">
|
||||
<p>Create, search or pay an payment request.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form asp-action="GetPaymentRequests" method="get" class="pull-right">
|
||||
<input type="hidden" asp-for="Count" />
|
||||
<div class="input-group">
|
||||
<input asp-for="TimezoneOffset" type="hidden" />
|
||||
<input asp-for="SearchTerm" class="form-control" style="width:300px;" />
|
||||
<div class="input-group-append">
|
||||
<button type="submit" class="btn btn-primary" title="Search invoice">
|
||||
<span class="fa fa-search"></span> Search
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">Toggle Dropdown</span>
|
||||
</button>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<a class="dropdown-item" href="/payment-requests?Count=@Model.Count&SearchTerm=includearchived%3Atrue">Include Archived Payment Reqs</a>
|
||||
<div role="separator" class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="/payment-requests?SearchTerm=">Unfiltered</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span asp-validation-for="SearchTerm" class="text-danger"></span>
|
||||
</form>
|
||||
|
||||
<div class="row button-row">
|
||||
<div class="col-lg-12">
|
||||
<div class="col-lg-12 pl-0">
|
||||
<a asp-action="EditPaymentRequest" class="btn btn-primary" role="button" id="CreatePaymentRequest"><span class="fa fa-plus"></span> Create a new payment request</a>
|
||||
<a href="https://docs.btcpayserver.org/PaymentRequests/" target="_blank">
|
||||
<span class="fa fa-question-circle-o" title="More information..."></span>
|
||||
|
@ -32,86 +57,41 @@
|
|||
<div class="col-lg-12">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Expiry</th>
|
||||
<th class="text-right">Price</th>
|
||||
<th class="text-right">Status</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Expiry</th>
|
||||
<th class="text-right">Price</th>
|
||||
<th class="text-right">Status</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Title</td>
|
||||
<td>@(item.ExpiryDate?.ToString("g") ?? "No Expiry")</td>
|
||||
<td class="text-right">@item.Amount @item.Currency</td>
|
||||
<td class="text-right">@item.Status</td>
|
||||
<td class="text-right">
|
||||
<a asp-action="EditPaymentRequest" asp-route-id="@item.Id">Edit</a>
|
||||
<span> - </span>
|
||||
<a asp-action="ViewPaymentRequest" asp-route-id="@item.Id">View</a>
|
||||
<span> - </span>
|
||||
<a target="_blank" asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(item.Id)}")">Invoices</a>
|
||||
<span> - </span>
|
||||
<a target="_blank" asp-action="PayPaymentRequest" asp-route-id="@item.Id">Pay</a>
|
||||
<span> - </span>
|
||||
<a target="_blank" asp-action="ClonePaymentRequest" asp-route-id="@item.Id">Clone</a>
|
||||
<span> - </span>
|
||||
<a asp-action="TogglePaymentRequestArchival" asp-route-id="@item.Id">@(item.Archived ? "Unarchive" : "Archive")</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td>@item.Title</td>
|
||||
<td>@(item.ExpiryDate?.ToString("g") ?? "No Expiry")</td>
|
||||
<td class="text-right">@item.Amount @item.Currency</td>
|
||||
<td class="text-right">@item.Status</td>
|
||||
<td class="text-right">
|
||||
<a asp-action="EditPaymentRequest" asp-route-id="@item.Id">Edit</a>
|
||||
<span> - </span>
|
||||
<a asp-action="ViewPaymentRequest" asp-route-id="@item.Id">View</a>
|
||||
<span> - </span>
|
||||
<a target="_blank" asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(item.Id)}")">Invoices</a>
|
||||
<span> - </span>
|
||||
<a target="_blank" asp-action="PayPaymentRequest" asp-route-id="@item.Id">Pay</a>
|
||||
<span> - </span>
|
||||
<a target="_blank" asp-action="ClonePaymentRequest" asp-route-id="@item.Id">Clone</a>
|
||||
<span> - </span>
|
||||
<a asp-action="TogglePaymentRequestArchival" asp-route-id="@item.Id">@(item.Archived ? "Unarchive" : "Archive")</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="d-flex ">
|
||||
|
||||
|
||||
<nav aria-label="...">
|
||||
<ul class="pagination">
|
||||
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
|
||||
<a class="page-link" tabindex="-1" href="@Url.Action("GetPaymentRequests", new
|
||||
{
|
||||
skip = Math.Max(0, Model.Skip - Model.Count),
|
||||
count = Model.Count,
|
||||
})">
|
||||
Previous
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">@(Model.Skip + 1) to @(Model.Skip + Model.Count) of @Model.Total</span>
|
||||
</li>
|
||||
<li class="page-item @(Model.Total > (Model.Skip + Model.Count) ? null : "disabled")">
|
||||
<a class="page-link" href="@Url.Action("GetPaymentRequests", new
|
||||
{
|
||||
skip = Model.Skip + Model.Count,
|
||||
count = Model.Count,
|
||||
})">
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
<a class="ml-2 mt-1" href="@Url.Action("GetPaymentRequests", new
|
||||
{
|
||||
skip = Model.Skip,
|
||||
count = Model.Count,
|
||||
includeArchived = !Model.IncludeArchived
|
||||
})">
|
||||
@if (Model.IncludeArchived)
|
||||
{
|
||||
<span> Hide archived payment requests. </span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span> Show archived payment requests.</span>
|
||||
}
|
||||
|
||||
</a>
|
||||
</div>
|
||||
<partial name="_TableFooterPager" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
70
BTCPayServer/Views/Shared/_TableFooterPager.cshtml
Normal file
70
BTCPayServer/Views/Shared/_TableFooterPager.cshtml
Normal file
|
@ -0,0 +1,70 @@
|
|||
@model BasePagingViewModel
|
||||
|
||||
<nav aria-label="..." class="w-100">
|
||||
@if (Model.Total != 0)
|
||||
{
|
||||
<ul class="pagination float-left">
|
||||
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
|
||||
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)">«</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
@if (Model.Total <= Model.Count)
|
||||
{
|
||||
<span class="page-link">
|
||||
1–@Model.Total
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="page-link">
|
||||
@(Model.Skip + 1)–@(Model.Skip + Model.Count), Total: @Model.Total
|
||||
</span>
|
||||
}
|
||||
</li>
|
||||
<li class="page-item @(Model.Total > (Model.Skip + Model.Count) ? null : "disabled")">
|
||||
<a class="page-link" href="@NavigatePages(1, Model.Count)">»</a>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
<ul class="pagination float-right">
|
||||
<li class="page-item disabled">
|
||||
<span class="page-link">Page Size:</span>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 50 ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, 50)">50</a>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 100 ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, 100)">100</a>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 250 ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, 250)">250</a>
|
||||
</li>
|
||||
<li class="page-item @(Model.Count == 500 ? "active" : null)">
|
||||
<a class="page-link" href="@NavigatePages(0, 500)">500</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@{
|
||||
string NavigatePages(int prevNext, int count)
|
||||
{
|
||||
var skip = Model.Skip;
|
||||
if (prevNext == -1)
|
||||
{
|
||||
skip = Math.Max(0, Model.Skip - Model.Count);
|
||||
}
|
||||
else if (prevNext == 1)
|
||||
{
|
||||
skip = Model.Skip + count;
|
||||
}
|
||||
|
||||
var act = Url.Action(null, new
|
||||
{
|
||||
searchTerm = Model.SearchTerm,
|
||||
timezoneOffset = Model.TimezoneOffset,
|
||||
skip = skip,
|
||||
count = count,
|
||||
});
|
||||
|
||||
return act;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,9 @@
|
|||
$(function () {
|
||||
$(function () {
|
||||
// initialize timezone offset value if field is present in page
|
||||
var timezoneOffset = new Date().getTimezoneOffset();
|
||||
$("#TimezoneOffset").val(timezoneOffset);
|
||||
|
||||
// localize all elements that have localizeDate class
|
||||
$(".localizeDate").each(function (index) {
|
||||
var serverDate = $(this).text();
|
||||
var localDate = new Date(serverDate);
|
||||
|
|
Loading…
Add table
Reference in a new issue