mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Update WalletTransactions pagination default settings (#4074)
* Update WalletTransactions pagination default settings Remove the numeric page selection and add displaying data of last 30 days by default. * Update WalletTransactions to show txs based on Days * Update text formatting on WalletTransactions view Keeps the logic changes. Just undo the formatting of the file from previous commit * Update WalletTransactions to show all after second load Utilize Model.Days instead of new variables Moved javascript code to PageFootContent section * Update WalletTransactions to use ajax for infinite scroll * Cleanups * Apply skip and count only when not prefiltering * Infinite scroll mode * Improve datetime formatting and switching * Upgrade NBXplorer to include get_wallets_recent bugfix * Revert "Upgrade NBXplorer to include get_wallets_recent bugfix" This reverts commit b390d942d74d88bb1da3ab8e3407184a527175ef. * JS fixes * Upgrade ChromeDriver and BundleMinifier Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
@@ -88,24 +88,23 @@ namespace BTCPayServer.Abstractions.Extensions
|
|||||||
|
|
||||||
public static HtmlString ToBrowserDate(this DateTimeOffset date)
|
public static HtmlString ToBrowserDate(this DateTimeOffset date)
|
||||||
{
|
{
|
||||||
var displayDate = date.ToString("o", CultureInfo.InvariantCulture);
|
var displayDate = date.ToString("g", CultureInfo.InvariantCulture);
|
||||||
return new HtmlString($"<span class='localizeDate'>{displayDate}</span>");
|
var dateTime = date.ToString("s", CultureInfo.InvariantCulture);
|
||||||
|
return new HtmlString($"<time datetime=\"{dateTime}\" data-relative=\"{date.ToTimeAgo()}\">{displayDate}</time>");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HtmlString ToBrowserDate(this DateTime date)
|
public static HtmlString ToBrowserDate(this DateTime date)
|
||||||
{
|
{
|
||||||
var displayDate = date.ToString("o", CultureInfo.InvariantCulture);
|
var displayDate = date.ToString("g", CultureInfo.InvariantCulture);
|
||||||
return new HtmlString($"<span class='localizeDate'>{displayDate}</span>");
|
var dateTime = date.ToString("s", CultureInfo.InvariantCulture);
|
||||||
|
return new HtmlString($"<time datetime=\"{dateTime}\" data-relative=\"{date.ToTimeAgo()}\">{displayDate}</time>");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ToTimeAgo(this DateTimeOffset date)
|
public static string ToTimeAgo(this DateTimeOffset date) => (DateTimeOffset.UtcNow - date).ToTimeAgo();
|
||||||
{
|
|
||||||
var diff = DateTimeOffset.UtcNow - date;
|
public static string ToTimeAgo(this DateTime date) => (DateTimeOffset.UtcNow - date).ToTimeAgo();
|
||||||
var formatted = diff.TotalSeconds > 0
|
|
||||||
? $"{diff.TimeString()} ago"
|
public static string ToTimeAgo(this TimeSpan diff) => diff.TotalSeconds > 0 ? $"{diff.TimeString()} ago" : $"in {diff.Negate().TimeString()}";
|
||||||
: $"in {diff.Negate().TimeString()}";
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string TimeString(this TimeSpan timeSpan)
|
public static string TimeString(this TimeSpan timeSpan)
|
||||||
{
|
{
|
||||||
@@ -117,16 +116,14 @@ namespace BTCPayServer.Abstractions.Extensions
|
|||||||
{
|
{
|
||||||
return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}";
|
return $"{(int)timeSpan.TotalMinutes} minute{Plural((int)timeSpan.TotalMinutes)}";
|
||||||
}
|
}
|
||||||
if (timeSpan.Days < 1)
|
return timeSpan.Days < 1
|
||||||
{
|
? $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}"
|
||||||
return $"{(int)timeSpan.TotalHours} hour{Plural((int)timeSpan.TotalHours)}";
|
: $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
|
||||||
}
|
|
||||||
return $"{(int)timeSpan.TotalDays} day{Plural((int)timeSpan.TotalDays)}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Plural(int value)
|
private static string Plural(int value)
|
||||||
{
|
{
|
||||||
return value > 1 ? "s" : string.Empty;
|
return value == 1 ? string.Empty : "s";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
|
||||||
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
<PackageReference Include="Selenium.Support" Version="4.1.1" />
|
||||||
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
|
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
|
||||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="104.0.5112.7900" />
|
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="105.0.5195.5200" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
|
||||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.5" />
|
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.5" />
|
||||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
|
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
|
||||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
<PackageReference Include="BundlerMinifier.Core" Version="3.2.449" />
|
||||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||||
<PackageReference Include="Dapper" Version="2.0.123" />
|
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||||
|
|||||||
@@ -320,14 +320,16 @@ namespace BTCPayServer.Controllers
|
|||||||
// if we couldn't filter at the db level, we need to apply skip and count
|
// if we couldn't filter at the db level, we need to apply skip and count
|
||||||
if (!preFiltering)
|
if (!preFiltering)
|
||||||
{
|
{
|
||||||
model.Transactions = model.Transactions.Skip(skip).Take(count)
|
model.Transactions = model.Transactions.Skip(skip).Take(count).ToList();
|
||||||
.ToList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
model.CryptoCode = walletId.CryptoCode;
|
model.CryptoCode = walletId.CryptoCode;
|
||||||
|
|
||||||
return View(model);
|
//If ajax call then load the partial view
|
||||||
|
return Request.Headers["X-Requested-With"] == "XMLHttpRequest"
|
||||||
|
? PartialView("_WalletTransactionsList", model)
|
||||||
|
: View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{walletId}/histogram/{type}")]
|
[HttpGet("{walletId}/histogram/{type}")]
|
||||||
|
|||||||
@@ -29,8 +29,6 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
delegate('click', '#switchTimeFormat', switchTimeFormat)
|
|
||||||
|
|
||||||
delegate('click', '.changeInvoiceState', e => {
|
delegate('click', '.changeInvoiceState', e => {
|
||||||
const { invoiceId, newState } = e.target.dataset;
|
const { invoiceId, newState } = e.target.dataset;
|
||||||
const pavpill = $("#pavpill_" + invoiceId);
|
const pavpill = $("#pavpill_" + invoiceId);
|
||||||
@@ -286,9 +284,7 @@
|
|||||||
<input id="selectAllCheckbox" type="checkbox" class="form-check-input" />
|
<input id="selectAllCheckbox" type="checkbox" class="form-check-input" />
|
||||||
<th style="min-width:90px;" class="col-md-auto">
|
<th style="min-width:90px;" class="col-md-auto">
|
||||||
Date
|
Date
|
||||||
<a id="switchTimeFormat" href="#">
|
<a id="switchTimeFormat" href="#" class="fa fa-clock-o" title="Switch date format"></a>
|
||||||
<span class="fa fa-clock-o" title="Switch date format"></span>
|
|
||||||
</a>
|
|
||||||
</th>
|
</th>
|
||||||
<th class="text-nowrap">Order Id</th>
|
<th class="text-nowrap">Order Id</th>
|
||||||
<th class="text-nowrap">Invoice Id</th>
|
<th class="text-nowrap">Invoice Id</th>
|
||||||
@@ -304,11 +300,7 @@
|
|||||||
<td class="only-for-js">
|
<td class="only-for-js">
|
||||||
<input name="selectedItems" type="checkbox" class="selector form-check-input" value="@invoice.InvoiceId" />
|
<input name="selectedItems" type="checkbox" class="selector form-check-input" value="@invoice.InvoiceId" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>@invoice.Date.ToBrowserDate()</td>
|
||||||
<span class="switchTimeFormat" data-switch="@invoice.Date.ToTimeAgo()">
|
|
||||||
@invoice.Date.ToBrowserDate()
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td style="max-width:120px;">
|
<td style="max-width:120px;">
|
||||||
@if (invoice.RedirectUrl != string.Empty)
|
@if (invoice.RedirectUrl != string.Empty)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,17 +33,15 @@
|
|||||||
<table class="table table-hover table-responsive-md">
|
<table class="table table-hover table-responsive-md">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="30px" class="only-for-js">
|
<th style="width:30px" class="only-for-js">
|
||||||
@if (Model.Items.Count > 0)
|
@if (Model.Items.Count > 0)
|
||||||
{
|
{
|
||||||
<input name="selectedItems" id="selectAllCheckbox" type="checkbox" class="form-check-input" />
|
<input name="selectedItems" id="selectAllCheckbox" type="checkbox" class="form-check-input" />
|
||||||
}
|
}
|
||||||
</th>
|
</th>
|
||||||
<th width="190px">
|
<th>
|
||||||
Date
|
Date
|
||||||
<a href="#" id="switchTimeFormat">
|
<a href="#" id="switchTimeFormat" class="fa fa-clock-o" title="Switch date format"></a>
|
||||||
<span class="fa fa-clock-o" title="Switch date format"></span>
|
|
||||||
</a>
|
|
||||||
</th>
|
</th>
|
||||||
<th>Message</th>
|
<th>Message</th>
|
||||||
<th class="text-end">Actions</th>
|
<th class="text-end">Actions</th>
|
||||||
@@ -56,11 +54,7 @@
|
|||||||
<td class="only-for-js">
|
<td class="only-for-js">
|
||||||
<input name="selectedItems" type="checkbox" class="selector form-check-input" value="@item.Id" />
|
<input name="selectedItems" type="checkbox" class="selector form-check-input" value="@item.Id" />
|
||||||
</td>
|
</td>
|
||||||
<td class="toggleRowCheckbox">
|
<td class="toggleRowCheckbox">@item.Created.ToBrowserDate()</td>
|
||||||
<span class="switchTimeFormat" data-switch="@item.Created.ToTimeAgo()">
|
|
||||||
@item.Created.ToBrowserDate()
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="toggleRowCheckbox">
|
<td class="toggleRowCheckbox">
|
||||||
@item.Body
|
@item.Body
|
||||||
</td>
|
</td>
|
||||||
@@ -130,8 +124,6 @@ else
|
|||||||
updateSelectors();
|
updateSelectors();
|
||||||
})
|
})
|
||||||
|
|
||||||
delegate('click', '#switchTimeFormat', switchTimeFormat)
|
|
||||||
|
|
||||||
delegate('click', '.btn-toggle-seen', e => {
|
delegate('click', '.btn-toggle-seen', e => {
|
||||||
const row = $(e.target).parents(".notification-row").toggleClass("loading");
|
const row = $(e.target).parents(".notification-row").toggleClass("loading");
|
||||||
const guid = row.data("guid");
|
const guid = row.data("guid");
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
@model ListTransactionsViewModel
|
@model ListTransactionsViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var walletId = Context.GetRouteValue("walletId").ToString();
|
var walletId = Context.GetRouteValue("walletId").ToString();
|
||||||
var labelFilter = Context.Request.Query["labelFilter"].ToString();
|
var labelFilter = Context.Request.Query["labelFilter"].ToString();
|
||||||
|
|
||||||
Layout = "../Shared/_NavLayout.cshtml";
|
Layout = "../Shared/_NavLayout.cshtml";
|
||||||
ViewData.SetActivePage(WalletsNavPages.Transactions, $"{Model.CryptoCode} Transactions", walletId);
|
ViewData.SetActivePage(WalletsNavPages.Transactions, $"{Model.CryptoCode} Transactions", walletId);
|
||||||
}
|
}
|
||||||
@@ -34,14 +36,6 @@
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switchTimeFormat {
|
|
||||||
display: block;
|
|
||||||
max-width: 125px;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-container {
|
.badge-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
@@ -70,6 +64,10 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: var(--btcpay-space-m);
|
padding-left: var(--btcpay-space-m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#LoadingIndicator {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,12 +77,81 @@
|
|||||||
|
|
||||||
@* Custom Range Modal *@
|
@* Custom Range Modal *@
|
||||||
<script>
|
<script>
|
||||||
delegate('click', '#switchTimeFormat', switchTimeFormat);
|
let observer = null;
|
||||||
|
let $loadMore = document.getElementById('LoadMore');
|
||||||
|
const $actions = document.getElementById('ListActions');
|
||||||
|
const $list = document.getElementById('WalletTransactionsList');
|
||||||
|
const $indicator = document.getElementById('LoadingIndicator');
|
||||||
|
|
||||||
delegate('click', '#selectAllCheckbox', e => {
|
delegate('click', '#selectAllCheckbox', e => {
|
||||||
document.querySelectorAll(".selector").forEach(checkbox => {
|
document.querySelectorAll(".selector").forEach(checkbox => {
|
||||||
checkbox.checked = e.target.checked;
|
checkbox.checked = e.target.checked;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
delegate('click', '#GoToTop', () => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
});
|
||||||
|
|
||||||
|
delegate('click', '#LoadMore', async () => {
|
||||||
|
$loadMore.setAttribute('disabled', 'disabled');
|
||||||
|
await loadMoreTransactions();
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($actions && $actions.offsetTop - window.innerHeight > 0) {
|
||||||
|
document.getElementById('GoToTop').classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = @Safe.Json(Model.Count);
|
||||||
|
const skipInitial = @Safe.Json(Model.Skip);
|
||||||
|
const loadMoreUrl = @Safe.Json(Url.Action("WalletTransactions", new { walletId, labelFilter, skip = Model.Skip, count = Model.Count }));
|
||||||
|
let skip = @Safe.Json(Model.Skip);
|
||||||
|
|
||||||
|
async function loadMoreTransactions() {
|
||||||
|
$indicator.classList.remove('d-none');
|
||||||
|
|
||||||
|
const skipNext = skip + count;
|
||||||
|
const url = loadMoreUrl.replace(`skip=${skipInitial}`, `skip=${skipNext}`)
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
'Accept': 'text/html',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const html = await response.text();
|
||||||
|
$list.innerHTML += html;
|
||||||
|
skip = skipNext;
|
||||||
|
|
||||||
|
if ($loadMore) {
|
||||||
|
// remove load more button
|
||||||
|
$loadMore.remove();
|
||||||
|
$loadMore = null;
|
||||||
|
|
||||||
|
// switch to infinite scroll mode
|
||||||
|
observer = new IntersectionObserver(async entries => {
|
||||||
|
const { isIntersecting } = entries[0];
|
||||||
|
if (isIntersecting) {
|
||||||
|
await loadMoreTransactions();
|
||||||
|
}
|
||||||
|
}, { rootMargin: '128px' });
|
||||||
|
|
||||||
|
// the actions div marks the end of the list table
|
||||||
|
observer.observe($actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (html.trim() === '') {
|
||||||
|
// in case the response html was empty, remove the observer and stop loading
|
||||||
|
observer.unobserve($actions);
|
||||||
|
}
|
||||||
|
} else if ($loadMore) {
|
||||||
|
$loadMore.removeAttribute('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
$indicator.classList.add('d-none');
|
||||||
|
formatDateTimes(document.getElementById('switchTimeFormat').dataset.mode);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +199,7 @@
|
|||||||
<div style="clear:both"></div>
|
<div style="clear:both"></div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col table-responsive-md">
|
<div class="col table-responsive-md" id="walletTable">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead class="thead-inverse">
|
<thead class="thead-inverse">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -141,9 +208,7 @@
|
|||||||
</th>
|
</th>
|
||||||
<th style="max-width:125px;">
|
<th style="max-width:125px;">
|
||||||
Date
|
Date
|
||||||
<a id="switchTimeFormat" href="#">
|
<a id="switchTimeFormat" class="fa fa-clock-o" title="Switch date format" href="#"></a>
|
||||||
<span class="fa fa-clock-o" title="Switch date format"></span>
|
|
||||||
</a>
|
|
||||||
</th>
|
</th>
|
||||||
<th class="text-start">Label</th>
|
<th class="text-start">Label</th>
|
||||||
<th>Transaction Id</th>
|
<th>Transaction Id</th>
|
||||||
@@ -151,136 +216,27 @@
|
|||||||
<th class="text-end" style="min-width:60px"></th>
|
<th class="text-end" style="min-width:60px"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="WalletTransactionsList">
|
||||||
@foreach (var transaction in Model.Transactions)
|
<partial name="_WalletTransactionsList" model="Model" />
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td class="only-for-js">
|
|
||||||
<input name="selectedTransactions" type="checkbox" class="selector form-check-input" form="WalletActions" value="@transaction.Id"/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="switchTimeFormat" data-switch="@transaction.Timestamp.ToTimeAgo()">
|
|
||||||
@transaction.Timestamp.ToBrowserDate()
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="text-start">
|
|
||||||
@if (transaction.Labels.Any())
|
|
||||||
{
|
|
||||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
|
||||||
@foreach (var label in transaction.Labels)
|
|
||||||
{
|
|
||||||
<div class="badge-container rounded-2" style="background-color:@label.Color;">
|
|
||||||
<div
|
|
||||||
class="badge transactionLabel position-relative d-block"
|
|
||||||
style="background-color:@label.Color;padding-right: 16px; z-index: 1;"
|
|
||||||
data-bs-html="true"
|
|
||||||
data-bs-toggle="tooltip"
|
|
||||||
data-bs-custom-class="label-tooltip"
|
|
||||||
title="@label.Tooltip">
|
|
||||||
<a asp-route-labelFilter="@label.Text" style="color:@label.TextColor !important;">@label.Text</a>
|
|
||||||
|
|
||||||
<form
|
|
||||||
asp-route-walletId="@Context.GetRouteValue("walletId")"
|
|
||||||
asp-action="ModifyTransaction"
|
|
||||||
method="post"
|
|
||||||
class="removeTransactionLabelForm">
|
|
||||||
<input type="hidden" name="transactionId" value="@transaction.Id"/>
|
|
||||||
<button
|
|
||||||
name="removelabel"
|
|
||||||
style="color: @label.Color; filter: brightness(0.5);"
|
|
||||||
type="submit"
|
|
||||||
value="@label.Text">
|
|
||||||
<span class="fa fa-close"></span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
@if (!string.IsNullOrEmpty(label.Link))
|
|
||||||
{
|
|
||||||
<a href="@label.Link" target="_blank" class="badge transaction-details-icon" style="background-color: @label.Color; filter: brightness(1.1);" rel="noreferrer noopener">
|
|
||||||
<span class="fa fa-info-circle" title="Transaction details" style="color: @label.Color; filter: brightness(0.5);">
|
|
||||||
<span class="visually-hidden">Transaction details</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td class="smMaxWidth text-truncate @(transaction.IsConfirmed ? "" : "unconf")">
|
|
||||||
<a href="@transaction.Link" target="_blank" rel="noreferrer noopener">
|
|
||||||
@transaction.Id
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
@if (transaction.Positive)
|
|
||||||
{
|
|
||||||
<td class="text-end text-success">@transaction.Balance</td>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<td class="text-end text-danger">@transaction.Balance</td>
|
|
||||||
}
|
|
||||||
<td class="text-end">
|
|
||||||
<div class="dropstart d-inline-block me-2">
|
|
||||||
<span class="fa fa-tags cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<form asp-action="ModifyTransaction" method="post" style="width:260px;"
|
|
||||||
asp-route-walletId="@Context.GetRouteValue("walletId")">
|
|
||||||
<input type="hidden" name="transactionId" value="@transaction.Id"/>
|
|
||||||
<div class="input-group input-group-sm p-2">
|
|
||||||
<input name="addlabel" placeholder="Label name" maxlength="20" type="text" class="form-control form-control-sm"/>
|
|
||||||
<button type="submit" class="btn btn-primary btn-sm"><span class="fa fa-plus"></span></button>
|
|
||||||
</div>
|
|
||||||
@if (Model.Labels.Count > 0)
|
|
||||||
{
|
|
||||||
<div class="dropdown-divider"></div>
|
|
||||||
<div class="py-2 px-3 d-flex flex-wrap gap-2">
|
|
||||||
@foreach (var label in Model.Labels)
|
|
||||||
{
|
|
||||||
@if (transaction.Labels.Contains(label))
|
|
||||||
{
|
|
||||||
<button name="removelabel" class="bg-transparent border-0 p-0" type="submit" value="@label.Text"><span class="badge" style="background-color:@label.Color;color:@label.TextColor"><span class="fa fa-check"></span> @label.Text</span></button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<button name="addlabelclick" class="bg-transparent border-0 p-0" type="submit" value="@label.Text"><span class="badge" style="background-color:@label.Color;color:@label.TextColor">@label.Text</span></button>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="dropstart d-inline-block">
|
|
||||||
@if (string.IsNullOrEmpty(transaction.Comment))
|
|
||||||
{
|
|
||||||
<span class="fa fa-comment cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="fa fa-commenting cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
|
||||||
}
|
|
||||||
<div class="dropdown-menu">
|
|
||||||
<form asp-action="ModifyTransaction" method="post" style="width:260px;"
|
|
||||||
asp-route-walletId="@Context.GetRouteValue("walletId")">
|
|
||||||
<input type="hidden" name="transactionId" value="@transaction.Id"/>
|
|
||||||
<div class="input-group p-2">
|
|
||||||
<textarea name="addcomment" maxlength="200" rows="2" cols="20" class="form-control form-control-sm p-1">@transaction.Comment</textarea>
|
|
||||||
</div>
|
|
||||||
<div class="p-2">
|
|
||||||
<button type="submit" class="btn btn-primary btn-sm w-100">Save comment</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<noscript>
|
||||||
<vc:pager view-model="Model"/>
|
<vc:pager view-model="Model"/>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<div class="text-center only-for-js d-none" id="LoadingIndicator">
|
||||||
|
<div class="spinner-border spinner-border-sm text-secondary ms-2" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-wrap align-items-center justify-content-center gap-3 mb-5 only-for-js" id="ListActions">
|
||||||
|
<button type="button" class="btn btn-secondary d-flex align-items-center" id="LoadMore">
|
||||||
|
Load more
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary d-none" id="GoToTop">Go to top</button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
119
BTCPayServer/Views/UIWallets/_WalletTransactionsList.cshtml
Normal file
119
BTCPayServer/Views/UIWallets/_WalletTransactionsList.cshtml
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
@model ListTransactionsViewModel
|
||||||
|
|
||||||
|
@foreach (var transaction in Model.Transactions)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td class="only-for-js">
|
||||||
|
<input name="selectedTransactions" type="checkbox" class="selector form-check-input" form="WalletActions" value="@transaction.Id" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@transaction.Timestamp.ToBrowserDate()
|
||||||
|
</td>
|
||||||
|
<td class="text-start">
|
||||||
|
@if (transaction.Labels.Any())
|
||||||
|
{
|
||||||
|
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||||
|
@foreach (var label in transaction.Labels)
|
||||||
|
{
|
||||||
|
<div class="badge-container rounded-2" style="background-color:@label.Color;">
|
||||||
|
<div class="badge transactionLabel position-relative d-block"
|
||||||
|
style="background-color:@label.Color;padding-right: 16px; z-index: 1;"
|
||||||
|
data-bs-html="true"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-custom-class="label-tooltip"
|
||||||
|
title="@label.Tooltip">
|
||||||
|
<a asp-route-labelFilter="@label.Text" style="color:@label.TextColor !important;">@label.Text</a>
|
||||||
|
|
||||||
|
<form asp-route-walletId="@Context.GetRouteValue("walletId")"
|
||||||
|
asp-action="ModifyTransaction"
|
||||||
|
method="post"
|
||||||
|
class="removeTransactionLabelForm">
|
||||||
|
<input type="hidden" name="transactionId" value="@transaction.Id" />
|
||||||
|
<button name="removelabel"
|
||||||
|
style="color: @label.Color; filter: brightness(0.5);"
|
||||||
|
type="submit"
|
||||||
|
value="@label.Text">
|
||||||
|
<span class="fa fa-close"></span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
@if (!string.IsNullOrEmpty(label.Link))
|
||||||
|
{
|
||||||
|
<a href="@label.Link" target="_blank" class="badge transaction-details-icon" style="background-color: @label.Color; filter: brightness(1.1);" rel="noreferrer noopener">
|
||||||
|
<span class="fa fa-info-circle" title="Transaction details" style="color: @label.Color; filter: brightness(0.5);">
|
||||||
|
<span class="visually-hidden">Transaction details</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="smMaxWidth text-truncate @(transaction.IsConfirmed ? "" : "unconf")">
|
||||||
|
<a href="@transaction.Link" target="_blank" rel="noreferrer noopener">
|
||||||
|
@transaction.Id
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
@if (transaction.Positive)
|
||||||
|
{
|
||||||
|
<td class="text-end text-success">@transaction.Balance</td>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<td class="text-end text-danger">@transaction.Balance</td>
|
||||||
|
}
|
||||||
|
<td class="text-end">
|
||||||
|
<div class="dropstart d-inline-block me-2">
|
||||||
|
<span class="fa fa-tags cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<form asp-action="ModifyTransaction" method="post" style="width:260px;" asp-route-walletId="@Context.GetRouteValue("walletId")">
|
||||||
|
<input type="hidden" name="transactionId" value="@transaction.Id" />
|
||||||
|
<div class="input-group input-group-sm p-2">
|
||||||
|
<input name="addlabel" placeholder="Label name" maxlength="20" type="text" class="form-control form-control-sm" />
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm"><span class="fa fa-plus"></span></button>
|
||||||
|
</div>
|
||||||
|
@if (Model.Labels.Count > 0)
|
||||||
|
{
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<div class="py-2 px-3 d-flex flex-wrap gap-2">
|
||||||
|
@foreach (var label in Model.Labels)
|
||||||
|
{
|
||||||
|
@if (transaction.Labels.Contains(label))
|
||||||
|
{
|
||||||
|
<button name="removelabel" class="bg-transparent border-0 p-0" type="submit" value="@label.Text"><span class="badge" style="background-color:@label.Color;color:@label.TextColor"><span class="fa fa-check"></span> @label.Text</span></button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button name="addlabelclick" class="bg-transparent border-0 p-0" type="submit" value="@label.Text"><span class="badge" style="background-color:@label.Color;color:@label.TextColor">@label.Text</span></button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dropstart d-inline-block">
|
||||||
|
@if (string.IsNullOrEmpty(transaction.Comment))
|
||||||
|
{
|
||||||
|
<span class="fa fa-comment cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="fa fa-commenting cursor-pointer" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"></span>
|
||||||
|
}
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<form asp-action="ModifyTransaction" method="post" style="width:260px;" asp-route-walletId="@Context.GetRouteValue("walletId")">
|
||||||
|
<input type="hidden" name="transactionId" value="@transaction.Id" />
|
||||||
|
<div class="input-group p-2">
|
||||||
|
<textarea name="addcomment" maxlength="200" rows="2" cols="20" class="form-control form-control-sm p-1">@transaction.Comment</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm w-100">Save comment</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
@@ -96,6 +96,14 @@ a.unobtrusive-link {
|
|||||||
transform: rotate(-180deg);
|
transform: rotate(-180deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time[datetime] {
|
||||||
|
display: block;
|
||||||
|
max-width: 125px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
/* Badges */
|
/* Badges */
|
||||||
.badge-new {
|
.badge-new {
|
||||||
background: #d4edda;
|
background: #d4edda;
|
||||||
|
|||||||
@@ -1,6 +1,30 @@
|
|||||||
const flatpickrInstances = [];
|
const flatpickrInstances = [];
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
|
||||||
|
const dtFormatOpts = { dateStyle: 'short', timeStyle: 'short' };
|
||||||
|
|
||||||
|
const formatDateTimes = mode => {
|
||||||
|
if (!mode) mode = 'localized';
|
||||||
|
// select only elements which haven't been initialized before, those without data-localized
|
||||||
|
document.querySelectorAll("time[datetime]:not([data-localized])").forEach($el => {
|
||||||
|
const date = new Date($el.getAttribute("datetime"));
|
||||||
|
// initialize and set localized attribute
|
||||||
|
$el.dataset.localized = new Intl.DateTimeFormat('default', dtFormatOpts).format(date);
|
||||||
|
// set text to chosen mode
|
||||||
|
if ($el.dataset[mode]) $el.innerText = $el.dataset[mode];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const switchTimeFormat = event => {
|
||||||
|
const curr = event.target.dataset.mode || 'localized';
|
||||||
|
const mode = curr === 'relative' ? 'localized' : 'relative';
|
||||||
|
document.querySelectorAll("time[datetime]").forEach($el => {
|
||||||
|
$el.innerText = $el.dataset[mode];
|
||||||
|
});
|
||||||
|
event.target.dataset.mode = mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
// sticky header
|
// sticky header
|
||||||
const stickyHeader = document.querySelector('.sticky-header-setup + .sticky-header');
|
const stickyHeader = document.querySelector('.sticky-header-setup + .sticky-header');
|
||||||
if (stickyHeader) {
|
if (stickyHeader) {
|
||||||
@@ -12,13 +36,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
$("#TimezoneOffset").val(timezoneOffset);
|
$("#TimezoneOffset").val(timezoneOffset);
|
||||||
|
|
||||||
// localize all elements that have localizeDate class
|
// localize all elements that have localizeDate class
|
||||||
$(".localizeDate").each(function (index) {
|
formatDateTimes();
|
||||||
var serverDate = $(this).text();
|
|
||||||
var localDate = new Date(serverDate);
|
|
||||||
|
|
||||||
var dateString = localDate.toLocaleDateString() + " " + localDate.toLocaleTimeString();
|
|
||||||
$(this).text(dateString);
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateTimeAgo(){
|
function updateTimeAgo(){
|
||||||
var timeagoElements = $("[data-timeago-unixms]");
|
var timeagoElements = $("[data-timeago-unixms]");
|
||||||
@@ -120,6 +138,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Time Format
|
||||||
|
delegate('click', '#switchTimeFormat', switchTimeFormat);
|
||||||
|
|
||||||
// Theme Switch
|
// Theme Switch
|
||||||
delegate('click', '.btcpay-theme-switch', e => {
|
delegate('click', '.btcpay-theme-switch', e => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -172,14 +193,4 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function switchTimeFormat() {
|
|
||||||
$(".switchTimeFormat").each(function (index) {
|
|
||||||
var htmlVal = $(this).html();
|
|
||||||
var switchVal = $(this).attr("data-switch");
|
|
||||||
|
|
||||||
$(this).html(switchVal);
|
|
||||||
$(this).attr("data-switch", htmlVal);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user