refactor: lnbits-wallet-charts and clean up paymentFiltering (#3526)

This commit is contained in:
dni ⚡
2025-11-20 15:01:37 +01:00
committed by GitHub
parent 3e0d0d9896
commit faac56c14c
12 changed files with 314 additions and 357 deletions
@@ -1,6 +1,6 @@
window.app.component('lnbits-payment-list', {
template: '#lnbits-payment-list',
props: ['update', 'lazy', 'wallet'],
props: ['update', 'lazy', 'wallet', 'paymentFilter'],
mixins: [window.windowMixin],
data() {
return {
@@ -31,9 +31,6 @@ window.app.component('lnbits-payment-list', {
rowsNumber: 10
},
search: '',
filter: {
'status[ne]': 'failed'
},
loading: false
},
searchDate: {from: null, to: null},
@@ -156,24 +153,27 @@ window.app.component('lnbits-payment-list', {
}
}
if (this.searchDate.from) {
this.paymentsTable.filter['time[ge]'] =
this.searchDate.from + 'T00:00:00'
this.paymentFilter['time[ge]'] = this.searchDate.from + 'T00:00:00'
}
if (this.searchDate.to) {
this.paymentsTable.filter['time[le]'] = this.searchDate.to + 'T23:59:59'
this.paymentFilter['time[le]'] = this.searchDate.to + 'T23:59:59'
}
this.fetchPayments()
},
clearDateSeach() {
this.searchDate = {from: null, to: null}
delete this.paymentsTable.filter['time[ge]']
delete this.paymentsTable.filter['time[le]']
delete this.paymentFilter['time[ge]']
delete this.paymentFilter['time[le]']
this.fetchPayments()
},
fetchPayments(props) {
this.$emit('filter-changed', {...this.paymentsTable.filter})
const params = LNbits.utils.prepareFilterQuery(this.paymentsTable, props)
const params = LNbits.utils.prepareFilterQuery(
this.paymentsTable,
props,
this.paymentFilter
)
this.paymentsTable.loading = true
return LNbits.api
.getPayments(this.currentWallet, params)
.then(response => {
@@ -326,34 +326,34 @@ window.app.component('lnbits-payment-list', {
},
handleFilterChanged() {
const {success, pending, failed, incoming, outgoing} = this.searchStatus
delete this.paymentsTable.filter['status[ne]']
delete this.paymentsTable.filter['status[eq]']
let paymentFilter = this.paymentFilter || {}
delete paymentFilter['status[ne]']
delete paymentFilter['status[eq]']
if (success && pending && failed) {
// No status filter
} else if (success && pending) {
this.paymentsTable.filter['status[ne]'] = 'failed'
paymentFilter['status[ne]'] = 'failed'
} else if (success && failed) {
this.paymentsTable.filter['status[ne]'] = 'pending'
paymentFilter['status[ne]'] = 'pending'
} else if (failed && pending) {
this.paymentsTable.filter['status[ne]'] = 'success'
} else if (success) {
this.paymentsTable.filter['status[eq]'] = 'success'
} else if (pending) {
this.paymentsTable.filter['status[eq]'] = 'pending'
} else if (failed) {
this.paymentsTable.filter['status[eq]'] = 'failed'
paymentFilter['status[ne]'] = 'success'
} else if (success && !pending && !failed) {
paymentFilter['status[eq]'] = 'success'
} else if (pending && !success && !failed) {
paymentFilter['status[eq]'] = 'pending'
} else if (failed && !success && !pending) {
paymentFilter['status[eq]'] = 'failed'
}
delete this.paymentsTable.filter['amount[ge]']
delete this.paymentsTable.filter['amount[le]']
if (incoming && outgoing) {
delete paymentFilter['amount[ge]']
delete paymentFilter['amount[le]']
if ((incoming && outgoing) || (!incoming && !outgoing)) {
// do nothing
} else if (incoming) {
this.paymentsTable.filter['amount[ge]'] = 0
} else if (outgoing) {
this.paymentsTable.filter['amount[le]'] = 0
} else if (incoming && !outgoing) {
paymentFilter['amount[ge]'] = 0
} else if (outgoing && !incoming) {
paymentFilter['amount[le]'] = 0
}
this.paymentFilter = paymentFilter
}
},
watch: {
@@ -0,0 +1,226 @@
window.app.component('lnbits-wallet-charts', {
template: '#lnbits-wallet-charts',
mixins: [window.windowMixin],
props: ['paymentFilter', 'chartConfig'],
data() {
return {
debounceTimeoutValue: 1337,
debounceTimeout: null,
chartData: [],
chartDataPointCount: 0,
walletBalanceChart: null,
walletBalanceInOut: null,
walletPaymentInOut: null,
colorPrimary: Quasar.colors.changeAlpha(
Quasar.colors.getPaletteColor('primary'),
0.3
),
colorSecondary: Quasar.colors.changeAlpha(
Quasar.colors.getPaletteColor('secondary'),
0.3
),
barOptions: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
stacked: true
},
y: {
stacked: true
}
}
}
}
},
watch: {
paymentFilter: {
deep: true,
handler() {
this.changeCharts()
}
},
chartConfig: {
deep: true,
handler(val) {
this.$q.localStorage.setItem('lnbits.wallets.chartConfig', val)
this.changeCharts()
}
}
},
methods: {
// Debounce chart drawing and data fetching, if its called multiple times in quick succession
// (e.g. when changing filters) chart.js will error because of a race condition trying to
// destroy and redraw charts at the same time
changeCharts() {
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout)
}
this.debounceTimeout = setTimeout(async () => {
await this.fetchChartData()
this.drawCharts()
}, this.debounceTimeoutValue)
},
filterChartData() {
const timeFrom = this.paymentFilter['time[ge]'] + 'T00:00:00'
const timeTo = this.paymentFilter['time[le]'] + 'T23:59:59'
let totalBalance = 0
let data = this.chartData.map(p => {
if (this.paymentFilter['amount[ge]'] !== undefined) {
totalBalance += p.balance_in
return {...p, balance: totalBalance, balance_out: 0, count_out: 0}
}
if (this.paymentFilter['amount[le]'] !== undefined) {
totalBalance -= p.balance_out
return {...p, balance: totalBalance, balance_in: 0, count_in: 0}
}
return {...p}
})
data = data.filter(p => {
if (this.paymentFilter['time[ge]'] && this.paymentFilter['time[le]']) {
return p.date >= timeFrom && p.date <= timeTo
}
if (this.paymentFilter['time[ge]']) {
return p.date >= timeFrom
}
if (this.paymentFilter['time[le]']) {
return p.date <= timeTo
}
return true
})
const labels = data.map(s =>
new Date(s.date).toLocaleString('default', {
month: 'short',
day: 'numeric'
})
)
this.chartDataPointCount = data.length
return {data, labels}
},
drawBalanceInOutChart(data, labels) {
if (this.walletBalanceInOut) {
this.walletBalanceInOut.destroy()
}
this.walletBalanceInOut = new Chart(
this.$refs.walletBalanceInOut.getContext('2d'),
{
type: 'bar',
options: this.barOptions,
data: {
labels,
datasets: [
{
label: 'Balance In',
borderRadius: 5,
data: data.map(s => s.balance_in),
backgroundColor: this.colorPrimary
},
{
label: 'Balance Out',
borderRadius: 5,
data: data.map(s => s.balance_out),
backgroundColor: this.colorSecondary
}
]
}
}
)
},
drawPaymentInOut(data, labels) {
if (this.walletPaymentInOut) {
this.walletPaymentInOut.destroy()
}
this.walletPaymentInOut = new Chart(
this.$refs.walletPaymentInOut.getContext('2d'),
{
type: 'bar',
options: this.barOptions,
data: {
labels,
datasets: [
{
label: 'Payments In',
data: data.map(s => s.count_in),
backgroundColor: this.colorPrimary
},
{
label: 'Payments Out',
data: data.map(s => -s.count_out),
backgroundColor: this.colorSecondary
}
]
}
}
)
},
drawBalanceChart(data, labels) {
const ref = this.$refs.walletBalanceChart
if (this.walletBalanceChart) {
this.walletBalanceChart.destroy()
}
this.walletBalanceChart = new Chart(ref.getContext('2d'), {
type: 'line',
options: {
responsive: true,
maintainAspectRatio: false
},
data: {
labels,
datasets: [
{
label: 'Balance',
data: data.map(s => s.balance),
pointStyle: false,
backgroundColor: this.colorPrimary,
borderColor: this.colorPrimary,
borderWidth: 2,
fill: true,
tension: 0.7,
fill: 1
},
{
label: 'Fees',
data: data.map(s => s.fee),
pointStyle: false,
backgroundColor: this.colorSecondary,
borderColor: this.colorSecondary,
borderWidth: 1,
fill: true,
tension: 0.7,
fill: 1
}
]
}
})
},
drawCharts() {
const {data, labels} = this.filterChartData()
if (this.chartConfig.showBalanceChart) {
this.drawBalanceChart(data, labels)
}
if (this.chartConfig.showBalanceInOutChart) {
this.drawBalanceInOutChart(data, labels)
}
if (this.chartConfig.showPaymentInOutChart) {
this.drawPaymentInOut(data, labels)
}
},
async fetchChartData() {
try {
const {data} = await LNbits.api.request(
'GET',
`/api/v1/payments/stats/daily?wallet_id=${this.g.wallet.id}`
)
this.chartData = data
} catch (error) {
console.warn(error)
LNbits.utils.notifyApiError(error)
}
}
},
async created() {
await this.fetchChartData()
this.drawCharts()
}
})