diff --git a/basicswap/static/js/offerstable.js b/basicswap/static/js/offerstable.js index ccb62fc..c48eb32 100644 --- a/basicswap/static/js/offerstable.js +++ b/basicswap/static/js/offerstable.js @@ -268,7 +268,7 @@ function getTimeUntilNextExpiration() { return timeUntilExpiration > 0 && timeUntilExpiration < earliest ? timeUntilExpiration : earliest; }, Infinity); - return nextExpiration === Infinity ? 300 : Math.max(MIN_REFRESH_INTERVAL, Math.min(nextExpiration, 300)); + return Math.max(MIN_REFRESH_INTERVAL, Math.min(nextExpiration, 300)); } function getNoOffersMessage() { @@ -393,30 +393,34 @@ window.tableRateModule = { } }; -function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount) { +function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) { const profitLossElement = row.querySelector('.profit-loss'); if (!profitLossElement) { console.warn('Profit loss element not found in row'); return; } - calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount) - .then(profitLossPercentage => { - if (profitLossPercentage === null) { + calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) + .then(percentDiff => { + if (percentDiff === null) { profitLossElement.textContent = 'N/A'; profitLossElement.className = 'profit-loss text-lg font-bold text-gray-500'; console.log(`Unable to calculate profit/loss for ${fromCoin} to ${toCoin}`); return; } - const colorClass = getProfitColorClass(profitLossPercentage); - profitLossElement.textContent = `${profitLossPercentage > 0 ? '+' : ''}${profitLossPercentage}%`; + const formattedPercentDiff = percentDiff.toFixed(2); + const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" : + (percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff); + + const colorClass = getProfitColorClass(percentDiff); + profitLossElement.textContent = `${percentDiffDisplay}%`; profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`; const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`; const tooltipElement = document.getElementById(tooltipId); if (tooltipElement) { - const tooltipContent = createTooltipContent(isSentOffers, fromCoin, toCoin, fromAmount, toAmount); + const tooltipContent = createTooltipContent(isOwnOffer, fromCoin, toCoin, fromAmount, toAmount); tooltipElement.innerHTML = `
${tooltipContent} @@ -425,7 +429,7 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount) { `; } - console.log(`Updated profit/loss display: ${profitLossElement.textContent}, isSentOffers: ${isSentOffers}`); + console.log(`Updated profit/loss display: ${profitLossElement.textContent}, isOwnOffer: ${isOwnOffer}`); }) .catch(error => { console.error('Error in updateProfitLoss:', error); @@ -692,78 +696,50 @@ function filterAndSortData() { return filteredData; } -async function updateOffersTable() { - console.log('Starting updateOffersTable function'); - console.log(`Is Sent Offers page: ${isSentOffers}`); - - try { - const priceData = await fetchLatestPrices(); - if (!priceData) { - console.error('Failed to fetch latest prices. Using last known prices or proceeding without price data.'); - } else { - console.log('Latest prices fetched successfully'); - latestPrices = priceData; - } +function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) { + const profitLossElement = row.querySelector('.profit-loss'); + if (!profitLossElement) { + console.warn('Profit loss element not found in row'); + return; + } - let validOffers = getValidOffers(); - console.log(`Valid offers: ${validOffers.length}`); + calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) + .then(percentDiff => { + if (percentDiff === null) { + profitLossElement.textContent = 'N/A'; + profitLossElement.className = 'profit-loss text-lg font-bold text-gray-500'; + console.log(`Unable to calculate profit/loss for ${fromCoin} to ${toCoin}`); + return; + } - if (validOffers.length === 0) { - console.log('No valid offers found. Handling no offers scenario.'); - handleNoOffersScenario(); - return; - } + // Format the percentage to two decimal places + const formattedPercentDiff = percentDiff.toFixed(2); + const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" : + (percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff); - const totalPages = Math.max(1, Math.ceil(validOffers.length / itemsPerPage)); - currentPage = Math.min(currentPage, totalPages); + const colorClass = getProfitColorClass(percentDiff); + profitLossElement.textContent = `${percentDiffDisplay}%`; + profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`; + + const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`; + const tooltipElement = document.getElementById(tooltipId); + if (tooltipElement) { + const tooltipContent = createTooltipContent(isOwnOffer, fromCoin, toCoin, fromAmount, toAmount); + tooltipElement.innerHTML = ` +
+ ${tooltipContent} +
+
+ `; + } - const startIndex = (currentPage - 1) * itemsPerPage; - const endIndex = startIndex + itemsPerPage; - const itemsToDisplay = validOffers.slice(startIndex, endIndex); - - console.log(`Displaying offers ${startIndex + 1} to ${endIndex} of ${validOffers.length}`); - - offersBody.innerHTML = ''; - - for (const offer of itemsToDisplay) { - const row = createTableRow(offer, isSentOffers); - if (row) { - offersBody.appendChild(row); - try { - const fromAmount = parseFloat(offer.amount_from); - const toAmount = parseFloat(offer.amount_to); - await updateProfitLoss(row, offer.coin_from, offer.coin_to, fromAmount, toAmount); - } catch (error) { - console.error(`Error updating profit/loss for offer ${offer.offer_id}:`, error); - } - } - } - - updateRowTimes(); - initializeFlowbiteTooltips(); - updatePaginationInfo(); - - if (tableRateModule && typeof tableRateModule.initializeTable === 'function') { - tableRateModule.initializeTable(); - } - - logOfferStatus(); - - lastRefreshTime = Date.now(); - nextRefreshCountdown = getTimeUntilNextExpiration(); - updateLastRefreshTime(); - updateNextRefreshTime(); - - if (newEntriesCountSpan) { - newEntriesCountSpan.textContent = validOffers.length; - } - - } catch (error) { - console.error('Error updating offers table:', error); - offersBody.innerHTML = `An error occurred while updating the offers table. Please try again later.`; - } finally { - setRefreshButtonLoading(false); - } + console.log(`Updated profit/loss display: ${profitLossElement.textContent}, isOwnOffer: ${isOwnOffer}`); + }) + .catch(error => { + console.error('Error in updateProfitLoss:', error); + profitLossElement.textContent = 'Error'; + profitLossElement.className = 'profit-loss text-lg font-bold text-red-500'; + }); } function createTableRow(offer, isSentOffers) { @@ -771,8 +747,12 @@ function createTableRow(offer, isSentOffers) { row.className = `opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600`; row.setAttribute('data-offer-id', offer.offer_id); - const coinFrom = symbolToCoinName[coinNameToSymbol[offer.coin_from]] || offer.coin_from; - const coinTo = symbolToCoinName[coinNameToSymbol[offer.coin_to]] || offer.coin_to; + const coinFrom = offer.coin_from ? (symbolToCoinName[coinNameToSymbol[offer.coin_from]] || offer.coin_from) : 'Unknown'; + const coinTo = offer.coin_to ? (symbolToCoinName[coinNameToSymbol[offer.coin_to]] || offer.coin_to) : 'Unknown'; + + if (coinFrom === 'Unknown' || coinTo === 'Unknown') { + console.warn(`Invalid coin data for offer ${offer.offer_id}: coinFrom=${coinFrom}, coinTo=${coinTo}`); + } const postedTime = formatTimeAgo(offer.created_at); const expiresIn = formatTimeLeft(offer.expire_at); @@ -780,7 +760,13 @@ function createTableRow(offer, isSentOffers) { const currentTime = Math.floor(Date.now() / 1000); const isActuallyExpired = currentTime > offer.expire_at; - const { buttonClass, buttonText } = getButtonProperties(isActuallyExpired, isSentOffers, offer.is_own_offer, offer.is_revoked); + // Determine if this offer should be treated as a sent offer + const isOwnOffer = offer.is_own_offer; + + const { buttonClass, buttonText } = getButtonProperties(isActuallyExpired, isSentOffers, isOwnOffer); + + const fromAmount = parseFloat(offer.amount_from) || 0; + const toAmount = parseFloat(offer.amount_to) || 0; row.innerHTML = ` ${createTimeColumn(offer, postedTime, expiresIn)} @@ -791,12 +777,10 @@ function createTableRow(offer, isSentOffers) { ${createRateColumn(offer, coinFrom, coinTo)} ${createPercentageColumn(offer)} ${createActionColumn(offer, buttonClass, buttonText)} - ${createTooltips(offer, isSentOffers, coinFrom, coinTo, postedTime, expiresIn, isActuallyExpired)} + ${createTooltips(offer, isOwnOffer, coinFrom, coinTo, fromAmount, toAmount, postedTime, expiresIn, isActuallyExpired)} `; - const fromAmount = parseFloat(offer.amount_from); - const toAmount = parseFloat(offer.amount_to); - updateProfitLoss(row, coinFrom, coinTo, fromAmount, toAmount, isSentOffers); + updateProfitLoss(row, coinFrom, coinTo, fromAmount, toAmount, isOwnOffer); return row; } @@ -847,30 +831,116 @@ function getButtonProperties(isActuallyExpired, isSentOffers, isTreatedAsSentOff } } -function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount) { +async function updateOffersTable() { + console.log('Starting updateOffersTable function'); + console.log(`Is Sent Offers page: ${isSentOffers}`); + + try { + const priceData = await fetchLatestPrices(); + if (!priceData) { + console.error('Failed to fetch latest prices. Using last known prices or proceeding without price data.'); + } else { + console.log('Latest prices fetched successfully'); + latestPrices = priceData; + } + + let validOffers = getValidOffers(); + console.log(`Valid offers: ${validOffers.length}`); + + if (validOffers.length === 0) { + console.log('No valid offers found. Handling no offers scenario.'); + handleNoOffersScenario(); + return; + } + + const totalPages = Math.max(1, Math.ceil(validOffers.length / itemsPerPage)); + currentPage = Math.min(currentPage, totalPages); + + const startIndex = (currentPage - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const itemsToDisplay = validOffers.slice(startIndex, endIndex); + + console.log(`Displaying offers ${startIndex + 1} to ${endIndex} of ${validOffers.length}`); + + offersBody.innerHTML = ''; + + for (const offer of itemsToDisplay) { + const row = createTableRow(offer, isSentOffers); + if (row) { + offersBody.appendChild(row); + try { + const fromAmount = parseFloat(offer.amount_from); + const toAmount = parseFloat(offer.amount_to); + await updateProfitLoss(row, offer.coin_from, offer.coin_to, fromAmount, toAmount, offer.is_own_offer); + } catch (error) { + console.error(`Error updating profit/loss for offer ${offer.offer_id}:`, error); + } + } + } + + updateRowTimes(); + initializeFlowbiteTooltips(); + updatePaginationInfo(); + + if (tableRateModule && typeof tableRateModule.initializeTable === 'function') { + tableRateModule.initializeTable(); + } + + logOfferStatus(); + + lastRefreshTime = Date.now(); + nextRefreshCountdown = getTimeUntilNextExpiration(); + updateLastRefreshTime(); + updateNextRefreshTime(); + + if (newEntriesCountSpan) { + newEntriesCountSpan.textContent = validOffers.length; + } + + } catch (error) { + console.error('Error updating offers table:', error); + offersBody.innerHTML = `An error occurred while updating the offers table. Please try again later.`; + } finally { + setRefreshButtonLoading(false); + } +} + +function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) { const profitLossElement = row.querySelector('.profit-loss'); if (!profitLossElement) { console.warn('Profit loss element not found in row'); return; } - calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount) - .then(profitLossPercentage => { - if (profitLossPercentage === null) { + if (!fromCoin || !toCoin) { + console.error(`Invalid coin names: fromCoin=${fromCoin}, toCoin=${toCoin}`); + profitLossElement.textContent = 'Error'; + profitLossElement.className = 'profit-loss text-lg font-bold text-red-500'; + return; + } + + calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) + .then(percentDiff => { + if (percentDiff === null) { profitLossElement.textContent = 'N/A'; - profitLossElement.className = 'profit-loss text-lg font-bold text-white'; + profitLossElement.className = 'profit-loss text-lg font-bold text-gray-500'; console.log(`Unable to calculate profit/loss for ${fromCoin} to ${toCoin}`); return; } - const colorClass = getProfitColorClass(profitLossPercentage); - profitLossElement.textContent = `${profitLossPercentage > 0 ? '+' : ''}${profitLossPercentage}%`; - profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`; + // Format the percentage to two decimal places + const formattedPercentDiff = percentDiff.toFixed(2); + const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" : + (percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff); + const colorClass = getProfitColorClass(percentDiff); + profitLossElement.textContent = `${percentDiffDisplay}%`; + profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`; + const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`; const tooltipElement = document.getElementById(tooltipId); if (tooltipElement) { - const tooltipContent = createTooltipContent(isSentOffers, fromCoin, toCoin, fromAmount, toAmount); + const tooltipContent = createTooltipContent(isSentOffers || isOwnOffer, fromCoin, toCoin, fromAmount, toAmount); tooltipElement.innerHTML = `
${tooltipContent} @@ -879,7 +949,7 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount) { `; } - console.log(`Updated profit/loss display: ${profitLossElement.textContent}, isSentOffers: ${isSentOffers}`); + console.log(`Updated profit/loss display: ${profitLossElement.textContent}, isSentOffers: ${isSentOffers}, isOwnOffer: ${isOwnOffer}`); }) .catch(error => { console.error('Error in updateProfitLoss:', error); @@ -888,8 +958,8 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount) { }); } -async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount) { - console.log(`Calculating profit/loss for ${fromAmount} ${fromCoin} to ${toAmount} ${toCoin}, isSentOffers: ${isSentOffers}`); +async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwnOffer) { + console.log(`Calculating profit/loss for ${fromAmount} ${fromCoin} to ${toAmount} ${toCoin}, isOwnOffer: ${isOwnOffer}`); if (!latestPrices) { console.error('Latest prices not available. Unable to calculate profit/loss.'); @@ -910,20 +980,18 @@ async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount) { const fromValueUSD = fromAmount * fromPriceUSD; const toValueUSD = toAmount * toPriceUSD; - let profitPercentage; - - if (isSentOffers) { - // Sent Offer - profitPercentage = ((toValueUSD / fromValueUSD) - 1) * 100; + let percentDiff; + if (isOwnOffer) { + // For sent offers or own offers, always calculate as if selling + percentDiff = ((toValueUSD / fromValueUSD) - 1) * 100; } else { - // Offer Page - profitPercentage = ((fromValueUSD / toValueUSD) - 1) * 100; + // For received offers, calculate savings percentage + percentDiff = ((fromValueUSD / toValueUSD) - 1) * 100; } - console.log(`From value: $${fromValueUSD.toFixed(2)}, To value: $${toValueUSD.toFixed(2)}`); - console.log(`Profit percentage: ${profitPercentage.toFixed(2)}%, isSentOffers: ${isSentOffers}`); + console.log(`Percent difference: ${percentDiff.toFixed(2)}%`); - return profitPercentage.toFixed(2); + return percentDiff; } function getProfitColorClass(percentage) { @@ -955,30 +1023,17 @@ function getMarketRate(fromCoin, toCoin) { }); } -// todo function getTimerColor(offer) { const now = Math.floor(Date.now() / 1000); - const offerAge = now - offer.created_at; - const offerLifespan = offer.expire_at - offer.created_at; const timeLeft = offer.expire_at - now; - // New listing: - if (offerAge < offerLifespan * 0.1) { - return "#10B981"; // Green + if (timeLeft <= 300) { // 5 minutes or less + return "#9CA3AF"; // Grey + } else if (timeLeft <= 1800) { // 5-30 minutes + return "#3B82F6"; // Blue + } else { // More than 30 minutes + return "#10B981"; // Green/Turquoise } - - // Almost expired: - if (timeLeft < offerLifespan * 0.1) { - return "#9CA3AF"; // Gray - } - - // Fade from green to blue to gray - const bluePhase = (offerAge - offerLifespan * 0.1) / (offerLifespan * 0.8); - const r = Math.round(16 + (59 - 16) * bluePhase); - const g = Math.round(185 + (130 - 185) * bluePhase); - const b = Math.round(129 + (246 - 129) * bluePhase); - - return `rgb(${r}, ${g}, ${b})`; } function createTimeColumn(offer, postedTime, expiresIn) { @@ -1137,60 +1192,88 @@ function createActionColumn(offer, buttonClass, buttonText) { `; } -function createTooltips(offer, isSentOffers, coinFrom, coinTo, postedTime, expiresIn, isActuallyExpired) { +function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount, postedTime, expiresIn, isActuallyExpired) { const rate = parseFloat(offer.rate); - const fromSymbol = coinNameToSymbol[coinFrom] || coinFrom.toLowerCase(); - const toSymbol = coinNameToSymbol[coinTo] || coinTo.toLowerCase(); + const fromSymbol = getCoinSymbolLowercase(coinFrom); + const toSymbol = getCoinSymbolLowercase(coinTo); const fromPriceUSD = latestPrices[fromSymbol]?.usd || 0; const toPriceUSD = latestPrices[toSymbol]?.usd || 0; const rateInUSD = rate * toPriceUSD; - const combinedRateTooltip = createCombinedRateTooltip(offer, coinFrom, coinTo); + const combinedRateTooltip = createCombinedRateTooltip(offer, coinFrom, coinTo, treatAsSentOffer); - const fromAmount = parseFloat(offer.amount_from); - const toAmount = parseFloat(offer.amount_to); - const percentageTooltipContent = createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmount); + const percentageTooltipContent = createTooltipContent(treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount); return ` - + -