diff --git a/basicswap/static/js/bids_sentreceived.js b/basicswap/static/js/bids_sentreceived.js
index 3ad6af4..ae4effd 100644
--- a/basicswap/static/js/bids_sentreceived.js
+++ b/basicswap/static/js/bids_sentreceived.js
@@ -1,15 +1,19 @@
const PAGE_SIZE = 50;
+let activeFetchController = null;
+
const state = {
currentPage: {
+ all: 1,
sent: 1,
received: 1
},
isLoading: false,
isRefreshing: false,
- currentTab: 'sent',
+ currentTab: 'all',
wsConnected: false,
refreshPromise: null,
data: {
+ all: [],
sent: [],
received: []
},
@@ -24,6 +28,16 @@ const state = {
}
};
+document.addEventListener('tabactivated', function(event) {
+ if (event.detail && event.detail.tabId) {
+ const tabType = event.detail.type || (event.detail.tabId === '#all' ? 'all' :
+ (event.detail.tabId === '#sent' ? 'sent' : 'received'));
+ //console.log('Tab activation event received for:', tabType);
+ state.currentTab = tabType;
+ updateBidsTable();
+ }
+});
+
const STATE_MAP = {
1: ['Sent'],
2: ['Receiving'],
@@ -61,33 +75,43 @@ const STATE_MAP = {
};
const elements = {
- sentBidsBody: document.querySelector('#sent tbody'),
- receivedBidsBody: document.querySelector('#received tbody'),
+ allBidsBody: document.querySelector('#all-tbody'),
+ sentBidsBody: document.querySelector('#sent-tbody'),
+ receivedBidsBody: document.querySelector('#received-tbody'),
filterForm: document.querySelector('form'),
stateSelect: document.querySelector('select[name="state"]'),
sortBySelect: document.querySelector('select[name="sort_by"]'),
sortDirSelect: document.querySelector('select[name="sort_dir"]'),
withExpiredSelect: document.querySelector('select[name="with_expired"]'),
tabButtons: document.querySelectorAll('#myTab button'),
+ allContent: document.getElementById('all'),
sentContent: document.getElementById('sent'),
receivedContent: document.getElementById('received'),
+ allPaginationControls: document.getElementById('pagination-controls-all'),
sentPaginationControls: document.getElementById('pagination-controls-sent'),
receivedPaginationControls: document.getElementById('pagination-controls-received'),
+ prevPageAll: document.getElementById('prevPageAll'),
prevPageSent: document.getElementById('prevPageSent'),
- nextPageSent: document.getElementById('nextPageSent'),
prevPageReceived: document.getElementById('prevPageReceived'),
+ nextPageAll: document.getElementById('nextPageAll'),
+ nextPageSent: document.getElementById('nextPageSent'),
nextPageReceived: document.getElementById('nextPageReceived'),
+ currentPageAll: document.getElementById('currentPageAll'),
currentPageSent: document.getElementById('currentPageSent'),
currentPageReceived: document.getElementById('currentPageReceived'),
+ allBidsCount: document.getElementById('allBidsCount'),
sentBidsCount: document.getElementById('sentBidsCount'),
receivedBidsCount: document.getElementById('receivedBidsCount'),
+ statusDotAll: document.getElementById('status-dot-all'),
+ statusTextAll: document.getElementById('status-text-all'),
statusDotSent: document.getElementById('status-dot-sent'),
statusTextSent: document.getElementById('status-text-sent'),
statusDotReceived: document.getElementById('status-dot-received'),
statusTextReceived: document.getElementById('status-text-received'),
+ refreshAllBids: document.getElementById('refreshAllBids'),
refreshSentBids: document.getElementById('refreshSentBids'),
refreshReceivedBids: document.getElementById('refreshReceivedBids')
};
@@ -213,6 +237,7 @@ function cleanup() {
}
};
+ cleanupTableBody('all-tbody');
cleanupTableBody('sent-tbody');
cleanupTableBody('received-tbody');
@@ -234,11 +259,13 @@ function cleanup() {
clearAllAnimationFrames();
state.data = {
+ all: [],
sent: [],
received: []
};
state.currentPage = {
+ all: 1,
sent: 1,
received: 1
};
@@ -283,7 +310,7 @@ function cleanup() {
if (window.CleanupManager) CleanupManager.clearAll();
if (window.WebSocketManager) WebSocketManager.disconnect();
- state.data = { sent: [], received: [] };
+ state.data = { all: [], sent: [], received: [] };
state.isLoading = false;
Object.keys(elements).forEach(key => {
@@ -366,7 +393,7 @@ function cleanupRow(row) {
function optimizeMemoryUsage() {
const MAX_BIDS_IN_MEMORY = 500;
- ['sent', 'received'].forEach(type => {
+ ['all', 'sent', 'received'].forEach(type => {
if (state.data[type] && state.data[type].length > MAX_BIDS_IN_MEMORY) {
console.log(`Trimming ${type} bids data from ${state.data[type].length} to ${MAX_BIDS_IN_MEMORY}`);
state.data[type] = state.data[type].slice(0, MAX_BIDS_IN_MEMORY);
@@ -528,7 +555,9 @@ function filterAndSortData(bids) {
const coinName = selectedOption?.textContent.trim();
if (coinName) {
- const coinToMatch = state.currentTab === 'sent' ? bid.coin_to : bid.coin_from;
+ const coinToMatch = state.currentTab === 'all'
+ ? (bid.source === 'sent' ? bid.coin_to : bid.coin_from)
+ : (state.currentTab === 'sent' ? bid.coin_to : bid.coin_from);
if (!coinMatches(coinToMatch, coinName)) {
return false;
}
@@ -541,7 +570,10 @@ function filterAndSortData(bids) {
const coinName = selectedOption?.textContent.trim();
if (coinName) {
- const coinToMatch = state.currentTab === 'sent' ? bid.coin_from : bid.coin_to;
+ const coinToMatch = state.currentTab === 'all'
+ ? (bid.source === 'sent' ? bid.coin_from : bid.coin_to)
+ : (state.currentTab === 'sent' ? bid.coin_from : bid.coin_to);
+
if (!coinMatches(coinToMatch, coinName)) {
return false;
}
@@ -582,7 +614,8 @@ function filterAndSortData(bids) {
let matchesDisplayedLabel = false;
if (!matchesLabel && document) {
try {
- const tableId = state.currentTab === 'sent' ? 'sent' : 'received';
+ const tableId = state.currentTab === 'sent' ? 'sent' :
+ (state.currentTab === 'received' ? 'received' : 'all');
const cells = document.querySelectorAll(`#${tableId} a[href^="/identity/"]`);
for (const cell of cells) {
@@ -680,7 +713,7 @@ function updateCoinFilterImages() {
const updateLoadingState = (isLoading) => {
state.isLoading = isLoading;
- ['Sent', 'Received'].forEach(type => {
+ ['All', 'Sent', 'Received'].forEach(type => {
const refreshButton = elements[`refresh${type}Bids`];
const refreshText = refreshButton?.querySelector(`#refresh${type}Text`);
const refreshIcon = refreshButton?.querySelector('svg');
@@ -731,7 +764,7 @@ const updateConnectionStatus = (status) => {
const config = statusConfig[status] || statusConfig.connected;
- ['sent', 'received'].forEach(type => {
+ ['all', 'sent', 'received'].forEach(type => {
const dot = elements[`statusDot${type.charAt(0).toUpperCase() + type.slice(1)}`];
const text = elements[`statusText${type.charAt(0).toUpperCase() + type.slice(1)}`];
@@ -769,16 +802,69 @@ const processIdentityStats = (identity) => {
const createIdentityTooltipContent = (identity) => {
if (!identity) return '';
+ const address = identity.address || '';
+ let statsSection = '';
- const stats = processIdentityStats(identity);
- if (!stats) return '';
+ try {
+ const stats = processIdentityStats(identity);
+ if (stats) {
+ const getSuccessRateColor = (rate) => {
+ const numRate = parseFloat(rate);
+ if (numRate >= 80) return 'text-green-600';
+ if (numRate >= 60) return 'text-yellow-600';
+ return 'text-red-600';
+ };
- const getSuccessRateColor = (rate) => {
- const numRate = parseFloat(rate);
- if (numRate >= 80) return 'text-green-600';
- if (numRate >= 60) return 'text-yellow-600';
- return 'text-red-600';
- };
+ statsSection = `
+
+
Swap History:
+
+
+
+ ${stats.successRate}%
+
+
Success Rate
+
+
+
${stats.totalBids}
+
Total Trades
+
+
+
+
+
+ ${stats.totalSuccessful}
+
+
Successful
+
+
+
+ ${stats.totalRejected}
+
+
Rejected
+
+
+
+ ${stats.totalFailed}
+
+
Failed
+
+
+
+ `;
+ }
+ } catch (e) {
+ console.warn('Error processing identity stats:', e);
+ }
+
+ const addressSection = `
+
+
Bid From Address:
+
+ ${address || identity.address || ''}
+
+
+ `;
return `
@@ -789,12 +875,7 @@ const createIdentityTooltipContent = (identity) => {
` : ''}
-
-
Bid From Address:
-
- ${identity.address || ''}
-
-
+ ${addressSection}
${identity.note ? `
@@ -803,41 +884,7 @@ const createIdentityTooltipContent = (identity) => {
` : ''}
-
-
Swap History:
-
-
-
- ${stats.successRate}%
-
-
Success Rate
-
-
-
${stats.totalBids}
-
Total Trades
-
-
-
-
-
- ${stats.totalSuccessful}
-
-
Successful
-
-
-
- ${stats.totalRejected}
-
-
Rejected
-
-
-
- ${stats.totalFailed}
-
-
Failed
-
-
-
+ ${statsSection}
`;
};
@@ -954,12 +1001,117 @@ const forceTooltipDOMCleanup = () => {
}
}
+async function fetchAllBids() {
+ try {
+ const sentController = new AbortController();
+ const receivedController = new AbortController();
+
+ const sentPromise = fetch('/json/sentbids', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ },
+ body: JSON.stringify({
+ sort_by: state.filters.sort_by || 'created_at',
+ sort_dir: state.filters.sort_dir || 'desc',
+ with_expired: true,
+ state: state.filters.state ?? -1,
+ with_extra_info: true
+ }),
+ signal: sentController.signal
+ });
+
+ const receivedPromise = fetch('/json/bids', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ },
+ body: JSON.stringify({
+ sort_by: state.filters.sort_by || 'created_at',
+ sort_dir: state.filters.sort_dir || 'desc',
+ with_expired: true,
+ state: state.filters.state ?? -1,
+ with_extra_info: true
+ }),
+ signal: receivedController.signal
+ });
+
+ const timeoutId = setTimeout(() => {
+ sentController.abort();
+ receivedController.abort();
+ }, 30000);
+
+ const [sentResponse, receivedResponse] = await Promise.all([sentPromise, receivedPromise]);
+
+ clearTimeout(timeoutId);
+
+ if (!sentResponse.ok || !receivedResponse.ok) {
+ throw new Error(`HTTP error! Sent status: ${sentResponse.status}, Received status: ${receivedResponse.status}`);
+ }
+
+ const [sentData, receivedData] = await Promise.all([
+ sentResponse.json(),
+ receivedResponse.json()
+ ]);
+
+ const filteredSentData = filterAndSortData(sentData).map(bid => ({ ...bid, source: 'sent' }));
+ const filteredReceivedData = filterAndSortData(receivedData).map(bid => ({ ...bid, source: 'received' }));
+
+ const combined = [...filteredSentData, ...filteredReceivedData].sort((a, b) => {
+ const direction = state.filters.sort_dir === 'asc' ? 1 : -1;
+ return direction * (a.created_at - b.created_at);
+ });
+
+ return combined;
+ } catch (error) {
+ console.error('Error fetching all bids:', error);
+ throw error;
+ }
+}
+
const createTableRow = async (bid) => {
- const identity = await IdentityManager.getIdentityData(bid.addr_from);
+ const rawAddress = bid.addr_from || '';
+ let identity = null;
+ try {
+ identity = await IdentityManager.getIdentityData(rawAddress);
+ } catch (e) {
+ console.warn('Error loading identity for bid:', bid.bid_id, e);
+ }
+ if (!identity) {
+ identity = { address: rawAddress, label: null };
+ } else if (!identity.address) {
+ identity.address = rawAddress;
+ }
const uniqueId = `${bid.bid_id}_${Date.now()}`;
tooltipIdsToCleanup.add(`tooltip-identity-${uniqueId}`);
tooltipIdsToCleanup.add(`tooltip-status-${uniqueId}`);
const timeColor = getTimeStrokeColor(bid.expire_at);
+ const currentTabIsAll = state.currentTab === 'all';
+ const isSent = currentTabIsAll ? (bid.source === 'sent') : (state.currentTab === 'sent');
+ const sourceIndicator = currentTabIsAll ?
+ `
+ ${isSent ? 'Sent' : 'Received'}
+ ` : '';
+ let tooltipContent = '';
+ try {
+ tooltipContent = createIdentityTooltipContent(identity);
+ } catch (e) {
+ console.warn('Error creating tooltip content:', e);
+ }
+ if (!tooltipContent) {
+ tooltipContent = `
+
+
+
Bid From Address:
+
+ ${rawAddress}
+
+
+
+ `;
+ }
return `
@@ -972,7 +1124,10 @@ const createTableRow = async (bid) => {
- ${formatTime(bid.created_at)}
+
+ ${formatTime(bid.created_at)}
+ ${sourceIndicator}
+
@@ -981,11 +1136,11 @@ const createTableRow = async (bid) => {
@@ -1001,12 +1156,12 @@ const createTableRow = async (bid) => {
- ${state.currentTab === 'sent' ? bid.amount_to : bid.amount_from}
- ${state.currentTab === 'sent' ? bid.coin_to : bid.coin_from}
+ ${isSent ? bid.amount_to : bid.amount_from}
+ ${isSent ? bid.coin_to : bid.coin_from}
|
@@ -1015,12 +1170,12 @@ const createTableRow = async (bid) => {
- ${state.currentTab === 'sent' ? bid.amount_from : bid.amount_to}
- ${state.currentTab === 'sent' ? bid.coin_from : bid.coin_to}
+ ${isSent ? bid.amount_from : bid.amount_to}
+ ${isSent ? bid.coin_from : bid.coin_to}
|
@@ -1045,10 +1200,9 @@ const createTableRow = async (bid) => {
-
@@ -1071,6 +1225,131 @@ const createTableRow = async (bid) => {
`;
};
+function cleanupOffscreenTooltips() {
+ if (!window.TooltipManager) return;
+
+ const selector = '#' + state.currentTab + ' [data-tooltip-target]';
+ const tooltipTriggers = document.querySelectorAll(selector);
+
+ const farOffscreenTriggers = Array.from(tooltipTriggers).filter(trigger => {
+ const rect = trigger.getBoundingClientRect();
+ return (rect.bottom < -window.innerHeight * 2 ||
+ rect.top > window.innerHeight * 3);
+ });
+
+ farOffscreenTriggers.forEach(trigger => {
+ const targetId = trigger.getAttribute('data-tooltip-target');
+ if (targetId) {
+ const tooltipElement = document.getElementById(targetId);
+ if (tooltipElement) {
+ window.TooltipManager.destroy(trigger);
+ trigger.addEventListener('mouseenter', () => {
+ createTooltipForTrigger(trigger);
+ }, { once: true });
+ }
+ }
+ });
+}
+
+function implementVirtualizedRows() {
+ const tbody = elements[`${state.currentTab}BidsBody`];
+ if (!tbody) return;
+
+ const tableRows = tbody.querySelectorAll('tr');
+ if (tableRows.length < 30) return;
+
+ Array.from(tableRows).forEach(row => {
+ const rect = row.getBoundingClientRect();
+ const isVisible = (
+ rect.bottom >= 0 &&
+ rect.top <= window.innerHeight
+ );
+
+ if (!isVisible && (rect.bottom < -window.innerHeight || rect.top > window.innerHeight * 2)) {
+ const tooltipTriggers = row.querySelectorAll('[data-tooltip-target]');
+ tooltipTriggers.forEach(trigger => {
+ if (window.TooltipManager) {
+ window.TooltipManager.destroy(trigger);
+ }
+ });
+ }
+ });
+}
+
+async function fetchBids(type = state.currentTab) {
+ if (type === 'all') {
+ return fetchAllBids();
+ }
+
+ try {
+ if (activeFetchController) {
+ activeFetchController.abort();
+ }
+ activeFetchController = new AbortController();
+ const endpoint = type === 'sent' ? '/json/sentbids' : '/json/bids';
+ const withExpiredSelect = document.getElementById('with_expired');
+ const includeExpired = withExpiredSelect ? withExpiredSelect.value === 'true' : true;
+
+ //console.log(`Fetching ${type} bids, include expired:`, includeExpired);
+
+ const timeoutId = setTimeout(() => {
+ if (activeFetchController) {
+ activeFetchController.abort();
+ }
+ }, 30000);
+
+ const response = await fetch(endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ },
+ body: JSON.stringify({
+ sort_by: state.filters.sort_by || 'created_at',
+ sort_dir: state.filters.sort_dir || 'desc',
+ with_expired: true,
+ state: state.filters.state ?? -1,
+ with_extra_info: true
+ }),
+ signal: activeFetchController.signal
+ });
+
+ clearTimeout(timeoutId);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ //console.log(`Received raw ${type} data:`, data.length, 'bids');
+
+ state.filters.with_expired = includeExpired;
+
+ let processedData;
+ if (data.length > 500) {
+ processedData = await new Promise(resolve => {
+ setTimeout(() => {
+ const filtered = filterAndSortData(data);
+ resolve(filtered);
+ }, 10);
+ });
+ } else {
+ processedData = filterAndSortData(data);
+ }
+
+ return processedData;
+ } catch (error) {
+ if (error.name === 'AbortError') {
+ console.log('Fetch request was aborted');
+ } else {
+ console.error(`Error in fetch${type.charAt(0).toUpperCase() + type.slice(1)}Bids:`, error);
+ }
+ throw error;
+ } finally {
+ activeFetchController = null;
+ }
+}
+
const updateTableContent = async (type) => {
const tbody = elements[`${type}BidsBody`];
if (!tbody) return;
@@ -1220,146 +1499,6 @@ const createTooltipForTrigger = (trigger) => {
}
};
-function optimizeForLargeDatasets() {
- if (state.data[state.currentTab]?.length > 50) {
-
- const simplifyTooltips = tooltipIdsToCleanup.size > 50;
-
- implementVirtualizedRows();
-
- let scrollTimeout;
- window.addEventListener('scroll', () => {
- clearTimeout(scrollTimeout);
- scrollTimeout = setTimeout(() => {
- cleanupOffscreenTooltips();
- }, 150);
- }, { passive: true });
- }
-}
-
-function cleanupOffscreenTooltips() {
- if (!window.TooltipManager) return;
-
- const selector = '#' + state.currentTab + ' [data-tooltip-target]';
- const tooltipTriggers = document.querySelectorAll(selector);
-
- const farOffscreenTriggers = Array.from(tooltipTriggers).filter(trigger => {
- const rect = trigger.getBoundingClientRect();
- return (rect.bottom < -window.innerHeight * 2 ||
- rect.top > window.innerHeight * 3);
- });
-
- farOffscreenTriggers.forEach(trigger => {
- const targetId = trigger.getAttribute('data-tooltip-target');
- if (targetId) {
- const tooltipElement = document.getElementById(targetId);
- if (tooltipElement) {
- window.TooltipManager.destroy(trigger);
- trigger.addEventListener('mouseenter', () => {
- createTooltipForTrigger(trigger);
- }, { once: true });
- }
- }
- });
-}
-
-function implementVirtualizedRows() {
- const tbody = elements[`${state.currentTab}BidsBody`];
- if (!tbody) return;
-
- const tableRows = tbody.querySelectorAll('tr');
- if (tableRows.length < 30) return;
-
- Array.from(tableRows).forEach(row => {
- const rect = row.getBoundingClientRect();
- const isVisible = (
- rect.bottom >= 0 &&
- rect.top <= window.innerHeight
- );
-
- if (!isVisible && (rect.bottom < -window.innerHeight || rect.top > window.innerHeight * 2)) {
- const tooltipTriggers = row.querySelectorAll('[data-tooltip-target]');
- tooltipTriggers.forEach(trigger => {
- if (window.TooltipManager) {
- window.TooltipManager.destroy(trigger);
- }
- });
- }
- });
-}
-
-let activeFetchController = null;
-
-const fetchBids = async () => {
- try {
- if (activeFetchController) {
- activeFetchController.abort();
- }
- activeFetchController = new AbortController();
- const endpoint = state.currentTab === 'sent' ? '/json/sentbids' : '/json/bids';
- const withExpiredSelect = document.getElementById('with_expired');
- const includeExpired = withExpiredSelect ? withExpiredSelect.value === 'true' : true;
-
- //console.log('Fetching bids, include expired:', includeExpired);
-
- const timeoutId = setTimeout(() => {
- if (activeFetchController) {
- activeFetchController.abort();
- }
- }, 30000);
-
- const response = await fetch(endpoint, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json'
- },
- body: JSON.stringify({
- sort_by: state.filters.sort_by || 'created_at',
- sort_dir: state.filters.sort_dir || 'desc',
- with_expired: true,
- state: state.filters.state ?? -1,
- with_extra_info: true
- }),
- signal: activeFetchController.signal
- });
-
- clearTimeout(timeoutId);
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- const data = await response.json();
- //console.log('Received raw data:', data.length, 'bids');
-
- state.filters.with_expired = includeExpired;
-
- let processedData;
- if (data.length > 500) {
- processedData = await new Promise(resolve => {
- setTimeout(() => {
- const filtered = filterAndSortData(data);
- resolve(filtered);
- }, 10);
- });
- } else {
- processedData = filterAndSortData(data);
- }
-
- return processedData;
- } catch (error) {
- if (error.name === 'AbortError') {
- console.log('Fetch request was aborted');
- } else {
- console.error('Error in fetchBids:', error);
- }
- throw error;
- } finally {
- activeFetchController = null;
- }
-};
-
const updateBidsTable = async () => {
if (state.isLoading) {
return;
@@ -1369,7 +1508,13 @@ const updateBidsTable = async () => {
state.isLoading = true;
updateLoadingState(true);
- const bids = await fetchBids();
+ let bids;
+
+ if (state.currentTab === 'all') {
+ bids = await fetchAllBids();
+ } else {
+ bids = await fetchBids();
+ }
// Add identity preloading if we're searching
if (state.filters.searchQuery && state.filters.searchQuery.length > 0) {
@@ -1416,7 +1561,11 @@ const updatePaginationControls = (type) => {
}
if (currentPageSpan) {
- currentPageSpan.textContent = totalPages > 0 ? state.currentPage[type] : 0;
+ if (totalPages > 0) {
+ currentPageSpan.innerHTML = `${state.currentPage[type]} of ${totalPages}`;
+ } else {
+ currentPageSpan.textContent = "0";
+ }
}
if (prevButton) {
@@ -1581,7 +1730,7 @@ function setupFilterEventListeners() {
}
const setupRefreshButtons = () => {
- ['Sent', 'Received'].forEach(type => {
+ ['All', 'Sent', 'Received'].forEach(type => {
const refreshButton = elements[`refresh${type}Bids`];
if (refreshButton) {
EventManager.add(refreshButton, 'click', async () => {
@@ -1597,30 +1746,35 @@ const setupRefreshButtons = () => {
state.isLoading = true;
updateLoadingState(true);
- const response = await fetch(state.currentTab === 'sent' ? '/json/sentbids' : '/json/bids', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- sort_by: state.filters.sort_by,
- sort_dir: state.filters.sort_dir,
- with_expired: state.filters.with_expired,
- state: state.filters.state,
- with_extra_info: true
- })
- });
+ if (lowerType === 'all') {
+ state.data.all = await fetchAllBids();
+ } else {
+ const response = await fetch(lowerType === 'sent' ? '/json/sentbids' : '/json/bids', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ sort_by: state.filters.sort_by,
+ sort_dir: state.filters.sort_dir,
+ with_expired: state.filters.with_expired,
+ state: state.filters.state,
+ with_extra_info: true
+ })
+ });
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data = await response.json();
+ if (!Array.isArray(data)) {
+ throw new Error('Invalid response format');
+ }
+
+ state.data[lowerType] = data;
}
-
- const data = await response.json();
- if (!Array.isArray(data)) {
- throw new Error('Invalid response format');
- }
-
- state.data[lowerType] = data;
+
await updateTableContent(lowerType);
updatePaginationControls(lowerType);
@@ -1648,8 +1802,10 @@ const switchTab = (tabId) => {
tooltipIdsToCleanup.clear();
- state.currentTab = tabId === '#sent' ? 'sent' : 'received';
+ state.currentTab = tabId === '#all' ? 'all' :
+ (tabId === '#sent' ? 'sent' : 'received');
+ elements.allContent.classList.add('hidden');
elements.sentContent.classList.add('hidden');
elements.receivedContent.classList.add('hidden');
@@ -1669,11 +1825,31 @@ const switchTab = (tabId) => {
tab.classList.add('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
}
});
+
setTimeout(() => {
updateBidsTable();
}, 10);
};
+window.switchTab = switchTab;
+
+function optimizeForLargeDatasets() {
+ if (state.data[state.currentTab]?.length > 50) {
+
+ const simplifyTooltips = tooltipIdsToCleanup.size > 50;
+
+ implementVirtualizedRows();
+
+ let scrollTimeout;
+ window.addEventListener('scroll', () => {
+ clearTimeout(scrollTimeout);
+ scrollTimeout = setTimeout(() => {
+ cleanupOffscreenTooltips();
+ }, 150);
+ }, { passive: true });
+ }
+}
+
const setupEventListeners = () => {
const filterControls = document.querySelector('.flex.flex-wrap.justify-center');
if (filterControls) {
@@ -1708,10 +1884,12 @@ const setupEventListeners = () => {
}
});
+ elements.allContent.classList.toggle('hidden', targetId !== '#all');
elements.sentContent.classList.toggle('hidden', targetId !== '#sent');
elements.receivedContent.classList.toggle('hidden', targetId !== '#received');
- state.currentTab = targetId === '#sent' ? 'sent' : 'received';
+ state.currentTab = targetId === '#all' ? 'all' :
+ (targetId === '#sent' ? 'sent' : 'received');
state.currentPage[state.currentTab] = 1;
if (window.TooltipManager) {
@@ -1724,7 +1902,7 @@ const setupEventListeners = () => {
});
}
- ['Sent', 'Received'].forEach(type => {
+ ['All', 'Sent', 'Received'].forEach(type => {
const lowerType = type.toLowerCase();
if (elements[`prevPage${type}`]) {
@@ -1863,6 +2041,11 @@ function setupMemoryMonitoring() {
window.TooltipManager.cleanup();
}
+ if (state.data.all.length > 1000) {
+ console.log('Trimming all bids data');
+ state.data.all = state.data.all.slice(0, 1000);
+ }
+
if (state.data.sent.length > 1000) {
console.log('Trimming sent bids data');
state.data.sent = state.data.sent.slice(0, 1000);
@@ -1912,11 +2095,13 @@ function initialize() {
setTimeout(() => {
updateClearFiltersButton();
- state.currentTab = 'sent';
+ state.currentTab = 'all';
state.filters.state = -1;
updateBidsTable();
}, 100);
+ setupMemoryMonitoring();
+
window.cleanupBidsTable = cleanup;
}
@@ -1925,74 +2110,3 @@ if (document.readyState === 'loading') {
} else {
initialize();
}
-
-(function() {
- function handleBidsTabFromHash() {
- if (window.location.pathname !== '/bids') {
- return;
- }
-
- const hash = window.location.hash;
-
- if (hash) {
- const tabName = hash.substring(1);
- let tabId;
- switch (tabName.toLowerCase()) {
- case 'sent':
- tabId = '#sent';
- break;
- case 'received':
- tabId = '#received';
- break;
- default:
- tabId = '#sent';
- }
- switchTab(tabId);
- } else {
- switchTab('#sent');
- }
- }
-
- function switchTab(tabId) {
- const targetTabBtn = document.querySelector(`[data-tabs-target="${tabId}"]`);
- if (targetTabBtn) {
- targetTabBtn.click();
- }
- }
-
- function setupBidsTabNavigation() {
- handleBidsTabFromHash();
- window.addEventListener('hashchange', handleBidsTabFromHash);
- const originalSwitchTab = window.switchTab || null;
-
- window.switchTab = function(tabId) {
- const newTabName = tabId.replace('#', '');
- if (window.location.hash !== `#${newTabName}`) {
- history.replaceState(null, null, `#${newTabName}`);
- }
- if (originalSwitchTab && typeof originalSwitchTab === 'function') {
- originalSwitchTab(tabId);
- } else {
- const targetTabBtn = document.querySelector(`[data-tabs-target="${tabId}"]`);
- if (targetTabBtn) {
- targetTabBtn.click();
- }
- }
- };
-
- const tabButtons = document.querySelectorAll('[data-tabs-target]');
- tabButtons.forEach(btn => {
- btn.addEventListener('click', function() {
- const tabId = this.getAttribute('data-tabs-target');
- const tabName = tabId.replace('#', '');
- history.replaceState(null, null, `#${tabName}`);
- });
- });
- }
-
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', setupBidsTabNavigation);
- } else {
- setupBidsTabNavigation();
- }
-})();
diff --git a/basicswap/static/js/bids_sentreceived_export.js b/basicswap/static/js/bids_sentreceived_export.js
index acee851..3021267 100644
--- a/basicswap/static/js/bids_sentreceived_export.js
+++ b/basicswap/static/js/bids_sentreceived_export.js
@@ -4,17 +4,18 @@ const BidExporter = {
return 'No data to export';
}
- const isSent = type === 'sent';
+ const isAllTab = type === 'all';
const headers = [
'Date/Time',
'Bid ID',
'Offer ID',
'From Address',
- isSent ? 'You Send Amount' : 'You Receive Amount',
- isSent ? 'You Send Coin' : 'You Receive Coin',
- isSent ? 'You Receive Amount' : 'You Send Amount',
- isSent ? 'You Receive Coin' : 'You Send Coin',
+ ...(isAllTab ? ['Type'] : []),
+ 'You Send Amount',
+ 'You Send Coin',
+ 'You Receive Amount',
+ 'You Receive Coin',
'Status',
'Created At',
'Expires At'
@@ -23,11 +24,13 @@ const BidExporter = {
let csvContent = headers.join(',') + '\n';
bids.forEach(bid => {
+ const isSent = isAllTab ? (bid.source === 'sent') : (type === 'sent');
const row = [
`"${formatTime(bid.created_at)}"`,
`"${bid.bid_id}"`,
`"${bid.offer_id}"`,
`"${bid.addr_from}"`,
+ ...(isAllTab ? [`"${bid.source}"`] : []),
isSent ? bid.amount_from : bid.amount_to,
`"${isSent ? bid.coin_from : bid.coin_to}"`,
isSent ? bid.amount_to : bid.amount_from,
@@ -103,6 +106,15 @@ const BidExporter = {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
if (typeof state !== 'undefined' && typeof EventManager !== 'undefined') {
+ const exportAllButton = document.getElementById('exportAllBids');
+ if (exportAllButton) {
+ EventManager.add(exportAllButton, 'click', (e) => {
+ e.preventDefault();
+ state.currentTab = 'all';
+ BidExporter.exportCurrentView();
+ });
+ }
+
const exportSentButton = document.getElementById('exportSentBids');
if (exportSentButton) {
EventManager.add(exportSentButton, 'click', (e) => {
@@ -128,9 +140,14 @@ const originalCleanup = window.cleanup || function(){};
window.cleanup = function() {
originalCleanup();
+ const exportAllButton = document.getElementById('exportAllBids');
const exportSentButton = document.getElementById('exportSentBids');
const exportReceivedButton = document.getElementById('exportReceivedBids');
+ if (exportAllButton && typeof EventManager !== 'undefined') {
+ EventManager.remove(exportAllButton, 'click');
+ }
+
if (exportSentButton && typeof EventManager !== 'undefined') {
EventManager.remove(exportSentButton, 'click');
}
diff --git a/basicswap/static/js/ui/bids-tab-navigation.js b/basicswap/static/js/ui/bids-tab-navigation.js
index d1acb93..c88d732 100644
--- a/basicswap/static/js/ui/bids-tab-navigation.js
+++ b/basicswap/static/js/ui/bids-tab-navigation.js
@@ -1,36 +1,39 @@
(function() {
'use strict';
+ const originalOnload = window.onload;
- document.addEventListener('DOMContentLoaded', initBidsTabNavigation);
- window.addEventListener('load', handleHashChange);
- window.addEventListener('hashchange', preventScrollOnHashChange);
+ window.onload = function() {
+ if (typeof originalOnload === 'function') {
+ originalOnload();
+ }
+
+ setTimeout(function() {
+ initBidsTabNavigation();
+ handleInitialNavigation();
+ }, 100);
+ };
+
+ document.addEventListener('DOMContentLoaded', function() {
+ initBidsTabNavigation();
+ });
+
+ window.addEventListener('hashchange', handleHashChange);
+
+ window.bidsTabNavigationInitialized = false;
function initBidsTabNavigation() {
- const sentTabButton = document.getElementById('sent-tab');
- const receivedTabButton = document.getElementById('received-tab');
-
- if (!sentTabButton || !receivedTabButton) {
+ if (window.bidsTabNavigationInitialized) {
return;
}
-
+
document.querySelectorAll('.bids-tab-link').forEach(link => {
link.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
-
const targetTabId = this.getAttribute('data-tab-target');
-
if (targetTabId) {
if (window.location.pathname === '/bids') {
- const oldScrollPosition = window.scrollY;
-
- activateTab(targetTabId);
-
- setTimeout(function() {
- window.scrollTo(0, oldScrollPosition);
-
- history.replaceState(null, null, '#' + targetTabId.replace('#', ''));
- }, 0);
+ navigateToTabDirectly(targetTabId);
} else {
localStorage.setItem('bidsTabToActivate', targetTabId.replace('#', ''));
window.location.href = '/bids';
@@ -39,35 +42,28 @@
});
});
- const tabToActivate = localStorage.getItem('bidsTabToActivate');
- if (tabToActivate) {
- localStorage.removeItem('bidsTabToActivate');
- activateTab('#' + tabToActivate);
- } else if (window.location.pathname === '/bids' && !window.location.hash) {
- activateTab('#sent');
- }
+ window.bidsTabNavigationInitialized = true;
+ console.log('Bids tab navigation initialized');
}
- function preventScrollOnHashChange(e) {
+ function handleInitialNavigation() {
if (window.location.pathname !== '/bids') {
return;
}
+
+ const tabToActivate = localStorage.getItem('bidsTabToActivate');
- e.preventDefault();
-
- const oldScrollPosition = window.scrollY;
- const hash = window.location.hash;
-
- if (hash) {
- const tabId = `#${hash.replace('#', '')}`;
- activateTab(tabId);
+ if (tabToActivate) {
+ //console.log('Activating tab from localStorage:', tabToActivate);
+ localStorage.removeItem('bidsTabToActivate');
+ activateTabWithRetry('#' + tabToActivate);
+ } else if (window.location.hash) {
+ //console.log('Activating tab from hash:', window.location.hash);
+ activateTabWithRetry(window.location.hash);
} else {
- activateTab('#sent');
+ //console.log('Activating default tab: #all');
+ activateTabWithRetry('#all');
}
-
- setTimeout(function() {
- window.scrollTo(0, oldScrollPosition);
- }, 0);
}
function handleHashChange() {
@@ -75,50 +71,141 @@
return;
}
- const oldScrollPosition = window.scrollY;
const hash = window.location.hash;
-
if (hash) {
- const tabId = `#${hash.replace('#', '')}`;
- activateTab(tabId);
+ //console.log('Hash changed, activating tab:', hash);
+ activateTabWithRetry(hash);
} else {
- activateTab('#sent');
+ //console.log('Hash cleared, activating default tab: #all');
+ activateTabWithRetry('#all');
}
+ }
+
+ function activateTabWithRetry(tabId, retryCount = 0) {
+ const normalizedTabId = tabId.startsWith('#') ? tabId : '#' + tabId;
+
+ if (normalizedTabId !== '#all' && normalizedTabId !== '#sent' && normalizedTabId !== '#received') {
+ //console.log('Invalid tab ID, defaulting to #all');
+ activateTabWithRetry('#all');
+ return;
+ }
+
+ const tabButtonId = normalizedTabId === '#all' ? 'all-tab' :
+ (normalizedTabId === '#sent' ? 'sent-tab' : 'received-tab');
+ const tabButton = document.getElementById(tabButtonId);
+
+ if (!tabButton) {
+ if (retryCount < 5) {
+ //console.log('Tab button not found, retrying...', retryCount + 1);
+ setTimeout(() => {
+ activateTabWithRetry(normalizedTabId, retryCount + 1);
+ }, 100);
+ } else {
+ //console.error('Failed to find tab button after retries');
+ }
+ return;
+ }
+
+ //console.log('Activating tab:', normalizedTabId);
+
+ tabButton.click();
+
+ if (window.Tabs) {
+ const tabsEl = document.querySelector('[data-tabs-toggle="#bidstab"]');
+ if (tabsEl) {
+ const allTabs = Array.from(tabsEl.querySelectorAll('[role="tab"]'));
+ const targetTab = allTabs.find(tab => tab.getAttribute('data-tabs-target') === normalizedTabId);
+
+ if (targetTab) {
+
+ allTabs.forEach(tab => {
+ tab.setAttribute('aria-selected', tab === targetTab ? 'true' : 'false');
+
+ if (tab === targetTab) {
+ tab.classList.add('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
+ tab.classList.remove('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
+ } else {
+ tab.classList.remove('bg-gray-100', 'dark:bg-gray-600', 'text-gray-900', 'dark:text-white');
+ tab.classList.add('hover:text-gray-600', 'hover:bg-gray-50', 'dark:hover:bg-gray-500');
+ }
+ });
+
+ const allContent = document.getElementById('all');
+ const sentContent = document.getElementById('sent');
+ const receivedContent = document.getElementById('received');
+
+ if (allContent && sentContent && receivedContent) {
+ allContent.classList.toggle('hidden', normalizedTabId !== '#all');
+ sentContent.classList.toggle('hidden', normalizedTabId !== '#sent');
+ receivedContent.classList.toggle('hidden', normalizedTabId !== '#received');
+ }
+ }
+ }
+ }
+
+ const allPanel = document.getElementById('all');
+ const sentPanel = document.getElementById('sent');
+ const receivedPanel = document.getElementById('received');
+
+ if (allPanel && sentPanel && receivedPanel) {
+ allPanel.classList.toggle('hidden', normalizedTabId !== '#all');
+ sentPanel.classList.toggle('hidden', normalizedTabId !== '#sent');
+ receivedPanel.classList.toggle('hidden', normalizedTabId !== '#received');
+ }
+
+ const newHash = normalizedTabId.replace('#', '');
+ if (window.location.hash !== '#' + newHash) {
+ history.replaceState(null, null, '#' + newHash);
+ }
+
+ triggerDataLoad(normalizedTabId);
+ }
+
+ function triggerDataLoad(tabId) {
+ setTimeout(() => {
+ if (window.state) {
+ window.state.currentTab = tabId === '#all' ? 'all' :
+ (tabId === '#sent' ? 'sent' : 'received');
+
+ if (typeof window.updateBidsTable === 'function') {
+ //console.log('Triggering data load for', tabId);
+ window.updateBidsTable();
+ }
+ }
+
+ const event = new CustomEvent('tabactivated', {
+ detail: {
+ tabId: tabId,
+ type: tabId === '#all' ? 'all' :
+ (tabId === '#sent' ? 'sent' : 'received')
+ }
+ });
+ document.dispatchEvent(event);
+
+ if (window.TooltipManager && typeof window.TooltipManager.cleanup === 'function') {
+ setTimeout(() => {
+ window.TooltipManager.cleanup();
+ if (typeof window.initializeTooltips === 'function') {
+ window.initializeTooltips();
+ }
+ }, 200);
+ }
+ }, 100);
+ }
+
+ function navigateToTabDirectly(tabId) {
+ const oldScrollPosition = window.scrollY;
+
+ activateTabWithRetry(tabId);
setTimeout(function() {
window.scrollTo(0, oldScrollPosition);
}, 0);
}
- function activateTab(tabId) {
- if (tabId !== '#sent' && tabId !== '#received') {
- tabId = '#sent';
- }
-
- const tabButtonId = tabId === '#sent' ? 'sent-tab' : 'received-tab';
- const tabButton = document.getElementById(tabButtonId);
-
- if (tabButton) {
- const oldScrollPosition = window.scrollY;
-
- tabButton.click();
-
- setTimeout(function() {
- window.scrollTo(0, oldScrollPosition);
- }, 0);
- }
- }
-
window.navigateToBidsTab = function(tabId) {
if (window.location.pathname === '/bids') {
- const oldScrollPosition = window.scrollY;
-
- activateTab('#' + (tabId === 'sent' || tabId === 'received' ? tabId : 'sent'));
-
- setTimeout(function() {
- window.scrollTo(0, oldScrollPosition);
- history.replaceState(null, null, '#' + tabId);
- }, 0);
+ navigateToTabDirectly('#' + tabId);
} else {
localStorage.setItem('bidsTabToActivate', tabId);
window.location.href = '/bids';
diff --git a/basicswap/templates/bids.html b/basicswap/templates/bids.html
index 3cd6ebc..2bd172a 100644
--- a/basicswap/templates/bids.html
+++ b/basicswap/templates/bids.html
@@ -10,7 +10,7 @@
-
Sent Bids / Received Bids
+
All Bids / Sent Bids / Received Bids
View, and manage bids.
@@ -28,7 +28,12 @@
-
-
+ -
+
Sent Bids ({{ sent_bids_count }})
@@ -167,7 +172,106 @@
-
+
+
+
+
+
+
+
+
+
+
+ |
+
+ Date/Time
+
+ |
+
+
+ Details
+
+ |
+
+
+ You Send
+
+ |
+
+
+ You Receive
+
+ |
+
+
+ Status
+
+ |
+
+
+ Actions
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ Connecting...
+
+
+ All Bids: 0
+
+ {% if debug_ui_mode == true %}
+
+
+ Refresh
+
+ {% endif %}
+
+
+
+ Export CSV
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -208,7 +312,7 @@
-
+
@@ -264,6 +368,7 @@
+