Chart polish (#2973)

This commit is contained in:
Vlad Stan 2025-02-17 14:30:16 +02:00 committed by GitHub
parent 60de308c8f
commit c08623277e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 170 additions and 130 deletions

View file

@ -440,6 +440,8 @@ async def get_payments_daily_stats(
balance_total: float = 0 balance_total: float = 0
_none = PaymentDailyStats(date=datetime.now(timezone.utc)) _none = PaymentDailyStats(date=datetime.now(timezone.utc))
if len(data_in) == 0 and len(data_out) == 0:
return []
if len(data_in) == 0: if len(data_in) == 0:
data_in = [_none] data_in = [_none]
if len(data_out) == 0: if len(data_out) == 0:

View file

@ -4,34 +4,43 @@
<!----> <!---->
{% block scripts %} {{ window_vars(user) }}{% endblock %} {% block page %} {% block scripts %} {{ window_vars(user) }}{% endblock %} {% block page %}
<div class="row q-col-gutter-md q-mb-md">
<div class="col-12">
<q-card>
<div>
<div class="q-gutter-y-md">
<q-tabs v-model="tab" align="left">
<q-tab
name="user"
:label="$t('account_settings')"
@update="val => tab = val.name"
></q-tab>
<q-tab
name="theme"
:label="$t('look_and_feel')"
@update="val => tab = val.name"
></q-tab>
<q-tab
name="api_acls"
:label="$t('access_control_list')"
@update="val => tab = val.name"
></q-tab>
</q-tabs>
</div>
</div>
</q-card>
</div>
</div>
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md">
<div v-if="user" class="col-md-12 col-lg-6 q-gutter-y-md"> <div v-if="user" class="col-md-12 col-lg-6 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<q-tabs v-model="tab" align="justify">
<q-tab
name="user"
:label="$t('account_settings')"
@update="val => tab = val.name"
></q-tab>
<q-tab
name="theme"
:label="$t('look_and_feel')"
@update="val => tab = val.name"
></q-tab>
<q-tab
name="api_acls"
:label="$t('access_control_list')"
@update="val => tab = val.name"
></q-tab>
</q-tabs>
<q-tab-panels v-model="tab"> <q-tab-panels v-model="tab">
<q-tab-panel name="user"> <q-tab-panel name="user">
<div v-if="credentialsData.show"> <div v-if="credentialsData.show">
<q-separator></q-separator>
<q-card-section> <q-card-section>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
@ -132,11 +141,10 @@
</q-card-section> </q-card-section>
</div> </div>
<div v-else> <div v-else>
<q-card-section> <q-card-section v-if="user.extra.picture">
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<q-img <q-img
v-if="user.extra.picture"
style="max-width: 100px" style="max-width: 100px"
:src="user.extra.picture" :src="user.extra.picture"
class="float-right" class="float-right"
@ -144,7 +152,6 @@
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
<q-separator></q-separator>
<q-card-section> <q-card-section>
<q-input <q-input

View file

@ -481,27 +481,37 @@
>{% endfor %} >{% endfor %}
</q-card> </q-card>
{% endif %} {% endif %}
<q-card v-if="chartConfig.showBalance"> <div
<q-card-section class="q-pa-none"> v-show="chartDataPointCount"
<div style="height: 200px" class="q-pa-sm"> class="col-12 col-md-5 q-gutter-y-md"
<canvas ref="walletBalanceChart"></canvas> >
</div> <q-card v-if="chartConfig.showBalance">
</q-card-section> <q-card-section class="q-pa-none">
</q-card> <div style="height: 200px" class="q-pa-sm">
<q-card v-if="chartConfig.showBalanceInOut"> <canvas ref="walletBalanceChart"></canvas>
<q-card-section class="q-pa-none"> </div>
<div style="height: 200px" class="q-pa-sm"> </q-card-section>
<canvas ref="walletBalanceInOut"></canvas> </q-card>
</div> <q-card v-if="chartConfig.showBalanceInOut">
</q-card-section> <q-card-section class="q-pa-none">
</q-card> <div style="height: 200px" class="q-pa-sm">
<q-card v-if="chartConfig.showPaymentCountInOut"> <canvas ref="walletBalanceInOut"></canvas>
<q-card-section class="q-pa-none"> </div>
<div style="height: 200px" class="q-pa-sm"> </q-card-section>
<canvas ref="walletPaymentsInOut"></canvas> </q-card>
</div> <q-card v-if="chartConfig.showPaymentCountInOut">
</q-card-section> <q-card-section class="q-pa-none">
</q-card> <div style="height: 200px" class="q-pa-sm">
<canvas ref="walletPaymentsInOut"></canvas>
</div>
</q-card-section>
</q-card>
</div>
<div v-if="hasChartActive && !chartDataPointCount">
<q-card>
<q-card-section> No chart data available</q-card-section>
</q-card>
</div>
</div> </div>
</div> </div>

View file

@ -8,52 +8,7 @@
<q-card> <q-card>
<div class="q-pa-sm q-pl-lg"> <div class="q-pa-sm q-pl-lg">
<div class="row items-center justify-between q-gutter-xs"> <div class="row items-center justify-between q-gutter-xs">
<div class="col"> <div class="col"></div>
<div class="float-left">
<q-chip
v-if="searchDate.timeFrom"
removable
@remove="removeCreatedFrom()"
:label="searchDate.timeFrom"
class="ellipsis"
>
</q-chip>
<q-icon name="event" class="cursor-pointer">
<q-popup-proxy
cover
transition-show="scale"
transition-hide="scale"
>
<q-date v-model="searchDate.timeFrom" mask="YYYY-MM-DD">
<div class="row">
<q-btn
label="Search"
color="primary"
flat
@click="fetchPayments()"
class="float-left"
v-close-popup
/>
</div>
</q-date>
<q-date v-model="searchDate.timeTo" mask="YYYY-MM-DD">
<div class="row items-center justify-end">
<q-btn v-close-popup label="Close" color="primary" flat />
</div>
</q-date>
</q-popup-proxy>
</q-icon>
<q-chip
removable
v-if="searchDate.timeTo"
@remove="removeCreatedTo()"
:label="searchDate.timeTo"
class="ellipsis"
>
</q-chip>
</div>
</div>
<div class="float-left"> <div class="float-left">
<q-checkbox <q-checkbox
dense dense
@ -115,7 +70,46 @@
</q-checkbox> </q-checkbox>
</div> </div>
<q-separator vertical class="q-ma-sm"></q-separator> <q-separator vertical class="q-ma-sm"></q-separator>
<q-btn icon="event" outline flat>
<q-popup-proxy
cover
transition-show="scale"
transition-hide="scale"
>
<q-date v-model="searchDate" mask="YYYY-MM-DD" range />
<div class="row">
<div class="col-6">
<q-btn
label="Search"
@click="searchByDate()"
color="primary"
flat
class="float-left"
v-close-popup
/>
</div>
<div class="col-6">
<q-btn
v-close-popup
@click="clearDateSeach()"
label="Clear"
class="float-right"
color="grey"
flat
/>
</div>
</div>
</q-popup-proxy>
<q-badge
v-if="searchDate?.to || searchDate?.from"
class="q-mt-lg q-mr-md"
color="primary"
rounded
floating
style="border-radius: 6px"
/>
</q-btn>
<q-separator vertical class="q-ma-sm"></q-separator>
<div> <div>
<q-btn <q-btn
v-if="g.user.admin" v-if="g.user.admin"
@ -214,7 +208,6 @@
:columns="paymentsTable.columns" :columns="paymentsTable.columns"
v-model:pagination="paymentsTable.pagination" v-model:pagination="paymentsTable.pagination"
:filter="paymentsTable.search" :filter="paymentsTable.search"
:loading="paymentsTable.loading"
@request="fetchPayments" @request="fetchPayments"
> >
<template v-slot:header="props"> <template v-slot:header="props">
@ -272,8 +265,8 @@
@click="showDetailsToggle(props.row)" @click="showDetailsToggle(props.row)"
v-if="props.row.status === 'success'" v-if="props.row.status === 'success'"
size="14px" size="14px"
:name="props.row.amount < 0 ? 'call_made' : 'call_received'" :name="props.row.outgoing ? 'call_made' : 'call_received'"
:color="props.row.amount < 0 ? 'pink' : 'green'" :color="props.row.outgoing ? 'pink' : 'green'"
class="cursor-pointer" class="cursor-pointer"
></q-icon> ></q-icon>
<q-icon <q-icon

View file

@ -4,10 +4,7 @@ window.PaymentsPageLogic = {
return { return {
payments: [], payments: [],
dailyChartData: [], dailyChartData: [],
searchDate: { searchDate: {from: null, to: null},
timeFrom: null,
timeTo: null
},
searchData: { searchData: {
wallet_id: null, wallet_id: null,
payment_hash: null, payment_hash: null,
@ -104,7 +101,7 @@ window.PaymentsPageLogic = {
}, },
search: null, search: null,
hideEmpty: true, hideEmpty: true,
loading: true loading: false
}, },
chartsReady: false, chartsReady: false,
showDetails: false, showDetails: false,
@ -116,10 +113,6 @@ window.PaymentsPageLogic = {
this.chartsReady = true this.chartsReady = true
await this.$nextTick() await this.$nextTick()
this.initCharts() this.initCharts()
this.searchDate.timeFrom = moment()
.subtract(1, 'month')
.format('YYYY-MM-DD')
this.searchDate.timeTo = moment().format('YYYY-MM-DD')
await this.fetchPayments() await this.fetchPayments()
}, },
computed: {}, computed: {},
@ -132,11 +125,11 @@ window.PaymentsPageLogic = {
delete filter['time[ge]'] delete filter['time[ge]']
delete filter['time[le]'] delete filter['time[le]']
if (this.searchDate.timeFrom) { if (this.searchDate.from) {
filter['time[ge]'] = this.searchDate.timeFrom + 'T00:00:00' filter['time[ge]'] = this.searchDate.from + 'T00:00:00'
} }
if (this.searchDate.timeTo) { if (this.searchDate.to) {
filter['time[le]'] = this.searchDate.timeTo + 'T23:59:59' filter['time[le]'] = this.searchDate.to + 'T23:59:59'
} }
this.paymentsTable.filter = filter this.paymentsTable.filter = filter
@ -156,7 +149,7 @@ window.PaymentsPageLogic = {
p.tag = p.extra.tag p.tag = p.extra.tag
} }
p.timeFrom = moment(p.created_at).fromNow() p.timeFrom = moment(p.created_at).fromNow()
p.outgoing = p.amount < 0
p.amount = p.amount =
new Intl.NumberFormat(window.LOCALE).format(p.amount / 1000) + new Intl.NumberFormat(window.LOCALE).format(p.amount / 1000) +
' sats' ' sats'
@ -173,7 +166,6 @@ window.PaymentsPageLogic = {
console.error(error) console.error(error)
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
} finally { } finally {
this.paymentsTable.loading = false
this.updateCharts(props) this.updateCharts(props)
} }
}, },
@ -183,15 +175,30 @@ window.PaymentsPageLogic = {
} }
await this.fetchPayments() await this.fetchPayments()
}, },
clearDateSeach() {
this.searchDate = {from: null, to: null}
delete this.paymentsTable.filter['time[ge]']
delete this.paymentsTable.filter['time[le]']
this.fetchPayments()
},
searchByDate() {
if (typeof this.searchDate === 'string') {
this.searchDate = {
from: this.searchDate,
to: this.searchDate
}
}
if (this.searchDate.from) {
this.paymentsTable.filter['time[ge]'] =
this.searchDate.from + 'T00:00:00'
}
if (this.searchDate.to) {
this.paymentsTable.filter['time[le]'] = this.searchDate.to + 'T23:59:59'
}
async removeCreatedFrom() { this.fetchPayments()
this.searchDate.timeFrom = null
await this.fetchPayments()
},
async removeCreatedTo() {
this.searchDate.timeTo = null
await this.fetchPayments()
}, },
showDetailsToggle(payment) { showDetailsToggle(payment) {
this.paymentDetails = payment this.paymentDetails = payment
return (this.showDetails = !this.showDetails) return (this.showDetails = !this.showDetails)
@ -303,17 +310,17 @@ window.PaymentsPageLogic = {
`/api/v1/payments/stats/daily?${noTimeParams}` `/api/v1/payments/stats/daily?${noTimeParams}`
) )
const timeFrom = this.searchDate.timeFrom + 'T00:00:00' const timeFrom = this.searchDate.from + 'T00:00:00'
const timeTo = this.searchDate.timeTo + 'T00:00:00' const timeTo = this.searchDate.to + 'T23:59:59'
this.lnbitsBalance = data[data.length - 1].balance this.lnbitsBalance = data[data.length - 1].balance
data = data.filter(p => { data = data.filter(p => {
if (this.searchDate.timeFrom && this.searchDate.timeTo) { if (this.searchDate.from && this.searchDate.to) {
return p.date >= timeFrom && p.date <= timeTo return p.date >= timeFrom && p.date <= timeTo
} }
if (this.searchDate.timeFrom) { if (this.searchDate.from) {
return p.date >= timeFrom return p.date >= timeFrom
} }
if (this.searchDate.timeTo) { if (this.searchDate.to) {
return p.date <= timeTo return p.date <= timeTo
} }
return true return true

View file

@ -116,6 +116,7 @@ window.WalletPageLogic = {
primaryColor: this.$q.localStorage.getItem('lnbits.primaryColor'), primaryColor: this.$q.localStorage.getItem('lnbits.primaryColor'),
secondaryColor: this.$q.localStorage.getItem('lnbits.secondaryColor'), secondaryColor: this.$q.localStorage.getItem('lnbits.secondaryColor'),
chartData: [], chartData: [],
chartDataPointCount: 0,
chartConfig: { chartConfig: {
showBalance: true, showBalance: true,
showBalanceInOut: true, showBalanceInOut: true,
@ -151,6 +152,13 @@ window.WalletPageLogic = {
}, },
wallet() { wallet() {
return this.g.wallet return this.g.wallet
},
hasChartActive() {
return (
this.chartConfig.showBalance ||
this.chartConfig.showBalanceInOut ||
this.chartConfig.showPaymentCountInOut
)
} }
}, },
methods: { methods: {
@ -824,11 +832,7 @@ window.WalletPageLogic = {
this.chartConfig = {} this.chartConfig = {}
return return
} }
if ( if (!this.hasChartActive) {
!this.chartConfig.showBalance &&
!this.chartConfig.showBalanceInOut &&
!this.chartConfig.showPaymentCountInOut
) {
return return
} }
@ -868,6 +872,7 @@ window.WalletPageLogic = {
day: 'numeric' day: 'numeric'
}) })
) )
this.chartDataPointCount = data.length
return {data, labels} return {data, labels}
}, },
refreshCharts() { refreshCharts() {
@ -964,12 +969,20 @@ window.WalletPageLogic = {
{ {
label: 'Balance In', label: 'Balance In',
borderRadius: 5, borderRadius: 5,
data: data.map(s => s.balance_in) data: data.map(s => s.balance_in),
backgroundColor: LNbits.utils.hexAlpha(
this.primaryColor,
0.3
)
}, },
{ {
label: 'Balance Out', label: 'Balance Out',
borderRadius: 5, borderRadius: 5,
data: data.map(s => s.balance_out) data: data.map(s => s.balance_out),
backgroundColor: LNbits.utils.hexAlpha(
this.secondaryColor,
0.3
)
} }
] ]
} }
@ -1005,11 +1018,19 @@ window.WalletPageLogic = {
datasets: [ datasets: [
{ {
label: 'Payments In', label: 'Payments In',
data: data.map(s => s.count_in) data: data.map(s => s.count_in),
backgroundColor: LNbits.utils.hexAlpha(
this.primaryColor,
0.3
)
}, },
{ {
label: 'Payments Out', label: 'Payments Out',
data: data.map(s => -s.count_out) data: data.map(s => -s.count_out),
backgroundColor: LNbits.utils.hexAlpha(
this.secondaryColor,
0.3
)
} }
] ]
} }

View file

@ -106,7 +106,7 @@
<span>OFFLINE</span> <span>OFFLINE</span>
</q-badge> </q-badge>
<q-btn-dropdown <q-btn-dropdown
v-if="g.user" v-if="g.user || isUserAuthorized"
flat flat
rounded rounded
size="sm" size="sm"