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")