From f3d0d8652be52219cb6a1a090370af111e4d20c1 Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 9 Nov 2025 00:14:49 +0100 Subject: [PATCH] Add Recent Transactions pagination and table view to with filtering Convert the Recent Transactions card from a list view to a paginated table with enhanced filtering capabilities for super users. Frontend changes: - Replace q-list with q-table for better data presentation - Add pagination with configurable page size (default: 20 items) - Add transaction filter dropdown for super users to filter by username - Define table columns: Status, Date, Description, User, Amount, Fiat, Reference - Implement prev/next page navigation with page info display - Add filter controls with clear filter button Backend changes (views_api.py): - Add pagination support with limit/offset parameters - Add filter_user_id parameter for filtering by user (super user only) - Enrich transaction entries with user_id and username from account lookups - Return paginated response with total count and pagination metadata Database changes (crud.py): - Update get_all_journal_entries() to support offset parameter - Update get_journal_entries_by_user() to support offset parameter - Add count_all_journal_entries() for total count - Add count_journal_entries_by_user() for user-specific count This improves the Recent Transactions UX by providing better organization, easier navigation through large transaction lists, and the ability for admins to filter transactions by user. --- static/js/index.js | 32 +++++++- templates/castle/index.html | 149 +++++++++++++++++++++++++----------- views_api.py | 38 ++++++++- 3 files changed, 172 insertions(+), 47 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index 6e4aacb..41ff3e4 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -17,6 +17,9 @@ window.app = Vue.createApp({ has_next: false, has_prev: false }, + transactionFilter: { + user_id: null // For filtering by user + }, accounts: [], currencies: [], users: [], @@ -183,6 +186,17 @@ window.app = Vue.createApp({ } }, computed: { + transactionColumns() { + return [ + { name: 'flag', label: 'Status', field: 'flag', align: 'left', sortable: true }, + { name: 'date', label: 'Date', field: 'entry_date', align: 'left', sortable: true }, + { name: 'description', label: 'Description', field: 'description', align: 'left', sortable: false }, + { name: 'username', label: 'User', field: 'username', align: 'left', sortable: true }, + { name: 'amount', label: 'Amount (sats)', field: 'amount', align: 'right', sortable: false }, + { name: 'fiat', label: 'Fiat Amount', field: 'fiat', align: 'right', sortable: false }, + { name: 'reference', label: 'Reference', field: 'reference', align: 'left', sortable: false } + ] + }, expenseAccounts() { return this.accounts.filter(a => a.account_type === 'expense') }, @@ -330,9 +344,15 @@ window.app = Vue.createApp({ const limit = parseInt(this.transactionPagination.limit) || 20 + // Build query params with filter + let queryParams = `limit=${limit}&offset=${currentOffset}` + if (this.transactionFilter.user_id) { + queryParams += `&filter_user_id=${this.transactionFilter.user_id}` + } + const response = await LNbits.api.request( 'GET', - `/castle/api/v1/entries/user?limit=${limit}&offset=${currentOffset}`, + `/castle/api/v1/entries/user?${queryParams}`, this.g.user.wallets[0].inkey ) @@ -346,6 +366,16 @@ window.app = Vue.createApp({ LNbits.utils.notifyApiError(error) } }, + applyTransactionFilter() { + // Reset to first page when applying filter + this.transactionPagination.offset = 0 + this.loadTransactions(0) + }, + clearTransactionFilter() { + this.transactionFilter.user_id = null + this.transactionPagination.offset = 0 + this.loadTransactions(0) + }, nextTransactionsPage() { if (this.transactionPagination.has_next) { const newOffset = this.transactionPagination.offset + this.transactionPagination.limit diff --git a/templates/castle/index.html b/templates/castle/index.html index 82b8c1d..2de6dfb 100644 --- a/templates/castle/index.html +++ b/templates/castle/index.html @@ -335,61 +335,122 @@ - - - - - + + +
+
+ + + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
No transactions yet
diff --git a/views_api.py b/views_api.py index c884360..ecb4975 100644 --- a/views_api.py +++ b/views_api.py @@ -267,10 +267,12 @@ async def api_get_user_entries( wallet: WalletTypeInfo = Depends(require_invoice_key), limit: int = 20, offset: int = 0, + filter_user_id: str = None, ) -> dict: """Get journal entries that affect the current user's accounts""" from lnbits.settings import settings as lnbits_settings - from .crud import count_all_journal_entries, count_journal_entries_by_user + from lnbits.core.crud.users import get_user + from .crud import count_all_journal_entries, count_journal_entries_by_user, get_account # If super user, show all journal entries if wallet.wallet.user == lnbits_settings.super_user: @@ -280,8 +282,40 @@ async def api_get_user_entries( entries = await get_journal_entries_by_user(wallet.wallet.user, limit, offset) total = await count_journal_entries_by_user(wallet.wallet.user) + # Filter by user_id if specified (super user only) + if filter_user_id and wallet.wallet.user == lnbits_settings.super_user: + entries = [e for e in entries if any( + line.account_id in [acc["id"] for acc in await db.fetchall( + "SELECT id FROM accounts WHERE user_id = :user_id", + {"user_id": filter_user_id} + )] + for line in e.lines + )] + total = len(entries) + + # Enrich entries with username information + enriched_entries = [] + for entry in entries: + # Find user_id from entry lines (look for user-specific accounts) + entry_user_id = None + entry_username = None + + for line in entry.lines: + account = await get_account(line.account_id) + if account and account.user_id: + entry_user_id = account.user_id + user = await get_user(account.user_id) + entry_username = user.username if user and user.username else account.user_id[:16] + "..." + break + + enriched_entries.append({ + **entry.dict(), + "user_id": entry_user_id, + "username": entry_username, + }) + return { - "entries": entries, + "entries": enriched_entries, "total": total, "limit": limit, "offset": offset,