Enhance transaction pagination and filtering functionality with improved caching and UI updates

This commit is contained in:
void-57 2026-01-14 18:01:27 +05:30
parent 28c819ec85
commit 655e59c850

View File

@ -1182,6 +1182,7 @@
async update(newElements) { async update(newElements) {
this.elementsToRender = newElements; this.elementsToRender = newElements;
this.currentPage = 1; this.currentPage = 1;
currentPage = 1; // Sync global state
this.totalPages = Math.ceil(newElements.length / this.pageSize); this.totalPages = Math.ceil(newElements.length / this.pageSize);
await this.renderCurrentPage(); await this.renderCurrentPage();
this.renderPagination(); this.renderPagination();
@ -1299,38 +1300,83 @@
} }
async fetchNextPage() { async fetchNextPage() {
try {
const targetPage = this.currentPage + 1;
console.log(`Going to next page: ${targetPage}`);
// Check if we have cached data for this page
if (transactionPageCache.has(targetPage)) {
console.log(`Using cached data for page ${targetPage}`);
const cachedPage = transactionPageCache.get(targetPage);
// Apply current filter
const filter =
document.getElementById("filter_selector")?.value || "all";
let filteredTransactions = cachedPage.formatted;
if (filter === "sent") {
filteredTransactions = cachedPage.formatted.filter(
(tx) => tx.type === "out"
);
} else if (filter === "received") {
filteredTransactions = cachedPage.formatted.filter(
(tx) => tx.type === "in"
);
}
// Clear container and render cached transactions
this.container.innerHTML = "";
if (filteredTransactions.length > 0) {
for (const tx of filteredTransactions) {
try {
const card = await this.renderFn(tx);
if (card && card instanceof Element) {
this.container.appendChild(card);
}
} catch (error) {
console.error("Error rendering transaction card:", error);
}
}
} else {
this.container.innerHTML =
'<p class="text-center margin-top-2">No transactions found for this filter</p>';
}
// Update page state
this.currentPage = targetPage;
currentPage = targetPage; // Sync global state
// Update page indicator
const pageInfo = document.getElementById("page_info");
if (pageInfo) pageInfo.textContent = `Page ${this.currentPage}`;
// Update button states
const prevButton = document.querySelector(
".pagination-controls button:first-child"
);
if (prevButton) prevButton.disabled = this.currentPage <= 1;
const nextButton = document.getElementById("next_page_button");
if (nextButton) nextButton.disabled = !hasMoreTransactions && this.currentPage >= transactionPageCache.size;
return;
}
// If not in cache, fetch new transactions from blockchain
if (!hasMoreTransactions) return; if (!hasMoreTransactions) return;
// Show loading state // Show loading state
this.container.innerHTML = this.container.innerHTML =
'<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>'; '<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>';
try {
const addressInput = currentWalletAddress; const addressInput = currentWalletAddress;
if (!addressInput) { if (!addressInput) {
this.container.innerHTML = this.container.innerHTML =
'<p class="text-center margin-top-2">No address input found</p>'; '<p class="text-center margin-top-2">No address input found</p>';
return; return;
} }
const address = addressInput;
if (!address) {
this.container.innerHTML =
'<p class="text-center margin-top-2">No address specified</p>';
return;
}
console.log("Fetching next page for address:", address);
try {
new solanaWeb3.PublicKey(address);
} catch (error) {
this.container.innerHTML =
'<p class="text-center margin-top-2">Invalid address</p>';
return;
}
if (!lastFetchedSignature) { if (!lastFetchedSignature) {
this.container.innerHTML = this.container.innerHTML =
'<p class="text-center margin-top-2">No more transactions to load</p>'; '<p class="text-center margin-top-2">No more transactions to load</p>';
@ -1338,64 +1384,54 @@
return; return;
} }
console.log(
"Fetching next page with signature:",
lastFetchedSignature
);
// Get next batch of signatures using the last signature from previous batch // Get next batch of signatures using the last signature from previous batch
const newSignatures = await getSolanaTransactionHistory( const newSignatures = await getSolanaTransactionHistory(
address, addressInput,
lastFetchedSignature, lastFetchedSignature,
false false
); );
console.log("Fetched new signatures:", newSignatures?.length);
if (!newSignatures || newSignatures.length === 0) { if (!newSignatures || newSignatures.length === 0) {
hasMoreTransactions = false; hasMoreTransactions = false;
this.container.innerHTML = this.container.innerHTML =
'<p class="text-center margin-top-2">No more transactions to load</p>'; '<p class="text-center margin-top-2">No more transactions to load</p>';
const nextBtn = document.getElementById("next_page_button");
// Update button states if (nextBtn) nextBtn.disabled = true;
getRef("next_page_button").disabled = true;
return; return;
} }
// Process the new transactions // Process the new transactions
const newTransactions = await processTransactions(newSignatures); const newTransactions = await processTransactions(newSignatures);
// Format and filter the transactions using the specific formatTransaction function // Format
const formattedTxs = newTransactions.map((tx) => { const formattedTxsBatch = newTransactions.map((tx) => {
return formatTransaction(address, tx); return formatTransaction(addressInput, tx);
}); });
// Save this page for instant backward navigation // Save this page for instant navigation
transactionPageCache.set(currentPage + 1, { transactionPageCache.set(targetPage, {
signatures: newSignatures, signatures: newSignatures,
transactions: newTransactions, transactions: newTransactions,
formatted: formattedTxs formatted: formattedTxsBatch
}); });
// Apply current filter // Apply current filter
const filterSelector = document.getElementById("filter_selector"); const filterSelector = document.getElementById("filter_selector");
const filter = filterSelector ? filterSelector.value : "all"; const filter = filterSelector ? filterSelector.value : "all";
let filteredTransactions = formattedTxs; let filteredTransactions = formattedTxsBatch;
if (filter === "sent") { if (filter === "sent") {
filteredTransactions = formattedTxs.filter( filteredTransactions = formattedTxsBatch.filter(
(tx) => tx.type === "out" (tx) => tx.type === "out"
); );
} else if (filter === "received") { } else if (filter === "received") {
filteredTransactions = formattedTxs.filter( filteredTransactions = formattedTxsBatch.filter(
(tx) => tx.type === "in" (tx) => tx.type === "in"
); );
} }
// Clear previous content and show new transactions // Render
this.container.innerHTML = ""; this.container.innerHTML = "";
// Add new transactions to the display
if (filteredTransactions.length > 0) { if (filteredTransactions.length > 0) {
for (const tx of filteredTransactions) { for (const tx of filteredTransactions) {
try { try {
@ -1410,20 +1446,21 @@
'<p class="text-center margin-top-2">No transactions found for this filter</p>'; '<p class="text-center margin-top-2">No transactions found for this filter</p>';
} }
currentPage++; this.currentPage = targetPage;
currentPage = targetPage; // Sync global state
// Update page indicator // Update UI
const pageInfo = document.getElementById("page_info"); const pageInfo = document.getElementById("page_info");
if (pageInfo) pageInfo.textContent = `Page ${currentPage}`; if (pageInfo) pageInfo.textContent = `Page ${this.currentPage}`;
// Update button states const prevBtn = document.querySelector(
const prevButton = document.querySelector(
".pagination-controls button:first-child" ".pagination-controls button:first-child"
); );
if (prevButton) prevButton.disabled = false; if (prevBtn) prevBtn.disabled = false;
const nextBtn = document.getElementById("next_page_button");
if (nextBtn) nextBtn.disabled = !hasMoreTransactions;
const nextButton = document.getElementById("next_page_button");
if (nextButton) nextButton.disabled = !hasMoreTransactions;
} catch (error) { } catch (error) {
console.error("Error fetching next page:", error, error.stack); console.error("Error fetching next page:", error, error.stack);
this.container.innerHTML = this.container.innerHTML =
@ -1448,7 +1485,7 @@
return; return;
} }
const targetPage = currentPage - 1; const targetPage = this.currentPage - 1;
console.log(`Going to previous page: ${targetPage}`); console.log(`Going to previous page: ${targetPage}`);
// Check if we have cached data for this page // Check if we have cached data for this page
@ -1491,17 +1528,18 @@
} }
// Update page state // Update page state
currentPage = targetPage; this.currentPage = targetPage;
currentPage = targetPage; // Sync global state
// Update page indicator // Update page indicator
const pageInfo = document.getElementById("page_info"); const pageInfo = document.getElementById("page_info");
if (pageInfo) pageInfo.textContent = `Page ${currentPage}`; if (pageInfo) pageInfo.textContent = `Page ${this.currentPage}`;
// Update button states // Update button states
const prevButton = document.querySelector( const prevButton = document.querySelector(
".pagination-controls button:first-child" ".pagination-controls button:first-child"
); );
if (prevButton) prevButton.disabled = currentPage <= 1; if (prevButton) prevButton.disabled = this.currentPage <= 1;
const nextButton = document.getElementById("next_page_button"); const nextButton = document.getElementById("next_page_button");
if (nextButton) nextButton.disabled = false; // Can always go forward from cached page if (nextButton) nextButton.disabled = false; // Can always go forward from cached page
@ -1698,7 +1736,7 @@
const preBalance = tx.meta.preBalances[addressIndex] || 0; const preBalance = tx.meta.preBalances[addressIndex] || 0;
const postBalance = tx.meta.postBalances[addressIndex] || 0; const postBalance = tx.meta.postBalances[addressIndex] || 0;
return (postBalance - preBalance) / solanaWeb3.LAMPORTS_PER_SOL; return Number(((postBalance - preBalance) / solanaWeb3.LAMPORTS_PER_SOL).toFixed(9));
} catch (error) { } catch (error) {
console.error("Error calculating transaction amount:", error); console.error("Error calculating transaction amount:", error);
return 0; return 0;
@ -1775,70 +1813,7 @@
); );
transactionsLazyLoader.init(); transactionsLazyLoader.init();
} }
// Handle filter changes
getRef("filter_selector").addEventListener("change", async (e) => {
const address = currentWalletAddress;
if (!address) return;
// Show loading state
getRef("transactions_list").innerHTML =
'<sm-spinner class="justify-self-center margin-top-1-5"></sm-spinner>';
try {
// Don't refetch transactions - use the already processed ones stored in memory
// Just filter what we already have
const filter = e.target.value || "all";
let filteredTransactions;
if (filter === "sent") {
filteredTransactions = formattedTxs.filter(
(tx) => tx.type === "out"
);
} else if (filter === "received") {
filteredTransactions = formattedTxs.filter(
(tx) => tx.type === "in"
);
} else {
// "all" filter - use all transactions
filteredTransactions = formattedTxs;
}
// Clear container
getRef("transactions_list").innerHTML = "";
// Render filtered transactions
if (filteredTransactions.length > 0) {
for (const tx of filteredTransactions) {
try {
const card = await render.transactionCard(tx);
if (card) getRef("transactions_list").appendChild(card);
} catch (error) {
console.error("Error rendering transaction card:", error);
}
}
} else {
getRef("transactions_list").innerHTML =
'<p class="text-center margin-top-2">No transactions found for this filter</p>';
}
// Update pagination info
const pageInfo = document.getElementById("page_info");
if (pageInfo) pageInfo.textContent = `Page ${currentPage}`;
// Update button states
const prevButton = document.querySelector(
".pagination-controls button:first-child"
);
if (prevButton) prevButton.disabled = currentPage <= 1;
const nextButton = document.getElementById("next_page_button");
if (nextButton) nextButton.disabled = !hasMoreTransactions;
} catch (error) {
console.error("Error applying filter:", error);
getRef("transactions_list").innerHTML =
'<p class="text-center margin-top-2">Error filtering transactions</p>';
}
});
// Enable transaction card click to view details // Enable transaction card click to view details
const list = getRef("transactions_list"); const list = getRef("transactions_list");
@ -2130,6 +2105,47 @@
} }
} }
async function applyFilter() {
const filterSelector = document.getElementById("filter_selector");
if (!filterSelector) return;
const filter = filterSelector.value || "all";
const container = document.getElementById("transactions_list");
if (!container) return;
// Get current page data from cache
const cachedPage = transactionPageCache.get(currentPage);
if (!cachedPage) return;
let filtered = cachedPage.formatted;
if (filter === "sent") {
filtered = cachedPage.formatted.filter((tx) => tx.type === "out");
} else if (filter === "received") {
filtered = cachedPage.formatted.filter((tx) => tx.type === "in");
}
// Show loading spinner for visual feedback
container.innerHTML = '<div class="flex justify-center w-100 margin-top-2"><sm-spinner></sm-spinner></div>';
// Use a timeout to let the spinner show and keep UI snappy
setTimeout(async () => {
const filteredList = filtered; // Capture current filtered state
// Render all cards first in memory to avoid jumping
const cards = await Promise.all(filteredList.map(tx => render.transactionCard(tx)));
container.innerHTML = "";
if (cards.length > 0) {
cards.forEach(card => {
if (card) container.appendChild(card);
});
} else {
container.innerHTML = '<p class="text-center margin-top-2">No transactions found for this filter in this page</p>';
}
}, 300);
}
function handleInvalidSearch() { function handleInvalidSearch() {
if (document.startViewTransition) if (document.startViewTransition)
document.startViewTransition(() => { document.startViewTransition(() => {
@ -2249,10 +2265,7 @@
<h4>Transactions</h4> <h4>Transactions</h4>
<sm-chips <sm-chips
id="filter_selector" id="filter_selector"
onchange=${(e) => onchange=${() => applyFilter()}
render.transactions(
getRef("wallet_address_input").value
)}
> >
<sm-chip value="all" selected>All</sm-chip> <sm-chip value="all" selected>All</sm-chip>
<sm-chip value="sent">Sent</sm-chip> <sm-chip value="sent">Sent</sm-chip>
@ -2972,9 +2985,9 @@
<div class="error-state"> <div class="error-state">
<div class="error-icon">⚠️</div> <div class="error-icon">⚠️</div>
<h3>Error Loading Transaction</h3> <h3>Error Loading Transaction</h3>
<p></p> <p>Failed to fetch transaction details. Please try again later.</p>
<button <button class="button" onclick="location.reload()">
<button Retry
</button> </button>
</div> </div>
` `
@ -3738,25 +3751,7 @@
return uint8Array; return uint8Array;
} }
function bnObjectToUint8(bnObject) {
const bn = bnObject._bn; // Extract the BN instance from the object
const words = bn.words; // Get the words array from the BN instance
// Convert each word to its corresponding bytes
const byteArray = [];
for (let i = 0; i < words.length; i++) {
const word = words[i];
byteArray.push((word >> 24) & 0xff); // Extract first byte
byteArray.push((word >> 16) & 0xff); // Extract second byte
byteArray.push((word >> 8) & 0xff); // Extract third byte
byteArray.push(word & 0xff); // Extract fourth byte
}
// Create Uint8Array from the byte array
const uint8Array = new Uint8Array(byteArray);
return uint8Array;
}
function retrieveSolanaAddr() { function retrieveSolanaAddr() {
function retrieve() { function retrieve() {
let seed = getRef("retrieve_btc_addr_field").value.trim(); let seed = getRef("retrieve_btc_addr_field").value.trim();