mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 18:11:36 +01:00
Update Invoice Views (#3264)
* updates create invoice * updates invoice list * formats * updates row * updates * Improve invoice list markup and fix mass action form * Responsive invoice table * Improve spacing on invoice detail view * Improve archive message * Responsive status change partial * Add test case for mass archiving * Add mass unarchiving Closes #3270. Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com> Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
parent
3c5d809cf9
commit
4b941a5145
@ -393,6 +393,7 @@ namespace BTCPayServer.Tests
|
||||
var storeUrl = s.Driver.Url;
|
||||
s.ClickOnAllSectionLinks();
|
||||
s.GoToInvoices();
|
||||
Assert.Contains("There are no invoices matching your criteria.", s.Driver.PageSource);
|
||||
var invoiceId = s.CreateInvoice();
|
||||
s.FindAlertMessage();
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
@ -415,6 +416,23 @@ namespace BTCPayServer.Tests
|
||||
s.GoToInvoices();
|
||||
Assert.Contains(invoiceId, s.Driver.PageSource);
|
||||
|
||||
// archive via list
|
||||
s.Driver.FindElement(By.CssSelector($".selector[value=\"{invoiceId}\"]")).Click();
|
||||
s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click();
|
||||
s.Driver.FindElement(By.Id("ActionsDropdownArchive")).Click();
|
||||
Assert.Contains("1 invoice archived", s.FindAlertMessage().Text);
|
||||
Assert.DoesNotContain(invoiceId, s.Driver.PageSource);
|
||||
|
||||
// unarchive via list
|
||||
s.Driver.FindElement(By.Id("SearchOptionsToggle")).Click();
|
||||
s.Driver.FindElement(By.Id("SearchOptionsIncludeArchived")).Click();
|
||||
Assert.Contains(invoiceId, s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector($".selector[value=\"{invoiceId}\"]")).Click();
|
||||
s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click();
|
||||
s.Driver.FindElement(By.Id("ActionsDropdownUnarchive")).Click();
|
||||
Assert.Contains("1 invoice unarchived", s.FindAlertMessage().Text);
|
||||
Assert.Contains(invoiceId, s.Driver.PageSource);
|
||||
|
||||
// When logout out we should not be able to access store and invoice details
|
||||
s.Logout();
|
||||
s.Driver.Navigate().GoToUrl(storeUrl);
|
||||
|
@ -437,8 +437,12 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
case "archive":
|
||||
await _InvoiceRepository.MassArchive(selectedItems);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice(s) archived.";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice{(selectedItems.Length == 1 ? "" : "s")} archived.";
|
||||
break;
|
||||
|
||||
case "unarchive":
|
||||
await _InvoiceRepository.MassArchive(selectedItems, false);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{selectedItems.Length} invoice{(selectedItems.Length == 1 ? "" : "s")} unarchived.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -763,6 +767,8 @@ namespace BTCPayServer.Controllers
|
||||
invoiceQuery.Skip = model.Skip;
|
||||
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
|
||||
|
||||
model.IncludeArchived = invoiceQuery.IncludeArchived;
|
||||
|
||||
foreach (var invoice in list)
|
||||
{
|
||||
var state = invoice.GetInvoiceState();
|
||||
|
@ -10,6 +10,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public List<InvoiceModel> Invoices { get; set; } = new List<InvoiceModel>();
|
||||
public string[] StoreIds { get; set; }
|
||||
public string StoreId { get; set; }
|
||||
public bool IncludeArchived { get; set; }
|
||||
}
|
||||
|
||||
public class InvoiceModel
|
||||
|
@ -446,7 +446,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
}
|
||||
}
|
||||
|
||||
public async Task MassArchive(string[] invoiceIds)
|
||||
public async Task MassArchive(string[] invoiceIds, bool archive = true)
|
||||
{
|
||||
using (var context = _applicationDbContextFactory.CreateContext())
|
||||
{
|
||||
@ -458,7 +458,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
|
||||
foreach (InvoiceData invoice in items)
|
||||
{
|
||||
invoice.Archived = true;
|
||||
invoice.Archived = archive;
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
|
@ -47,7 +47,8 @@
|
||||
|
||||
<h4 class="mt-5 mb-4">Invoice Details</h4>
|
||||
}
|
||||
<div class="form-group">
|
||||
<div class="d-flex justify-content-between">
|
||||
<div class="form-group flex-fill me-4">
|
||||
<label asp-for="Amount" class="form-label"></label>
|
||||
<input asp-for="Amount" class="form-control" />
|
||||
<span asp-validation-for="Amount" class="text-danger"></span>
|
||||
@ -57,6 +58,7 @@
|
||||
<input asp-for="Currency" class="form-control" />
|
||||
<span asp-validation-for="Currency" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="OrderId" class="form-label"></label>
|
||||
<input asp-for="OrderId" class="form-control" />
|
||||
@ -145,7 +147,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group mt-4">
|
||||
<input type="submit" value="Create" class="btn btn-primary" id="Create" />
|
||||
</div>
|
||||
|
@ -314,7 +314,7 @@
|
||||
|
||||
@if (Model.Deliveries.Count != 0)
|
||||
{
|
||||
<h3 class="mb-3">Webhook deliveries</h3>
|
||||
<h3 class="mb-3 mt-4">Webhook deliveries</h3>
|
||||
<ul class="list-group mb-5">
|
||||
@foreach (var delivery in Model.Deliveries)
|
||||
{
|
||||
@ -369,7 +369,7 @@
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<h3 class="mb-0">Events</h3>
|
||||
<table class="table table-hover table-responsive-md">
|
||||
|
@ -11,8 +11,8 @@
|
||||
<h5 class="alert-heading">Updated in v1.4.0</h5>
|
||||
<p class="mb-2">Invoice states have been updated to match the Greenfield API:</p>
|
||||
<div class="row">
|
||||
<div class="col col-12 col-sm-6">
|
||||
<ul class="list-unstyled mb-sm-0">
|
||||
<div class="col-12 col-md-6">
|
||||
<ul class="list-unstyled mb-md-0">
|
||||
<li>
|
||||
<span class="badge badge-processing">Paid</span>
|
||||
<span class="mx-1">is now shown as</span>
|
||||
@ -30,7 +30,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col col-12 col-sm-6 d-flex justify-content-sm-end align-items-sm-end">
|
||||
<div class="col-12 col-md-6 d-flex justify-content-md-end align-items-md-end">
|
||||
<button name="command" type="submit" value="save" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="alert">Don't Show Again</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@
|
||||
}
|
||||
|
||||
@section PageHeadContent {
|
||||
<style type="text/css">
|
||||
<style>
|
||||
.invoice-payments {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
@ -16,14 +16,6 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wraptext200 {
|
||||
max-width: 200px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pavpill {
|
||||
display: inline-block;
|
||||
padding: 0.3em 0.5em;
|
||||
@ -69,6 +61,13 @@
|
||||
background: #329f80;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* pull mass action form up, so that it is besides the search form */
|
||||
@@media (min-width: 992px) {
|
||||
#MassAction {
|
||||
margin-top: -4rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
@ -198,43 +197,6 @@
|
||||
|
||||
<partial name="InvoiceStatusChangePartial" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 mb-5 mb-lg-2 ms-auto">
|
||||
<form asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" method="get">
|
||||
<input type="hidden" asp-for="Count"/>
|
||||
<input asp-for="TimezoneOffset" type="hidden"/>
|
||||
<div class="input-group">
|
||||
<a href="#help" class="input-group-text text-secondary text-decoration-none" data-bs-toggle="collapse">
|
||||
<span class="fa fa-filter"></span>
|
||||
</a>
|
||||
<input asp-for="SearchTerm" class="form-control"/>
|
||||
<button type="submit" class="btn btn-secondary" title="Search invoice">
|
||||
<span class="fa fa-search"></span> Search
|
||||
</button>
|
||||
<button type="button" id="SearchOptionsToggle" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="SearchOptionsToggle">
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="status:invalid@{@storeIds}">Invalid Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="status:processing,status:settled@{@storeIds}">Paid Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="exceptionstatus:paidLate@{@storeIds}">Paid Late Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="exceptionstatus:paidPartial@{@storeIds}">Paid Partial Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="exceptionstatus:paidOver@{@storeIds}">Paid Over Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="unusual:true@{@storeIds}">Unusual Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="includearchived:true@{@storeIds}">Archived Invoices</a>
|
||||
<div role="separator" class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-timezoneoffset="0" asp-route-searchTerm="startDate:-24h@{@storeIds}">Last 24 hours</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-timezoneoffset="0" asp-route-searchTerm="startDate:-3d@{@storeIds}">Last 3 days</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-timezoneoffset="0" asp-route-searchTerm="startDate:-7d@{@storeIds}">Last 7 days</a>
|
||||
<button type="button" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#customRangeModal">Custom Range</button>
|
||||
<div role="separator" class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="?searchTerm=">Unfiltered</a>
|
||||
</div>
|
||||
</div>
|
||||
<span asp-validation-for="SearchTerm" class="text-danger"></span>
|
||||
</form>
|
||||
|
||||
@* Custom Range Modal *@
|
||||
<div class="modal fade" id="customRangeModal" tabindex="-1" role="dialog" aria-labelledby="customRangeModalTitle" aria-hidden="true" data-bs-backdrop="static">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document" style="max-width: 550px;">
|
||||
@ -279,11 +241,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row collapse" id="help">
|
||||
<div class="col @(Model.Total > 0 ? "pt-3 pb-lg-5" : "")">
|
||||
<div id="help" class="row collapse">
|
||||
<div class="col-xl-8 pb-3">
|
||||
<p>
|
||||
You can search for invoice Id, deposit address, price, order id, store id, any buyer information and any product information.
|
||||
Be sure to split your search parameters with comma, for example:<br />
|
||||
@ -304,17 +264,57 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<form class="col-lg-6 col-xl-8 mb-4" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" method="get">
|
||||
<input type="hidden" asp-for="Count" />
|
||||
<input asp-for="TimezoneOffset" type="hidden" />
|
||||
<div class="input-group">
|
||||
<a href="#help" class="input-group-text text-secondary text-decoration-none" data-bs-toggle="collapse">
|
||||
<span class="fa fa-filter"></span>
|
||||
</a>
|
||||
<input asp-for="SearchTerm" class="form-control" />
|
||||
<button type="submit" class="btn btn-secondary" title="Search invoice">
|
||||
<span class="fa fa-search"></span> Search
|
||||
</button>
|
||||
<button type="button" id="SearchOptionsToggle" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="visually-hidden">Toggle Dropdown</span>
|
||||
</button>
|
||||
|
||||
@if (Model.Total > 0)
|
||||
{
|
||||
<form method="post" id="MassAction" asp-action="MassAction" class="mt-lg-n5">
|
||||
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="SearchOptionsToggle">
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="status:invalid@{@storeIds}">Invalid Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="status:processing,status:settled@{@storeIds}">Paid Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="exceptionstatus:paidLate@{@storeIds}">Paid Late Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="exceptionstatus:paidPartial@{@storeIds}">Paid Partial Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="exceptionstatus:paidOver@{@storeIds}">Paid Over Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="unusual:true@{@storeIds}">Unusual Invoices</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-searchTerm="includearchived:true@{@storeIds}" id="SearchOptionsIncludeArchived">Archived Invoices</a>
|
||||
<div role="separator" class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-timezoneoffset="0" asp-route-searchTerm="startDate:-24h@{@storeIds}">Last 24 hours</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-timezoneoffset="0" asp-route-searchTerm="startDate:-3d@{@storeIds}">Last 3 days</a>
|
||||
<a class="dropdown-item" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" asp-route-count="@Model.Count" asp-route-timezoneoffset="0" asp-route-searchTerm="startDate:-7d@{@storeIds}">Last 7 days</a>
|
||||
<button type="button" class="dropdown-item" data-bs-toggle="modal" data-bs-target="#customRangeModal">Custom Range</button>
|
||||
<div role="separator" class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="?searchTerm=">Unfiltered</a>
|
||||
</div>
|
||||
</div>
|
||||
<span asp-validation-for="SearchTerm" class="text-danger"></span>
|
||||
</form>
|
||||
|
||||
<form method="post" id="MassAction" asp-action="MassAction" class="">
|
||||
<div class="d-inline-flex align-items-center pb-2 float-lg-end mb-2">
|
||||
<input type="hidden" name="storeId" value="@Model.StoreId" />
|
||||
<a href="https://docs.btcpayserver.org/Accounting/" class="ms-2 ms-lg-0 me-lg-2 order-1 order-lg-0" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
<span class="me-2">
|
||||
<button class="btn btn-secondary dropdown-toggle mb-1" type="button" id="ActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Actions
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="ActionsDropdownToggle">
|
||||
<button type="submit" asp-action="MassAction" class="dropdown-item" name="command" value="archive"><i class="fa fa-archive"></i> Archive</button>
|
||||
<button type="submit" asp-action="MassAction" class="dropdown-item" name="command" value="archive" id="ActionsDropdownArchive"><i class="fa fa-archive"></i> Archive</button>
|
||||
@if (Model.IncludeArchived)
|
||||
{
|
||||
<button type="submit" asp-action="MassAction" class="dropdown-item" name="command" value="unarchive" id="ActionsDropdownUnarchive"><i class="fa fa-archive"></i> Unarchive</button>
|
||||
}
|
||||
</div>
|
||||
</span>
|
||||
<span>
|
||||
@ -326,28 +326,28 @@
|
||||
<a asp-action="Export" asp-route-timezoneoffset="0" asp-route-format="json" asp-route-searchTerm="@Model.SearchTerm" class="dropdown-item export-link" target="_blank">JSON</a>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<a href="https://docs.btcpayserver.org/Accounting/" class="ms-1" target="_blank" rel="noreferrer noopener">
|
||||
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
|
||||
</a>
|
||||
|
||||
<table id="invoices" class="table table-hover table-responsive-md mt-4">
|
||||
@if (Model.Total > 0)
|
||||
{
|
||||
<div style="clear:both"></div>
|
||||
<div class="table-responsive">
|
||||
<table id="invoices" class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:2rem;" class="only-for-js">
|
||||
<input id="selectAllCheckbox" type="checkbox" class="form-check-input" />
|
||||
</th>
|
||||
<th style="min-width:90px;" class="col-md-auto">
|
||||
Date
|
||||
<a id="switchTimeFormat" href="#">
|
||||
<span class="fa fa-clock-o" title="Switch date format"></span>
|
||||
</a>
|
||||
</th>
|
||||
<th style="max-width: 180px;">OrderId</th>
|
||||
<th>InvoiceId</th>
|
||||
<th style="min-width: 150px;">Status</th>
|
||||
<th style="text-align:right">Amount</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
<th class="text-nowrap">Order Id</th>
|
||||
<th class="text-nowrap">Invoice Id</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Amount</th>
|
||||
<th class="text-end">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -362,17 +362,17 @@
|
||||
@invoice.Date.ToBrowserDate()
|
||||
</span>
|
||||
</td>
|
||||
<td style="max-width: 180px;">
|
||||
<td style="max-width:120px;">
|
||||
@if (invoice.RedirectUrl != string.Empty)
|
||||
{
|
||||
<a href="@invoice.RedirectUrl" class="wraptext200" rel="noreferrer noopener">@invoice.OrderId</a>
|
||||
<a href="@invoice.RedirectUrl" class="wraptextAuto" rel="noreferrer noopener">@invoice.OrderId</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@invoice.OrderId</span>
|
||||
}
|
||||
</td>
|
||||
<td>@invoice.InvoiceId</td>
|
||||
<td class="text-break">@invoice.InvoiceId</td>
|
||||
<td>
|
||||
@if (invoice.Details.Archived)
|
||||
{
|
||||
@ -421,8 +421,8 @@
|
||||
<span class="badge">@paymentType.GetBadge()</span>
|
||||
}
|
||||
</td>
|
||||
<td style="text-align:right">@invoice.AmountCurrency</td>
|
||||
<td style="text-align:right">
|
||||
<td class="text-end text-nowrap">@invoice.AmountCurrency</td>
|
||||
<td class="text-end text-nowrap">
|
||||
@if (invoice.ShowCheckout)
|
||||
{
|
||||
<span>
|
||||
@ -452,9 +452,9 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<vc:pager view-model="Model" />
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -462,3 +462,4 @@ else
|
||||
There are no invoices matching your criteria.
|
||||
</p>
|
||||
}
|
||||
</form>
|
||||
|
Loading…
Reference in New Issue
Block a user