diff --git a/application/views/bandmap/list.php b/application/views/bandmap/list.php
index affae57cf..9c1c8c62d 100644
--- a/application/views/bandmap/list.php
+++ b/application/views/bandmap/list.php
@@ -4,7 +4,6 @@
var cat_timeout_interval = "optionslib->get_option('cat_timeout_interval'); ?>";
var dxcluster_maxage = optionslib->get_option('dxcluster_maxage') ?? 60; ?>;
var custom_date_format = "";
- var popup_warning = "= __("Pop-up was blocked! Please allow pop-ups for this site permanently."); ?>";
// Detect OS for proper keyboard shortcuts
var isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
@@ -12,6 +11,8 @@
var lang_click_to_prepare_logging = "= __("Click to prepare logging."); ?> (" + modKey + "+Click = __("to tune frequency"); ?>)";
// Bandmap toast messages
+ var lang_bandmap_popup_blocked = "= __("Pop-up Blocked"); ?>";
+ var lang_bandmap_popup_warning = "= __("Pop-up was blocked! Please allow pop-ups for this site permanently."); ?>";
var lang_bandmap_cat_required = "= __("CAT Control Required"); ?>";
var lang_bandmap_enable_cat = "= __("Enable CAT Control to tune the radio"); ?>";
var lang_bandmap_clear_filters = "= __("Clear Filters"); ?>";
@@ -36,6 +37,45 @@
var lang_bandmap_modes_applied = "= __("Modes applied. Band filter preserved (CAT Control is active)"); ?>";
var lang_bandmap_favorites_applied = "= __("Applied your favorite bands and modes"); ?>";
+ // Bandmap filter status messages
+ var lang_bandmap_loading_data = "= __("Loading data from DX Cluster"); ?>";
+ var lang_bandmap_last_fetched = "= __("Last fetched for"); ?>";
+ var lang_bandmap_max_age = "= __("Max Age"); ?>";
+ var lang_bandmap_fetched_at = "= __("Fetched at"); ?>";
+ var lang_bandmap_next_update = "= __("Next update in"); ?>";
+ var lang_bandmap_minutes = "= __("minutes"); ?>";
+ var lang_bandmap_seconds = "= __("seconds"); ?>";
+
+ // Bandmap filter labels
+ var lang_bandmap_not_worked = "= __("Not worked"); ?>";
+ var lang_bandmap_lotw_user = "= __("LoTW User"); ?>";
+ var lang_bandmap_new_callsign = "= __("New Callsign"); ?>";
+ var lang_bandmap_new_continent = "= __("New Continent"); ?>";
+ var lang_bandmap_new_country = "= __("New Country"); ?>";
+ var lang_bandmap_worked_before = "= __("Worked Before"); ?>";
+ var lang_bandmap_confirmed = "= __("Confirmed"); ?>";
+
+ // Bandmap tooltip messages
+ var lang_bandmap_fresh_spot = "= __("Fresh spot (< 5 minutes old)"); ?>";
+ var lang_bandmap_click_view_qrz = "= __("Click to view"); ?>";
+ var lang_bandmap_on_qrz = "= __("on QRZ.com"); ?>";
+ var lang_bandmap_see_details = "= __("See details for"); ?>";
+ var lang_bandmap_worked_on = "= __("Worked on"); ?>";
+ var lang_bandmap_not_worked_band = "= __("Not worked on this band"); ?>";
+
+ // Bandmap UI messages
+ var lang_bandmap_exit_fullscreen = "= __("Exit Fullscreen"); ?>";
+ var lang_bandmap_toggle_fullscreen = "= __("Toggle Fullscreen"); ?>";
+ var lang_bandmap_cat_band_control = "= __("Band filtering is controlled by your radio when CAT Control is enabled"); ?>";
+ var lang_bandmap_click_to_qso = "= __("Click to prepare logging"); ?>";
+ var lang_bandmap_ctrl_click_tune = "= __("to tune frequency"); ?>";
+ var lang_bandmap_requires_cat = "= __("(requires CAT Control)"); ?>";
+ var lang_bandmap_spotter = "= __("Spotter"); ?>";
+ var lang_bandmap_comment = "= __("Comment"); ?>";
+ var lang_bandmap_age = "= __("Age"); ?>";
+ var lang_bandmap_time = "= __("Time"); ?>";
+
+
// DataTables messages
var lang_bandmap_loading_spots = "= __("Loading spots..."); ?>";
var lang_bandmap_no_spots_found = "= __("No spots found"); ?>";
@@ -50,8 +90,6 @@
-
-
diff --git a/assets/js/radiohelpers.js b/assets/js/radiohelpers.js
index f98857e2a..4854554fd 100644
--- a/assets/js/radiohelpers.js
+++ b/assets/js/radiohelpers.js
@@ -1,69 +1,184 @@
-function frequencyToBand(frequency) {
- result = parseInt(frequency);
+/**
+ * Convert frequency to ham radio band name
+ * @param {number} frequency - Frequency value
+ * @param {string} unit - Unit of frequency: 'Hz' (default) or 'kHz'
+ * @returns {string} Band name (e.g., '20m', '2m', '70cm') or 'All' if not in a known band
+ */
+function frequencyToBand(frequency, unit = 'Hz') {
+ // Convert to Hz if input is in kHz
+ const freqHz = (unit.toLowerCase() === 'khz') ? frequency * 1000 : parseInt(frequency);
- if(result >= 14000000 && result <= 14400000) {
- return '20m';
+ // MF/HF Bands
+ if (freqHz >= 1800000 && freqHz <= 2000000) return '160m';
+ if (freqHz >= 3500000 && freqHz <= 4000000) return '80m';
+ if (freqHz >= 5250000 && freqHz <= 5450000) return '60m';
+ if (freqHz >= 7000000 && freqHz <= 7300000) return '40m';
+ if (freqHz >= 10100000 && freqHz <= 10150000) return '30m';
+ if (freqHz >= 14000000 && freqHz <= 14350000) return '20m';
+ if (freqHz >= 18068000 && freqHz <= 18168000) return '17m';
+ if (freqHz >= 21000000 && freqHz <= 21450000) return '15m';
+ if (freqHz >= 24890000 && freqHz <= 24990000) return '12m';
+ if (freqHz >= 28000000 && freqHz <= 29700000) return '10m';
+
+ // VHF Bands
+ if (freqHz >= 50000000 && freqHz <= 54000000) return '6m';
+ if (freqHz >= 70000000 && freqHz <= 71000000) return '4m';
+ if (freqHz >= 144000000 && freqHz <= 148000000) return '2m';
+ if (freqHz >= 222000000 && freqHz <= 225000000) return '1.25m';
+
+ // UHF Bands
+ if (freqHz >= 420000000 && freqHz <= 450000000) return '70cm';
+ if (freqHz >= 902000000 && freqHz <= 928000000) return '33cm';
+ if (freqHz >= 1240000000 && freqHz <= 1300000000) return '23cm';
+
+ // SHF Bands
+ if (freqHz >= 2300000000 && freqHz <= 2450000000) return '13cm';
+ if (freqHz >= 3300000000 && freqHz <= 3500000000) return '9cm';
+ if (freqHz >= 5650000000 && freqHz <= 5925000000) return '6cm';
+ if (freqHz >= 10000000000 && freqHz <= 10500000000) return '3cm';
+ if (freqHz >= 24000000000 && freqHz <= 24250000000) return '1.25cm';
+ if (freqHz >= 47000000000 && freqHz <= 47200000000) return '6mm';
+ if (freqHz >= 75500000000 && freqHz <= 81000000000) return '4mm';
+ if (freqHz >= 119980000000 && freqHz <= 120020000000) return '2.5mm';
+ if (freqHz >= 142000000000 && freqHz <= 149000000000) return '2mm';
+ if (freqHz >= 241000000000 && freqHz <= 250000000000) return '1mm';
+
+ return 'All';
+}
+
+/**
+ * Alias for backward compatibility - converts frequency in kHz to band name
+ * @deprecated Use frequencyToBand(frequency, 'kHz') instead
+ * @param {number} freq_khz - Frequency in kilohertz
+ * @returns {string} Band name or 'All'
+ */
+function frequencyToBandKhz(freq_khz) {
+ return frequencyToBand(freq_khz, 'kHz');
+}
+
+/**
+ * Determine appropriate radio mode based on spot mode and frequency
+ * @param {string} spotMode - Mode from DX spot (e.g., 'CW', 'SSB', 'FT8')
+ * @param {number} freqHz - Frequency in Hz
+ * @returns {string} Radio mode (CW, USB, LSB, RTTY, AM, FM)
+ */
+function determineRadioMode(spotMode, freqHz) {
+ if (!spotMode) {
+ // No mode specified - use frequency to determine USB/LSB
+ return freqHz < 10000000 ? 'LSB' : 'USB'; // Below 10 MHz = LSB, above = USB
}
- else if(result >= 18000000 && result <= 19000000) {
- return '17m';
+
+ const modeUpper = spotMode.toUpperCase();
+
+ // CW modes
+ if (modeUpper === 'CW' || modeUpper === 'A1A') {
+ return 'CW';
}
- else if(result >= 1810000 && result <= 2000000) {
- return '160m';
+
+ // Digital modes - use RTTY as standard digital mode
+ const digitalModes = ['FT8', 'FT4', 'PSK', 'RTTY', 'JT65', 'JT9', 'WSPR', 'FSK', 'MFSK', 'OLIVIA', 'CONTESTI', 'DOMINO'];
+ for (let i = 0; i < digitalModes.length; i++) {
+ if (modeUpper.indexOf(digitalModes[i]) !== -1) {
+ return 'RTTY';
+ }
}
- else if(result >= 3000000 && result <= 4000000) {
- return '80m';
+
+ // Phone modes or SSB - determine USB/LSB based on frequency
+ if (modeUpper.indexOf('SSB') !== -1 || modeUpper.indexOf('PHONE') !== -1 ||
+ modeUpper === 'USB' || modeUpper === 'LSB' || modeUpper === 'AM' || modeUpper === 'FM') {
+ // If already USB or LSB, use as-is
+ if (modeUpper === 'USB') return 'USB';
+ if (modeUpper === 'LSB') return 'LSB';
+ if (modeUpper === 'AM') return 'AM';
+ if (modeUpper === 'FM') return 'FM';
+
+ // Otherwise determine based on frequency
+ return freqHz < 10000000 ? 'LSB' : 'USB';
}
- else if(result >= 5250000 && result <= 5450000) {
- return '60m';
+
+ // Default: use frequency to determine USB/LSB
+ return freqHz < 10000000 ? 'LSB' : 'USB';
+}
+
+/**
+ * Ham radio band groupings by frequency range
+ * MF = Medium Frequency (300 kHz - 3 MHz) - 160m
+ * HF = High Frequency (3-30 MHz) - 80m through 10m
+ * VHF = Very High Frequency (30-300 MHz) - 6m through 1.25m
+ * UHF = Ultra High Frequency (300 MHz-3 GHz) - 70cm through 23cm
+ * SHF = Super High Frequency (3-30 GHz) - 13cm and above
+ */
+const BAND_GROUPS = {
+ 'MF': ['160m'],
+ 'HF': ['80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m'],
+ 'VHF': ['6m', '4m', '2m', '1.25m'],
+ 'UHF': ['70cm', '33cm', '23cm'],
+ 'SHF': ['13cm', '9cm', '6cm', '3cm', '1.25cm', '6mm', '4mm', '2.5mm', '2mm', '1mm']
+};
+
+/**
+ * Map individual band to its band group (MF, HF, VHF, UHF, SHF)
+ * @param {string} band - Band identifier (e.g., '20m', '2m', '70cm', '13cm')
+ * @returns {string|null} Band group name or null if band not found
+ */
+function getBandGroup(band) {
+ for (const [group, bands] of Object.entries(BAND_GROUPS)) {
+ if (bands.includes(band)) return group;
}
- else if(result >= 7000000 && result <= 7500000) {
- return '40m';
+ return null;
+}
+
+/**
+ * Get all bands in a band group
+ * @param {string} group - Band group name (MF, HF, VHF, UHF, or SHF)
+ * @returns {Array} Array of band identifiers or empty array if group not found
+ */
+function getBandsInGroup(group) {
+ return BAND_GROUPS[group] || [];
+}
+
+/**
+ * Categorize amateur radio mode into phone/cw/digi for filtering
+ * @param {string} mode - Mode name (e.g., 'USB', 'CW', 'FT8', 'phone')
+ * @returns {string|null} Mode category: 'phone', 'cw', 'digi', or null if unknown
+ */
+function getModeCategory(mode) {
+ if (!mode) return null;
+
+ const modeLower = mode.toLowerCase();
+
+ // Check if already a category
+ if (['phone', 'cw', 'digi'].includes(modeLower)) {
+ return modeLower;
}
- else if(result >= 10000000 && result <= 11000000) {
- return '30m';
+
+ const modeUpper = mode.toUpperCase();
+
+ // CW modes
+ if (['CW', 'CWR', 'A1A'].includes(modeUpper) || modeLower.includes('cw')) {
+ return 'cw';
}
- else if(result >= 21000000 && result <= 21600000) {
- return '15m';
+
+ // Phone modes (voice)
+ if (['SSB', 'LSB', 'USB', 'FM', 'AM', 'DV', 'PHONE', 'C3E', 'J3E'].includes(modeUpper)) {
+ return 'phone';
}
- else if(result >= 24000000 && result <= 25000000) {
- return '12m';
+
+ // Digital modes
+ const digitalModes = ['RTTY', 'PSK', 'PSK31', 'PSK63', 'FT8', 'FT4', 'JT65', 'JT9', 'MFSK',
+ 'OLIVIA', 'CONTESTIA', 'HELL', 'THROB', 'SSTV', 'FAX', 'PACKET', 'PACTOR',
+ 'THOR', 'DOMINO', 'MT63', 'ROS', 'WSPR', 'VARA', 'ARDOP', 'WINMOR'];
+ if (digitalModes.includes(modeUpper)) {
+ return 'digi';
}
- else if(result >= 28000000 && result <= 30000000) {
- return '10m';
- }
- else if(result >= 50000000 && result <= 56000000) {
- return '6m';
- }
- else if(result >= 70000000 && result <= 75000000) {
- return '4m';
- }
- else if(result >= 144000000 && result <= 148000000) {
- return '2m';
- }
- else if(result >= 219000000 && result <= 225000000) {
- return '1.25m';
- }
- else if(result >= 420000000 && result <= 450000000) {
- return '70cm';
- }
- else if(result >= 902000000 && result <= 928000000) {
- return '33cm';
- }
- else if(result >= 1200000000 && result <= 1600000000) {
- return '23cm';
- }
- else if(result >= 2300000000 && result <= 2890800000) {
- return '13cm';
- }
- else if(result >= 3300000000 && result <= 3500000000) {
- return '9cm';
- }
- else if(result >= 5650000000 && result <= 5925000000) {
- return '6cm';
- }
- else if(result >= 10000000000 && result <= 10525000000) {
- return '3cm';
+
+ // Check for digital mode substrings
+ if (modeLower.includes('ft') || modeLower.includes('psk') || modeLower.includes('rtty') ||
+ modeLower.includes('jt') || modeLower === 'digi' || modeLower === 'data') {
+ return 'digi';
}
+
+ return null;
}
function catmode(mode) {
diff --git a/assets/js/sections/bandmap_list.js b/assets/js/sections/bandmap_list.js
index 4a568a6ad..2e698ca57 100644
--- a/assets/js/sections/bandmap_list.js
+++ b/assets/js/sections/bandmap_list.js
@@ -2,21 +2,16 @@
* @fileoverview DX Cluster Bandmap for Wavelog
* @version 2.0.0
* @author Wavelog Development Team
- * @date 2024-2025
*
* @description
* Advanced real-time DX spot filtering and display system with intelligent
* client/server architecture, smart caching, CAT control integration, and
* comprehensive multi-criteria filtering capabilities.
- *
- * @requires jQuery 3.x+
- * @requires DataTables 1.13+
- * @requires Bootstrap 5.x
+
* @requires base_url (global from Wavelog)
* @requires dxcluster_provider (global from Wavelog)
* @requires dxcluster_maxage (global from Wavelog)
* @requires custom_date_format (global from Wavelog)
- * @requires popup_warning (global from Wavelog)
* @requires cat_timeout_interval (global from Wavelog)
* @requires lang_* translation variables (global from Wavelog)
*
@@ -44,25 +39,59 @@
'use strict';
// ========================================
-// CONFIGURATION
+// CONFIGURATION & CONSTANTS
// ========================================
+
const SPOT_REFRESH_INTERVAL = 60; // Auto-refresh interval in seconds
-// Configure DataTables error mode BEFORE document ready
-// This prevents alert dialogs from showing
-if (typeof jQuery !== 'undefined' && jQuery.fn && jQuery.fn.dataTable) {
- jQuery.fn.dataTable.ext.errMode = function(settings, helpPage, message) {
- console.error('=== DataTables Error (pre-init) ===');
- console.error('Message:', message);
- console.error('Help page:', helpPage);
- console.error('Settings:', settings);
- };
-} else {
- console.warn('DataTables not found at pre-init stage');
-}
+// Filter button configurations
+const BAND_BUTTONS = [
+ { id: '#toggle160mFilter', band: '160m' },
+ { id: '#toggle80mFilter', band: '80m' },
+ { id: '#toggle60mFilter', band: '60m' },
+ { id: '#toggle40mFilter', band: '40m' },
+ { id: '#toggle30mFilter', band: '30m' },
+ { id: '#toggle20mFilter', band: '20m' },
+ { id: '#toggle17mFilter', band: '17m' },
+ { id: '#toggle15mFilter', band: '15m' },
+ { id: '#toggle12mFilter', band: '12m' },
+ { id: '#toggle10mFilter', band: '10m' }
+];
+
+const BAND_GROUP_BUTTONS = [
+ { id: '#toggleVHFFilter', group: 'VHF' },
+ { id: '#toggleUHFFilter', group: 'UHF' },
+ { id: '#toggleSHFFilter', group: 'SHF' }
+];
+
+const MODE_BUTTONS = [
+ { id: '#toggleCwFilter', mode: 'cw', icon: 'fa-wave-square' },
+ { id: '#toggleDigiFilter', mode: 'digi', icon: 'fa-keyboard' },
+ { id: '#togglePhoneFilter', mode: 'phone', icon: 'fa-microphone' }
+];
+
+const CONTINENT_BUTTONS = [
+ { id: '#toggleAfricaFilter', continent: 'AF' },
+ { id: '#toggleAntarcticaFilter', continent: 'AN' },
+ { id: '#toggleAsiaFilter', continent: 'AS' },
+ { id: '#toggleEuropeFilter', continent: 'EU' },
+ { id: '#toggleNorthAmericaFilter', continent: 'NA' },
+ { id: '#toggleOceaniaFilter', continent: 'OC' },
+ { id: '#toggleSouthAmericaFilter', continent: 'SA' }
+];
+
+const GEO_FLAGS = ['POTA', 'SOTA', 'IOTA', 'WWFF'];
+
+// ========================================
+// MAIN APPLICATION
+// ========================================
$(function() {
+ // ========================================
+ // DATATABLES ERROR HANDLING
+ // ========================================
+
// Configure DataTables to log errors to console instead of showing alert dialogs
// MUST be set before any DataTable is initialized
if ($.fn.dataTable) {
@@ -81,33 +110,72 @@ $(function() {
console.error('$.fn.dataTable not available!');
}
+ // ========================================
+ // UTILITY FUNCTIONS
+ // ========================================
+
+ /**
+ * Get current values from all filter selects
+ * @returns {Object} Object containing all filter values
+ */
+ function getAllFilterValues() {
+ return {
+ cwn: $('#cwnSelect').val() || [],
+ deCont: $('#decontSelect').val() || [],
+ continent: $('#continentSelect').val() || [],
+ band: $('#band').val() || [],
+ mode: $('#mode').val() || [],
+ additionalFlags: $('#additionalFlags').val() || [],
+ requiredFlags: ($('#requiredFlags').val() || []).filter(v => v !== 'None')
+ };
+ }
+
+ /**
+ * Check if a filter array contains default "All" or "Any" value
+ * @param {Array} values - Filter values array
+ * @param {string} defaultValue - Default value to check ('All' or 'Any')
+ * @returns {boolean} True if array contains only the default value
+ */
+ function isDefaultFilterValue(values, defaultValue = 'All') {
+ return values.length === 1 && values.includes(defaultValue);
+ }
+
+ /**
+ * Update button visual state (active/inactive)
+ * @param {string} buttonId - jQuery selector for button
+ * @param {boolean} isActive - Whether button should appear active
+ */
+ function updateButtonState(buttonId, isActive) {
+ const $btn = $(buttonId);
+ $btn.removeClass('btn-secondary btn-success');
+ $btn.addClass(isActive ? 'btn-success' : 'btn-secondary');
+ }
+
// ========================================
// FILTER UI MANAGEMENT
// ========================================
- // Check if any filters are active (not default "All"/"Any" values)
+ /**
+ * Check if any filters are active (not default "All"/"Any" values)
+ * @returns {boolean} True if any non-default filters are applied
+ */
function areFiltersApplied() {
- let cwnVal = $('#cwnSelect').val() || [];
- let decontVal = $('#decontSelect').val() || [];
- let continentVal = $('#continentSelect').val() || [];
- let bandVal = $('#band').val() || [];
- let modeVal = $('#mode').val() || [];
- let flagsVal = $('#additionalFlags').val() || [];
- let requiredVal = $('#requiredFlags').val() || [];
+ const filters = getAllFilterValues();
- // Check if anything is selected besides "All"/"Any"/"None"
- let isDefaultCwn = cwnVal.length === 1 && cwnVal.includes('All');
- let isDefaultDecont = decontVal.length === 1 && decontVal.includes('Any');
- let isDefaultContinent = continentVal.length === 1 && continentVal.includes('Any');
- let isDefaultBand = bandVal.length === 1 && bandVal.includes('All');
- let isDefaultMode = modeVal.length === 1 && modeVal.includes('All');
- let isDefaultFlags = flagsVal.length === 1 && flagsVal.includes('All');
- let isDefaultRequired = requiredVal.length === 0 || (requiredVal.length === 1 && requiredVal.includes('None'));
+ const isDefaultCwn = isDefaultFilterValue(filters.cwn);
+ const isDefaultDecont = isDefaultFilterValue(filters.deCont, 'Any');
+ const isDefaultContinent = isDefaultFilterValue(filters.continent, 'Any');
+ const isDefaultBand = isDefaultFilterValue(filters.band);
+ const isDefaultMode = isDefaultFilterValue(filters.mode);
+ const isDefaultFlags = isDefaultFilterValue(filters.additionalFlags);
+ const isDefaultRequired = filters.requiredFlags.length === 0;
return !(isDefaultCwn && isDefaultDecont && isDefaultContinent && isDefaultBand && isDefaultMode && isDefaultFlags && isDefaultRequired);
}
- // Update filter icon based on whether filters are active
+ /**
+ * Update filter icon based on whether filters are active
+ */
function updateFilterIcon() {
if (areFiltersApplied()) {
$('#filterIcon').removeClass('fa-filter').addClass('fa-filter-circle-xmark text-success');
@@ -116,275 +184,207 @@ $(function() {
}
}
- // Sync quick filter button states with their corresponding dropdown values
- function syncQuickFilterButtons() {
- let requiredFlags = ($('#requiredFlags').val() || []).filter(v => v !== 'None'); // Remove "None"
- let additionalFlags = $('#additionalFlags').val() || [];
- let cwnValues = $('#cwnSelect').val() || [];
- let modeValues = $('#mode').val() || [];
- let bandValues = $('#band').val() || [];
- let decontValues = $('#decontSelect').val() || [];
+ /**
+ * Toggle a value in a multi-select filter
+ * @param {string} selectId - jQuery selector for the select element
+ * @param {string} value - Value to toggle in the selection
+ * @param {string} defaultValue - Default value to restore if selection becomes empty (default: 'All')
+ * @param {boolean} applyFiltersAfter - Whether to trigger filter application (default: true)
+ * @param {number} debounceMs - Debounce delay in milliseconds (default: 0 for no debounce)
+ * @param {boolean} updateBadges - Whether to call updateBandCountBadges() (default: false)
+ */
+ function toggleFilterValue(selectId, value, defaultValue = 'All', applyFiltersAfter = true, debounceMs = 0, updateBadges = false) {
+ let currentValues = $(selectId).val() || [];
- // LoTW button
- if (requiredFlags.includes('lotw')) {
- $('#toggleLotwFilter').removeClass('btn-secondary').addClass('btn-success');
- } else {
- $('#toggleLotwFilter').removeClass('btn-success').addClass('btn-secondary');
+ // Remove default value if present
+ if (currentValues.includes(defaultValue)) {
+ currentValues = currentValues.filter(v => v !== defaultValue);
}
- // New Continent button
- if (requiredFlags.includes('newcontinent')) {
- $('#toggleNewContinentFilter').removeClass('btn-secondary').addClass('btn-success');
+ // Toggle the target value
+ if (currentValues.includes(value)) {
+ currentValues = currentValues.filter(v => v !== value);
+ // Restore default if empty
+ if (currentValues.length === 0) {
+ currentValues = [defaultValue];
+ }
} else {
- $('#toggleNewContinentFilter').removeClass('btn-success').addClass('btn-secondary');
+ currentValues.push(value);
}
- // 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');
+ // Update selectize
+ $(selectId).val(currentValues).trigger('change');
+ syncQuickFilterButtons();
+
+ // Update badge counts if requested
+ if (updateBadges && typeof updateBandCountBadges === 'function') {
+ updateBandCountBadges();
}
- // 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');
- } else {
- $('#toggleContestFilter').removeClass('btn-success').addClass('btn-secondary');
- }
-
- // Geo Hunter button (stays in Additional Flags)
- let geoFlags = ['POTA', 'SOTA', 'IOTA', 'WWFF'];
- let hasGeoFlag = geoFlags.some(flag => additionalFlags.includes(flag));
- if (hasGeoFlag) {
- $('#toggleGeoHunterFilter').removeClass('btn-secondary').addClass('btn-success');
- } else {
- $('#toggleGeoHunterFilter').removeClass('btn-success').addClass('btn-secondary');
- }
-
- // Fresh filter button
- if (additionalFlags.includes('Fresh')) {
- $('#toggleFreshFilter').removeClass('btn-secondary').addClass('btn-success');
- } else {
- $('#toggleFreshFilter').removeClass('btn-success').addClass('btn-secondary');
- }
-
- // CW mode button
- if (modeValues.includes('cw')) {
- $('#toggleCwFilter').removeClass('btn-secondary').addClass('btn-success');
- } else {
- $('#toggleCwFilter').removeClass('btn-success').addClass('btn-secondary');
- }
-
- // Digi mode button
- if (modeValues.includes('digi')) {
- $('#toggleDigiFilter').removeClass('btn-secondary').addClass('btn-success');
- } else {
- $('#toggleDigiFilter').removeClass('btn-success').addClass('btn-secondary');
- }
-
- // Phone mode button
- if (modeValues.includes('phone')) {
- $('#togglePhoneFilter').removeClass('btn-secondary').addClass('btn-success');
- } else {
- $('#togglePhoneFilter').removeClass('btn-success').addClass('btn-secondary');
- }
-
- // Check if "All" is selected for bands, modes, and continents
- let allBandsSelected = bandValues.length === 1 && bandValues.includes('All');
-
- // For modes: check if "All" is selected OR if all individual modes are selected
- let allModesSelected = (modeValues.length === 1 && modeValues.includes('All')) ||
- (modeValues.includes('cw') && modeValues.includes('digi') && modeValues.includes('phone'));
-
- // For continents: check if "Any" is selected OR if all continents are selected
- // All continents: AF, AN, AS, EU, NA, OC, SA (7 continents)
- let allContinentsSelected = (decontValues.length === 1 && decontValues.includes('Any')) ||
- (decontValues.includes('AF') && decontValues.includes('AN') &&
- decontValues.includes('AS') && decontValues.includes('EU') &&
- decontValues.includes('NA') && decontValues.includes('OC') &&
- decontValues.includes('SA'));
-
- // Band filter buttons - green if All, orange if specific band, gray if not selected
- // Always update colors, even when CAT Control is enabled (so users can see which band is active)
- let bandButtons = ['#toggle160mFilter', '#toggle80mFilter', '#toggle60mFilter', '#toggle40mFilter',
- '#toggle30mFilter', '#toggle20mFilter', '#toggle17mFilter', '#toggle15mFilter',
- '#toggle12mFilter', '#toggle10mFilter'];
- let bandIds = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m'];
-
- bandButtons.forEach((btnId, index) => {
- let $btn = $(btnId);
- $btn.removeClass('btn-secondary btn-success');
- if (allBandsSelected) {
- $btn.addClass('btn-success');
- } else if (bandValues.includes(bandIds[index])) {
- $btn.addClass('btn-success');
- } else {
- $btn.addClass('btn-secondary');
- }
- });
-
- // Band group buttons (VHF, UHF, SHF)
- let groupButtons = [
- { id: '#toggleVHFFilter', group: 'VHF' },
- { id: '#toggleUHFFilter', group: 'UHF' },
- { id: '#toggleSHFFilter', group: 'SHF' }
- ];
-
- groupButtons.forEach(btn => {
- let $btn = $(btn.id);
- $btn.removeClass('btn-secondary btn-success');
-
- if (allBandsSelected) {
- $btn.addClass('btn-success');
- } else {
- // Check if ALL bands in the group are selected (not just some)
- const groupBands = getBandsInGroup(btn.group);
- const allGroupBandsSelected = groupBands.every(b => bandValues.includes(b));
-
- if (allGroupBandsSelected) {
- $btn.addClass('btn-success');
+ // Apply filters with optional debounce
+ if (applyFiltersAfter) {
+ if (debounceMs > 0) {
+ clearTimeout(window.filterDebounceTimer);
+ window.filterDebounceTimer = setTimeout(() => {
+ applyFilters(false);
+ }, debounceMs);
} else {
- $btn.addClass('btn-secondary');
+ applyFilters(false);
}
}
- }); // Mode buttons - green if All, orange if selected, blue if not
- let modeButtons = [
- { id: '#toggleCwFilter', mode: 'cw', icon: 'fa-wave-square' },
- { id: '#toggleDigiFilter', mode: 'digi', icon: 'fa-keyboard' },
- { id: '#togglePhoneFilter', mode: 'phone', icon: 'fa-microphone' }
+ }
+
+ /**
+ * Sync quick filter button states with their corresponding dropdown values
+ */
+ function syncQuickFilterButtons() {
+ const filters = getAllFilterValues();
+
+ // Required flags buttons
+ const requiredFlagButtons = [
+ { id: '#toggleLotwFilter', flag: 'lotw' },
+ { id: '#toggleNewContinentFilter', flag: 'newcontinent' },
+ { id: '#toggleDxccNeededFilter', flag: 'newcountry' },
+ { id: '#toggleNewCallsignFilter', flag: 'newcallsign' },
+ { id: '#toggleContestFilter', flag: 'Contest' }
];
- modeButtons.forEach(btn => {
- let $btn = $(btn.id);
- $btn.removeClass('btn-secondary btn-success');
-
- if (allModesSelected) {
- $btn.addClass('btn-success');
- } else if (modeValues.includes(btn.mode)) {
- $btn.addClass('btn-success');
- } else {
- $btn.addClass('btn-secondary');
- }
+ requiredFlagButtons.forEach(btn => {
+ updateButtonState(btn.id, filters.requiredFlags.includes(btn.flag));
});
- // 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');
- }
+ // Geo Hunter button (stays in Additional Flags)
+ const hasGeoFlag = GEO_FLAGS.some(flag => filters.additionalFlags.includes(flag));
+ updateButtonState('#toggleGeoHunterFilter', hasGeoFlag);
- let continentButtons = [
- { id: '#toggleAfricaFilter', continent: 'AF' },
- { id: '#toggleAntarcticaFilter', continent: 'AN' },
- { id: '#toggleAsiaFilter', continent: 'AS' },
- { id: '#toggleEuropeFilter', continent: 'EU' },
- { id: '#toggleNorthAmericaFilter', continent: 'NA' },
- { id: '#toggleOceaniaFilter', continent: 'OC' },
- { id: '#toggleSouthAmericaFilter', continent: 'SA' }
- ];
+ // Fresh filter button
+ updateButtonState('#toggleFreshFilter', filters.additionalFlags.includes('Fresh'));
- continentButtons.forEach(btn => {
- let $btn = $(btn.id);
- $btn.removeClass('btn-secondary btn-success');
- if (allContinentsSelected) {
- $btn.addClass('btn-success');
- } else if (decontValues.includes(btn.continent)) {
- $btn.addClass('btn-success');
- } else {
- $btn.addClass('btn-secondary');
- }
+ // Mode buttons
+ MODE_BUTTONS.forEach(btn => {
+ updateButtonState(btn.id, filters.mode.includes(btn.mode));
+ });
+
+ // Check if "All" is selected for bands, modes, and continents
+ const allBandsSelected = isDefaultFilterValue(filters.band);
+ const allModesSelected = isDefaultFilterValue(filters.mode) ||
+ (filters.mode.includes('cw') && filters.mode.includes('digi') && filters.mode.includes('phone'));
+ const allContinentsSelected = isDefaultFilterValue(filters.deCont, 'Any') ||
+ (filters.deCont.includes('AF') && filters.deCont.includes('AN') &&
+ filters.deCont.includes('AS') && filters.deCont.includes('EU') &&
+ filters.deCont.includes('NA') && filters.deCont.includes('OC') &&
+ filters.deCont.includes('SA'));
+
+ // Band filter buttons - always update colors (for CAT Control visibility)
+ BAND_BUTTONS.forEach(btn => {
+ const isActive = allBandsSelected || filters.band.includes(btn.band);
+ updateButtonState(btn.id, isActive);
+ });
+
+ // Band group buttons (VHF, UHF, SHF)
+ BAND_GROUP_BUTTONS.forEach(btn => {
+ const groupBands = getBandsInGroup(btn.group);
+ const allGroupBandsSelected = groupBands.every(b => filters.band.includes(b));
+ const isActive = allBandsSelected || allGroupBandsSelected;
+ updateButtonState(btn.id, isActive);
+ });
+
+ // Mode buttons
+ MODE_BUTTONS.forEach(btn => {
+ const isActive = allModesSelected || filters.mode.includes(btn.mode);
+ updateButtonState(btn.id, isActive);
+ });
+
+ // "All Continents" button
+ updateButtonState('#toggleAllContinentsFilter', allContinentsSelected);
+
+ // Individual continent buttons
+ CONTINENT_BUTTONS.forEach(btn => {
+ const isActive = allContinentsSelected || filters.deCont.includes(btn.continent);
+ updateButtonState(btn.id, isActive);
});
}
- // Add checkbox-style indicators (☑/☐) to multi-select dropdowns
+ /**
+ * Add checkbox-style indicators (☑/☐) to multi-select dropdowns
+ * @param {string} selectId - ID of the select element
+ */
function updateSelectCheckboxes(selectId) {
let $select = $('#' + selectId);
$select.find('option').each(function() {
let $option = $(this);
let originalText = $option.data('original-text');
- if (!originalText) {
- originalText = $option.text();
- $option.data('original-text', originalText);
- }
-
- if ($option.is(':selected')) {
- $option.text('☑ ' + originalText);
- } else {
- $option.text('☐ ' + originalText);
- }
- });
- }
-
- // Initialize checkbox indicators for all filter selects
- function initFilterCheckboxes() {
- ['cwnSelect', 'decontSelect', 'continentSelect', 'band', 'mode', 'additionalFlags', 'requiredFlags'].forEach(function(selectId) {
- updateSelectCheckboxes(selectId);
- $('#' + selectId).on('change', function() {
- updateSelectCheckboxes(selectId);
- });
- });
- }
-
- // Handle "All"/"Any" option behavior in multi-selects
- // If "All" is selected with other options, keep only "All"
- // If nothing selected, default back to "All"/"Any"
- function handleAllOption(selectId) {
- $('#' + selectId).on('change', function() {
- let selected = $(this).val() || [];
-
- if (selected.includes('All') || selected.includes('Any')) {
- let allValue = selected.includes('All') ? 'All' : 'Any';
- if (selected.length > 1) {
- $(this).val([allValue]);
+ if (!originalText) {
+ originalText = $option.text();
+ $option.data('original-text', originalText);
}
- } else if (selected.length === 0) {
- let allValue = (selectId === 'decontSelect' || selectId === 'continentSelect') ? 'Any' : 'All';
- $(this).val([allValue]);
- }
- updateFilterIcon();
-
- // Sync button states when band, mode, or continent filters change
- if (selectId === 'band' || selectId === 'mode' || selectId === 'decontSelect' || selectId === 'continentSelect') {
- syncQuickFilterButtons();
- }
- });
- }
-
- // Apply "All" handler to all filter dropdowns
- ['cwnSelect', 'decontSelect', 'continentSelect', 'band', 'mode', 'additionalFlags'].forEach(handleAllOption);
-
- // Required flags filter - handle "None" option
- $('#requiredFlags').on('change', function() {
- let currentValues = $(this).val() || [];
-
- // If "None" is selected, deselect all others
- if (currentValues.includes('None')) {
- if (currentValues.length > 1) {
- // User selected something else, remove "None"
- currentValues = currentValues.filter(v => v !== 'None');
- }
- } else if (currentValues.length === 0) {
- // If nothing is selected, select "None"
- currentValues = ['None'];
+ if ($option.is(':selected')) {
+ $option.text('☑ ' + originalText);
+ } else {
+ $option.text('☐ ' + originalText);
+ }
+ });
}
- $(this).val(currentValues);
- updateFilterIcon();
- });
+ // Initialize checkbox indicators for all filter selects
+ function initFilterCheckboxes() {
+ ['cwnSelect', 'decontSelect', 'continentSelect', 'band', 'mode', 'additionalFlags', 'requiredFlags'].forEach(function(selectId) {
+ updateSelectCheckboxes(selectId);
+ $('#' + selectId).on('change', function() {
+ updateSelectCheckboxes(selectId);
+ });
+ });
+ }
+
+ // Handle "All"/"Any" option behavior in multi-selects
+ // If "All" is selected with other options, keep only "All"
+ // If nothing selected, default back to "All"/"Any"
+ function handleAllOption(selectId) {
+ $('#' + selectId).on('change', function() {
+ let selected = $(this).val() || [];
+
+ if (selected.includes('All') || selected.includes('Any')) {
+ let allValue = selected.includes('All') ? 'All' : 'Any';
+ if (selected.length > 1) {
+ $(this).val([allValue]);
+ }
+ } else if (selected.length === 0) {
+ let allValue = (selectId === 'decontSelect' || selectId === 'continentSelect') ? 'Any' : 'All';
+ $(this).val([allValue]);
+ }
+
+ updateFilterIcon();
+
+ // Sync button states when band, mode, or continent filters change
+ if (selectId === 'band' || selectId === 'mode' || selectId === 'decontSelect' || selectId === 'continentSelect') {
+ syncQuickFilterButtons();
+ }
+ });
+ }
+
+ // Apply "All" handler to all filter dropdowns
+ ['cwnSelect', 'decontSelect', 'continentSelect', 'band', 'mode', 'additionalFlags'].forEach(handleAllOption);
+
+ // Required flags filter - handle "None" option
+ $('#requiredFlags').on('change', function() {
+ let currentValues = $(this).val() || [];
+
+ // If "None" is selected, deselect all others
+ if (currentValues.includes('None')) {
+ if (currentValues.length > 1) {
+ // User selected something else, remove "None"
+ currentValues = currentValues.filter(v => v !== 'None');
+ }
+ } else if (currentValues.length === 0) {
+ // If nothing is selected, select "None"
+ currentValues = ['None'];
+ }
+
+ $(this).val(currentValues);
+ updateFilterIcon();
+ });
// ========================================
// DATATABLE CONFIGURATION
@@ -458,7 +458,6 @@ $(function() {
}
}
-
// Default row click: prepare QSO logging with callsign, frequency, mode
let rowData = table.row(this).data();
if (!rowData) return;
@@ -494,7 +493,8 @@ $(function() {
}
prepareLogging(call, qrg, mode, spotData);
-}); return table;
+ });
+ return table;
}
// ========================================
@@ -575,7 +575,7 @@ $(function() {
allFilters = allFilters.concat(clientFilters);
}
- let loadingMessage = 'Loading data from DX Cluster';
+ let loadingMessage = lang_bandmap_loading_data;
if (allFilters.length > 0) {
loadingMessage += '...';
} else {
@@ -625,17 +625,17 @@ $(function() {
}
// Build tooltip for status message (fetch information)
- let fetchTooltipLines = ['Last fetched for:'];
+ let fetchTooltipLines = [lang_bandmap_last_fetched + ':'];
fetchTooltipLines.push('Band: ' + (lastFetchParams.band || 'All'));
fetchTooltipLines.push('Continent: ' + (lastFetchParams.continent || 'All'));
fetchTooltipLines.push('Mode: ' + (lastFetchParams.mode || 'All'));
- fetchTooltipLines.push('Max Age: ' + (lastFetchParams.maxAge || '120') + ' min');
+ fetchTooltipLines.push(lang_bandmap_max_age + ': ' + (lastFetchParams.maxAge || '120') + ' min');
if (lastFetchParams.timestamp) {
let fetchTime = new Date(lastFetchParams.timestamp);
let fetchTimeStr = fetchTime.getHours().toString().padStart(2, '0') + ':' +
fetchTime.getMinutes().toString().padStart(2, '0') + ':' +
fetchTime.getSeconds().toString().padStart(2, '0');
- fetchTooltipLines.push('Fetched at: ' + fetchTimeStr);
+ fetchTooltipLines.push(lang_bandmap_fetched_at + ': ' + fetchTimeStr);
}
$('#statusMessage').text(statusMessage).attr('title', fetchTooltipLines.join('\n'));
@@ -1101,8 +1101,8 @@ $(function() {
// de Cont column: spotter's continent
data[0].push((single.dxcc_spotter && single.dxcc_spotter.cont) ? single.dxcc_spotter.cont : '');
-// de CQZ column: spotter's CQ Zone
-data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spotter.cqz : ''); // Build medal badge - show only highest priority: continent > country > callsign
+ // de CQZ column: spotter's CQ Zone
+ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spotter.cqz : ''); // Build medal badge - show only highest priority: continent > country > callsign
let medals = '';
if (single.worked_continent === false) {
// New Continent (not worked before) - Gold medal
@@ -1771,81 +1771,19 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
return '
' + content + '';
}
- // Map frequency (in kHz) to ham band name
+ /**
+ * Map frequency (in kHz) to ham band name
+ * Uses frequencyToBand() from radiohelpers.js with 'kHz' parameter
+ * @param {number} freq_khz - Frequency in kilohertz
+ * @returns {string} Band name (e.g., '20m', '2m') or 'All' if not in a known band
+ */
function getBandFromFrequency(freq_khz) {
- if (freq_khz >= 1800 && freq_khz <= 2000) return '160m';
- if (freq_khz >= 3500 && freq_khz <= 4000) return '80m';
- if (freq_khz >= 5250 && freq_khz <= 5450) return '60m';
- if (freq_khz >= 7000 && freq_khz <= 7300) return '40m';
- if (freq_khz >= 10100 && freq_khz <= 10150) return '30m';
- if (freq_khz >= 14000 && freq_khz <= 14350) return '20m';
- if (freq_khz >= 18068 && freq_khz <= 18168) return '17m';
- if (freq_khz >= 21000 && freq_khz <= 21450) return '15m';
- if (freq_khz >= 24890 && freq_khz <= 24990) return '12m';
- if (freq_khz >= 28000 && freq_khz <= 29700) return '10m';
- if (freq_khz >= 50000 && freq_khz <= 54000) return '6m';
- if (freq_khz >= 70000 && freq_khz <= 71000) return '4m';
- if (freq_khz >= 144000 && freq_khz <= 148000) return '2m';
- if (freq_khz >= 222000 && freq_khz <= 225000) return '1.25m';
- if (freq_khz >= 420000 && freq_khz <= 450000) return '70cm';
- if (freq_khz >= 902000 && freq_khz <= 928000) return '33cm';
- if (freq_khz >= 1240000 && freq_khz <= 1300000) return '23cm';
- if (freq_khz >= 2300000 && freq_khz <= 2450000) return '13cm';
- return 'All';
+ return frequencyToBand(freq_khz, 'kHz');
}
- // Map individual bands to their band groups (VHF, UHF, SHF)
- function getBandGroup(band) {
- const VHF_BANDS = ['6m', '4m', '2m', '1.25m'];
- const UHF_BANDS = ['70cm', '33cm', '23cm'];
- const SHF_BANDS = ['13cm', '9cm', '6cm', '3cm', '1.25cm', '6mm', '4mm', '2.5mm', '2mm', '1mm'];
-
- if (VHF_BANDS.includes(band)) return 'VHF';
- if (UHF_BANDS.includes(band)) return 'UHF';
- if (SHF_BANDS.includes(band)) return 'SHF';
- return null; // MF/HF bands don't have groups
- }
-
- // Get all bands in a band group
- function getBandsInGroup(group) {
- const BAND_GROUPS = {
- 'VHF': ['6m', '4m', '2m', '1.25m'],
- 'UHF': ['70cm', '33cm', '23cm'],
- 'SHF': ['13cm', '9cm', '6cm', '3cm', '1.25cm', '6mm', '4mm', '2.5mm', '2mm', '1mm']
- };
- return BAND_GROUPS[group] || [];
- }
-
- // Categorize mode as phone/cw/digi for filtering
- function getModeCategory(mode) {
- if (!mode) return null;
-
- // Mode can come from server as lowercase category names (phone, cw, digi)
- // or as actual mode names (SSB, LSB, FT8, etc.)
- let modeLower = mode.toLowerCase();
-
- // Check if already a category
- if (['phone', 'cw', 'digi'].includes(modeLower)) {
- return modeLower;
- }
-
- // Otherwise categorize by mode name
- mode = mode.toUpperCase();
-
- // Phone modes
- if (['SSB', 'LSB', 'USB', 'FM', 'AM', 'DV'].includes(mode)) return 'phone';
-
- // CW modes
- if (['CW', 'CWR'].includes(mode)) return 'cw';
-
- // Digital modes
- if (['RTTY', 'PSK', 'PSK31', 'PSK63', 'FT8', 'FT4', 'JT65', 'JT9', 'MFSK',
- 'OLIVIA', 'CONTESTIA', 'HELL', 'SSTV', 'FAX', 'PACKET', 'PACTOR',
- 'THOR', 'DOMINO', 'MT63', 'ROS', 'WSPR'].includes(mode)) return 'digi';
-
- // Return null for uncategorized modes instead of 'All'
- return null;
- }
+ // Use BAND_GROUPS from radiohelpers.js (loaded globally in footer)
+ // Note: These functions are now available globally, but we keep local references for consistency
+ // If radiohelpers not loaded, fallback to local definition (shouldn't happen in production)
// Get selected values from multi-select dropdown
function getSelectedValues(selectId) {
@@ -2095,9 +2033,6 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
let bc_qsowin = new BroadcastChannel('qso_window');
let pong_rcvd = false;
- // Debounce timer for de continent filter changes (3 second cooldown)
- let decontFilterTimeout = null;
-
bc_qsowin.onmessage = function (ev) {
if (ev.data == 'pong') {
qso_window_last_seen=Date.now();
@@ -2117,53 +2052,9 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
let wait4pong = 2000;
let check_intv = 100;
- /**
- * Determine appropriate radio mode based on spot mode and frequency
- * Similar to dxwaterfall.js logic
- * @param {string} spotMode - Mode from the spot (e.g., 'CW', 'SSB', 'FT8')
- * @param {number} freqHz - Frequency in Hz
- * @returns {string} Radio mode ('CW', 'USB', 'LSB', 'RTTY', etc.)
- */
- function determineRadioMode(spotMode, freqHz) {
- if (!spotMode) {
- // No mode specified - use frequency to determine USB/LSB
- return freqHz < 10000000 ? 'LSB' : 'USB'; // Below 10 MHz = LSB, above = USB
- }
-
- const modeUpper = spotMode.toUpperCase();
-
- // CW modes
- if (modeUpper === 'CW' || modeUpper === 'A1A') {
- return 'CW';
- }
-
- // Digital modes - use RTTY as standard digital mode
- const digitalModes = ['FT8', 'FT4', 'PSK', 'RTTY', 'JT65', 'JT9', 'WSPR', 'FSK', 'MFSK', 'OLIVIA', 'CONTESTI', 'DOMINO'];
- for (let i = 0; i < digitalModes.length; i++) {
- if (modeUpper.indexOf(digitalModes[i]) !== -1) {
- return 'RTTY';
- }
- }
-
- // Phone modes or SSB - determine USB/LSB based on frequency
- if (modeUpper.indexOf('SSB') !== -1 || modeUpper.indexOf('PHONE') !== -1 ||
- modeUpper === 'USB' || modeUpper === 'LSB' || modeUpper === 'AM' || modeUpper === 'FM') {
- // If already USB or LSB, use as-is
- if (modeUpper === 'USB') return 'USB';
- if (modeUpper === 'LSB') return 'LSB';
- if (modeUpper === 'AM') return 'AM';
- if (modeUpper === 'FM') return 'FM';
-
- // Otherwise determine based on frequency
- return freqHz < 10000000 ? 'LSB' : 'USB';
- }
-
- // Default: use frequency to determine USB/LSB
- return freqHz < 10000000 ? 'LSB' : 'USB';
- }
-
/**
* Tune radio to specified frequency when CAT Control is active
+ * Uses determineRadioMode() from radiohelpers.js for mode selection
* @param {number} freqHz - Frequency in Hz
* @param {string} mode - Mode (optional, e.g., 'USB', 'LSB', 'CW')
*/
@@ -2268,19 +2159,18 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
clearInterval(check_pong);
let cl = message; // Use the message object with all fields
- let newWindow = window.open(base_url + 'index.php/qso?manual=1', '_blank');
-
- if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
- $('#errormessage').html(popup_warning).addClass('alert alert-danger').show();
- setTimeout(function() {
- $('#errormessage').fadeOut();
- }, 3000);
- } else {
- newWindow.focus();
- // Show toast notification when opening new QSO window
- showToast(lang_bandmap_qso_prepared, `${lang_bandmap_callsign_sent} ${call} ${lang_bandmap_sent_to_form}`, 'bg-success text-white', 3000);
- }
+ let newWindow = window.open(base_url + 'index.php/qso?manual=1', '_blank');
+ if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
+ // Pop-up was blocked - show toast notification
+ if (typeof showToast === 'function') {
+ showToast(lang_bandmap_popup_blocked, lang_bandmap_popup_warning, 'bg-danger text-white', 5000);
+ }
+ } else {
+ newWindow.focus();
+ // Show toast notification when opening new QSO window
+ showToast(lang_bandmap_qso_prepared, `${lang_bandmap_callsign_sent} ${call} ${lang_bandmap_sent_to_form}`, 'bg-success text-white', 3000);
+ }
bc2qso.onmessage = function(ev) {
if (ready_listener == true) {
if (ev.data === 'ready') {
@@ -2507,12 +2397,8 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
// Override updateCATui to add bandmap-specific behavior
window.updateCATui = function(data) {
-
-
+ // Determine band from frequency
const band = frequencyToBand(data.frequency);
-
-
-
// Store current radio frequency (convert Hz to kHz)
currentRadioFrequency = data.frequency / 1000;
@@ -2554,25 +2440,24 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
updateFrequencyGradientColors();
}
- // Call cat.js's original updateCATui for standard CAT UI updates
- if (typeof catJsUpdateCATui === 'function') {
+ // Call cat.js's original updateCATui for standard CAT UI updates
+ if (typeof catJsUpdateCATui === 'function') {
+ // Store current band selection before calling cat.js updateCATui
+ const bandBeforeUpdate = $("#band").val();
- // Store current band selection before calling cat.js updateCATui
- const bandBeforeUpdate = $("#band").val();
+ catJsUpdateCATui(data);
- catJsUpdateCATui(data);
-
- // If CAT Control is OFF, restore the band selection
- // (cat.js updateCATui automatically sets band based on frequency, but we don't want that on bandmap unless CAT Control is ON)
- if (!isCatTrackingEnabled && bandBeforeUpdate) {
- $("#band").val(bandBeforeUpdate);
- updateSelectCheckboxes('band');
+ // If CAT Control is OFF, restore the band selection
+ // (cat.js updateCATui automatically sets band based on frequency, but we don't want that on bandmap unless CAT Control is ON)
+ if (!isCatTrackingEnabled && bandBeforeUpdate) {
+ $("#band").val(bandBeforeUpdate);
+ updateSelectCheckboxes('band');
+ }
+ } else {
+ console.warn('Bandmap: cat.js updateCATui not available');
}
- } else {
- console.warn('Bandmap: cat.js updateCATui not available');
- }
-};
+ };
$.fn.dataTable.moment(custom_date_format + ' HH:mm');
@@ -2592,7 +2477,7 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
container.addClass('bandmap-fullscreen');
$('body').addClass('fullscreen-active');
icon.removeClass('fa-expand').addClass('fa-compress');
- $('#fullscreenToggle').attr('title', 'Exit Fullscreen');
+ $('#fullscreenToggle').attr('title', lang_bandmap_exit_fullscreen);
isFullscreen = true;
@@ -2617,7 +2502,7 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
container.removeClass('bandmap-fullscreen');
$('body').removeClass('fullscreen-active');
icon.removeClass('fa-compress').addClass('fa-expand');
- $(this).attr('title', 'Toggle Fullscreen');
+ $(this).attr('title', lang_bandmap_toggle_fullscreen);
isFullscreen = false;
@@ -2734,216 +2619,22 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
applyFilters(false);
});
- // Band filter buttons
- $('#toggle160mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('160m')) {
- currentValues = currentValues.filter(v => v !== '160m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('160m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle80mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('80m')) {
- currentValues = currentValues.filter(v => v !== '80m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('80m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle60mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('60m')) {
- currentValues = currentValues.filter(v => v !== '60m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('60m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle40mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('40m')) {
- currentValues = currentValues.filter(v => v !== '40m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('40m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle30mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('30m')) {
- currentValues = currentValues.filter(v => v !== '30m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('30m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle20mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('20m')) {
- currentValues = currentValues.filter(v => v !== '20m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('20m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle17mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('17m')) {
- currentValues = currentValues.filter(v => v !== '17m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('17m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle15mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('15m')) {
- currentValues = currentValues.filter(v => v !== '15m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('15m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle12mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('12m')) {
- currentValues = currentValues.filter(v => v !== '12m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('12m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle10mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('10m')) {
- currentValues = currentValues.filter(v => v !== '10m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('10m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle6mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('6m')) {
- currentValues = currentValues.filter(v => v !== '6m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('6m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle4mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('4m')) {
- currentValues = currentValues.filter(v => v !== '4m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('4m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle2mFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('2m')) {
- currentValues = currentValues.filter(v => v !== '2m');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('2m');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle70cmFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('70cm')) {
- currentValues = currentValues.filter(v => v !== '70cm');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('70cm');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
-
- $('#toggle23cmFilter').on('click', function() {
- let currentValues = $('#band').val() || [];
- if (currentValues.includes('All')) currentValues = currentValues.filter(v => v !== 'All');
- if (currentValues.includes('23cm')) {
- currentValues = currentValues.filter(v => v !== '23cm');
- if (currentValues.length === 0) currentValues = ['All'];
- } else {
- currentValues.push('23cm');
- }
- $('#band').val(currentValues).trigger('change');
- syncQuickFilterButtons();
- applyFilters(false);
- });
+ // Band filter buttons - Individual bands
+ $('#toggle160mFilter').on('click', () => toggleFilterValue('#band', '160m'));
+ $('#toggle80mFilter').on('click', () => toggleFilterValue('#band', '80m'));
+ $('#toggle60mFilter').on('click', () => toggleFilterValue('#band', '60m'));
+ $('#toggle40mFilter').on('click', () => toggleFilterValue('#band', '40m'));
+ $('#toggle30mFilter').on('click', () => toggleFilterValue('#band', '30m'));
+ $('#toggle20mFilter').on('click', () => toggleFilterValue('#band', '20m'));
+ $('#toggle17mFilter').on('click', () => toggleFilterValue('#band', '17m'));
+ $('#toggle15mFilter').on('click', () => toggleFilterValue('#band', '15m'));
+ $('#toggle12mFilter').on('click', () => toggleFilterValue('#band', '12m'));
+ $('#toggle10mFilter').on('click', () => toggleFilterValue('#band', '10m'));
+ $('#toggle6mFilter').on('click', () => toggleFilterValue('#band', '6m'));
+ $('#toggle4mFilter').on('click', () => toggleFilterValue('#band', '4m'));
+ $('#toggle2mFilter').on('click', () => toggleFilterValue('#band', '2m'));
+ $('#toggle70cmFilter').on('click', () => toggleFilterValue('#band', '70cm'));
+ $('#toggle23cmFilter').on('click', () => toggleFilterValue('#band', '23cm'));
// Band group filter buttons (VHF, UHF, SHF, SAT)
$('#toggleVHFFilter').on('click', function() {
@@ -3024,205 +2715,20 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
updateBandCountBadges();
// Debounce the filter application (3 second cooldown)
- clearTimeout(decontFilterTimeout);
- decontFilterTimeout = setTimeout(function() {
+ clearTimeout(window.filterDebounceTimer);
+ window.filterDebounceTimer = setTimeout(function() {
applyFilters(false);
}, 3000);
});
- // Continent filter buttons (spotter's continent - de continent)
- $('#toggleAfricaFilter').on('click', function() {
- let currentValues = $('#decontSelect').val() || [];
- if (currentValues.includes('Any')) currentValues = currentValues.filter(v => v !== 'Any');
- if (currentValues.includes('AF')) {
- currentValues = currentValues.filter(v => v !== 'AF');
- if (currentValues.length === 0) currentValues = ['Any'];
- } else {
- currentValues.push('AF');
- // Check if all continents are now selected
- if (currentValues.includes('AF') && currentValues.includes('AN') && currentValues.includes('AS') &&
- currentValues.includes('EU') && currentValues.includes('NA') && currentValues.includes('OC') &&
- currentValues.includes('SA')) {
- 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);
- });
-
- $('#toggleAntarcticaFilter').on('click', function() {
- let currentValues = $('#decontSelect').val() || [];
- if (currentValues.includes('Any')) currentValues = currentValues.filter(v => v !== 'Any');
- if (currentValues.includes('AN')) {
- currentValues = currentValues.filter(v => v !== 'AN');
- if (currentValues.length === 0) currentValues = ['Any'];
- } else {
- currentValues.push('AN');
- // Check if all continents are now selected
- if (currentValues.includes('AF') && currentValues.includes('AN') && currentValues.includes('AS') &&
- currentValues.includes('EU') && currentValues.includes('NA') && currentValues.includes('OC') &&
- currentValues.includes('SA')) {
- 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);
- });
-
- $('#toggleAsiaFilter').on('click', function() {
- let currentValues = $('#decontSelect').val() || [];
- if (currentValues.includes('Any')) currentValues = currentValues.filter(v => v !== 'Any');
- if (currentValues.includes('AS')) {
- currentValues = currentValues.filter(v => v !== 'AS');
- if (currentValues.length === 0) currentValues = ['Any'];
- } else {
- currentValues.push('AS');
- // Check if all continents are now selected
- if (currentValues.includes('AF') && currentValues.includes('AN') && currentValues.includes('AS') &&
- currentValues.includes('EU') && currentValues.includes('NA') && currentValues.includes('OC') &&
- currentValues.includes('SA')) {
- 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);
- });
-
- $('#toggleEuropeFilter').on('click', function() {
- let currentValues = $('#decontSelect').val() || [];
- if (currentValues.includes('Any')) currentValues = currentValues.filter(v => v !== 'Any');
- if (currentValues.includes('EU')) {
- currentValues = currentValues.filter(v => v !== 'EU');
- if (currentValues.length === 0) currentValues = ['Any'];
- } else {
- currentValues.push('EU');
- // Check if all continents are now selected
- if (currentValues.includes('AF') && currentValues.includes('AN') && currentValues.includes('AS') &&
- currentValues.includes('EU') && currentValues.includes('NA') && currentValues.includes('OC') &&
- currentValues.includes('SA')) {
- 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);
- });
-
- $('#toggleNorthAmericaFilter').on('click', function() {
- let currentValues = $('#decontSelect').val() || [];
- if (currentValues.includes('Any')) currentValues = currentValues.filter(v => v !== 'Any');
- if (currentValues.includes('NA')) {
- currentValues = currentValues.filter(v => v !== 'NA');
- if (currentValues.length === 0) currentValues = ['Any'];
- } else {
- currentValues.push('NA');
- // Check if all continents are now selected
- if (currentValues.includes('AF') && currentValues.includes('AN') && currentValues.includes('AS') &&
- currentValues.includes('EU') && currentValues.includes('NA') && currentValues.includes('OC') &&
- currentValues.includes('SA')) {
- 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);
- });
-
- $('#toggleOceaniaFilter').on('click', function() {
- let currentValues = $('#decontSelect').val() || [];
- if (currentValues.includes('Any')) currentValues = currentValues.filter(v => v !== 'Any');
- if (currentValues.includes('OC')) {
- currentValues = currentValues.filter(v => v !== 'OC');
- if (currentValues.length === 0) currentValues = ['Any'];
- } else {
- currentValues.push('OC');
- // Check if all continents are now selected
- if (currentValues.includes('AF') && currentValues.includes('AN') && currentValues.includes('AS') &&
- currentValues.includes('EU') && currentValues.includes('NA') && currentValues.includes('OC') &&
- currentValues.includes('SA')) {
- 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);
- });
-
- $('#toggleSouthAmericaFilter').on('click', function() {
- let currentValues = $('#decontSelect').val() || [];
- if (currentValues.includes('Any')) currentValues = currentValues.filter(v => v !== 'Any');
- if (currentValues.includes('SA')) {
- currentValues = currentValues.filter(v => v !== 'SA');
- if (currentValues.length === 0) currentValues = ['Any'];
- } else {
- currentValues.push('SA');
- // Check if all continents are now selected
- if (currentValues.includes('AF') && currentValues.includes('AN') && currentValues.includes('AS') &&
- currentValues.includes('EU') && currentValues.includes('NA') && currentValues.includes('OC') &&
- currentValues.includes('SA')) {
- currentValues = ['Any'];
- }
- }
- $('#decontSelect').val(currentValues).trigger('change');
- syncQuickFilterButtons();
-
- // Debounce the filter application (3 second cooldown)
- clearTimeout(decontFilterTimeout);
- decontFilterTimeout = setTimeout(function() {
- applyFilters(false);
- }, 3000);
- });
+ // Continent filter buttons (spotter's continent - de continent) - with 3s debounce
+ $('#toggleAfricaFilter').on('click', () => toggleFilterValue('#decontSelect', 'AF', 'Any', true, 3000, true));
+ $('#toggleAntarcticaFilter').on('click', () => toggleFilterValue('#decontSelect', 'AN', 'Any', true, 3000, true));
+ $('#toggleAsiaFilter').on('click', () => toggleFilterValue('#decontSelect', 'AS', 'Any', true, 3000, true));
+ $('#toggleEuropeFilter').on('click', () => toggleFilterValue('#decontSelect', 'EU', 'Any', true, 3000, true));
+ $('#toggleNorthAmericaFilter').on('click', () => toggleFilterValue('#decontSelect', 'NA', 'Any', true, 3000, true));
+ $('#toggleOceaniaFilter').on('click', () => toggleFilterValue('#decontSelect', 'OC', 'Any', true, 3000, true));
+ $('#toggleSouthAmericaFilter').on('click', () => toggleFilterValue('#decontSelect', 'SA', 'Any', true, 3000, true));
// Toggle LoTW User filter
$('#toggleLotwFilter').on('click', function() {
@@ -3478,7 +2984,7 @@ data[0].push((single.dxcc_spotter && single.dxcc_spotter.cqz) ? single.dxcc_spot
// Add info icon and message to band filter label in popup
const bandLabel = $('#band').closest('.mb-3').find('label');
if (!bandLabel.find('.cat-control-info').length) {
- bandLabel.append('
');
+ bandLabel.append('
');
}
}