btcpayserver/BTCPayServer/Views/UIWallets/WalletTransactions.cshtml
d11n b334e1aa00
Date display improvements (#4191)
Fixes styling issues introduced in #4074, because the `max-width` was to small for localized dates.

Also adds the ability to choose the prefered initial display format, which can be the localized or relative date.
2022-10-07 13:29:03 +09:00

255 lines
9.8 KiB
Plaintext

@model ListTransactionsViewModel
@{
var walletId = Context.GetRouteValue("walletId").ToString();
var labelFilter = Context.Request.Query["labelFilter"].ToString();
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePage(WalletsNavPages.Transactions, $"{Model.CryptoCode} Transactions", walletId);
}
@section PageHeadContent {
<style>
.smMaxWidth {
max-width: 125px;
}
@@media (min-width: 990px) {
.smMaxWidth {
max-width: 200px;
}
}
/* pull actions area, so that it is besides the search form */
@@media (min-width: 1200px) {
#Filter + #Dropdowns {
margin-top: -4rem;
float: right;
}
#Filter + #Dropdowns #Actions {
order: 1;
}
}
.unconf > * {
opacity: 0.5;
}
.badge-container {
display: flex;
flex-wrap: nowrap;
}
.removeTransactionLabelForm {
display: inline;
position: absolute;
right: 4px;
}
.removeTransactionLabelForm button {
cursor: pointer;
display: inline;
padding: 0;
background-color: transparent;
border: 0;
}
.label-tooltip .tooltip-inner {
max-width: 15rem;
text-align: left;
}
.label-tooltip ul {
margin: 0;
padding-left: var(--btcpay-space-m);
}
#LoadingIndicator {
margin-bottom: 1.5rem;
}
</style>
}
@section PageFootContent {
@*Without async, somehow selenium do not manage to click on links in this page*@
<script src="~/modal/btcpay.js" asp-append-version="true" async></script>
@* Custom Range Modal *@
<script>
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 => {
document.querySelectorAll(".selector").forEach(checkbox => {
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>
}
@if (Model.Transactions.Any())
{
@if (Model.Labels.Any())
{
<div class="col-xl-7 col-xxl-8 mb-4" id="Filter">
<div class="input-group">
<span class="input-group-text">Filter</span>
<div class="form-control d-flex flex-wrap gap-2 align-items-center">
@foreach (var label in Model.Labels)
{
<a asp-route-labelFilter="@label.Text" class="badge position-relative text-white flex-grow-0" style="background-color:@label.Color;color:@label.TextColor !important;">@label.Text</a>
}
</div>
@if (labelFilter != null)
{
<a asp-route-labelFilter="" class="btn btn-secondary d-flex align-items-center">Clear filter</a>
}
</div>
</div>
}
<div class="d-inline-flex align-items-center gap-3" id="Dropdowns">
<div class="dropdown ms-auto" id="Actions">
<button class="btn btn-secondary dropdown-toggle" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Actions
</button>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="ActionsDropdownToggle">
<form id="WalletActions" method="post" asp-action="WalletActions" asp-route-walletId="@walletId">
<button id="BumpFee" name="command" type="submit" class="dropdown-item" value="cpfp">Bump fee (CPFP)</button>
</form>
</div>
</div>
<div class="dropdown d-inline-flex align-items-center gap-3" id="Export">
<button class="btn btn-secondary dropdown-toggle" type="button" id="ExportDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Export
</button>
<div class="dropdown-menu" aria-labelledby="ExportDropdownToggle">
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="csv" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportCSV">CSV</a>
<a asp-action="Export" asp-route-walletId="@walletId" asp-route-format="json" asp-route-labelFilter="@labelFilter" class="dropdown-item export-link" target="_blank" id="ExportJSON">JSON</a>
</div>
</div>
</div>
<div style="clear:both"></div>
<div class="row">
<div class="col table-responsive-md" id="walletTable">
<table class="table table-hover">
<thead class="thead-inverse">
<tr>
<th style="width:2rem;" class="only-for-js">
<input id="selectAllCheckbox" type="checkbox" class="form-check-input" />
</th>
<th class="w-150px">
<div class="d-flex align-items-center gap-1">
Date
<button type="button" class="btn btn-link p-0 fa fa-clock-o switch-time-format" title="Switch date format" id="switchTimeFormat"></button>
</div>
</th>
<th class="text-start">Label</th>
<th>Transaction Id</th>
<th class="text-end">Amount</th>
<th class="text-end" style="min-width:60px"></th>
</tr>
</thead>
<tbody id="WalletTransactionsList">
<partial name="_WalletTransactionsList" model="Model" />
</tbody>
</table>
</div>
</div>
<noscript>
<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
{
<p class="text-secondary mt-3">
There are no transactions yet.
</p>
}
<p class="mt-4 mb-0">
If BTCPay Server shows you an invalid balance, <a asp-action="WalletRescan" asp-route-walletId="@Context.GetRouteValue("walletId")">rescan your wallet</a>.
<br />
If some transactions appear in BTCPay Server, but are missing in another wallet, <a href="https://docs.btcpayserver.org/FAQ/Wallet/#missing-payments-in-my-software-or-hardware-wallet" rel="noreferrer noopener">follow these instructions</a>.
</p>