mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-02-08 07:44:25 +01:00
Fix: Improve the responsivity of the Reporting page (#6846)
This commit is contained in:
@@ -4,21 +4,34 @@
|
||||
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
|
||||
@model StoreReportsViewModel
|
||||
@{
|
||||
ViewData.SetActivePage(StoreNavPages.Reporting, StringLocalizer["Reporting"]);
|
||||
Csp.UnsafeEval();
|
||||
ViewData.SetActivePage(StoreNavPages.Reporting, StringLocalizer["Reporting"]);
|
||||
Csp.UnsafeEval();
|
||||
}
|
||||
|
||||
@section PageHeadContent
|
||||
{
|
||||
@* Set a height for the responsive table container to make it work with the fixed table headers.
|
||||
Details described here: thttps://uxdesign.cc/position-stuck-96c9f55d9526 *@
|
||||
<style>
|
||||
#app .table-responsive { max-height: 80vh; }
|
||||
#app #charts { gap: var(--btcpay-space-l) var(--btcpay-space-xxl); }
|
||||
#app #charts article { flex: 1 1 450px; }
|
||||
main .dropdown-menu.show { z-index: 99999; }
|
||||
#app .table-responsive {
|
||||
max-height: 80vh;
|
||||
}
|
||||
|
||||
#app #charts {
|
||||
gap: var(--btcpay-space-l) var(--btcpay-space-xxl);
|
||||
}
|
||||
|
||||
#app #charts article {
|
||||
flex: 1 1 450px;
|
||||
}
|
||||
|
||||
main .dropdown-menu.show {
|
||||
z-index: 99999;
|
||||
}
|
||||
</style>
|
||||
}
|
||||
|
||||
|
||||
<div class="sticky-header">
|
||||
<h2>
|
||||
@ViewData["Title"]
|
||||
@@ -27,40 +40,50 @@
|
||||
</a>
|
||||
</h2>
|
||||
<div>
|
||||
<a cheat-mode="true" class="btn btn-outline-info text-nowrap" asp-action="StoreReports" asp-route-fakeData="true" asp-route-viewName="@Model.Request?.ViewName">Create fake data</a>
|
||||
<button id="page-primary" class="btn btn-primary text-nowrap" type="button" data-action="exportCSV">Export</button>
|
||||
<a cheat-mode="true" class="btn btn-outline-info text-nowrap" asp-action="StoreReports" asp-route-fakeData="true"
|
||||
asp-route-viewName="@Model.Request?.ViewName">Create fake data</a>
|
||||
<button id="page-primary" class="btn btn-primary text-nowrap" type="button" data-action="exportCSV">Export</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-sm-row align-items-center gap-3 mb-l">
|
||||
<div class="dropdown" v-pre>
|
||||
<button id="ViewNameToggle" class="btn btn-secondary dropdown-toggle dropdown-toggle-custom-caret" type="button" data-bs-toggle="dropdown" aria-expanded="false">@Model.Request.ViewName</button>
|
||||
<div class="dropdown-menu" aria-labelledby="ViewNameToggle">
|
||||
@foreach (var v in Model.AvailableViews)
|
||||
{
|
||||
<a href="#" data-view="@v" class="available-view dropdown-item @(Model.Request.ViewName == v ? "custom-active" : "")">@v</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input id="fromDate" class="form-control flatdtpicker" type="datetime-local"
|
||||
data-fdtp='{ "enableTime": true, "enableSeconds": true, "dateFormat": "Y-m-d H:i:S", "time_24hr": true, "defaultHour": 0 }'
|
||||
placeholder="Start Date" />
|
||||
<button type="button" class="btn btn-primary input-group-clear" title="@StringLocalizer["Clear"]">
|
||||
<vc:icon symbol="close" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<input id="toDate" class="form-control flatdtpicker" type="datetime-local"
|
||||
data-fdtp='{ "enableTime": true, "enableSeconds": true, "dateFormat": "Y-m-d H:i:S", "time_24hr": true, "defaultHour": 0 }'
|
||||
placeholder="End Date" />
|
||||
<button type="button" class="btn btn-primary input-group-clear" title="@StringLocalizer["Clear"]">
|
||||
<vc:icon symbol="close" />
|
||||
</button>
|
||||
<div class="row">
|
||||
<div class="col-xl-8 col-xxl-constrain">
|
||||
<nav id="SectionNav">
|
||||
<div class="nav">
|
||||
@foreach (var v in Model.AvailableViews)
|
||||
{
|
||||
<a href="#" data-view="@v" class="available-view nav-link @(Model.Request.ViewName == v ? "active" : "")" role="tab">@v</a>
|
||||
}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="d-flex gap-3">
|
||||
<div class="form-group">
|
||||
<label for="fromDate" class="form-label">@StringLocalizer["Start Date"]</label>
|
||||
<input id="fromDate" name="fromDate"
|
||||
data-fdtp='{ "enableTime": true, "enableSeconds": true, "dateFormat": "Y-m-d H:i:S", "time_24hr": true, "defaultHour": 0 }'
|
||||
class="form-control flatdtpicker" placeholder="@StringLocalizer["Start Date"]" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="toDate" class="form-label">@StringLocalizer["End Date"]</label>
|
||||
<input id="toDate" name="toDate" class="form-control flatdtpicker"
|
||||
data-fdtp='{ "enableTime": true, "enableSeconds": true, "dateFormat": "Y-m-d H:i:S", "time_24hr": true, "defaultHour": 0 }'
|
||||
placeholder="@StringLocalizer["End Date"]" />
|
||||
</div>
|
||||
<div id="searchGroup" v-cloak class="form-group d-flex align-items-end">
|
||||
<button id="searchBtn" class="btn btn-primary" :disabled="loading" type="button">
|
||||
<span v-if="loading" class="spinner-border spinner-border-sm me-1" role="status" style="margin-left: -6px;"></span>
|
||||
<span v-else class="me-1"><vc:icon symbol="actions-search" /></span>
|
||||
<span text-translate="true">Search</span>
|
||||
</button>
|
||||
<span class="text-danger invalid-feedback field-validation-error" v-if="error">{{ error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="app" v-cloak class="w-100-fixed">
|
||||
<div v-if="srv.charts && srv.charts.some(hasChartData)" id="charts" class="d-flex flex-wrap mb-5">
|
||||
<div id="app" v-if="!loading" v-cloak class="w-100-fixed">
|
||||
<div v-if="srv.charts && srv.charts.some(hasChartData)" id="charts" class="d-flex flex-wrap mb-3">
|
||||
<article v-for="chart in srv.charts" v-if="hasChartData(chart)">
|
||||
<h3>{{ chart.name }}</h3>
|
||||
<div class="table-responsive">
|
||||
@@ -74,8 +97,12 @@
|
||||
<tbody>
|
||||
<tr v-for="(row, rowIndex) in chart.rows">
|
||||
<td v-for="(group, groupIndex) in row.groups" :rowspan="group.rowCount">
|
||||
<template v-if="group.name === true"><vc:icon symbol="checkmark" css-class="text-success" /></template>
|
||||
<template v-else-if="group.name === false"><vc:icon symbol="cross" css-class="text-danger" /></template>
|
||||
<template v-if="group.name === true">
|
||||
<vc:icon symbol="checkmark" css-class="text-success" />
|
||||
</template>
|
||||
<template v-else-if="group.name === false">
|
||||
<vc:icon symbol="cross" css-class="text-danger" />
|
||||
</template>
|
||||
<template v-else-if="['Settled', 'Processing', 'Invalid', 'Expired', 'New', 'Pending'].includes(group.name)">
|
||||
<span class="badge" :class="`badge-${group.name.toLowerCase()}`">{{ displayValue(group.name) }}</span>
|
||||
</template>
|
||||
@@ -83,8 +110,11 @@
|
||||
</td>
|
||||
<td v-if="row.isTotal" :colspan="row.rLevel">Total</td>
|
||||
<td v-for="(value, columnIndex) in row.values" class="text-end">
|
||||
<template v-if="chart.aggregates[columnIndex] === 'BalanceChange' && (value >= 0 || typeof value === 'object' && value.d >= 0)"><span class="text-success">{{ displayValue(value) }}</span></template>
|
||||
<template v-else-if="chart.aggregates[columnIndex] === 'BalanceChange' && (value < 0 || typeof value === 'object' && value.d < 0)"><span class="text-danger">{{ displayValue(value) }}</span></template>
|
||||
<template v-if="chart.aggregates[columnIndex] === 'BalanceChange' && (value >= 0 || typeof value === 'object' && value.d >= 0)">
|
||||
<span class="text-success">{{ displayValue(value) }}</span></template>
|
||||
<template
|
||||
v-else-if="chart.aggregates[columnIndex] === 'BalanceChange' && (value < 0 || typeof value === 'object' && value.d < 0)">
|
||||
<span class="text-danger">{{ displayValue(value) }}</span></template>
|
||||
<template v-else>{{ displayValue(value) }}</template>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -127,7 +157,8 @@
|
||||
target="_blank"
|
||||
v-if="srv.result.fields[columnIndex].type === 'invoice_id'">{{ displayValue(value) }}</a>
|
||||
<template v-else-if="srv.result.fields[columnIndex].type === 'tx_id'">
|
||||
<vc:truncate-center text="value" is-vue="true" padding="15" classes="truncate-center-id" link="getExplorerUrl(value, row[columnIndex-1])" />
|
||||
<vc:truncate-center text="value" is-vue="true" padding="15" classes="truncate-center-id"
|
||||
link="getExplorerUrl(value, row[columnIndex-1])" />
|
||||
</template>
|
||||
<template
|
||||
v-else-if="value && srv.result.fields[columnIndex].name.toLowerCase().endsWith('url')">
|
||||
@@ -136,15 +167,22 @@
|
||||
</template>
|
||||
<template v-else>{{ displayValue(value) }}</template>
|
||||
</template>
|
||||
<template v-else-if="value && ['Address'].includes(srv.result.fields[columnIndex].name)" >
|
||||
<template v-else-if="value && ['Address'].includes(srv.result.fields[columnIndex].name)">
|
||||
<vc:truncate-center text="value" is-vue="true" padding="15" classes="truncate-center-id" />
|
||||
</template>
|
||||
<template v-else-if="srv.result.fields[columnIndex].type === 'datetime'">{{ displayDate(value) }}</template>
|
||||
<span v-else-if="srv.result.fields[columnIndex].type === 'boolean' && value === true"><vc:icon symbol="checkmark" css-class="text-success" /></span>
|
||||
<span v-else-if="srv.result.fields[columnIndex].type === 'boolean' && value === false"><vc:icon symbol="cross" css-class="text-danger"/></span>
|
||||
<span v-else-if="['BalanceChange'].includes(srv.result.fields[columnIndex].name) && (value >= 0 || typeof value === 'object' && value.d >= 0)" class="text-success">{{ displayValue(value) }}</span>
|
||||
<span v-else-if="['BalanceChange'].includes(srv.result.fields[columnIndex].name) && (value < 0 || typeof value === 'object' && value.d < 0)" class="text-danger">{{ displayValue(value) }}</span>
|
||||
<span v-else-if="['State'].includes(srv.result.fields[columnIndex].name)" class="badge" :class="`badge-${value.toLowerCase()}`">{{ displayValue(value) }}</span>
|
||||
<span v-else-if="srv.result.fields[columnIndex].type === 'boolean' && value === true"><vc:icon symbol="checkmark"
|
||||
css-class="text-success" /></span>
|
||||
<span v-else-if="srv.result.fields[columnIndex].type === 'boolean' && value === false"><vc:icon symbol="cross"
|
||||
css-class="text-danger" /></span>
|
||||
<span
|
||||
v-else-if="['BalanceChange'].includes(srv.result.fields[columnIndex].name) && (value >= 0 || typeof value === 'object' && value.d >= 0)"
|
||||
class="text-success">{{ displayValue(value) }}</span>
|
||||
<span
|
||||
v-else-if="['BalanceChange'].includes(srv.result.fields[columnIndex].name) && (value < 0 || typeof value === 'object' && value.d < 0)"
|
||||
class="text-danger">{{ displayValue(value) }}</span>
|
||||
<span v-else-if="['State'].includes(srv.result.fields[columnIndex].name)" class="badge"
|
||||
:class="`badge-${value.toLowerCase()}`">{{ displayValue(value) }}</span>
|
||||
<template v-else>{{ displayValue(value) }}</template>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -155,12 +193,13 @@
|
||||
</article>
|
||||
</div>
|
||||
|
||||
|
||||
@section PageFootContent {
|
||||
<script src="~/vendor/decimal.js/decimal.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/FileSaver/FileSaver.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/papaparse/papaparse.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
||||
<script>const srv = @Safe.Json(Model);</script>
|
||||
<script src="~/js/datatable.js" asp-append-version="true"></script>
|
||||
<script src="~/js/store-reports.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/decimal.js/decimal.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/FileSaver/FileSaver.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/papaparse/papaparse.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/vuejs/vue.min.js" asp-append-version="true"></script>
|
||||
<script>const srv = @Safe.Json(Model);</script>
|
||||
<script src="~/js/datatable.js" asp-append-version="true"></script>
|
||||
<script src="~/js/store-reports.js" asp-append-version="true"></script>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
let app, origData;
|
||||
let app, searchBtnApp, origData;
|
||||
srv.sortBy = function (field, event) {
|
||||
for (let key in this.fieldViews) {
|
||||
if (this.fieldViews.hasOwnProperty(key)) {
|
||||
@@ -8,12 +8,10 @@ srv.sortBy = function (field, event) {
|
||||
if (sortedField && (fieldView.sortBy === "" || fieldView.sortBy === "desc")) {
|
||||
fieldView.sortByTitle = "asc";
|
||||
fieldView.sortBy = "asc";
|
||||
}
|
||||
else if (sortedField && (fieldView.sortByTitle === "asc")) {
|
||||
} else if (sortedField && (fieldView.sortByTitle === "asc")) {
|
||||
fieldView.sortByTitle = "desc";
|
||||
fieldView.sortBy = "desc";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
fieldView.sortByTitle = "";
|
||||
fieldView.sortBy = "";
|
||||
}
|
||||
@@ -23,9 +21,11 @@ srv.sortBy = function (field, event) {
|
||||
document.querySelectorAll('.sort-column').forEach($a => {
|
||||
$a.innerHTML = $a.innerHTML.replace(/#actions-sort-(asc|desc)"/, '#actions-sort"')
|
||||
})
|
||||
const { sort } = event.currentTarget.dataset;
|
||||
const {sort} = event.currentTarget.dataset;
|
||||
const next = sort === '' || sort === 'desc' ? 'asc' : 'desc';
|
||||
event.currentTarget.innerHTML = event.currentTarget.innerHTML.replace(`#actions-sort"`, `#actions-sort-${next}"`)
|
||||
const icon = event.currentTarget.querySelector('svg');
|
||||
if (icon)
|
||||
icon.setAttribute('href', `#actions-sort-${next}`);
|
||||
}
|
||||
|
||||
srv.applySort = function () {
|
||||
@@ -74,15 +74,17 @@ srv.updateFieldViews = function () {
|
||||
const field = this.result.fields[i];
|
||||
if (!this.fieldViews.hasOwnProperty(field.name)) {
|
||||
this.fieldViews[field.name] =
|
||||
{
|
||||
sortBy: "",
|
||||
sortByTitle: ""
|
||||
};
|
||||
{
|
||||
sortBy: "",
|
||||
sortByTitle: ""
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
delegate("click", "#searchBtn", function () {
|
||||
fetchStoreReports();
|
||||
})
|
||||
delegate("input", ".flatdtpicker", function () {
|
||||
// We don't use vue to bind dates, because VueJS break the flatpickr as soon as binding occurs.
|
||||
let to = document.getElementById("toDate").value
|
||||
@@ -96,20 +98,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
srv.request.timePeriod.from = from;
|
||||
srv.request.timePeriod.to = to;
|
||||
fetchStoreReports();
|
||||
});
|
||||
|
||||
delegate("click", "[data-action='exportCSV']", downloadCSV);
|
||||
|
||||
const $viewNameToggle = document.getElementById("ViewNameToggle")
|
||||
|
||||
delegate("click", ".available-view", function (e) {
|
||||
e.preventDefault();
|
||||
const { view } = e.target.dataset;
|
||||
$viewNameToggle.innerText = view;
|
||||
document.querySelectorAll(".available-view").forEach($el => $el.classList.remove("custom-active"));
|
||||
e.target.classList.add("custom-active");
|
||||
const {view} = e.target.dataset;
|
||||
document.querySelectorAll(".available-view").forEach($el => $el.classList.remove("active"));
|
||||
e.target.classList.add("active");
|
||||
srv.request.viewName = view;
|
||||
fetchStoreReports();
|
||||
fetchStoreReports(true)
|
||||
});
|
||||
|
||||
let to = new Date();
|
||||
@@ -124,17 +123,24 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
srv.request = srv.request || {};
|
||||
srv.request.timePeriod = srv.request.timePeriod || {};
|
||||
srv.request.timePeriod.to = moment(to).unix();
|
||||
srv.request.viewName = srv.request.viewName || "Payments";
|
||||
srv.request.viewName = srv.request.viewName || "Invoices";
|
||||
srv.request.timePeriod.from = moment(from).unix();
|
||||
srv.request.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
srv.result = { fields: [], values: [] };
|
||||
updateUIDateRange();
|
||||
srv.result = {fields: [], values: []};
|
||||
searchBtnApp = new Vue({
|
||||
el: '#searchGroup',
|
||||
data() {
|
||||
return { loading: false, error: "" };
|
||||
},
|
||||
});
|
||||
app = new Vue({
|
||||
el: '#app',
|
||||
data() { return { srv } },
|
||||
data() {
|
||||
return {srv, loading: false};
|
||||
},
|
||||
methods: {
|
||||
hasChartData(chart) {
|
||||
return chart.rows.length || chart.hasGrandTotal;
|
||||
return chart && (chart.rows.length || chart.hasGrandTotal);
|
||||
},
|
||||
titleCase(str, shorten) {
|
||||
const result = str.replace(/([A-Z])/g, " $1");
|
||||
@@ -145,13 +151,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
displayDate
|
||||
}
|
||||
});
|
||||
updateUIDateRange();
|
||||
fetchStoreReports();
|
||||
});
|
||||
|
||||
const dtFormatter = new Intl.DateTimeFormat('default', { dateStyle: 'short', timeStyle: 'short' });
|
||||
const dtFormatter = new Intl.DateTimeFormat('default', {dateStyle: 'short', timeStyle: 'short'});
|
||||
|
||||
function displayDate(val) {
|
||||
if(!val){
|
||||
if (!val) {
|
||||
return val;
|
||||
}
|
||||
const date = new Date(val);
|
||||
@@ -170,7 +177,7 @@ function updateUIDateRange() {
|
||||
// This function modify all the fields of a given type
|
||||
function modifyFields(fields, data, type, action) {
|
||||
const fieldIndices = fields
|
||||
.map((f, i) => ({ i: i, type: f.type }))
|
||||
.map((f, i) => ({i: i, type: f.type}))
|
||||
.filter(f => f.type === type)
|
||||
.map(f => f.i);
|
||||
if (fieldIndices.length === 0)
|
||||
@@ -181,6 +188,7 @@ function modifyFields(fields, data, type, action) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function downloadCSV() {
|
||||
if (!origData) return;
|
||||
const data = clone(origData);
|
||||
@@ -188,42 +196,72 @@ function downloadCSV() {
|
||||
// Convert ISO8601 dates to YYYY-MM-DD HH:mm:ss so the CSV easily integrate with Excel
|
||||
modifyFields(srv.result.fields, data, 'amount', displayValue)
|
||||
modifyFields(srv.result.fields, data, 'datetime', v => v ? moment(v).format('YYYY-MM-DD HH:mm:ss') : v);
|
||||
const csv = Papa.unparse({ fields: srv.result.fields.map(f => f.name), data });
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||||
const csv = Papa.unparse({fields: srv.result.fields.map(f => f.name), data});
|
||||
const blob = new Blob([csv], {type: 'text/csv;charset=utf-8;'});
|
||||
saveAs(blob, "export.csv");
|
||||
}
|
||||
|
||||
async function fetchStoreReports() {
|
||||
const result = await fetch(window.location, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(srv.request)
|
||||
});
|
||||
let fetchPromise = null;
|
||||
var abortFetching = new AbortController()
|
||||
|
||||
srv.result = await result.json();
|
||||
srv.dataUpdated();
|
||||
|
||||
// Dates from API are UTC, convert them to local time
|
||||
modifyFields(srv.result.fields, srv.result.data, 'datetime', a => a? moment(a).format(): a);
|
||||
var urlParams = new URLSearchParams(new URL(window.location).search);
|
||||
urlParams.set("viewName", srv.request.viewName);
|
||||
urlParams.set("from", srv.request.timePeriod.from);
|
||||
urlParams.set("to", srv.request.timePeriod.to);
|
||||
history.replaceState(null, null, "?" + urlParams.toString());
|
||||
updateUIDateRange();
|
||||
|
||||
srv.charts = [];
|
||||
for (let i = 0; i < srv.result.charts.length; i++) {
|
||||
const chart = srv.result.charts[i];
|
||||
const table = createTable(chart, srv.result.fields.map(f => f.name), srv.result.data);
|
||||
table.name = chart.name;
|
||||
srv.charts.push(table);
|
||||
function setLoading(val)
|
||||
{
|
||||
searchBtnApp.loading = val;
|
||||
app.loading = val;
|
||||
}
|
||||
async function fetchStoreReports(abort) {
|
||||
if (abort)
|
||||
{
|
||||
abortFetching.abort();
|
||||
}
|
||||
if (fetchPromise) {
|
||||
await fetchPromise;
|
||||
}
|
||||
abortFetching = new AbortController();
|
||||
fetchPromise = (async () => {
|
||||
setLoading(true);
|
||||
searchBtnApp.error = "";
|
||||
try {
|
||||
const result = await fetch(window.location, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(srv.request),
|
||||
signal: abortFetching.signal
|
||||
});
|
||||
|
||||
app.srv = srv;
|
||||
srv.result = await result.json();
|
||||
srv.dataUpdated();
|
||||
setLoading(false);
|
||||
|
||||
// Dates from API are UTC, convert them to local time
|
||||
modifyFields(srv.result.fields, srv.result.data, 'datetime', a => a ? moment(a).format() : a);
|
||||
var urlParams = new URLSearchParams(new URL(window.location).search);
|
||||
urlParams.set("viewName", srv.request.viewName);
|
||||
urlParams.set("from", srv.request.timePeriod.from);
|
||||
urlParams.set("to", srv.request.timePeriod.to);
|
||||
history.replaceState(null, null, "?" + urlParams.toString());
|
||||
updateUIDateRange();
|
||||
|
||||
srv.charts = [];
|
||||
for (let i = 0; i < srv.result.charts.length; i++) {
|
||||
const chart = srv.result.charts[i];
|
||||
const table = createTable(chart, srv.result.fields.map(f => f.name), srv.result.data);
|
||||
table.name = chart.name;
|
||||
srv.charts.push(table);
|
||||
}
|
||||
|
||||
app.srv = srv;
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
if (e.name !== 'AbortError') {
|
||||
searchBtnApp.error = e.message;
|
||||
}
|
||||
}
|
||||
})();
|
||||
await fetchPromise;
|
||||
}
|
||||
|
||||
function getInvoiceUrl(value) {
|
||||
@@ -231,6 +269,7 @@ function getInvoiceUrl(value) {
|
||||
return;
|
||||
return srv.invoiceTemplateUrl.replace("INVOICE_ID", value);
|
||||
}
|
||||
|
||||
window.getInvoiceUrl = getInvoiceUrl;
|
||||
|
||||
function getExplorerUrl(tx_id, cryptoCode) {
|
||||
@@ -241,4 +280,5 @@ function getExplorerUrl(tx_id, cryptoCode) {
|
||||
return null;
|
||||
return explorer.replace("TX_ID", tx_id);
|
||||
}
|
||||
|
||||
window.getExplorerUrl = getExplorerUrl;
|
||||
|
||||
@@ -378,7 +378,7 @@ h2 .icon.icon-info {
|
||||
.widget {
|
||||
--widget-padding: var(--btcpay-space-m);
|
||||
--widget-chart-width: 100vw;
|
||||
|
||||
|
||||
border: 1px solid var(--btcpay-body-border-light);
|
||||
border-radius: var(--btcpay-border-radius-l);
|
||||
padding: var(--widget-padding);
|
||||
@@ -546,7 +546,7 @@ h2 .icon.icon-info {
|
||||
.widget.store-lightning-services .services-list .service {
|
||||
--service-width: 3rem;
|
||||
}
|
||||
|
||||
|
||||
.widget .store-number {
|
||||
flex: 0 1 100%;
|
||||
}
|
||||
@@ -598,7 +598,7 @@ h2 .icon.icon-info {
|
||||
grid-column-start: 9;
|
||||
grid-column-end: 13;
|
||||
}
|
||||
|
||||
|
||||
.widget.store-numbers {
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
@@ -650,7 +650,7 @@ h2 .icon.icon-info {
|
||||
.btcpay-list-select-item {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex: 1 1 45%;
|
||||
flex: 1 1 45%;
|
||||
align-items: center;
|
||||
padding: .75rem var(--btcpay-space-s);
|
||||
cursor: pointer;
|
||||
@@ -697,7 +697,7 @@ input:checked + label.btcpay-list-select-item {
|
||||
--wrap-max-width: none;
|
||||
--wrap-padding-vertical: var(--btcpay-space-l);
|
||||
--wrap-padding-horizontal: var(--btcpay-space-m);
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
@@ -705,7 +705,7 @@ input:checked + label.btcpay-list-select-item {
|
||||
margin: 0 auto;
|
||||
padding: var(--wrap-padding-vertical) var(--wrap-padding-horizontal);
|
||||
}
|
||||
|
||||
|
||||
/* gradually try to set better but less supported values and units */
|
||||
.min-vh-100,
|
||||
.public-page-wrap {
|
||||
@@ -808,7 +808,7 @@ a.store-powered-by:hover .logo-brand-dark {
|
||||
--icon-size: 64px;
|
||||
--icon-border-size: var(--btcpay-space-xs);
|
||||
--icon-border-color: var(--btcpay-white);
|
||||
|
||||
|
||||
max-width: 320px;
|
||||
min-width: var(--qr-size);
|
||||
margin: 0 auto;
|
||||
@@ -1090,7 +1090,7 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
|
||||
border-radius: var(--btcpay-border-radius-l);
|
||||
padding: var(--btcpay-space-xs) var(--btcpay-space-s);
|
||||
}
|
||||
|
||||
|
||||
.truncate-center-text {
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
@@ -1161,6 +1161,15 @@ input.ts-wrapper.form-control:not(.ts-hidden-accessible,.ts-inline) {
|
||||
.blazor-status .btn-close .icon {
|
||||
--icon-size: .75rem;
|
||||
}
|
||||
|
||||
.btn .icon {
|
||||
--icon-size: 1.25rem;
|
||||
vertical-align: text-bottom;
|
||||
/*Without this the icon + text are in the middle,*/
|
||||
/*but the visual balance is off, and it doesn't feel center*/
|
||||
margin-left: calc(var(--icon-size) / -2);
|
||||
}
|
||||
|
||||
.btn.btn-lg .icon {
|
||||
--icon-size: 1.75rem;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user