[feat] filter payments in wallet (#2997)

* fix: search payments
* fix rogue button
* feat: UI polishing
* feat: better charts

---------

Co-authored-by: Tiago Vasconcelos <talvasconcelos@gmail.com>
This commit is contained in:
Vlad Stan
2025-02-26 11:34:57 +01:00
committed by GitHub
co-authored by Tiago Vasconcelos
parent 5dc1705fa6
commit 5aa1f9b0f8
10 changed files with 154 additions and 72 deletions
@@ -35,6 +35,7 @@
<q-table :rows="wallets" :columns="walletTable.columns"> <q-table :rows="wallets" :columns="walletTable.columns">
<template v-slot:header="props"> <template v-slot:header="props">
<q-tr :props="props"> <q-tr :props="props">
<q-th auto-width v-if="g.user.super_user"></q-th>
<q-th auto-width></q-th> <q-th auto-width></q-th>
<q-th <q-th
auto-width auto-width
@@ -47,12 +48,14 @@
</template> </template>
<template v-slot:body="props"> <template v-slot:body="props">
<q-tr :props="props"> <q-tr :props="props">
<q-td auto-width> <q-td auto-width v-if="g.user.super_user">
<lnbits-update-balance <lnbits-update-balance
:wallet_id="props.row.id" :wallet_id="props.row.id"
@credit-value="handleBalanceUpdate" @credit-value="handleBalanceUpdate"
class="q-mr-md" class="q-mr-md"
></lnbits-update-balance> ></lnbits-update-balance>
</q-td>
<q-td auto-width>
<q-btn <q-btn
round round
icon="menu" icon="menu"
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+4 -1
View File
@@ -82,6 +82,7 @@ window.localisation.en = {
create_invoice: 'Create Invoice', create_invoice: 'Create Invoice',
camera_tooltip: 'Use camera to scan an invoice/QR', camera_tooltip: 'Use camera to scan an invoice/QR',
export_csv: 'Export to CSV', export_csv: 'Export to CSV',
export_csv_details: 'Export to CSV with details',
chart_tooltip: 'Show chart', chart_tooltip: 'Show chart',
pending: 'Pending', pending: 'Pending',
copy_invoice: 'Copy invoice', copy_invoice: 'Copy invoice',
@@ -539,5 +540,7 @@ window.localisation.en = {
reset_wallet_keys_desc: reset_wallet_keys_desc:
'Reset the API keys for this wallet. This will invalidate the current keys and generate new ones.', 'Reset the API keys for this wallet. This will invalidate the current keys and generate new ones.',
view_list: 'View wallets as list', view_list: 'View wallets as list',
view_column: 'View wallets as rows' view_column: 'View wallets as rows',
filter_payments: 'Filter payments',
filter_date: 'Filter by date'
} }
+1 -1
View File
@@ -359,7 +359,7 @@ window.LNbits = {
prepareFilterQuery(tableConfig, props) { prepareFilterQuery(tableConfig, props) {
if (props) { if (props) {
tableConfig.pagination = props.pagination tableConfig.pagination = props.pagination
tableConfig.filter = {...tableConfig.filter, ...props.filter} Object.assign(tableConfig.filter, props.filter)
} }
const pagination = tableConfig.pagination const pagination = tableConfig.pagination
tableConfig.loading = true tableConfig.loading = true
-1
View File
@@ -595,7 +595,6 @@ window.app.component('username-password', {
}, },
async signInWithNostr() { async signInWithNostr() {
try { try {
console.log('### signInWithNostr')
const nostrToken = await this.createNostrToken() const nostrToken = await this.createNostrToken()
if (!nostrToken) { if (!nostrToken) {
return return
+38 -10
View File
@@ -6,7 +6,6 @@ window.app.component('payment-list', {
data() { data() {
return { return {
denomination: LNBITS_DENOMINATION, denomination: LNBITS_DENOMINATION,
failedPaymentsToggle: false,
payments: [], payments: [],
paymentsTable: { paymentsTable: {
columns: [ columns: [
@@ -39,6 +38,13 @@ window.app.component('payment-list', {
loading: false loading: false
}, },
searchDate: {from: null, to: null}, searchDate: {from: null, to: null},
searchStatus: {
success: true,
pending: true,
failed: false,
incoming: true,
outgoing: true
},
exportTagName: '', exportTagName: '',
exportPaymentTagList: [], exportPaymentTagList: [],
paymentsCSV: { paymentsCSV: {
@@ -263,18 +269,40 @@ window.app.component('payment-list', {
console.error(e) console.error(e)
return `${amount} ???` return `${amount} ???`
} }
},
handleFilterChanged() {
const {success, pending, failed, incoming, outgoing} = this.searchStatus
delete this.paymentsTable.filter['status[ne]']
delete this.paymentsTable.filter['status[eq]']
if (success && pending && failed) {
// No status filter
} else if (success && pending) {
this.paymentsTable.filter['status[ne]'] = 'failed'
} else if (success && failed) {
this.paymentsTable.filter['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'
}
delete this.paymentsTable.filter['amount[ge]']
delete this.paymentsTable.filter['amount[le]']
if (incoming && outgoing) {
// do nothing
} else if (incoming) {
this.paymentsTable.filter['amount[ge]'] = 0
} else if (outgoing) {
this.paymentsTable.filter['amount[le]'] = 0
}
} }
}, },
watch: { watch: {
failedPaymentsToggle(newVal) {
if (newVal === false) {
this.paymentsTable.filter['status[ne]'] = 'failed'
} else {
delete this.paymentsTable.filter['status[ne]']
}
this.paymentsTable.pagination.page = 1
this.fetchPayments()
},
'paymentsTable.search': { 'paymentsTable.search': {
handler() { handler() {
const props = {} const props = {}
-3
View File
@@ -352,9 +352,6 @@ window.UsersPageLogic = {
}) })
.catch(LNbits.utils.notifyApiError) .catch(LNbits.utils.notifyApiError)
}, },
exportUsers() {
console.log('export users')
},
async showAccountPage(user_id) { async showAccountPage(user_id) {
this.activeUser.showPassword = false this.activeUser.showPassword = false
this.activeUser.showUserId = false this.activeUser.showUserId = false
+17 -1
View File
@@ -854,7 +854,9 @@ window.WalletPageLogic = {
handleFilterChange(value = {}) { handleFilterChange(value = {}) {
if ( if (
this.paymentsFilter['time[ge]'] !== value['time[ge]'] || this.paymentsFilter['time[ge]'] !== value['time[ge]'] ||
this.paymentsFilter['time[le]'] !== value['time[le]'] this.paymentsFilter['time[le]'] !== value['time[le]'] ||
this.paymentsFilter['amount[ge]'] !== value['amount[ge]'] ||
this.paymentsFilter['amount[le]'] !== value['amount[le]']
) { ) {
this.refreshCharts() this.refreshCharts()
} }
@@ -884,6 +886,19 @@ window.WalletPageLogic = {
filterChartData(data) { filterChartData(data) {
const timeFrom = this.paymentsFilter['time[ge]'] + 'T00:00:00' const timeFrom = this.paymentsFilter['time[ge]'] + 'T00:00:00'
const timeTo = this.paymentsFilter['time[le]'] + 'T23:59:59' const timeTo = this.paymentsFilter['time[le]'] + 'T23:59:59'
let totalBalance = 0
data = data.map(p => {
if (this.paymentsFilter['amount[ge]'] !== undefined) {
totalBalance += p.balance_in
return {...p, balance: totalBalance, balance_out: 0, count_out: 0}
}
if (this.paymentsFilter['amount[le]'] !== undefined) {
totalBalance -= p.balance_out
return {...p, balance: totalBalance, balance_in: 0, count_in: 0}
}
return {...p}
})
data = data.filter(p => { data = data.filter(p => {
if ( if (
this.paymentsFilter['time[ge]'] && this.paymentsFilter['time[ge]'] &&
@@ -899,6 +914,7 @@ window.WalletPageLogic = {
} }
return true return true
}) })
const labels = data.map(s => const labels = data.map(s =>
new Date(s.date).toLocaleString('default', { new Date(s.date).toLocaleString('default', {
month: 'short', month: 'short',
+88 -52
View File
@@ -419,7 +419,6 @@
dense dense
use-input use-input
use-chips use-chips
multiple
hide-dropdown-icon hide-dropdown-icon
></q-select> ></q-select>
<div v-else-if="o.type === 'bool'"> <div v-else-if="o.type === 'bool'">
@@ -639,16 +638,100 @@
</q-input> </q-input>
</div> </div>
<div class="gt-sm col-auto"> <div class="gt-sm col-auto">
<q-btn icon="event" flat color="grey">
<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-badge>
<q-tooltip>
<span v-text="$t('filter_date')"></span>
</q-tooltip>
</q-btn>
<q-btn color="grey" icon="filter_alt" flat>
<q-menu>
<q-item dense>
<q-checkbox
v-model="searchStatus.success"
@click="handleFilterChanged"
label="Success Payments"
></q-checkbox>
</q-item>
<q-item dense>
<q-checkbox
v-model="searchStatus.pending"
@click="handleFilterChanged"
label="Pending Payments"
></q-checkbox>
</q-item>
<q-item dense>
<q-checkbox
v-model="searchStatus.failed"
@click="handleFilterChanged"
label="Failed Payments"
></q-checkbox>
</q-item>
<q-separator></q-separator>
<q-item dense>
<q-checkbox
v-model="searchStatus.incoming"
@click="handleFilterChanged"
label="Incoming Payments"
></q-checkbox>
</q-item>
<q-item dense>
<q-checkbox
v-model="searchStatus.outgoing"
@click="handleFilterChanged"
label="Outgoing Payments"
></q-checkbox>
</q-item>
</q-menu>
<q-tooltip>
<span v-text="$t('filter_payments')"></span>
</q-tooltip>
</q-btn>
<q-btn-dropdown <q-btn-dropdown
dense
outline outline
persistent persistent
dense icon="archive"
split
class="q-mr-sm" class="q-mr-sm"
color="grey" color="grey"
label="Export"
split
@click="exportCSV(false)" @click="exportCSV(false)"
> >
<q-tooltip>
<span v-text="$t('export_csv')"></span>
</q-tooltip>
<q-list> <q-list>
<q-item> <q-item>
<q-item-section> <q-item-section>
@@ -688,59 +771,12 @@
outline outline
color="grey" color="grey"
@click="exportCSV(true)" @click="exportCSV(true)"
label="Export to CSV with details" :label="$t('export_csv_details')"
></q-btn> ></q-btn>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
</q-btn-dropdown> </q-btn-dropdown>
<q-btn icon="event" outline flat color="grey">
<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-checkbox
v-model="failedPaymentsToggle"
checked-icon="warning"
unchecked-icon="warning_off"
:color="failedPaymentsToggle ? 'yellow' : 'grey'"
size="xs"
>
<q-tooltip>
<span v-text="`Include failed payments`"></span>
</q-tooltip>
</q-checkbox>
</div> </div>
</div> </div>
<div class="row q-my-md"></div> <div class="row q-my-md"></div>