mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Invoice: Unify status display and functionality (#5360)
* Invoice: Unify status display and functionality Consolidates the invoice status display and functionality (mark setted or invalid) across the dashboard, list and details pages. * Test fix --------- Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
d44efce225
commit
2846c38ff5
15 changed files with 160 additions and 205 deletions
|
@ -1,12 +1,12 @@
|
|||
namespace BTCPayServer.Client.Models
|
||||
namespace BTCPayServer.Client.Models;
|
||||
public enum InvoiceExceptionStatus
|
||||
{
|
||||
public enum InvoiceExceptionStatus
|
||||
{
|
||||
None,
|
||||
PaidLate,
|
||||
PaidPartial,
|
||||
Marked,
|
||||
Invalid,
|
||||
PaidOver
|
||||
}
|
||||
None,
|
||||
PaidLate,
|
||||
PaidPartial,
|
||||
Marked,
|
||||
Invalid,
|
||||
PaidOver
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -556,24 +556,24 @@ namespace BTCPayServer.Tests
|
|||
s.AddDerivationScheme();
|
||||
s.GoToInvoices();
|
||||
s.CreateInvoice();
|
||||
s.Driver.FindElement(By.Id("markStatusDropdownMenuButton")).Click();
|
||||
s.Driver.FindElements(By.ClassName("changeInvoiceState"))[0].Click();
|
||||
s.Driver.FindElement(By.CssSelector("[data-invoice-state-badge] .dropdown-toggle")).Click();
|
||||
s.Driver.FindElements(By.CssSelector("[data-invoice-state-badge] .dropdown-menu button"))[0].Click();
|
||||
TestUtils.Eventually(() => Assert.Contains("Invalid (marked)", s.Driver.PageSource));
|
||||
s.Driver.Navigate().Refresh();
|
||||
|
||||
s.Driver.FindElement(By.Id("markStatusDropdownMenuButton")).Click();
|
||||
s.Driver.FindElements(By.ClassName("changeInvoiceState"))[0].Click();
|
||||
s.Driver.FindElement(By.CssSelector("[data-invoice-state-badge] .dropdown-toggle")).Click();
|
||||
s.Driver.FindElements(By.CssSelector("[data-invoice-state-badge] .dropdown-menu button"))[0].Click();
|
||||
TestUtils.Eventually(() => Assert.Contains("Settled (marked)", s.Driver.PageSource));
|
||||
|
||||
s.Driver.Navigate().Refresh();
|
||||
|
||||
s.Driver.FindElement(By.Id("markStatusDropdownMenuButton")).Click();
|
||||
s.Driver.FindElements(By.ClassName("changeInvoiceState"))[0].Click();
|
||||
s.Driver.FindElement(By.CssSelector("[data-invoice-state-badge] .dropdown-toggle")).Click();
|
||||
s.Driver.FindElements(By.CssSelector("[data-invoice-state-badge] .dropdown-menu button"))[0].Click();
|
||||
TestUtils.Eventually(() => Assert.Contains("Invalid (marked)", s.Driver.PageSource));
|
||||
s.Driver.Navigate().Refresh();
|
||||
|
||||
s.Driver.FindElement(By.Id("markStatusDropdownMenuButton")).Click();
|
||||
s.Driver.FindElements(By.ClassName("changeInvoiceState"))[0].Click();
|
||||
s.Driver.FindElement(By.CssSelector("[data-invoice-state-badge] .dropdown-toggle")).Click();
|
||||
s.Driver.FindElements(By.CssSelector("[data-invoice-state-badge] .dropdown-menu button"))[0].Click();
|
||||
TestUtils.Eventually(() => Assert.Contains("Settled (marked)", s.Driver.PageSource));
|
||||
}
|
||||
|
||||
|
|
67
BTCPayServer/Components/InvoiceStatus/Default.cshtml
Normal file
67
BTCPayServer/Components/InvoiceStatus/Default.cshtml
Normal file
|
@ -0,0 +1,67 @@
|
|||
@using BTCPayServer.Services.Invoices
|
||||
@using BTCPayServer.Abstractions.Extensions
|
||||
@model BTCPayServer.Components.InvoiceStatus.InvoiceStatusViewModel
|
||||
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
|
||||
|
||||
@{
|
||||
var state = Model.State.ToString();
|
||||
var badgeClass = Model.State.Status.ToModernStatus().ToString().ToLower();
|
||||
var canMark = !string.IsNullOrEmpty(Model.InvoiceId) && (Model.State.CanMarkComplete() || Model.State.CanMarkInvalid());
|
||||
}
|
||||
<div class="d-inline-flex align-items-center gap-2">
|
||||
@if (Model.IsArchived)
|
||||
{
|
||||
<span class="badge bg-warning">archived</span>
|
||||
}
|
||||
<div class="badge badge-@badgeClass" data-invoice-state-badge="@Model.InvoiceId">
|
||||
@if (canMark)
|
||||
{
|
||||
<span class="dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@state
|
||||
</span>
|
||||
<div class="dropdown-menu">
|
||||
@if (Model.State.CanMarkInvalid())
|
||||
{
|
||||
<button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="invalid">
|
||||
Mark as invalid
|
||||
</button>
|
||||
}
|
||||
@if (Model.State.CanMarkComplete())
|
||||
{
|
||||
<button type="button" class="dropdown-item lh-base" data-invoice-id="@Model.InvoiceId" data-new-state="settled">
|
||||
Mark as settled
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@state
|
||||
}
|
||||
</div>
|
||||
@if (Model.Payments != null)
|
||||
{
|
||||
foreach (var paymentMethodId in Model.Payments.Select(payment => payment.GetPaymentMethodId()).Distinct())
|
||||
{
|
||||
var image = PaymentMethodHandlerDictionary[paymentMethodId]?.GetCryptoImage(paymentMethodId);
|
||||
var badge = paymentMethodId.PaymentType.GetBadge();
|
||||
if (!string.IsNullOrEmpty(image) || !string.IsNullOrEmpty(badge))
|
||||
{
|
||||
<span class="d-inline-flex align-items-center gap-1">
|
||||
@if (!string.IsNullOrEmpty(image))
|
||||
{
|
||||
<img src="@Context.Request.GetRelativePathOrAbsolute(image)" alt="@paymentMethodId.PaymentType.ToString()" style="height:1.5em" />
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(badge))
|
||||
{
|
||||
@badge
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (Model.HasRefund)
|
||||
{
|
||||
<span class="badge bg-warning">Refund</span>
|
||||
}
|
||||
</div>
|
22
BTCPayServer/Components/InvoiceStatus/InvoiceStatus.cs
Normal file
22
BTCPayServer/Components/InvoiceStatus/InvoiceStatus.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System.Collections.Generic;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Components.InvoiceStatus
|
||||
{
|
||||
public class InvoiceStatus : ViewComponent
|
||||
{
|
||||
public IViewComponentResult Invoke(InvoiceState state, List<PaymentEntity> payments, string invoiceId, bool isArchived = false, bool hasRefund = false)
|
||||
{
|
||||
var vm = new InvoiceStatusViewModel
|
||||
{
|
||||
State = state,
|
||||
Payments = payments,
|
||||
InvoiceId = invoiceId,
|
||||
IsArchived = isArchived,
|
||||
HasRefund = hasRefund
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
|
||||
namespace BTCPayServer.Components.InvoiceStatus
|
||||
{
|
||||
public class InvoiceStatusViewModel
|
||||
{
|
||||
public InvoiceState State { get; set; }
|
||||
public List<PaymentEntity> Payments { get; set; }
|
||||
public string InvoiceId { get; set; }
|
||||
public bool IsArchived { get; set; }
|
||||
public bool HasRefund { get; set; }
|
||||
}
|
||||
}
|
|
@ -52,41 +52,8 @@
|
|||
<a asp-controller="UIInvoice" asp-action="Invoice" asp-route-invoiceId="@invoice.InvoiceId" class="text-break">@invoice.InvoiceId</a>
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@if (invoice.Details.Archived)
|
||||
{
|
||||
<span class="badge bg-warning">archived</span>
|
||||
}
|
||||
<span class="badge badge-@invoice.Status.Status.ToModernStatus().ToString().ToLower()">
|
||||
@invoice.Status.Status.ToModernStatus().ToString()
|
||||
@if (invoice.Status.ExceptionStatus != InvoiceExceptionStatus.None)
|
||||
{
|
||||
@($"({invoice.Status.ExceptionStatus.ToString()})")
|
||||
}
|
||||
</span>
|
||||
@foreach (var paymentMethodId in invoice.Details.Payments.Select(payment => payment.GetPaymentMethodId()).Distinct())
|
||||
{
|
||||
var image = PaymentMethodHandlerDictionary[paymentMethodId]?.GetCryptoImage(paymentMethodId);
|
||||
var badge = paymentMethodId.PaymentType.GetBadge();
|
||||
if (!string.IsNullOrEmpty(image) || !string.IsNullOrEmpty(badge))
|
||||
{
|
||||
<span class="d-inline-flex align-items-center gap-1">
|
||||
@if (!string.IsNullOrEmpty(image))
|
||||
{
|
||||
<img src="@Context.Request.GetRelativePathOrAbsolute(image)" alt="@paymentMethodId.PaymentType.ToString()" style="height:1.5em" />
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(badge))
|
||||
{
|
||||
@badge
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@if (invoice.HasRefund)
|
||||
{
|
||||
<span class="badge bg-warning">Refund</span>
|
||||
}
|
||||
</div>
|
||||
<vc:invoice-status state="invoice.Status" payments="invoice.Details.Payments" invoice-id="@invoice.InvoiceId"
|
||||
is-archived="invoice.Details.Archived" has-refund="invoice.HasRefund" />
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<span data-sensitive>@DisplayFormatter.Currency(invoice.Amount, invoice.Currency)</span>
|
||||
|
|
|
@ -12,7 +12,6 @@ public class StoreRecentInvoiceViewModel
|
|||
public string Currency { get; set; }
|
||||
public InvoiceState Status { get; set; }
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
public InvoiceDetailsModel Details { get; set; }
|
||||
public bool HasRefund { get; set; }
|
||||
}
|
||||
|
|
|
@ -150,15 +150,14 @@ namespace BTCPayServer.Controllers
|
|||
Events = invoice.Events,
|
||||
Metadata = metaData,
|
||||
Archived = invoice.Archived,
|
||||
HasRefund = invoice.Refunds.Any(),
|
||||
CanRefund = invoiceState.CanRefund(),
|
||||
Refunds = invoice.Refunds,
|
||||
ShowCheckout = invoice.Status == InvoiceStatusLegacy.New,
|
||||
ShowReceipt = invoice.Status.ToModernStatus() == InvoiceStatus.Settled && (invoice.ReceiptOptions?.Enabled ?? receipt.Enabled is true),
|
||||
Deliveries = (await _InvoiceRepository.GetWebhookDeliveries(invoiceId))
|
||||
.Select(c => new Models.StoreViewModels.DeliveryViewModel(c))
|
||||
.ToList(),
|
||||
CanMarkInvalid = invoiceState.CanMarkInvalid(),
|
||||
CanMarkSettled = invoiceState.CanMarkComplete(),
|
||||
.ToList()
|
||||
};
|
||||
|
||||
var details = InvoicePopulatePayments(invoice);
|
||||
|
@ -1154,7 +1153,7 @@ namespace BTCPayServer.Controllers
|
|||
CanMarkInvalid = state.CanMarkInvalid(),
|
||||
CanMarkSettled = state.CanMarkComplete(),
|
||||
Details = InvoicePopulatePayments(invoice),
|
||||
HasRefund = invoice.Refunds.Any(data => !data.PullPaymentData.Archived)
|
||||
HasRefund = invoice.Refunds.Any()
|
||||
});
|
||||
}
|
||||
return View(model);
|
||||
|
|
|
@ -125,7 +125,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
}
|
||||
public InvoiceMetadata TypedMetadata { get; set; }
|
||||
public DateTimeOffset MonitoringDate { get; internal set; }
|
||||
public List<Data.InvoiceEventData> Events { get; internal set; }
|
||||
public List<InvoiceEventData> Events { get; internal set; }
|
||||
public string NotificationEmail { get; internal set; }
|
||||
public Dictionary<string, object> Metadata { get; set; }
|
||||
public Dictionary<string, object> ReceiptData { get; set; }
|
||||
|
@ -134,12 +134,10 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
public bool Archived { get; set; }
|
||||
public bool CanRefund { get; set; }
|
||||
public bool ShowCheckout { get; set; }
|
||||
public bool CanMarkSettled { get; set; }
|
||||
public bool CanMarkInvalid { get; set; }
|
||||
public bool CanMarkStatus => CanMarkSettled || CanMarkInvalid;
|
||||
public List<RefundData> Refunds { get; set; }
|
||||
public bool ShowReceipt { get; set; }
|
||||
public bool Overpaid { get; set; }
|
||||
public bool HasRefund { get; set; }
|
||||
public bool StillDue { get; set; }
|
||||
public bool HasRates { get; set; }
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
public List<InvoiceModel> Invoices { get; set; } = new ();
|
||||
public override int CurrentPageCount => Invoices.Count;
|
||||
public string StoreId { get; set; }
|
||||
|
||||
public string SearchText { get; set; }
|
||||
public SearchString Search { get; set; }
|
||||
public List<InvoiceAppModel> Apps { get; set; }
|
||||
|
@ -22,16 +21,13 @@ namespace BTCPayServer.Models.InvoicingModels
|
|||
public string OrderId { get; set; }
|
||||
public string RedirectUrl { get; set; }
|
||||
public string InvoiceId { get; set; }
|
||||
|
||||
public InvoiceState Status { get; set; }
|
||||
public bool CanMarkSettled { get; set; }
|
||||
public bool CanMarkInvalid { get; set; }
|
||||
public bool CanMarkStatus => CanMarkSettled || CanMarkInvalid;
|
||||
public bool ShowCheckout { get; set; }
|
||||
public string ExceptionStatus { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
|
||||
public InvoiceDetailsModel Details { get; set; }
|
||||
public bool HasRefund { get; set; }
|
||||
}
|
||||
|
|
|
@ -978,7 +978,15 @@ namespace BTCPayServer.Services.Invoices
|
|||
}
|
||||
public override string ToString()
|
||||
{
|
||||
return Status.ToModernStatus() + (ExceptionStatus == InvoiceExceptionStatus.None ? string.Empty : $" ({ToString(ExceptionStatus)})");
|
||||
return Status.ToModernStatus() + ExceptionStatus switch
|
||||
{
|
||||
InvoiceExceptionStatus.PaidOver => " (paid over)",
|
||||
InvoiceExceptionStatus.PaidLate => " (paid late)",
|
||||
InvoiceExceptionStatus.PaidPartial => " (paid partial)",
|
||||
InvoiceExceptionStatus.Marked => " (marked)",
|
||||
InvoiceExceptionStatus.Invalid => " (invalid)",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,30 +34,6 @@
|
|||
|
||||
@section PageFootContent {
|
||||
<script>
|
||||
const alertClasses = { "Settled (marked)": 'success', "Invalid (marked)": 'danger' }
|
||||
|
||||
function changeInvoiceState(invoiceId, newState) {
|
||||
console.log(invoiceId, newState)
|
||||
const toggleButton = $("#markStatusDropdownMenuButton");
|
||||
toggleButton.attr("disabled", "disabled");
|
||||
|
||||
$.post(`${invoiceId}/changestate/${newState}`)
|
||||
.done(({ statusString }) => {
|
||||
const alertClass = alertClasses[statusString];
|
||||
toggleButton.replaceWith(`<span class="fs-6 fw-normal badge bg-${alertClass}">${statusString} <span class="fa fa-check"></span></span>`);
|
||||
})
|
||||
.fail(function () {
|
||||
toggleButton.removeAttr("disabled");
|
||||
alert("Invoice state update failed");
|
||||
});
|
||||
}
|
||||
|
||||
delegate('click', '[data-change-invoice-status-button]', e => {
|
||||
const button = e.target.closest('[data-change-invoice-status-button]')
|
||||
const { id, status } = button.dataset
|
||||
changeInvoiceState(id, status)
|
||||
})
|
||||
|
||||
const handleRefundResponse = async response => {
|
||||
const modalBody = document.querySelector('#RefundModal .modal-body')
|
||||
if (response.ok && response.redirected) {
|
||||
|
@ -282,32 +258,8 @@
|
|||
<tr>
|
||||
<th>State</th>
|
||||
<td>
|
||||
@if (Model.CanMarkStatus)
|
||||
{
|
||||
<div class="dropdown changeInvoiceStateToggle">
|
||||
<button class="btn btn-secondary btn-sm dropdown-toggle py-1 px-2" type="button" id="markStatusDropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@Model.State
|
||||
</button>
|
||||
<div class="dropdown-menu" aria-labelledby="markStatusDropdownMenuButton">
|
||||
@if (Model.CanMarkInvalid)
|
||||
{
|
||||
<button type="button" class="dropdown-item lh-base changeInvoiceState" data-id="@Model.Id" data-status="invalid" data-change-invoice-status-button>
|
||||
Mark as invalid
|
||||
</button>
|
||||
}
|
||||
@if (Model.CanMarkSettled)
|
||||
{
|
||||
<button type="button" class="dropdown-item lh-base changeInvoiceState" href="#" data-id="@Model.Id" data-status="settled" data-change-invoice-status-button>
|
||||
Mark as settled
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@Model.State
|
||||
}
|
||||
<vc:invoice-status invoice-id="@Model.Id" state="Model.State" payments="Model.Payments"
|
||||
is-archived="Model.Archived" has-refund="Model.HasRefund" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -60,23 +60,6 @@
|
|||
});
|
||||
});
|
||||
|
||||
delegate('click', '.changeInvoiceState', e => {
|
||||
const { invoiceId, newState } = e.target.dataset;
|
||||
const pavpill = $("#pavpill_" + invoiceId);
|
||||
const originalHtml = pavpill.html();
|
||||
pavpill.html("<span class='fa fa-bitcoin fa-spin' style='margin-left:16px;'></span>");
|
||||
|
||||
$.post("invoices/" + invoiceId + "/changestate/" + newState)
|
||||
.done(function (data) {
|
||||
const statusHtml = "<span class='badge badge-" + newState + "'>" + data.statusString + " <span class='fa fa-check'></span></span>";
|
||||
pavpill.replaceWith(statusHtml);
|
||||
})
|
||||
.fail(function (data) {
|
||||
pavpill.html(originalHtml.replace("dropdown-menu show", "dropdown-menu"));
|
||||
alert("Invoice state update failed");
|
||||
});
|
||||
})
|
||||
|
||||
delegate('click', '.showInvoice', e => {
|
||||
e.preventDefault();
|
||||
const { invoiceId } = e.target.dataset;
|
||||
|
@ -386,66 +369,8 @@
|
|||
</td>
|
||||
<td class="text-break">@invoice.InvoiceId</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@if (invoice.Details.Archived)
|
||||
{
|
||||
<span class="badge bg-warning">archived</span>
|
||||
}
|
||||
@if (invoice.CanMarkStatus)
|
||||
{
|
||||
<div id="pavpill_@invoice.InvoiceId" class="badge badge-@invoice.Status.Status.ToModernStatus().ToString().ToLower()">
|
||||
<span class="dropdown-toggle changeInvoiceStateToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
@invoice.Status.ToString()
|
||||
</span>
|
||||
<div class="dropdown-menu">
|
||||
@if (invoice.CanMarkInvalid)
|
||||
{
|
||||
<button type="button" class="dropdown-item lh-base changeInvoiceState" data-invoice-id="@invoice.InvoiceId" data-new-state="invalid">
|
||||
Mark as invalid
|
||||
</button>
|
||||
}
|
||||
@if (invoice.CanMarkSettled)
|
||||
{
|
||||
<button type="button" class="dropdown-item lh-base changeInvoiceState" data-invoice-id="@invoice.InvoiceId" data-new-state="settled">
|
||||
Mark as settled
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="badge badge-@invoice.Status.Status.ToModernStatus().ToString().ToLower()">
|
||||
@invoice.Status.Status.ToModernStatus().ToString()
|
||||
@if (invoice.Status.ExceptionStatus != InvoiceExceptionStatus.None)
|
||||
{
|
||||
@($"({invoice.Status.ExceptionStatus.ToString()})")
|
||||
}
|
||||
</span>
|
||||
}
|
||||
@foreach (var paymentMethodId in invoice.Details.Payments.Select(payment => payment.GetPaymentMethodId()).Distinct())
|
||||
{
|
||||
var image = PaymentMethodHandlerDictionary[paymentMethodId]?.GetCryptoImage(paymentMethodId);
|
||||
var badge = paymentMethodId.PaymentType.GetBadge();
|
||||
if (!string.IsNullOrEmpty(image) || !string.IsNullOrEmpty(badge))
|
||||
{
|
||||
<span class="d-inline-flex align-items-center gap-1">
|
||||
@if (!string.IsNullOrEmpty(image))
|
||||
{
|
||||
<img src="@Context.Request.GetRelativePathOrAbsolute(image)" alt="@paymentMethodId.PaymentType.ToString()" style="height:1.5em" />
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(badge))
|
||||
{
|
||||
@badge
|
||||
}
|
||||
</span>
|
||||
}
|
||||
}
|
||||
@if (invoice.HasRefund)
|
||||
{
|
||||
<span class="badge bg-warning">Refund</span>
|
||||
}
|
||||
</div>
|
||||
<vc:invoice-status state="invoice.Status" payments="invoice.Details.Payments" invoice-id="@invoice.InvoiceId"
|
||||
is-archived="invoice.Details.Archived" has-refund="invoice.HasRefund" />
|
||||
</td>
|
||||
<td class="text-end text-nowrap">
|
||||
<span data-sensitive>@DisplayFormatter.Currency(invoice.Amount, invoice.Currency)</span>
|
||||
|
|
|
@ -263,17 +263,6 @@ h2 svg.icon.icon-info {
|
|||
.card {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
#markStatusDropdownMenuButton {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0 !important;
|
||||
color: inherit;
|
||||
font-weight: var(--btcpay-font-weight-normal);
|
||||
font-size: var(--btcpay-body-font-size);
|
||||
}
|
||||
#markStatusDropdownMenuButton::after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Richtext editor */
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const baseUrl = Object.values(document.scripts).find(s => s.src.includes('/main/site.js')).src.split('/main/site.js').shift();
|
||||
|
||||
const flatpickrInstances = [];
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
|
||||
|
@ -268,6 +270,23 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
});
|
||||
});
|
||||
|
||||
// Invoice Status
|
||||
delegate('click', '[data-invoice-state-badge] [data-invoice-id][data-new-state]', async e => {
|
||||
const $button = e.target
|
||||
const $badge = $button.closest('[data-invoice-state-badge]')
|
||||
const { invoiceId, newState } = $button.dataset
|
||||
|
||||
$badge.classList.add('pe-none'); // disable further interaction
|
||||
const response = await fetch(`${baseUrl}/invoices/${invoiceId}/changestate/${newState}`, { method: 'POST' })
|
||||
if (response.ok) {
|
||||
const { statusString } = await response.json()
|
||||
$badge.outerHTML = `<div class="badge badge-${newState}" data-invoice-state-badge="${invoiceId}">${statusString}</div>`
|
||||
} else {
|
||||
$badge.classList.remove('pe-none');
|
||||
alert("Invoice state update failed");
|
||||
}
|
||||
})
|
||||
|
||||
// Time Format
|
||||
delegate('click', '.switch-time-format', switchTimeFormat);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue