diff --git a/application/views/bandmap/list.php b/application/views/bandmap/list.php index e604e4b89..b31147b4b 100644 --- a/application/views/bandmap/list.php +++ b/application/views/bandmap/list.php @@ -116,7 +116,9 @@ @@ -227,6 +229,7 @@
+ @@ -293,22 +296,25 @@
- - +
diff --git a/assets/css/bandmap_list.css b/assets/css/bandmap_list.css index 72519375b..5fa506f59 100644 --- a/assets/css/bandmap_list.css +++ b/assets/css/bandmap_list.css @@ -13,6 +13,69 @@ html { padding-right: 0.4em; } +/* Medal badge colors */ +.text-bg-gold { + background-color: #FFD700 !important; + color: #fff !important; + border: 1px solid #FFA500 !important; + text-shadow: 0 0 2px rgba(0,0,0,0.3); +} + +.text-bg-silver { + background-color: #A8A8A8 !important; + color: #fff !important; + border: 1px solid #E0E0E0 !important; +} + +.text-bg-bronze { + background-color: #CD7F32 !important; + color: #fff !important; + border: 1px solid #8B4513 !important; +} + +/* Badge borders for all badge types */ +.badge.text-bg-success { + border: 1px solid #198754 !important; +} + +.badge.text-bg-primary { + border: 1px solid #0a58ca !important; +} + +.badge.text-bg-info { + border: 1px solid #087990 !important; +} + +.badge.text-bg-warning { + border: 1px solid #cc9a06 !important; +} + +.badge.text-bg-danger { + border: 1px solid #b02a37 !important; +} + +.badge.text-bg-dark { + border: 1px solid #000 !important; +} + +.badge.text-bg-secondary { + border: 1px solid #41464b !important; +} + +/* Fix icon alignment in badges */ +.spottable .badge i { + vertical-align: middle; + line-height: 1; + display: inline-block; + text-align: center; + width: 100%; +} + +/* Center medal icons specifically */ +.spottable .badge i.fa-medal { + margin-left: 1px; +} + .fresh { transition: all 500ms ease; --bs-table-bg: #3981b2; @@ -117,16 +180,16 @@ tbody a { .spottable th:nth-child(2), .spottable td:nth-child(2) { width: 53px; } /* Band */ .spottable th:nth-child(3), .spottable td:nth-child(3) { width: 90px; } /* Frequency */ .spottable th:nth-child(4), .spottable td:nth-child(4) { width: 60px; } /* Mode */ -.spottable th:nth-child(5), .spottable td:nth-child(5) { width: 120px; } /* Callsign */ +.spottable th:nth-child(5), .spottable td:nth-child(5) { width: 115px; } /* Callsign (reduced by 5px) */ .spottable th:nth-child(6), .spottable td:nth-child(6) { width: 40px; } /* Continent */ .spottable th:nth-child(7), .spottable td:nth-child(7) { width: 50px; } /* CQ Zone */ .spottable th:nth-child(8), .spottable td:nth-child(8) { width: 50px; } /* Flag */ .spottable th:nth-child(9), .spottable td:nth-child(9) { width: 150px; } /* Entity (DXCC name) */ .spottable th:nth-child(10), .spottable td:nth-child(10) { width: 60px; } /* DXCC Number */ -.spottable th:nth-child(11), .spottable td:nth-child(11) { width: 120px; } /* de Callsign (Spotter) */ +.spottable th:nth-child(11), .spottable td:nth-child(11) { width: 115px; } /* de Callsign (Spotter) (reduced by 5px) */ .spottable th:nth-child(12), .spottable td:nth-child(12) { width: 50px; } /* de Cont */ .spottable th:nth-child(13), .spottable td:nth-child(13) { width: 50px; } /* de CQZ */ -.spottable th:nth-child(14), .spottable td:nth-child(14) { width: 110px; } /* Special (LoTW, POTA, etc) */ +.spottable th:nth-child(14), .spottable td:nth-child(14) { width: 120px; } /* Special (LoTW, POTA, etc) (increased by 10px) */ .spottable th:nth-child(15), .spottable td:nth-child(15) { min-width: 100px; width: 100%; } /* Message - fills remaining space */ /* Hidden class for responsive columns (controlled by JavaScript) */ @@ -158,11 +221,13 @@ tbody a { font-family: "Twemoji Country Flags", "Helvetica", "Comic Sans", sans-serif; font-style: normal; font-variant: normal; + font-size: 20px; + line-height: 1; } .spottable img.emoji { - height: 1.3em; - width: 1.3em; + height: 20px; + width: 20px; margin: 0 .05em 0 .1em; vertical-align: -0.25em; display: inline-block; diff --git a/assets/js/sections/bandmap_list.js b/assets/js/sections/bandmap_list.js index 4c83c196f..529dcf190 100644 --- a/assets/js/sections/bandmap_list.js +++ b/assets/js/sections/bandmap_list.js @@ -84,20 +84,27 @@ $(function() { $('#toggleLotwFilter').removeClass('btn-success').addClass('btn-secondary'); } - // Not Worked button - if (requiredFlags.includes('notworked')) { - $('#toggleNotWorkedFilter').removeClass('btn-secondary').addClass('btn-success'); + // New Continent button + if (requiredFlags.includes('newcontinent')) { + $('#toggleNewContinentFilter').removeClass('btn-secondary').addClass('btn-success'); } else { - $('#toggleNotWorkedFilter').removeClass('btn-success').addClass('btn-secondary'); + $('#toggleNewContinentFilter').removeClass('btn-success').addClass('btn-secondary'); } - // DXCC Needed button - if (cwnValues.length === 1 && cwnValues[0] === 'notwkd') { + // New Country button (previously DXCC Needed) + if (requiredFlags.includes('newcountry')) { $('#toggleDxccNeededFilter').removeClass('btn-secondary').addClass('btn-success'); } else { $('#toggleDxccNeededFilter').removeClass('btn-success').addClass('btn-secondary'); } + // New Callsign button (previously Not Worked) + if (requiredFlags.includes('newcallsign')) { + $('#toggleNewCallsignFilter').removeClass('btn-secondary').addClass('btn-success'); + } else { + $('#toggleNewCallsignFilter').removeClass('btn-success').addClass('btn-secondary'); + } + // Contest button (now in Required Flags) if (requiredFlags.includes('Contest')) { $('#toggleContestFilter').removeClass('btn-secondary').addClass('btn-success'); @@ -221,6 +228,15 @@ $(function() { }); // Continent filter buttons - green if Any or selected, gray if not + // "All" button - green when all continents are selected + let $allContinentsBtn = $('#toggleAllContinentsFilter'); + $allContinentsBtn.removeClass('btn-secondary btn-success'); + if (allContinentsSelected) { + $allContinentsBtn.addClass('btn-success'); + } else { + $allContinentsBtn.addClass('btn-secondary'); + } + let continentButtons = [ { id: '#toggleAfricaFilter', continent: 'AF' }, { id: '#toggleAntarcticaFilter', continent: 'AN' }, @@ -702,8 +718,14 @@ $(function() { if (reqFlag === 'lotw') { if (!single.dxcc_spotted || !single.dxcc_spotted.lotw_user) return; } - if (reqFlag === 'notworked') { - if (single.worked_call) return; // Reject if already worked + if (reqFlag === 'newcontinent') { + if (single.worked_continent !== false) return; // Only new continents + } + if (reqFlag === 'newcountry') { + if (single.worked_dxcc !== false) return; // Only new countries + } + if (reqFlag === 'newcallsign') { + if (single.worked_call !== false) return; // Only new callsigns } if (reqFlag === 'Contest') { if (!single.dxcc_spotted || !single.dxcc_spotted.isContest) return; @@ -811,67 +833,73 @@ $(function() { wked_info = ""; } - // Build LoTW badge with color coding based on last upload age - var lotw_badge = ''; - if (single.dxcc_spotted && single.dxcc_spotted.lotw_user) { - let lclass = ''; - if (single.dxcc_spotted.lotw_user > 365) { - lclass = 'lotw_info_red'; - } else if (single.dxcc_spotted.lotw_user > 30) { - lclass = 'lotw_info_orange'; - } else if (single.dxcc_spotted.lotw_user > 7) { - lclass = 'lotw_info_yellow'; - } - let lotw_title = 'LoTW User. Last upload was ' + single.dxcc_spotted.lotw_user + ' days ago'; - lotw_badge = '' + buildBadge('success ' + lclass, '', lotw_title, 'L') + ''; + // Build LoTW badge with color coding based on last upload age + var lotw_badge = ''; + if (single.dxcc_spotted && single.dxcc_spotted.lotw_user) { + let lclass = ''; + if (single.dxcc_spotted.lotw_user > 365) { + lclass = 'lotw_info_red'; + } else if (single.dxcc_spotted.lotw_user > 30) { + lclass = 'lotw_info_orange'; + } else if (single.dxcc_spotted.lotw_user > 7) { + lclass = 'lotw_info_yellow'; } + let lotw_title = 'LoTW User. Last upload was ' + single.dxcc_spotted.lotw_user + ' days ago'; + lotw_badge = '' + buildBadge('success ' + lclass, 'fa-upload', lotw_title) + ''; +} // Build activity badges (POTA, SOTA, WWFF, IOTA, Contest, Worked) - let activity_flags = ''; if (single.dxcc_spotted && single.dxcc_spotted.pota_ref) { - let pota_title = 'POTA: ' + single.dxcc_spotted.pota_ref; - if (single.dxcc_spotted.pota_mode) { - pota_title += ' (' + single.dxcc_spotted.pota_mode + ')'; - } - pota_title += ' - Click to view on POTA.app'; - let pota_url = 'https://pota.app/#/park/' + single.dxcc_spotted.pota_ref; - activity_flags += '' + buildBadge('success', 'fa-tree', pota_title) + ''; + let activity_flags = ''; + + if (single.dxcc_spotted && single.dxcc_spotted.pota_ref) { + let pota_title = 'POTA: ' + single.dxcc_spotted.pota_ref; + if (single.dxcc_spotted.pota_mode) { + pota_title += ' (' + single.dxcc_spotted.pota_mode + ')'; } + pota_title += ' - Click to view on POTA.app'; + let pota_url = 'https://pota.app/#/park/' + single.dxcc_spotted.pota_ref; + activity_flags += '' + buildBadge('success', 'fa-tree', pota_title) + ''; + } - if (single.dxcc_spotted && single.dxcc_spotted.sota_ref) { - let sota_title = 'SOTA: ' + single.dxcc_spotted.sota_ref + ' - Click to view on SOTL.as'; - let sota_url = 'https://sotl.as/summits/' + single.dxcc_spotted.sota_ref; - activity_flags += '' + buildBadge('primary', 'fa-mountain', sota_title) + ''; + if (single.dxcc_spotted && single.dxcc_spotted.sota_ref) { + let sota_title = 'SOTA: ' + single.dxcc_spotted.sota_ref + ' - Click to view on SOTL.as'; + let sota_url = 'https://sotl.as/summits/' + single.dxcc_spotted.sota_ref; + activity_flags += '' + buildBadge('primary', 'fa-mountain', sota_title) + ''; + } + + if (single.dxcc_spotted && single.dxcc_spotted.wwff_ref) { + let wwff_title = 'WWFF: ' + single.dxcc_spotted.wwff_ref + ' - Click to view on WWFF.co'; + let wwff_url = 'https://wwff.co/directory/?showRef=' + single.dxcc_spotted.wwff_ref; + activity_flags += '' + buildBadge('success', 'fa-leaf', wwff_title) + ''; + } + + if (single.dxcc_spotted && single.dxcc_spotted.iota_ref) { + let iota_title = 'IOTA: ' + single.dxcc_spotted.iota_ref + ' - Click to view on IOTA-World.org'; + let iota_url = 'https://www.iota-world.org/'; + activity_flags += '' + buildBadge('info', 'fa-water', iota_title) + ''; + } + + if (single.dxcc_spotted && single.dxcc_spotted.isContest) { + activity_flags += buildBadge('warning', 'fa-trophy', 'Contest'); + } + + // Add "Fresh" badge for spots less than 5 minutes old + let ageMinutesCheck = single.age || 0; + let isFresh = ageMinutesCheck < 5; + + if (single.worked_call) { + let worked_title = 'Worked Before'; + if (single.last_wked && single.last_wked.LAST_QSO && single.last_wked.LAST_MODE) { + worked_title = 'Worked: ' + single.last_wked.LAST_QSO + ' in ' + single.last_wked.LAST_MODE; } + let worked_badge_type = single.cnfmd_call ? 'success' : 'warning'; + // isLast is true only if fresh badge won't be added + activity_flags += buildBadge(worked_badge_type, 'fa-check-circle', worked_title, null, !isFresh); + } - if (single.dxcc_spotted && single.dxcc_spotted.wwff_ref) { - let wwff_title = 'WWFF: ' + single.dxcc_spotted.wwff_ref + ' - Click to view on WWFF.co'; - let wwff_url = 'https://wwff.co/directory/?showRef=' + single.dxcc_spotted.wwff_ref; - activity_flags += '' + buildBadge('success', 'fa-leaf', wwff_title) + ''; - } if (single.dxcc_spotted && single.dxcc_spotted.iota_ref) { - activity_flags += buildBadge('info', 'fa-island-tropical', 'IOTA: ' + single.dxcc_spotted.iota_ref); - } - - if (single.dxcc_spotted && single.dxcc_spotted.isContest) { - activity_flags += buildBadge('warning', 'fa-trophy', 'Contest'); - } - - // Add "Fresh" badge for spots less than 5 minutes old - let ageMinutesCheck = single.age || 0; - let isFresh = ageMinutesCheck < 5; - - if (single.worked_call) { - let worked_title = 'Worked Before'; - if (single.last_wked && single.last_wked.LAST_QSO && single.last_wked.LAST_MODE) { - worked_title = 'Worked: ' + single.last_wked.LAST_QSO + ' in ' + single.last_wked.LAST_MODE; - } - let worked_badge_type = single.cnfmd_call ? 'success' : 'warning'; - // isLast is true only if fresh badge won't be added - activity_flags += buildBadge(worked_badge_type, 'fa-check-circle', worked_title, null, !isFresh); - } - - if (isFresh) { - activity_flags += buildBadge('danger', 'fa-bolt', 'Fresh spot (< 5 minutes old)', null, true); - } // Build table row array + if (isFresh) { + activity_flags += buildBadge('danger', 'fa-bolt', 'Fresh spot (< 5 minutes old)', null, true); + } // Build table row array data[0] = []; // Age column: show age in minutes with auto-update attribute let ageMinutes = single.age || 0; let spotTimestamp = single.when ? new Date(single.when).getTime() : Date.now(); @@ -935,14 +963,25 @@ $(function() { // de Cont column: spotter's continent data[0].push(single.dxcc_spotter.cont || ''); - // de CQZ column: spotter's CQ Zone - data[0].push(single.dxcc_spotter.cqz || ''); + // de CQZ column: spotter's CQ Zone + data[0].push(single.dxcc_spotter.cqz || ''); - // Special column: combine LoTW and activity badges - let flags_column = lotw_badge + activity_flags; - data[0].push(flags_column); + // Build medal badge - show only highest priority: continent > country > callsign + let medals = ''; + if (single.worked_continent === false) { + // New Continent (not worked before) - Gold medal + medals += buildBadge('gold', 'fa-medal', 'New Continent'); + } else if (single.worked_dxcc === false) { + // New DXCC (not worked before) - Silver medal + medals += buildBadge('silver', 'fa-medal', 'New Country'); + } else if (single.worked_call === false) { + // New Callsign (not worked before) - Bronze medal + medals += buildBadge('bronze', 'fa-medal', 'New Callsign'); + } - // Message column + // Special column: combine medals, LoTW and activity badges + let flags_column = medals + lotw_badge + activity_flags; + data[0].push(flags_column); // Message column data[0].push(single.message || ''); // Add row to table (with "fresh" class for new spots animation) @@ -1040,8 +1079,14 @@ $(function() { if (reqFlag === 'lotw') { if (!spot.dxcc_spotted || !spot.dxcc_spotted.lotw_user) return; } - if (reqFlag === 'notworked') { - if (spot.worked_call) return; + if (reqFlag === 'newcontinent') { + if (spot.worked_continent !== false) return; + } + if (reqFlag === 'newcountry') { + if (spot.worked_dxcc !== false) return; + } + if (reqFlag === 'newcallsign') { + if (spot.worked_call !== false) return; } if (reqFlag === 'Contest') { if (!spot.dxcc_spotted || !spot.dxcc_spotted.isContest) return; @@ -1317,9 +1362,9 @@ $(function() { // isLast: if true, uses margin: 0 instead of negative margin function buildBadge(type, icon, title, text = null, isLast = false) { const margin = isLast ? '0' : '0 2px 0 0'; - const fontSize = text ? '0.7rem' : '0.65rem'; - const content = text ? text : ''; - return '' + content + ''; + const fontSize = text ? '0.75rem' : '0.7rem'; + const content = text ? text : ''; + return '' + content + ''; } // Map frequency (in kHz) to ham band name @@ -2276,6 +2321,24 @@ $(function() { applyFilters(false); }); + // "All" continents button - select all continents (de continent) + $('#toggleAllContinentsFilter').on('click', function() { + // Always set to "Any" to show "Any" selected in the filter popup + let currentValues = ['Any']; + + $('#decontSelect').val(currentValues).trigger('change'); + syncQuickFilterButtons(); + + // Update badge counts immediately (before debounced filter application) + updateBandCountBadges(); + + // Debounce the filter application (3 second cooldown) + clearTimeout(decontFilterTimeout); + decontFilterTimeout = setTimeout(function() { + applyFilters(false); + }, 3000); + }); + // Continent filter buttons (spotter's continent - de continent) $('#toggleAfricaFilter').on('click', function() { let currentValues = $('#decontSelect').val() || []; @@ -2493,22 +2556,22 @@ $(function() { applyFilters(false); }); - // Toggle Not Worked Before filter - $('#toggleNotWorkedFilter').on('click', function() { + // Toggle New Continent filter + $('#toggleNewContinentFilter').on('click', function() { let currentValues = $('#requiredFlags').val() || []; let btn = $(this); // Remove "None" if present currentValues = currentValues.filter(v => v !== 'None'); - if (currentValues.includes('notworked')) { - // Remove Not Worked filter - currentValues = currentValues.filter(v => v !== 'notworked'); + if (currentValues.includes('newcontinent')) { + // Remove New Continent filter + currentValues = currentValues.filter(v => v !== 'newcontinent'); if (currentValues.length === 0) currentValues = ['None']; btn.removeClass('btn-success').addClass('btn-secondary'); } else { - // Add Not Worked filter - currentValues.push('notworked'); + // Add New Continent filter + currentValues.push('newcontinent'); btn.removeClass('btn-secondary').addClass('btn-success'); } @@ -2516,22 +2579,49 @@ $(function() { applyFilters(false); }); - // Toggle DXCC Needed filter (not worked DXCC) + // Toggle New Country filter (previously DXCC Needed) $('#toggleDxccNeededFilter').on('click', function() { - let currentValues = $('#cwnSelect').val() || []; + let currentValues = $('#requiredFlags').val() || []; let btn = $(this); - if (currentValues.length === 1 && currentValues[0] === 'notwkd') { - // Remove DXCC filter - reset to All - currentValues = ['All']; + // Remove "None" if present + currentValues = currentValues.filter(v => v !== 'None'); + + if (currentValues.includes('newcountry')) { + // Remove New Country filter + currentValues = currentValues.filter(v => v !== 'newcountry'); + if (currentValues.length === 0) currentValues = ['None']; btn.removeClass('btn-success').addClass('btn-secondary'); } else { - // Set DXCC filter to Not Worked only - currentValues = ['notwkd']; + // Add New Country filter + currentValues.push('newcountry'); btn.removeClass('btn-secondary').addClass('btn-success'); } - $('#cwnSelect').val(currentValues).trigger('change'); + $('#requiredFlags').val(currentValues).trigger('change'); + applyFilters(false); + }); + + // Toggle New Callsign filter (previously Not Worked Before) + $('#toggleNewCallsignFilter').on('click', function() { + let currentValues = $('#requiredFlags').val() || []; + let btn = $(this); + + // Remove "None" if present + currentValues = currentValues.filter(v => v !== 'None'); + + if (currentValues.includes('newcallsign')) { + // Remove New Callsign filter + currentValues = currentValues.filter(v => v !== 'newcallsign'); + if (currentValues.length === 0) currentValues = ['None']; + btn.removeClass('btn-success').addClass('btn-secondary'); + } else { + // Add New Callsign filter + currentValues.push('newcallsign'); + btn.removeClass('btn-secondary').addClass('btn-success'); + } + + $('#requiredFlags').val(currentValues).trigger('change'); applyFilters(false); });