mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-26 07:23:06 +01:00
* Add reporting feature * Remove nodatime * Add summaries * work... * Add chart title * Fix error * Allow to set hour in the field * UI updates * Fix fake data * ViewDefinitions can be dynamic * Add items sold * Sticky table headers * Update JS and remove jQuery usages * JS click fix * Handle tag all invoices for app * fix dup row in items report * Can cancel invoice request * Add tests * Fake data for items sold * Rename Items to Products, improve navigation F5 * Use bordered table for summaries --------- Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
128 lines
5.7 KiB
Text
128 lines
5.7 KiB
Text
@using BTCPayServer.Abstractions.Extensions;
|
|
@using BTCPayServer.Client.Models;
|
|
@using BTCPayServer.Models.StoreReportsViewModels;
|
|
@using BTCPayServer.Views.Invoice;
|
|
@using BTCPayServer.Views.Stores;
|
|
@using BTCPayServer.Abstractions.Services;
|
|
@using Microsoft.AspNetCore.Routing;
|
|
@inject Safe Safe
|
|
@inject BTCPayServer.Security.ContentSecurityPolicies Csp
|
|
@model StoreReportsViewModel
|
|
@{
|
|
ViewData.SetActivePage(StoreNavPages.Reporting, "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; }</style>
|
|
}
|
|
|
|
<div class="sticky-header">
|
|
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
|
|
<h2 class="mb-0">@ViewData["Title"]</h2>
|
|
<div class="d-flex flex-wrap gap-3">
|
|
<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 date</a>
|
|
<button id="exportCSV" class="btn btn-primary text-nowrap" type="button">
|
|
Export CSV
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex flex-column flex-sm-row align-items-sm-0center gap-3">
|
|
<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="Clear">
|
|
<span class="fa fa-times"></span>
|
|
</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="Clear">
|
|
<span class="fa fa-times"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="app" v-cloak class="w-100-fixed">
|
|
<article v-for="chart in srv.charts" class="mb-5">
|
|
<h3>{{ chart.name }}</h3>
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-hover w-auto">
|
|
<thead class="sticky-top bg-body">
|
|
<tr>
|
|
<th v-for="group in chart.groups">{{ group }}</th>
|
|
<th v-for="agg in chart.aggregates">{{ agg }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="row in chart.rows">
|
|
<td v-for="group in row.groups" :rowspan="group.rowCount">{{ group.name }}</td>
|
|
<td v-if="row.isTotal" :colspan="row.rLevel">Total</td>
|
|
<td v-for="value in row.values">{{ value }}</td>
|
|
</tr>
|
|
<tr v-if="chart.hasGrandTotal"><td :colspan="chart.groups.length">Grand total</td><td v-for="value in chart.grandTotalValues">{{ value }}</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</article>
|
|
<article>
|
|
<h3 id="raw-data">Raw data</h3>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover w-auto">
|
|
<thead class="sticky-top bg-body">
|
|
<tr>
|
|
<th v-for="field in srv.result.fields">
|
|
<a class="text-nowrap sort-column"
|
|
href="#"
|
|
:data-field="field.name"
|
|
@@click.prevent="srv.sortBy(field.name)"
|
|
:title="srv.fieldViews[field.name].sortByTitle">
|
|
{{ field.name }}
|
|
<span :class="srv.fieldViews[field.name].sortIconClass" />
|
|
</a>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(row, index) in srv.result.data" :key="index">
|
|
<td class="text-nowrap" v-for="(value, columnIndex) in row" :key="columnIndex">
|
|
<a :href="getInvoiceUrl(value)"
|
|
target="_blank"
|
|
v-if="srv.result.fields[columnIndex].type === 'invoice_id'">{{ value }}</a>
|
|
|
|
<a :href="getExplorerUrl(value, row[columnIndex-1])"
|
|
target="_blank"
|
|
rel="noreferrer noopener"
|
|
v-else-if="srv.result.fields[columnIndex].type === 'tx_id'">{{ value }}</a>
|
|
<span v-else>{{ value }}</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
|
|
@section PageFootContent {
|
|
<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>
|
|
}
|