mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 18:38:09 +01:00
GUI: Multi-select coin filtering / Various fixes. (#327)
* GUI: Multi-select coin filtering / Various fixes. * Use coin-manager / clean-up. * Fix BCH in filters + fix UX with bid pages modals when amount is empty. * Fix amount not empty. * Abandon Bid under debug_ui
This commit is contained in:
@@ -11349,6 +11349,11 @@ class BasicSwap(BaseApp, UIApp):
|
|||||||
if filter_include_sent is not None and filter_include_sent is not True:
|
if filter_include_sent is not None and filter_include_sent is not True:
|
||||||
query_suffix += " AND was_sent = 0"
|
query_suffix += " AND was_sent = 0"
|
||||||
|
|
||||||
|
filter_auto_accept_type = filters.get("auto_accept_type", None)
|
||||||
|
if filter_auto_accept_type and filter_auto_accept_type != "any":
|
||||||
|
query_suffix += " AND auto_accept_type = :filter_auto_accept_type"
|
||||||
|
query_data["filter_auto_accept_type"] = int(filter_auto_accept_type)
|
||||||
|
|
||||||
query_suffix += getOrderByStr(filters)
|
query_suffix += getOrderByStr(filters)
|
||||||
|
|
||||||
limit = filters.get("limit", None)
|
limit = filters.get("limit", None)
|
||||||
|
|||||||
@@ -278,6 +278,7 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes:
|
|||||||
"is_public": o.addr_to == swap_client.network_addr
|
"is_public": o.addr_to == swap_client.network_addr
|
||||||
or o.addr_to.strip() == "",
|
or o.addr_to.strip() == "",
|
||||||
}
|
}
|
||||||
|
offer_data["auto_accept_type"] = getattr(o, "auto_accept_type", 0)
|
||||||
if with_extra_info:
|
if with_extra_info:
|
||||||
offer_data["amount_negotiable"] = o.amount_negotiable
|
offer_data["amount_negotiable"] = o.amount_negotiable
|
||||||
offer_data["rate_negotiable"] = o.rate_negotiable
|
offer_data["rate_negotiable"] = o.rate_negotiable
|
||||||
@@ -293,6 +294,7 @@ def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes:
|
|||||||
offer_data["feerate_to"] = o.to_feerate
|
offer_data["feerate_to"] = o.to_feerate
|
||||||
|
|
||||||
offer_data["automation_strat_id"] = getattr(o, "auto_accept_type", 0)
|
offer_data["automation_strat_id"] = getattr(o, "auto_accept_type", 0)
|
||||||
|
offer_data["auto_accept_type"] = getattr(o, "auto_accept_type", 0)
|
||||||
|
|
||||||
if o.was_sent:
|
if o.was_sent:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -365,3 +365,147 @@ select.disabled-select-enabled {
|
|||||||
#toggle-auto-refresh[data-enabled="true"] {
|
#toggle-auto-refresh[data-enabled="true"] {
|
||||||
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
|
@apply bg-green-500 hover:bg-green-600 focus:ring-green-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Multi-select dropdown styles */
|
||||||
|
.multi-select-dropdown::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-dropdown::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-dropdown::-webkit-scrollbar-thumb {
|
||||||
|
background: #888;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-dropdown::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .multi-select-dropdown::-webkit-scrollbar-track {
|
||||||
|
background: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .multi-select-dropdown::-webkit-scrollbar-thumb {
|
||||||
|
background: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .multi-select-dropdown::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-dropdown input[type="checkbox"]:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border-color: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-dropdown label:focus-within {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#coin_to_button:focus,
|
||||||
|
#coin_from_button:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3) !important;
|
||||||
|
border-color: #3b82f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coin-badge {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
.coin-badge .remove {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
.coin-badge .remove:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.multi-select-dropdown {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 9999 !important;
|
||||||
|
position: fixed !important;
|
||||||
|
min-width: 200px;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
.multi-select-dropdown::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
.multi-select-dropdown::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
.multi-select-dropdown::-webkit-scrollbar-thumb {
|
||||||
|
background: #888;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.multi-select-dropdown::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
.dark .multi-select-dropdown::-webkit-scrollbar-track {
|
||||||
|
background: #374151;
|
||||||
|
}
|
||||||
|
.dark .multi-select-dropdown::-webkit-scrollbar-thumb {
|
||||||
|
background: #6b7280;
|
||||||
|
}
|
||||||
|
.dropdown-container {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.dropdown-container.open {
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
.filter-button-text {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-dropdown input[type="checkbox"] {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.multi-select-dropdown input[type="checkbox"]:focus {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3) !important;
|
||||||
|
border-color: #3b82f6 !important;
|
||||||
|
}
|
||||||
|
.multi-select-dropdown input[type="checkbox"]:checked {
|
||||||
|
background-color: #3b82f6 !important;
|
||||||
|
border-color: #3b82f6 !important;
|
||||||
|
}
|
||||||
|
.dark .multi-select-dropdown input[type="checkbox"] {
|
||||||
|
border-color: #6b7280;
|
||||||
|
background-color: #374151;
|
||||||
|
}
|
||||||
|
.dark .multi-select-dropdown input[type="checkbox"]:focus {
|
||||||
|
border-color: #3b82f6 !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3) !important;
|
||||||
|
}
|
||||||
|
.dark .multi-select-dropdown input[type="checkbox"]:checked {
|
||||||
|
background-color: #3b82f6 !important;
|
||||||
|
border-color: #3b82f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-dropdown label {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,64 +61,9 @@ const AmmTablesManager = (function() {
|
|||||||
function getImageFilename(coinSymbol) {
|
function getImageFilename(coinSymbol) {
|
||||||
if (!coinSymbol) return 'Unknown.png';
|
if (!coinSymbol) return 'Unknown.png';
|
||||||
|
|
||||||
const coinNameToSymbol = {
|
const icon = window.CoinManager.getCoinIcon(coinSymbol);
|
||||||
'bitcoin': 'BTC',
|
debugLog(`CoinManager returned icon: ${icon} for ${coinSymbol}`);
|
||||||
'monero': 'XMR',
|
return icon || 'Unknown.png';
|
||||||
'particl': 'PART',
|
|
||||||
'particl anon': 'PART_ANON',
|
|
||||||
'particl blind': 'PART_BLIND',
|
|
||||||
'litecoin': 'LTC',
|
|
||||||
'bitcoincash': 'BCH',
|
|
||||||
'bitcoin cash': 'BCH',
|
|
||||||
'firo': 'FIRO',
|
|
||||||
'zcoin': 'FIRO',
|
|
||||||
'pivx': 'PIVX',
|
|
||||||
'dash': 'DASH',
|
|
||||||
'ethereum': 'ETH',
|
|
||||||
'dogecoin': 'DOGE',
|
|
||||||
'decred': 'DCR',
|
|
||||||
'namecoin': 'NMC',
|
|
||||||
'zano': 'ZANO',
|
|
||||||
'wownero': 'WOW'
|
|
||||||
};
|
|
||||||
|
|
||||||
let normalizedInput = coinSymbol.toLowerCase();
|
|
||||||
|
|
||||||
if (coinNameToSymbol[normalizedInput]) {
|
|
||||||
normalizedInput = coinNameToSymbol[normalizedInput];
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizedSymbol = normalizedInput.toUpperCase();
|
|
||||||
|
|
||||||
if (normalizedSymbol === 'FIRO' || normalizedSymbol === 'ZCOIN') return 'Firo.png';
|
|
||||||
if (normalizedSymbol === 'BCH' || normalizedSymbol === 'BITCOINCASH') return 'Bitcoin-Cash.png';
|
|
||||||
if (normalizedSymbol === 'PART_ANON' || normalizedSymbol === 'PARTICL_ANON') return 'Particl.png';
|
|
||||||
if (normalizedSymbol === 'PART_BLIND' || normalizedSymbol === 'PARTICL_BLIND') return 'Particl.png';
|
|
||||||
|
|
||||||
if (window.CoinManager && window.CoinManager.getCoinBySymbol) {
|
|
||||||
const coin = window.CoinManager.getCoinBySymbol(normalizedSymbol);
|
|
||||||
if (coin && coin.image) return coin.image;
|
|
||||||
}
|
|
||||||
|
|
||||||
const coinImages = {
|
|
||||||
'BTC': 'Bitcoin.png',
|
|
||||||
'XMR': 'Monero.png',
|
|
||||||
'PART': 'Particl.png',
|
|
||||||
'LTC': 'Litecoin.png',
|
|
||||||
'FIRO': 'Firo.png',
|
|
||||||
'PIVX': 'PIVX.png',
|
|
||||||
'DASH': 'Dash.png',
|
|
||||||
'ETH': 'Ethereum.png',
|
|
||||||
'DOGE': 'Dogecoin.png',
|
|
||||||
'DCR': 'Decred.png',
|
|
||||||
'NMC': 'Namecoin.png',
|
|
||||||
'ZANO': 'Zano.png',
|
|
||||||
'WOW': 'Wownero.png'
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = coinImages[normalizedSymbol] || 'Unknown.png';
|
|
||||||
debugLog(`Coin symbol: ${coinSymbol}, normalized: ${normalizedSymbol}, image: ${result}`);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCoinDisplayName(coinId) {
|
function getCoinDisplayName(coinId) {
|
||||||
|
|||||||
@@ -352,7 +352,7 @@ const createBidTableRow = async (bid) => {
|
|||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
|
<span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
|
||||||
<img class="h-12"
|
<img class="h-12"
|
||||||
src="/static/images/coins/${bid.coin_from.replace(' ', '-')}.png"
|
src="/static/images/coins/${window.CoinManager.getCoinIcon(bid.coin_from)}"
|
||||||
alt="${bid.coin_from}"
|
alt="${bid.coin_from}"
|
||||||
onerror="this.src='/static/images/coins/default.png'">
|
onerror="this.src='/static/images/coins/default.png'">
|
||||||
</span>
|
</span>
|
||||||
@@ -361,7 +361,7 @@ const createBidTableRow = async (bid) => {
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
|
<span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
|
||||||
<img class="h-12"
|
<img class="h-12"
|
||||||
src="/static/images/coins/${bid.coin_to.replace(' ', '-')}.png"
|
src="/static/images/coins/${window.CoinManager.getCoinIcon(bid.coin_to)}"
|
||||||
alt="${bid.coin_to}"
|
alt="${bid.coin_to}"
|
||||||
onerror="this.src='/static/images/coins/default.png'">
|
onerror="this.src='/static/images/coins/default.png'">
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -509,6 +509,21 @@ function coinMatches(offerCoin, filterCoin) {
|
|||||||
return offerCoin === filterCoin;
|
return offerCoin === filterCoin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filterCoin.includes(' ') || offerCoin.includes(' ')) {
|
||||||
|
const filterFirstWord = filterCoin.split(' ')[0];
|
||||||
|
const offerFirstWord = offerCoin.split(' ')[0];
|
||||||
|
|
||||||
|
if (filterFirstWord === 'bitcoin' && offerFirstWord === 'bitcoin') {
|
||||||
|
const filterHasCash = filterCoin.includes('cash');
|
||||||
|
const offerHasCash = offerCoin.includes('cash');
|
||||||
|
return filterHasCash === offerHasCash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterFirstWord === offerFirstWord && filterFirstWord.length > 4) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const CoinManager = (function() {
|
|||||||
usesCryptoCompare: true,
|
usesCryptoCompare: true,
|
||||||
usesCoinGecko: true,
|
usesCoinGecko: true,
|
||||||
historicalDays: 30,
|
historicalDays: 30,
|
||||||
icon: 'Bitcoin-Cash.png'
|
icon: 'Bitcoin%20Cash.png'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
symbol: 'PIVX',
|
symbol: 'PIVX',
|
||||||
@@ -235,6 +235,31 @@ const CoinManager = (function() {
|
|||||||
const coin = getCoinByAnyIdentifier(coinIdentifier);
|
const coin = getCoinByAnyIdentifier(coinIdentifier);
|
||||||
if (!coin) return coinIdentifier.toLowerCase();
|
if (!coin) return coinIdentifier.toLowerCase();
|
||||||
return coin.coingeckoId;
|
return coin.coingeckoId;
|
||||||
|
},
|
||||||
|
getCoinIcon: function(identifier) {
|
||||||
|
if (!identifier) return null;
|
||||||
|
|
||||||
|
const normalizedId = identifier.toString().toLowerCase().trim();
|
||||||
|
if (normalizedId === 'particl anon' || normalizedId === 'part_anon' || normalizedId === 'particl_anon') {
|
||||||
|
return 'Particl.png';
|
||||||
|
}
|
||||||
|
if (normalizedId === 'particl blind' || normalizedId === 'part_blind' || normalizedId === 'particl_blind') {
|
||||||
|
return 'Particl.png';
|
||||||
|
}
|
||||||
|
if (normalizedId === 'litecoin mweb' || normalizedId === 'ltc_mweb' || normalizedId === 'litecoin_mweb') {
|
||||||
|
return 'Litecoin.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
const coin = getCoinByAnyIdentifier(identifier);
|
||||||
|
if (coin && coin.icon) {
|
||||||
|
return coin.icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
const capitalizedName = identifier.toString().split(' ')
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||||
|
.join('%20');
|
||||||
|
|
||||||
|
return `${capitalizedName}.png`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -152,6 +152,21 @@ const ConfigManager = (function() {
|
|||||||
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
|
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filterCoin.includes(' ') || offerCoin.includes(' ')) {
|
||||||
|
const filterFirstWord = filterCoin.split(' ')[0];
|
||||||
|
const offerFirstWord = offerCoin.split(' ')[0];
|
||||||
|
|
||||||
|
if (filterFirstWord === 'bitcoin' && offerFirstWord === 'bitcoin') {
|
||||||
|
const filterHasCash = filterCoin.includes('cash');
|
||||||
|
const offerHasCash = offerCoin.includes('cash');
|
||||||
|
return filterHasCash === offerHasCash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterFirstWord === offerFirstWord && filterFirstWord.length > 4) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (particlVariants.includes(filterCoin)) {
|
if (particlVariants.includes(filterCoin)) {
|
||||||
return offerCoin === filterCoin;
|
return offerCoin === filterCoin;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ function initializeTableRateModule() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function continueInitialization() {
|
function continueInitialization() {
|
||||||
updateCoinFilterImages();
|
|
||||||
fetchOffers().then(() => {
|
fetchOffers().then(() => {
|
||||||
applyFilters();
|
applyFilters();
|
||||||
if (!isSentOffers) {
|
if (!isSentOffers) {
|
||||||
@@ -157,65 +156,340 @@ function getValidOffers() {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredData = filterAndSortData();
|
return jsonData;
|
||||||
return filteredData;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveFilterSettings() {
|
function saveFilterSettings() {
|
||||||
const formData = new FormData(filterForm);
|
|
||||||
const filters = Object.fromEntries(formData);
|
|
||||||
|
|
||||||
const storageKey = isSentOffers ? 'sentOffersTableSettings' : 'networkOffersTableSettings';
|
const storageKey = isSentOffers ? 'sentOffersTableSettings' : 'networkOffersTableSettings';
|
||||||
|
|
||||||
|
const selectedCoinTo = getSelectedCoins('coin_to');
|
||||||
|
const selectedCoinFrom = getSelectedCoins('coin_from');
|
||||||
|
|
||||||
|
const statusSelect = document.getElementById('status');
|
||||||
|
const sentFromSelect = document.getElementById('sent_from');
|
||||||
|
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||||
|
|
||||||
localStorage.setItem(storageKey, JSON.stringify({
|
localStorage.setItem(storageKey, JSON.stringify({
|
||||||
coin_to: filters.coin_to,
|
coin_to: selectedCoinTo,
|
||||||
coin_from: filters.coin_from,
|
coin_from: selectedCoinFrom,
|
||||||
status: filters.status,
|
status: statusSelect ? statusSelect.value : 'any',
|
||||||
sent_from: filters.sent_from,
|
sent_from: sentFromSelect ? sentFromSelect.value : 'any',
|
||||||
|
auto_accept_type: autoAcceptTypeSelect ? autoAcceptTypeSelect.value : 'any',
|
||||||
sortColumn: currentSortColumn,
|
sortColumn: currentSortColumn,
|
||||||
sortDirection: currentSortDirection
|
sortDirection: currentSortDirection
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function coinMatches(offerCoin, filterCoin) {
|
|
||||||
if (!offerCoin || !filterCoin || filterCoin === 'any') return true;
|
|
||||||
|
|
||||||
offerCoin = offerCoin.toLowerCase();
|
function getSelectedCoins(filterType) {
|
||||||
filterCoin = filterCoin.toLowerCase();
|
|
||||||
|
|
||||||
if (offerCoin === filterCoin) return true;
|
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||||
|
if (!dropdown) {
|
||||||
if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
|
return ['any'];
|
||||||
(filterCoin === 'firo' || filterCoin === 'zcoin')) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((offerCoin === 'bitcoincash' && filterCoin === 'bitcoin cash') ||
|
|
||||||
(offerCoin === 'bitcoin cash' && filterCoin === 'bitcoincash')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const particlVariants = ['particl', 'particl anon', 'particl blind'];
|
const allCheckboxes = dropdown.querySelectorAll('input[type="checkbox"]');
|
||||||
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (particlVariants.includes(filterCoin)) {
|
const selected = [];
|
||||||
return offerCoin === filterCoin;
|
allCheckboxes.forEach((cb) => {
|
||||||
}
|
if (cb.checked && cb.value !== 'any') {
|
||||||
|
selected.push(cb.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return false;
|
return selected.length > 0 ? selected : ['any'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterAndSortData() {
|
function getCoinNameFromValue(value, filterType) {
|
||||||
const formData = new FormData(filterForm);
|
if (value === 'any') {
|
||||||
const filters = Object.fromEntries(formData);
|
return 'any';
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||||
|
if (!dropdown) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkbox = dropdown.querySelector(`input[value="${value}"]`);
|
||||||
|
if (checkbox) {
|
||||||
|
const label = checkbox.closest('label');
|
||||||
|
const spans = label.querySelectorAll('span');
|
||||||
|
|
||||||
|
const coinSpan = spans[spans.length - 1];
|
||||||
|
if (coinSpan) {
|
||||||
|
const text = coinSpan.textContent.trim();
|
||||||
|
|
||||||
|
const cleanText = text.replace(/\s*\(clear all\)\s*/, '');
|
||||||
|
return cleanText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCoinImage(coinName) {
|
||||||
|
return window.CoinManager.getCoinIcon(coinName);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFilterButtonText(filterType) {
|
||||||
|
const selected = getSelectedCoins(filterType);
|
||||||
|
const button = document.getElementById(`${filterType}_button`);
|
||||||
|
const textSpan = document.getElementById(`${filterType}_text`);
|
||||||
|
|
||||||
|
if (!button || !textSpan) return;
|
||||||
|
|
||||||
|
if (selected.length === 0 || (selected.length === 1 && selected[0] === 'any')) {
|
||||||
|
|
||||||
|
const defaultText = filterType === 'coin_to' ?
|
||||||
|
(isSentOffers ? 'Filter Receiving' : 'Filter Bids') :
|
||||||
|
(isSentOffers ? 'Filter Sending' : 'Filter Offers');
|
||||||
|
textSpan.textContent = defaultText;
|
||||||
|
} else {
|
||||||
|
const filterLabel = filterType === 'coin_to' ?
|
||||||
|
(isSentOffers ? 'Receiving' : 'Bids') :
|
||||||
|
(isSentOffers ? 'Sending' : 'Offers');
|
||||||
|
textSpan.textContent = `Filter ${filterLabel} (${selected.length} selected)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button.style.width = '210px';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCoinBadges(filterType) {
|
||||||
|
const selected = getSelectedCoins(filterType);
|
||||||
|
const badgesContainer = document.getElementById(`${filterType}_badges`);
|
||||||
|
const mainContainer = document.getElementById('selected_coins_container');
|
||||||
|
|
||||||
|
if (!badgesContainer) return;
|
||||||
|
|
||||||
|
badgesContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (selected.length > 0 && !(selected.length === 1 && selected[0] === 'any')) {
|
||||||
|
selected.forEach(coinValue => {
|
||||||
|
const coinName = getCoinNameFromValue(coinValue, filterType);
|
||||||
|
const badge = document.createElement('span');
|
||||||
|
|
||||||
|
|
||||||
|
const isBidsFilter = filterType === 'coin_to' && !isSentOffers;
|
||||||
|
const isOffersFilter = filterType === 'coin_from' && !isSentOffers;
|
||||||
|
const isReceivingFilter = filterType === 'coin_to' && isSentOffers;
|
||||||
|
const isSendingFilter = filterType === 'coin_from' && isSentOffers;
|
||||||
|
|
||||||
|
let badgeClass = 'inline-flex items-center px-3 py-2 rounded-full text-sm font-medium mr-2 mb-1';
|
||||||
|
if (isBidsFilter || isReceivingFilter) {
|
||||||
|
badgeClass += ' bg-blue-500 text-white dark:bg-blue-600 dark:text-white';
|
||||||
|
} else {
|
||||||
|
badgeClass += ' bg-green-400 text-white dark:bg-green-500 dark:text-white';
|
||||||
|
}
|
||||||
|
|
||||||
|
badge.className = badgeClass + ' cursor-pointer hover:opacity-80';
|
||||||
|
|
||||||
|
|
||||||
|
const coinImage = getCoinImage(coinName);
|
||||||
|
|
||||||
|
badge.innerHTML = `
|
||||||
|
<img src="/static/images/coins/${coinImage}" class="w-4 h-4 mr-1" alt="${coinName}" onerror="this.style.display='none'">
|
||||||
|
<span>${coinName}</span>
|
||||||
|
<button type="button" class="ml-1 text-current hover:text-red-500 focus:outline-none remove-coin-btn">
|
||||||
|
<span class="sr-only">Remove ${coinName}</span>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
CleanupManager.addListener(badge, 'click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
removeCoinFilter(filterType, coinValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeBtn = badge.querySelector('.remove-coin-btn');
|
||||||
|
if (closeBtn) {
|
||||||
|
CleanupManager.addListener(closeBtn, 'click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
removeCoinFilter(filterType, coinValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
badgesContainer.appendChild(badge);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (mainContainer) {
|
||||||
|
mainContainer.style.display = 'block';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const otherType = filterType === 'coin_to' ? 'coin_from' : 'coin_to';
|
||||||
|
const otherSelected = getSelectedCoins(otherType);
|
||||||
|
if (otherSelected.length === 0 || (otherSelected.length === 1 && otherSelected[0] === 'any')) {
|
||||||
|
if (mainContainer) {
|
||||||
|
mainContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function coinMatches(offerCoin, filterCoins) {
|
||||||
|
if (!offerCoin || !filterCoins) return true;
|
||||||
|
|
||||||
|
if (typeof filterCoins === 'string') {
|
||||||
|
filterCoins = [filterCoins];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterCoins.includes('any') || filterCoins.length === 0) return true;
|
||||||
|
|
||||||
|
const normalizedOfferCoin = offerCoin.toLowerCase().trim();
|
||||||
|
|
||||||
|
return filterCoins.some(filterCoin => {
|
||||||
|
if (!filterCoin) return false;
|
||||||
|
|
||||||
|
const normalizedFilterCoin = filterCoin.toLowerCase().trim();
|
||||||
|
|
||||||
|
if (normalizedOfferCoin === normalizedFilterCoin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((normalizedOfferCoin === 'firo' || normalizedOfferCoin === 'zcoin') &&
|
||||||
|
(normalizedFilterCoin === 'firo' || normalizedFilterCoin === 'zcoin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((normalizedOfferCoin === 'bitcoincash' && normalizedFilterCoin === 'bitcoin cash') ||
|
||||||
|
(normalizedOfferCoin === 'bitcoin cash' && normalizedFilterCoin === 'bitcoincash')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const particlVariants = ['particl', 'particl anon', 'particl blind'];
|
||||||
|
if (normalizedFilterCoin === 'particl' && particlVariants.includes(normalizedOfferCoin)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (particlVariants.includes(normalizedFilterCoin)) {
|
||||||
|
return normalizedOfferCoin === normalizedFilterCoin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedFilterCoin.includes(' ') || normalizedOfferCoin.includes(' ')) {
|
||||||
|
const filterFirstWord = normalizedFilterCoin.split(' ')[0];
|
||||||
|
const offerFirstWord = normalizedOfferCoin.split(' ')[0];
|
||||||
|
|
||||||
|
if (filterFirstWord === 'bitcoin' && offerFirstWord === 'bitcoin') {
|
||||||
|
const filterHasCash = normalizedFilterCoin.includes('cash');
|
||||||
|
const offerHasCash = normalizedOfferCoin.includes('cash');
|
||||||
|
return filterHasCash === offerHasCash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterFirstWord === offerFirstWord && filterFirstWord.length > 4) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDropdown(filterType) {
|
||||||
|
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||||
|
const button = document.getElementById(`${filterType}_button`);
|
||||||
|
const container = dropdown?.closest('.dropdown-container');
|
||||||
|
|
||||||
|
if (dropdown && button) {
|
||||||
|
const isVisible = dropdown.style.display !== 'none';
|
||||||
|
hideAllDropdowns();
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
const buttonRect = button.getBoundingClientRect();
|
||||||
|
dropdown.style.position = 'fixed';
|
||||||
|
dropdown.style.top = (buttonRect.bottom + 4) + 'px';
|
||||||
|
dropdown.style.left = buttonRect.left + 'px';
|
||||||
|
dropdown.style.width = buttonRect.width + 'px';
|
||||||
|
dropdown.style.display = 'block';
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
container.classList.add('open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideDropdown(filterType) {
|
||||||
|
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||||
|
const container = dropdown?.closest('.dropdown-container');
|
||||||
|
|
||||||
|
if (dropdown) {
|
||||||
|
dropdown.style.display = 'none';
|
||||||
|
if (container) {
|
||||||
|
container.classList.remove('open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideAllDropdowns() {
|
||||||
|
hideDropdown('coin_to');
|
||||||
|
hideDropdown('coin_from');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCoinCheckboxChange(filterType, checkbox) {
|
||||||
|
if (checkbox.value === 'any') {
|
||||||
|
|
||||||
|
if (checkbox.checked) {
|
||||||
|
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||||
|
if (dropdown) {
|
||||||
|
const otherCheckboxes = dropdown.querySelectorAll('input[type="checkbox"]:not([value="any"])');
|
||||||
|
otherCheckboxes.forEach(cb => cb.checked = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if (checkbox.checked) {
|
||||||
|
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||||
|
if (dropdown) {
|
||||||
|
const anyCheckbox = dropdown.querySelector('input[value="any"]');
|
||||||
|
if (anyCheckbox) anyCheckbox.checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilterButtonText(filterType);
|
||||||
|
updateCoinBadges(filterType);
|
||||||
|
applyFilters();
|
||||||
|
updateClearFiltersButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeCoinFilter(filterType, coinValue) {
|
||||||
|
|
||||||
|
const dropdown = document.getElementById(`${filterType}_dropdown`);
|
||||||
|
if (!dropdown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkbox = dropdown.querySelector(`input[value="${coinValue}"]`);
|
||||||
|
if (checkbox) {
|
||||||
|
checkbox.checked = false;
|
||||||
|
|
||||||
|
|
||||||
|
updateFilterButtonText(filterType);
|
||||||
|
updateCoinBadges(filterType);
|
||||||
|
applyFilters();
|
||||||
|
updateClearFiltersButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.removeCoinFilter = removeCoinFilter;
|
||||||
|
|
||||||
|
function filterAndSortData() {
|
||||||
saveFilterSettings();
|
saveFilterSettings();
|
||||||
|
|
||||||
let filteredData = [...originalJsonData];
|
let filteredData = [...originalJsonData];
|
||||||
|
|
||||||
const sentFromFilter = filters.sent_from || 'any';
|
const statusSelect = document.getElementById('status');
|
||||||
|
const sentFromSelect = document.getElementById('sent_from');
|
||||||
|
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||||
|
const selectedCoinTo = getSelectedCoins('coin_to');
|
||||||
|
const selectedCoinFrom = getSelectedCoins('coin_from');
|
||||||
|
|
||||||
|
const sentFromFilter = sentFromSelect ? sentFromSelect.value : 'any';
|
||||||
filteredData = filteredData.filter(offer => {
|
filteredData = filteredData.filter(offer => {
|
||||||
const isMatch = sentFromFilter === 'public' ? offer.is_public :
|
const isMatch = sentFromFilter === 'public' ? offer.is_public :
|
||||||
sentFromFilter === 'private' ? !offer.is_public :
|
sentFromFilter === 'private' ? !offer.is_public :
|
||||||
@@ -223,37 +497,43 @@ function filterAndSortData() {
|
|||||||
return isMatch;
|
return isMatch;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const autoAcceptTypeFilter = autoAcceptTypeSelect ? autoAcceptTypeSelect.value : 'any';
|
||||||
|
if (autoAcceptTypeFilter !== 'any') {
|
||||||
|
filteredData = filteredData.filter(offer => {
|
||||||
|
const offerAutoAcceptType = offer.auto_accept_type !== undefined ? offer.auto_accept_type : 0;
|
||||||
|
return offerAutoAcceptType === parseInt(autoAcceptTypeFilter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
filteredData = filteredData.filter(offer => {
|
filteredData = filteredData.filter(offer => {
|
||||||
if (!isSentOffers && isOfferExpired(offer)) {
|
if (!isSentOffers && isOfferExpired(offer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.coin_to && filters.coin_to !== 'any') {
|
|
||||||
const coinToSelect = document.getElementById('coin_to');
|
|
||||||
const selectedOption = coinToSelect?.querySelector(`option[value="${filters.coin_to}"]`);
|
|
||||||
const coinName = selectedOption?.textContent.trim();
|
|
||||||
|
|
||||||
if (coinName && !coinMatches(offer.coin_to, coinName)) {
|
if (selectedCoinTo.length > 0 && !(selectedCoinTo.length === 1 && selectedCoinTo[0] === 'any')) {
|
||||||
|
const coinNames = selectedCoinTo.map(value => getCoinNameFromValue(value, 'coin_to'));
|
||||||
|
const matches = coinMatches(offer.coin_to, coinNames);
|
||||||
|
if (!matches) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.coin_from && filters.coin_from !== 'any') {
|
|
||||||
const coinFromSelect = document.getElementById('coin_from');
|
|
||||||
const selectedOption = coinFromSelect?.querySelector(`option[value="${filters.coin_from}"]`);
|
|
||||||
const coinName = selectedOption?.textContent.trim();
|
|
||||||
|
|
||||||
if (coinName && !coinMatches(offer.coin_from, coinName)) {
|
if (selectedCoinFrom.length > 0 && !(selectedCoinFrom.length === 1 && selectedCoinFrom[0] === 'any')) {
|
||||||
|
const coinNames = selectedCoinFrom.map(value => getCoinNameFromValue(value, 'coin_from'));
|
||||||
|
const matches = coinMatches(offer.coin_from, coinNames);
|
||||||
|
if (!matches) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSentOffers && filters.status && filters.status !== 'any') {
|
if (isSentOffers && statusSelect && statusSelect.value !== 'any') {
|
||||||
const isExpired = offer.expire_at <= Math.floor(Date.now() / 1000);
|
const isExpired = offer.expire_at <= Math.floor(Date.now() / 1000);
|
||||||
const isRevoked = Boolean(offer.is_revoked);
|
const isRevoked = Boolean(offer.is_revoked);
|
||||||
|
|
||||||
let statusMatch = false;
|
let statusMatch = false;
|
||||||
switch (filters.status) {
|
switch (statusSelect.value) {
|
||||||
case 'active':
|
case 'active':
|
||||||
statusMatch = !isExpired && !isRevoked;
|
statusMatch = !isExpired && !isRevoked;
|
||||||
break;
|
break;
|
||||||
@@ -554,6 +834,7 @@ function formatInitialData(data) {
|
|||||||
amount_negotiable: Boolean(offer.amount_negotiable),
|
amount_negotiable: Boolean(offer.amount_negotiable),
|
||||||
is_revoked: Boolean(offer.is_revoked),
|
is_revoked: Boolean(offer.is_revoked),
|
||||||
is_public: offer.is_public !== undefined ? Boolean(offer.is_public) : false,
|
is_public: offer.is_public !== undefined ? Boolean(offer.is_public) : false,
|
||||||
|
auto_accept_type: offer.auto_accept_type !== undefined ? Number(offer.auto_accept_type) : 0,
|
||||||
unique_id: `${offer.offer_id}_${offer.created_at}_${offer.coin_from}_${offer.coin_to}`
|
unique_id: `${offer.offer_id}_${offer.created_at}_${offer.coin_from}_${offer.coin_to}`
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -721,28 +1002,7 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffe
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateCoinFilterImages() {
|
function updateCoinFilterImages() {
|
||||||
const coinToSelect = document.getElementById('coin_to');
|
return;
|
||||||
const coinFromSelect = document.getElementById('coin_from');
|
|
||||||
const coinToButton = document.getElementById('coin_to_button');
|
|
||||||
const coinFromButton = document.getElementById('coin_from_button');
|
|
||||||
|
|
||||||
function updateButtonImage(select, button) {
|
|
||||||
const selectedOption = select.options[select.selectedIndex];
|
|
||||||
const imagePath = selectedOption.getAttribute('data-image');
|
|
||||||
if (imagePath && select.value !== 'any') {
|
|
||||||
button.style.backgroundImage = `url(${imagePath})`;
|
|
||||||
button.style.backgroundSize = '25px 25px';
|
|
||||||
button.style.backgroundPosition = 'center';
|
|
||||||
button.style.backgroundRepeat = 'no-repeat';
|
|
||||||
} else {
|
|
||||||
button.style.backgroundImage = 'none';
|
|
||||||
}
|
|
||||||
button.style.minWidth = '25px';
|
|
||||||
button.style.minHeight = '25px';
|
|
||||||
}
|
|
||||||
|
|
||||||
updateButtonImage(coinToSelect, coinToButton);
|
|
||||||
updateButtonImage(coinFromSelect, coinFromButton);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateClearFiltersButton() {
|
function updateClearFiltersButton() {
|
||||||
@@ -799,11 +1059,7 @@ function cleanupTable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleNoOffersScenario() {
|
function handleNoOffersScenario() {
|
||||||
const formData = new FormData(filterForm);
|
const hasFilters = hasActiveFilters();
|
||||||
const filters = Object.fromEntries(formData);
|
|
||||||
const hasActiveFilters = filters.coin_to !== 'any' ||
|
|
||||||
filters.coin_from !== 'any' ||
|
|
||||||
(filters.status && filters.status !== 'any');
|
|
||||||
|
|
||||||
stopRefreshAnimation();
|
stopRefreshAnimation();
|
||||||
|
|
||||||
@@ -812,7 +1068,7 @@ function handleNoOffersScenario() {
|
|||||||
cleanupRow(row);
|
cleanupRow(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasActiveFilters) {
|
if (hasFilters) {
|
||||||
offersBody.innerHTML = `
|
offersBody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="9" class="text-center py-8">
|
<td colspan="9" class="text-center py-8">
|
||||||
@@ -1726,13 +1982,8 @@ function applyFilters() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function clearFilters() {
|
function clearFilters() {
|
||||||
filterForm.reset();
|
document.querySelectorAll('.coin-to-checkbox, .coin-from-checkbox').forEach(checkbox => {
|
||||||
|
checkbox.checked = checkbox.value === 'any';
|
||||||
const selectElements = filterForm.querySelectorAll('select');
|
|
||||||
selectElements.forEach(select => {
|
|
||||||
select.value = 'any';
|
|
||||||
const event = new Event('change', { bubbles: true });
|
|
||||||
select.dispatchEvent(event);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const statusSelect = document.getElementById('status');
|
const statusSelect = document.getElementById('status');
|
||||||
@@ -1740,6 +1991,22 @@ function clearFilters() {
|
|||||||
statusSelect.value = 'any';
|
statusSelect.value = 'any';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sentFromSelect = document.getElementById('sent_from');
|
||||||
|
if (sentFromSelect) {
|
||||||
|
sentFromSelect.value = 'any';
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||||
|
if (autoAcceptTypeSelect) {
|
||||||
|
autoAcceptTypeSelect.value = 'any';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilterButtonText('coin_to');
|
||||||
|
updateFilterButtonText('coin_from');
|
||||||
|
updateCoinBadges('coin_to');
|
||||||
|
updateCoinBadges('coin_from');
|
||||||
|
hideAllDropdowns();
|
||||||
|
|
||||||
jsonData = [...originalJsonData];
|
jsonData = [...originalJsonData];
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
|
|
||||||
@@ -1747,21 +2014,25 @@ function clearFilters() {
|
|||||||
localStorage.removeItem(storageKey);
|
localStorage.removeItem(storageKey);
|
||||||
|
|
||||||
updateOffersTable();
|
updateOffersTable();
|
||||||
updateCoinFilterImages();
|
|
||||||
updateClearFiltersButton();
|
updateClearFiltersButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasActiveFilters() {
|
function hasActiveFilters() {
|
||||||
const selectElements = filterForm.querySelectorAll('select');
|
const selectedCoinTo = getSelectedCoins('coin_to');
|
||||||
let hasChangedFilters = false;
|
const selectedCoinFrom = getSelectedCoins('coin_from');
|
||||||
|
|
||||||
selectElements.forEach(select => {
|
const hasCoinToFilter = selectedCoinTo.length > 0 && !(selectedCoinTo.length === 1 && selectedCoinTo[0] === 'any');
|
||||||
if (select.value !== 'any') {
|
const hasCoinFromFilter = selectedCoinFrom.length > 0 && !(selectedCoinFrom.length === 1 && selectedCoinFrom[0] === 'any');
|
||||||
hasChangedFilters = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return hasChangedFilters;
|
const statusSelect = document.getElementById('status');
|
||||||
|
const sentFromSelect = document.getElementById('sent_from');
|
||||||
|
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||||
|
|
||||||
|
const hasStatusFilter = statusSelect && statusSelect.value !== 'any';
|
||||||
|
const hasSentFromFilter = sentFromSelect && sentFromSelect.value !== 'any';
|
||||||
|
const hasAutoAcceptTypeFilter = autoAcceptTypeSelect && autoAcceptTypeSelect.value !== 'any';
|
||||||
|
|
||||||
|
return hasCoinToFilter || hasCoinFromFilter || hasStatusFilter || hasSentFromFilter || hasAutoAcceptTypeFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTimeLeft(timestamp) {
|
function formatTimeLeft(timestamp) {
|
||||||
@@ -1791,13 +2062,6 @@ function getCoinSymbolLowercase(coin) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function coinMatches(offerCoin, filterCoin) {
|
|
||||||
if (window.CoinManager) {
|
|
||||||
return window.CoinManager.coinMatches(offerCoin, filterCoin);
|
|
||||||
}
|
|
||||||
return window.config.coinMatches(offerCoin, filterCoin);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getProfitColorClass(percentage) {
|
function getProfitColorClass(percentage) {
|
||||||
const numericPercentage = parseFloat(percentage);
|
const numericPercentage = parseFloat(percentage);
|
||||||
if (numericPercentage > 0) return 'text-green-500';
|
if (numericPercentage > 0) return 'text-green-500';
|
||||||
@@ -1872,25 +2136,51 @@ function initializeTableEvents() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const coinToSelect = document.getElementById('coin_to');
|
const coinToButton = document.getElementById('coin_to_button');
|
||||||
const coinFromSelect = document.getElementById('coin_from');
|
const coinFromButton = document.getElementById('coin_from_button');
|
||||||
|
const coinToDropdown = document.getElementById('coin_to_dropdown');
|
||||||
|
const coinFromDropdown = document.getElementById('coin_from_dropdown');
|
||||||
const statusSelect = document.getElementById('status');
|
const statusSelect = document.getElementById('status');
|
||||||
const sentFromSelect = document.getElementById('sent_from');
|
const sentFromSelect = document.getElementById('sent_from');
|
||||||
|
|
||||||
if (coinToSelect) {
|
|
||||||
CleanupManager.addListener(coinToSelect, 'change', () => {
|
if (coinToButton && coinToDropdown) {
|
||||||
applyFilters();
|
CleanupManager.addListener(coinToButton, 'click', (e) => {
|
||||||
updateCoinFilterImages();
|
e.stopPropagation();
|
||||||
|
toggleDropdown('coin_to');
|
||||||
|
});
|
||||||
|
|
||||||
|
const coinToCheckboxes = coinToDropdown.querySelectorAll('.coin-to-checkbox');
|
||||||
|
coinToCheckboxes.forEach(checkbox => {
|
||||||
|
CleanupManager.addListener(checkbox, 'change', (e) => {
|
||||||
|
handleCoinCheckboxChange('coin_to', e.target);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (coinFromSelect) {
|
|
||||||
CleanupManager.addListener(coinFromSelect, 'change', () => {
|
if (coinFromButton && coinFromDropdown) {
|
||||||
applyFilters();
|
CleanupManager.addListener(coinFromButton, 'click', (e) => {
|
||||||
updateCoinFilterImages();
|
e.stopPropagation();
|
||||||
|
toggleDropdown('coin_from');
|
||||||
|
});
|
||||||
|
|
||||||
|
const coinFromCheckboxes = coinFromDropdown.querySelectorAll('.coin-from-checkbox');
|
||||||
|
coinFromCheckboxes.forEach(checkbox => {
|
||||||
|
CleanupManager.addListener(checkbox, 'change', (e) => {
|
||||||
|
handleCoinCheckboxChange('coin_from', e.target);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CleanupManager.addListener(window, 'resize', () => {
|
||||||
|
hideAllDropdowns();
|
||||||
|
});
|
||||||
|
|
||||||
|
CleanupManager.addListener(window, 'scroll', () => {
|
||||||
|
hideAllDropdowns();
|
||||||
|
});
|
||||||
|
|
||||||
if (statusSelect) {
|
if (statusSelect) {
|
||||||
CleanupManager.addListener(statusSelect, 'change', () => {
|
CleanupManager.addListener(statusSelect, 'change', () => {
|
||||||
applyFilters();
|
applyFilters();
|
||||||
@@ -1903,11 +2193,17 @@ function initializeTableEvents() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const autoAcceptTypeSelect = document.getElementById('auto_accept_type');
|
||||||
|
if (autoAcceptTypeSelect) {
|
||||||
|
CleanupManager.addListener(autoAcceptTypeSelect, 'change', () => {
|
||||||
|
applyFilters();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const clearFiltersBtn = document.getElementById('clearFilters');
|
const clearFiltersBtn = document.getElementById('clearFilters');
|
||||||
if (clearFiltersBtn) {
|
if (clearFiltersBtn) {
|
||||||
CleanupManager.addListener(clearFiltersBtn, 'click', () => {
|
CleanupManager.addListener(clearFiltersBtn, 'click', () => {
|
||||||
clearFilters();
|
clearFilters();
|
||||||
updateCoinFilterImages();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2133,7 +2429,12 @@ async function initializeTableAndData() {
|
|||||||
updateClearFiltersButton();
|
updateClearFiltersButton();
|
||||||
initializeTableEvents();
|
initializeTableEvents();
|
||||||
initializeTooltips();
|
initializeTooltips();
|
||||||
updateCoinFilterImages();
|
|
||||||
|
|
||||||
|
updateFilterButtonText('coin_to');
|
||||||
|
updateFilterButtonText('coin_from');
|
||||||
|
updateCoinBadges('coin_to');
|
||||||
|
updateCoinBadges('coin_from');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetchOffers();
|
await fetchOffers();
|
||||||
@@ -2152,7 +2453,23 @@ function loadSavedSettings() {
|
|||||||
if (saved) {
|
if (saved) {
|
||||||
const settings = JSON.parse(saved);
|
const settings = JSON.parse(saved);
|
||||||
|
|
||||||
['coin_to', 'coin_from', 'status', 'sent_from'].forEach(id => {
|
['coin_to', 'coin_from'].forEach(filterType => {
|
||||||
|
const savedCoins = settings[filterType];
|
||||||
|
if (savedCoins && Array.isArray(savedCoins)) {
|
||||||
|
|
||||||
|
document.querySelectorAll(`.${filterType}-checkbox`).forEach(cb => cb.checked = false);
|
||||||
|
|
||||||
|
savedCoins.forEach(coinValue => {
|
||||||
|
const checkbox = document.querySelector(`.${filterType}-checkbox[value="${coinValue}"]`);
|
||||||
|
if (checkbox) checkbox.checked = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateFilterButtonText(filterType);
|
||||||
|
updateCoinBadges(filterType);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
['status', 'sent_from', 'auto_accept_type'].forEach(id => {
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
if (element && settings[id]) element.value = settings[id];
|
if (element && settings[id]) element.value = settings[id];
|
||||||
});
|
});
|
||||||
@@ -2308,7 +2625,6 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
filterForm.querySelectorAll('select').forEach(select => {
|
filterForm.querySelectorAll('select').forEach(select => {
|
||||||
CleanupManager.addListener(select, 'change', () => {
|
CleanupManager.addListener(select, 'change', () => {
|
||||||
applyFilters();
|
applyFilters();
|
||||||
updateCoinFilterImages();
|
|
||||||
updateClearFiltersButton();
|
updateClearFiltersButton();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2319,6 +2635,23 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
CleanupManager.addListener(clearFiltersBtn, 'click', clearFilters);
|
CleanupManager.addListener(clearFiltersBtn, 'click', clearFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CleanupManager.addListener(document, 'click', (e) => {
|
||||||
|
const coinToDropdown = document.getElementById('coin_to_dropdown');
|
||||||
|
const coinFromDropdown = document.getElementById('coin_from_dropdown');
|
||||||
|
|
||||||
|
if (coinToDropdown && coinToDropdown.style.display !== 'none') {
|
||||||
|
if (!e.target.closest('#coin_to_button') && !e.target.closest('#coin_to_dropdown')) {
|
||||||
|
hideDropdown('coin_to');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (coinFromDropdown && coinFromDropdown.style.display !== 'none') {
|
||||||
|
if (!e.target.closest('#coin_from_button') && !e.target.closest('#coin_from_dropdown')) {
|
||||||
|
hideDropdown('coin_from');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const rowTimeInterval = setInterval(updateRowTimes, 30000);
|
const rowTimeInterval = setInterval(updateRowTimes, 30000);
|
||||||
if (CleanupManager.registerResource) {
|
if (CleanupManager.registerResource) {
|
||||||
CleanupManager.registerResource('rowTimeInterval', rowTimeInterval, () => {
|
CleanupManager.registerResource('rowTimeInterval', rowTimeInterval, () => {
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ const createSwapTableRow = async (swap) => {
|
|||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
|
<span class="inline-flex mr-3 align-middle items-center justify-center w-18 h-20 rounded">
|
||||||
<img class="h-12"
|
<img class="h-12"
|
||||||
src="/static/images/coins/${swap.coin_from.replace(' ', '-')}.png"
|
src="/static/images/coins/${window.CoinManager.getCoinIcon(swap.coin_from)}"
|
||||||
alt="${swap.coin_from}"
|
alt="${swap.coin_from}"
|
||||||
onerror="this.src='/static/images/coins/default.png'">
|
onerror="this.src='/static/images/coins/default.png'">
|
||||||
</span>
|
</span>
|
||||||
@@ -398,7 +398,7 @@ const createSwapTableRow = async (swap) => {
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
|
<span class="inline-flex ml-3 align-middle items-center justify-center w-18 h-20 rounded">
|
||||||
<img class="h-12"
|
<img class="h-12"
|
||||||
src="/static/images/coins/${swap.coin_to.replace(' ', '-')}.png"
|
src="/static/images/coins/${window.CoinManager.getCoinIcon(swap.coin_to)}"
|
||||||
alt="${swap.coin_to}"
|
alt="${swap.coin_to}"
|
||||||
onerror="this.src='/static/images/coins/default.png'">
|
onerror="this.src='/static/images/coins/default.png'">
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -532,12 +532,12 @@
|
|||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="edit_bid" type="submit" value="Edit Bid" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md shadow-button focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Edit Bid</button>
|
<button name="edit_bid" type="submit" value="Edit Bid" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md shadow-button focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Edit Bid</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if data.can_abandon == true and not edit_bid %}
|
{% if data.can_abandon == true and not edit_bid %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="abandon_bid" type="submit" value="Abandon Bid" onclick="return confirmPopup();" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Abandon Bid</button>
|
<button name="abandon_bid" type="submit" value="Abandon Bid" onclick="return confirmPopup();" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none">Abandon Bid</button>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if data.was_received and not edit_bid and data.can_accept_bid %}
|
{% if data.was_received and not edit_bid and data.can_accept_bid %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
|||||||
@@ -808,13 +808,13 @@
|
|||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="edit_bid" type="submit" value="Edit Bid" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Edit Bid</button>
|
<button name="edit_bid" type="submit" value="Edit Bid" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-coolGray-500 hover:text-coolGray-600 border border-coolGray-200 hover:border-coolGray-300 bg-white rounded-md focus:ring-0 focus:outline-none dark:text-white dark:hover:text-white dark:bg-gray-600 dark:hover:bg-gray-700 dark:border-gray-600 dark:hover:border-gray-600">Edit Bid</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if data.can_abandon == true %}
|
{% if data.can_abandon == true %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="abandon_bid" type="submit" value="Abandon Bid" onclick="return confirmPopup();" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md focus:ring-0 focus:outline-none">Abandon Bid</button>
|
<button name="abandon_bid" type="submit" value="Abandon Bid" onclick="return confirmPopup();" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md focus:ring-0 focus:outline-none">Abandon Bid</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% if data.was_received and not edit_bid and data.can_accept_bid %}
|
{% if data.was_received and not edit_bid and data.can_accept_bid %}
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button name="accept_bid" value="Accept Bid" type="submit" onclick='return confirmPopup("Accept");' class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">Accept Bid</button>
|
<button name="accept_bid" value="Accept Bid" type="submit" onclick='return confirmPopup("Accept");' class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">Accept Bid</button>
|
||||||
|
|||||||
@@ -846,6 +846,27 @@ function validateMaxAmount(input, maxAmount) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showConfirmModal() {
|
function showConfirmModal() {
|
||||||
|
const bidAmountSendInput = document.getElementById('bid_amount_send');
|
||||||
|
const bidAmountInput = document.getElementById('bid_amount');
|
||||||
|
|
||||||
|
let sendAmount = 0;
|
||||||
|
let receiveAmount = 0;
|
||||||
|
|
||||||
|
if (bidAmountSendInput && bidAmountSendInput.value) {
|
||||||
|
sendAmount = parseFloat(bidAmountSendInput.value) || 0;
|
||||||
|
}
|
||||||
|
if (bidAmountInput && bidAmountInput.value) {
|
||||||
|
receiveAmount = parseFloat(bidAmountInput.value) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendAmount <= 0 && bidAmountSendInput && !bidAmountSendInput.disabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receiveAmount <= 0 && bidAmountInput && !bidAmountInput.disabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
updateModalValues();
|
updateModalValues();
|
||||||
const modal = document.getElementById('confirmModal');
|
const modal = document.getElementById('confirmModal');
|
||||||
if (modal) {
|
if (modal) {
|
||||||
|
|||||||
@@ -199,97 +199,128 @@
|
|||||||
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div class="px-6 py-0 mt-5 h-full overflow-hidden">
|
<div class="px-6 py-0 h-full overflow-hidden">
|
||||||
<div class="border-coolGray-100">
|
<div class="border-coolGray-100">
|
||||||
<div class="flex flex-wrap items-center justify-between">
|
<div class="flex flex-wrap items-center justify-between">
|
||||||
<div class="w-full mx-auto pt-2">
|
<div class="w-full mx-auto pt-2">
|
||||||
<form method="post" id="filterForm">
|
<form method="post" id="filterForm">
|
||||||
<div class="flex items-center justify-center pb-4 dark:text-white">
|
<div class="pb-6 mt-6 border-coolGray-100">
|
||||||
<div class="rounded-b-md">
|
<div class="flex flex-wrap justify-center -m-1.5">
|
||||||
<div class="w-full md:w-0/12">
|
<div class="w-full md:w-auto p-1.5 hover-container">
|
||||||
<div class="lg:container flex flex-wrap justify-center">
|
<div class="flex justify-center md:justify-start">
|
||||||
<div class="md:w-auto hover-container">
|
<div class="relative">
|
||||||
<div class="flex flex-wrap">
|
{{ input_arrow_down_svg | safe }}
|
||||||
<div class="pt-3 px-3 md:w-auto hover-container">
|
<button type="button" id="coin_to_button" class="bg-gray-50 text-gray-900 appearance-none pr-10 pl-5 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none block w-full p-2.5 text-left focus:ring-0 whitespace-nowrap">
|
||||||
<div class="flex">
|
<span id="coin_to_text" class="filter-button-text">Filter {% if sent_offers %}Receiving{% else %}Bids{% endif %}</span>
|
||||||
<button id="coin_to_button" class="bg-gray-50 text-gray-900 appearance-none w-10 dark:bg-gray-500 dark:text-white border-l border-t border-b border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-l-lg flex items-center" disabled></button>
|
</button>
|
||||||
<div class="relative">
|
<div id="coin_to_dropdown" class="multi-select-dropdown bg-gray-50 dark:bg-gray-500 border border-gray-300 dark:border-gray-400 rounded-lg shadow-lg max-h-64 overflow-y-auto" style="display: none; position: absolute; z-index: 1000; min-width: 200px; top: 100%; left: 0;">
|
||||||
{{ input_arrow_down_svg | safe }}
|
<div class="p-2">
|
||||||
<select name="coin_to" id="coin_to" class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-none rounded-r-lg outline-none block w-full p-2.5 focus:ring-0">
|
<label class="flex items-center p-2 hover:bg-gray-200 dark:hover:bg-gray-600 rounded cursor-pointer">
|
||||||
<option value="any" {% if filters.coin_to==-1 %} selected{% endif %}>Filter {% if sent_offers %}Receiving{% else %}Bids{% endif %}</option>
|
<input type="checkbox" name="coin_to" value="any" class="mr-3 coin-to-checkbox" {% if filters.coin_to==-1 %}checked{% endif %}>
|
||||||
{% for c in coins %}
|
<span class="text-sm text-gray-900 dark:text-white">Any (clear all)</span>
|
||||||
<option class="text-sm" value="{{ c[0] }}" {% if filters.coin_to==c[0] %} selected{% endif %} data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}.png">{{ c[1] }}</option>
|
</label>
|
||||||
{% endfor %}
|
{% for c in coins %}
|
||||||
</select>
|
<label class="flex items-center p-2 hover:bg-gray-200 dark:hover:bg-gray-600 rounded cursor-pointer">
|
||||||
</div>
|
<input type="checkbox" name="coin_to" value="{{ c[0] }}" class="mr-3 coin-to-checkbox" {% if filters.coin_to==c[0] %}checked{% endif %}>
|
||||||
<div class="flex items-center">
|
<img src="/static/images/coins/{{ c[1]|replace(" ", "%20") }}.png" class="w-4 h-4 mr-2" alt="{{ c[1] }}">
|
||||||
<div class="w-full md:w-auto p-1.5">
|
<span class="text-sm text-gray-900 dark:text-white">{{ c[1] }}</span>
|
||||||
<p class="text-sm font-heading">{{ arrow_right_svg | safe }}</p>
|
</label>
|
||||||
</div>
|
{% endfor %}
|
||||||
</div>
|
|
||||||
<button id="coin_from_button" class="bg-gray-50 text-gray-900 appearance-none w-10 dark:bg-gray-500 dark:text-white border-l border-t border-b border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-l-lg flex items-center" disabled></button>
|
|
||||||
<div class="relative">
|
|
||||||
{{ input_arrow_down_svg | safe }}
|
|
||||||
<select name="coin_from" id="coin_from" class="bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-none rounded-r-lg outline-none block w-full p-2.5 focus:ring-0">
|
|
||||||
<option value="any" {% if filters.coin_from==-1 %} selected{% endif %}>Filter {% if sent_offers %}Sending{% else %}Offers{% endif %}</option>
|
|
||||||
{% for c in coins_from %}
|
|
||||||
<option class="text-sm" value="{{ c[0] }}" {% if filters.coin_from==c[0] %} selected{% endif %} data-image="/static/images/coins/{{ c[1]|replace(" ", "-") }}.png">{{ c[1] }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% if sent_offers %}
|
|
||||||
<div class="pt-3 px-3 md:w-auto hover-container">
|
|
||||||
<div class="flex">
|
|
||||||
<div class="relative">
|
|
||||||
{{ input_arrow_down_svg | safe }}
|
|
||||||
<select name="status" id="status" class="bg-gray-50 text-gray-900 appearance-none pr-10 pl-5 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none block w-full p-2.5 focus:ring-0">
|
|
||||||
<option value="any" {% if not filters.status %} selected{% endif %}>Filter Status</option>
|
|
||||||
<option value="active" {% if filters.status == 'active' %} selected{% endif %}>Active</option>
|
|
||||||
<option value="expired" {% if filters.status == 'expired' %} selected{% endif %}>Expired</option>
|
|
||||||
<option value="revoked" {% if filters.status == 'revoked' %} selected{% endif %}>Revoked</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pt-3 px-3 md:w-auto hover-container">
|
|
||||||
<div class="flex">
|
|
||||||
<div class="relative">
|
|
||||||
{{ input_arrow_down_svg | safe }}
|
|
||||||
<select name="sent_from" id="sent_from" class="bg-gray-50 text-gray-900 appearance-none pr-10 pl-5 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none block w-full p-2.5 focus:ring-0">
|
|
||||||
<option value="any" {% if not filters.sent_from %} selected{% endif %}>All Offers</option>
|
|
||||||
<option value="public" {% if filters.sent_from == 'public' %} selected{% endif %}>Public</option>
|
|
||||||
<option value="private" {% if filters.sent_from == 'private' %} selected{% endif %}>Private</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full lg:w-auto pt-3 px-3">
|
<div class="flex items-center">
|
||||||
<div class="relative">
|
<div class="w-full md:w-auto p-1.5">
|
||||||
<button type="button" id="clearFilters" class="transition-opacity duration-200 flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm hover:text-white dark:text-white dark:bg-gray-500 bg-coolGray-200 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-coolGray-200 dark:border-gray-400 rounded-md shadow-button focus:ring-0 focus:outline-none" disabled>
|
<p class="text-sm font-heading text-gray-500 dark:text-white">{{ arrow_right_svg | safe }}</p>
|
||||||
<span>Clear Filters</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full lg:w-auto pt-3 px-3">
|
<div class="relative">
|
||||||
<div class="relative">
|
{{ input_arrow_down_svg | safe }}
|
||||||
<button type="button" id="refreshOffers" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white bg-blue-600 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
|
<button type="button" id="coin_from_button" class="bg-gray-50 text-gray-900 appearance-none pr-10 pl-5 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none block w-full p-2.5 text-left focus:ring-0 whitespace-nowrap">
|
||||||
<svg id="refreshIcon" class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<span id="coin_from_text" class="filter-button-text">Filter {% if sent_offers %}Sending{% else %}Offers{% endif %}</span>
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
</button>
|
||||||
</svg>
|
<div id="coin_from_dropdown" class="multi-select-dropdown bg-gray-50 dark:bg-gray-500 border border-gray-300 dark:border-gray-400 rounded-lg shadow-lg max-h-64 overflow-y-auto" style="display: none; position: absolute; z-index: 1000; min-width: 200px; top: 100%; left: 0;">
|
||||||
<span id="refreshText">Refresh</span>
|
<div class="p-2">
|
||||||
</button>
|
<label class="flex items-center p-2 hover:bg-gray-200 dark:hover:bg-gray-600 rounded cursor-pointer">
|
||||||
|
<input type="checkbox" name="coin_from" value="any" class="mr-3 coin-from-checkbox" {% if filters.coin_from==-1 %}checked{% endif %}>
|
||||||
|
<span class="text-sm text-gray-900 dark:text-white">Any (clear all)</span>
|
||||||
|
</label>
|
||||||
|
{% for c in coins_from %}
|
||||||
|
<label class="flex items-center p-2 hover:bg-gray-200 dark:hover:bg-gray-600 rounded cursor-pointer">
|
||||||
|
<input type="checkbox" name="coin_from" value="{{ c[0] }}" class="mr-3 coin-from-checkbox" {% if filters.coin_from==c[0] %}checked{% endif %}>
|
||||||
|
<img src="/static/images/coins/{{ c[1]|replace(" ", "%20") }}.png" class="w-4 h-4 mr-2" alt="{{ c[1] }}">
|
||||||
|
<span class="text-sm text-gray-900 dark:text-white">{{ c[1] }}</span>
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if sent_offers %}
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
{{ input_arrow_down_svg | safe }}
|
||||||
|
<select name="status" id="status" class="bg-gray-50 text-gray-900 appearance-none pr-10 pl-5 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none block w-full p-2.5 focus:ring-0">
|
||||||
|
<option value="any" {% if not filters.status %} selected{% endif %}>Filter Status</option>
|
||||||
|
<option value="active" {% if filters.status == 'active' %} selected{% endif %}>Active</option>
|
||||||
|
<option value="expired" {% if filters.status == 'expired' %} selected{% endif %}>Expired</option>
|
||||||
|
<option value="revoked" {% if filters.status == 'revoked' %} selected{% endif %}>Revoked</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
{{ input_arrow_down_svg | safe }}
|
||||||
|
<select name="auto_accept_type" id="auto_accept_type" class="bg-gray-50 text-gray-900 appearance-none pr-10 pl-5 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none block w-full p-2.5 focus:ring-0">
|
||||||
|
<option value="any" {% if filters.auto_accept_type == 'any' %} selected{% endif %}>Auto Accept Type</option>
|
||||||
|
<option value="0" {% if filters.auto_accept_type == 0 %} selected{% endif %}>Manual</option>
|
||||||
|
<option value="1" {% if filters.auto_accept_type == 1 %} selected{% endif %}>Automatic</option>
|
||||||
|
<option value="2" {% if filters.auto_accept_type == 2 %} selected{% endif %}>Known Identities</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
{{ input_arrow_down_svg | safe }}
|
||||||
|
<select name="sent_from" id="sent_from" class="bg-gray-50 text-gray-900 appearance-none pr-10 pl-5 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none block w-full p-2.5 focus:ring-0">
|
||||||
|
<option value="any" {% if not filters.sent_from %} selected{% endif %}>All Offers</option>
|
||||||
|
<option value="public" {% if filters.sent_from == 'public' %} selected{% endif %}>Public</option>
|
||||||
|
<option value="private" {% if filters.sent_from == 'private' %} selected{% endif %}>Private</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
<button type="button" id="clearFilters" class="transition-opacity duration-200 flex justify-center w-full px-4 py-2.5 font-medium text-sm hover:text-white dark:text-white dark:bg-gray-500 bg-coolGray-200 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-coolGray-200 dark:border-gray-400 rounded-md shadow-button focus:ring-0 focus:outline-none" disabled>
|
||||||
|
<span>Clear Filters</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-full md:w-auto p-1.5">
|
||||||
|
<div class="relative">
|
||||||
|
<button type="button" id="refreshOffers" class="flex justify-center w-full px-4 py-2.5 font-medium text-sm text-white bg-blue-600 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
|
||||||
|
<svg id="refreshIcon" class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||||
|
</svg>
|
||||||
|
<span id="refreshText">Refresh</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div id="selected_coins_container" class="w-full py-3" style="display: none;">
|
||||||
|
<div class="flex flex-wrap justify-center gap-2">
|
||||||
|
<div id="coin_to_badges" class="flex flex-wrap gap-1"></div>
|
||||||
|
<div id="coin_from_badges" class="flex flex-wrap gap-1"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,7 +328,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div class="mt-5 lg:container mx-auto lg:px-0 px-6">
|
<div class="lg:container mx-auto lg:px-0 px-6">
|
||||||
<div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
<div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
|
||||||
<div class="px-0">
|
<div class="px-0">
|
||||||
<div class="w-auto mt-6 overflow-auto lg:overflow-hidden">
|
<div class="w-auto mt-6 overflow-auto lg:overflow-hidden">
|
||||||
|
|||||||
@@ -884,6 +884,7 @@ def page_offers(self, url_split, post_string, sent=False):
|
|||||||
"sort_dir": "desc",
|
"sort_dir": "desc",
|
||||||
"sent_from": "any" if sent is False else "only",
|
"sent_from": "any" if sent is False else "only",
|
||||||
"active": "any",
|
"active": "any",
|
||||||
|
"auto_accept_type": "any",
|
||||||
}
|
}
|
||||||
|
|
||||||
filter_prefix = "page_offers_sent" if sent else "page_offers"
|
filter_prefix = "page_offers_sent" if sent else "page_offers"
|
||||||
@@ -908,6 +909,16 @@ def page_offers(self, url_split, post_string, sent=False):
|
|||||||
sent_from = get_data_entry(form_data, "sent_from")
|
sent_from = get_data_entry(form_data, "sent_from")
|
||||||
ensure(sent_from in ["any", "only"], "Invalid sent filter")
|
ensure(sent_from in ["any", "only"], "Invalid sent filter")
|
||||||
filters["sent_from"] = sent_from
|
filters["sent_from"] = sent_from
|
||||||
|
if have_data_entry(form_data, "auto_accept_type"):
|
||||||
|
auto_accept_type = get_data_entry(form_data, "auto_accept_type")
|
||||||
|
ensure(
|
||||||
|
auto_accept_type in ["any", "0", "1", "2"],
|
||||||
|
"Invalid auto accept type filter",
|
||||||
|
)
|
||||||
|
if auto_accept_type == "any":
|
||||||
|
filters["auto_accept_type"] = "any"
|
||||||
|
else:
|
||||||
|
filters["auto_accept_type"] = int(auto_accept_type)
|
||||||
if have_data_entry(form_data, "active"):
|
if have_data_entry(form_data, "active"):
|
||||||
active_filter = get_data_entry(form_data, "active")
|
active_filter = get_data_entry(form_data, "active")
|
||||||
ensure(
|
ensure(
|
||||||
|
|||||||
Reference in New Issue
Block a user