From 69b8f6e2d3598da17c52d39de469794749d33f8a Mon Sep 17 00:00:00 2001 From: padreug Date: Sat, 8 Nov 2025 23:51:12 +0100 Subject: [PATCH] Adds pagination to transaction history Implements pagination for the transaction history, enabling users to navigate through their transactions in manageable chunks. This improves performance and user experience, especially for users with a large number of transactions. It also introduces total entry counts. --- crud.py | 48 +++++++++++++++++++++++++++++++++---- static/js/index.js | 34 +++++++++++++++++++++++--- templates/castle/index.html | 29 ++++++++++++++++++++++ views_api.py | 21 ++++++++++++---- 4 files changed, 120 insertions(+), 12 deletions(-) diff --git a/crud.py b/crud.py index 53d7aae..2004264 100644 --- a/crud.py +++ b/crud.py @@ -266,14 +266,14 @@ async def get_entry_lines(journal_entry_id: str) -> list[EntryLine]: return lines -async def get_all_journal_entries(limit: int = 100) -> list[JournalEntry]: +async def get_all_journal_entries(limit: int = 100, offset: int = 0) -> list[JournalEntry]: entries_data = await db.fetchall( """ SELECT * FROM journal_entries ORDER BY entry_date DESC, created_at DESC - LIMIT :limit + LIMIT :limit OFFSET :offset """, - {"limit": limit}, + {"limit": limit, "offset": offset}, ) entries = [] @@ -301,7 +301,7 @@ async def get_all_journal_entries(limit: int = 100) -> list[JournalEntry]: async def get_journal_entries_by_user( - user_id: str, limit: int = 100 + user_id: str, limit: int = 100, offset: int = 0 ) -> list[JournalEntry]: """Get journal entries that affect the user's accounts""" # Get all user-specific accounts @@ -320,6 +320,7 @@ async def get_journal_entries_by_user( placeholders = ','.join([f":account_{i}" for i in range(len(account_ids))]) params = {f"account_{i}": acc_id for i, acc_id in enumerate(account_ids)} params["limit"] = limit + params["offset"] = offset entries_data = await db.fetchall( f""" @@ -328,7 +329,7 @@ async def get_journal_entries_by_user( JOIN entry_lines el ON je.id = el.journal_entry_id WHERE el.account_id IN ({placeholders}) ORDER BY je.entry_date DESC, je.created_at DESC - LIMIT :limit + LIMIT :limit OFFSET :offset """, params, ) @@ -357,6 +358,43 @@ async def get_journal_entries_by_user( return entries +async def count_all_journal_entries() -> int: + """Count total number of journal entries""" + result = await db.fetchone( + "SELECT COUNT(*) as total FROM journal_entries" + ) + return result["total"] if result else 0 + + +async def count_journal_entries_by_user(user_id: str) -> int: + """Count journal entries that affect the user's accounts""" + # Get all user-specific accounts + user_accounts = await db.fetchall( + "SELECT id FROM accounts WHERE user_id = :user_id", + {"user_id": user_id}, + ) + + if not user_accounts: + return 0 + + account_ids = [acc["id"] for acc in user_accounts] + + # Count journal entries that have lines affecting these accounts + placeholders = ','.join([f":account_{i}" for i in range(len(account_ids))]) + params = {f"account_{i}": acc_id for i, acc_id in enumerate(account_ids)} + + result = await db.fetchone( + f""" + SELECT COUNT(DISTINCT je.id) as total + FROM journal_entries je + JOIN entry_lines el ON je.id = el.journal_entry_id + WHERE el.account_id IN ({placeholders}) + """, + params, + ) + return result["total"] if result else 0 + + # ===== BALANCE AND REPORTING ===== diff --git a/static/js/index.js b/static/js/index.js index d954432..c333238 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -10,6 +10,13 @@ window.app = Vue.createApp({ balance: null, allUserBalances: [], transactions: [], + transactionPagination: { + total: 0, + limit: 20, + offset: 0, + has_next: false, + has_prev: false + }, accounts: [], currencies: [], users: [], @@ -306,18 +313,39 @@ window.app = Vue.createApp({ console.error('Error loading all user balances:', error) } }, - async loadTransactions() { + async loadTransactions(offset = null) { try { + // Use provided offset or current pagination offset + const currentOffset = offset !== null ? offset : this.transactionPagination.offset + const response = await LNbits.api.request( 'GET', - '/castle/api/v1/entries/user', + `/castle/api/v1/entries/user?limit=${this.transactionPagination.limit}&offset=${currentOffset}`, this.g.user.wallets[0].inkey ) - this.transactions = response.data + + // Update transactions and pagination info + this.transactions = response.data.entries + this.transactionPagination.total = response.data.total + this.transactionPagination.offset = response.data.offset + this.transactionPagination.has_next = response.data.has_next + this.transactionPagination.has_prev = response.data.has_prev } catch (error) { LNbits.utils.notifyApiError(error) } }, + nextTransactionsPage() { + if (this.transactionPagination.has_next) { + const newOffset = this.transactionPagination.offset + this.transactionPagination.limit + this.loadTransactions(newOffset) + } + }, + prevTransactionsPage() { + if (this.transactionPagination.has_prev) { + const newOffset = Math.max(0, this.transactionPagination.offset - this.transactionPagination.limit) + this.loadTransactions(newOffset) + } + }, async loadAccounts() { try { const response = await LNbits.api.request( diff --git a/templates/castle/index.html b/templates/castle/index.html index 86613e7..82b8c1d 100644 --- a/templates/castle/index.html +++ b/templates/castle/index.html @@ -394,6 +394,35 @@ No transactions yet + + + +
+
+ +
+
+ {% raw %}{{ transactionPagination.offset + 1 }} - {{ Math.min(transactionPagination.offset + transactionPagination.limit, transactionPagination.total) }} of {{ transactionPagination.total }}{% endraw %} +
+
+ +
+
+
diff --git a/views_api.py b/views_api.py index a36d78f..c884360 100644 --- a/views_api.py +++ b/views_api.py @@ -265,16 +265,29 @@ async def api_get_journal_entries(limit: int = 100) -> list[JournalEntry]: @castle_api_router.get("/api/v1/entries/user") async def api_get_user_entries( wallet: WalletTypeInfo = Depends(require_invoice_key), - limit: int = 100, -) -> list[JournalEntry]: + limit: int = 20, + offset: int = 0, +) -> 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 # If super user, show all journal entries if wallet.wallet.user == lnbits_settings.super_user: - return await get_all_journal_entries(limit) + entries = await get_all_journal_entries(limit, offset) + total = await count_all_journal_entries() + else: + entries = await get_journal_entries_by_user(wallet.wallet.user, limit, offset) + total = await count_journal_entries_by_user(wallet.wallet.user) - return await get_journal_entries_by_user(wallet.wallet.user, limit) + return { + "entries": entries, + "total": total, + "limit": limit, + "offset": offset, + "has_next": (offset + limit) < total, + "has_prev": offset > 0, + } @castle_api_router.get("/api/v1/entries/pending")