diff --git a/application/controllers/Bandmap.php b/application/controllers/Bandmap.php index db75c0009..7ffc025a1 100644 --- a/application/controllers/Bandmap.php +++ b/application/controllers/Bandmap.php @@ -35,13 +35,13 @@ class Bandmap extends CI_Controller { $this->load->model('cat'); $this->load->model('bands'); $data['radios'] = $this->cat->radios(); + $data['radio_last_updated'] = $this->cat->last_updated()->row(); $data['bands'] = $this->bands->get_user_bands_for_qso_entry(); $footerData = []; $footerData['scripts'] = [ 'assets/js/moment.min.js', 'assets/js/datetime-moment.js', - 'assets/js/sections/bandmap_list.js' ]; // Get Date format @@ -71,4 +71,53 @@ class Bandmap extends CI_Controller { $this->load->view('bandmap/list',$pageData); $this->load->view('interface_assets/footer', $footerData); } + + // Get user's favorite bands and modes (active ones) + function get_user_favorites() { + session_write_close(); + + $this->load->model('bands'); + $this->load->model('usermodes'); + + // Get active bands + $activeBands = $this->bands->get_user_bands_for_qso_entry(false); // false = only active + $bandList = []; + + if (is_array($activeBands)) { + foreach ($activeBands as $group => $bands) { + if (is_array($bands)) { + foreach ($bands as $band) { + $bandList[] = $band; + } + } + } + } + + // Get active modes (user-specific) and categorize them + $activeModes = $this->usermodes->active(); + $modeCategories = [ + 'cw' => false, + 'phone' => false, + 'digi' => false + ]; + + if ($activeModes) { + foreach ($activeModes as $mode) { + $qrgmode = strtoupper($mode->qrgmode ?? ''); + if ($qrgmode === 'CW') { + $modeCategories['cw'] = true; + } elseif ($qrgmode === 'SSB') { + $modeCategories['phone'] = true; + } elseif ($qrgmode === 'DATA') { + $modeCategories['digi'] = true; + } + } + } + + header('Content-Type: application/json'); + echo json_encode([ + 'bands' => $bandList, + 'modes' => $modeCategories + ]); + } } diff --git a/application/models/Dxcluster_model.php b/application/models/Dxcluster_model.php index d3bea2b1e..03fbcce0b 100644 --- a/application/models/Dxcluster_model.php +++ b/application/models/Dxcluster_model.php @@ -249,11 +249,25 @@ class Dxcluster_model extends CI_Model { $spot->cnfmd_continent = $status['cnfmd_continent']; $spot->worked_continent = $status['worked_continent']; - // Use batch last_worked data - if ($spot->worked_call && isset($last_worked_batch[$callsign])) { - $spot->last_wked = $last_worked_batch[$callsign]; - $spot->last_wked->LAST_QSO = date($custom_date_format, strtotime($spot->last_wked->LAST_QSO)); + // Use batch last_worked data + if ($spot->worked_call && isset($last_worked_batch[$callsign])) { + $spot->last_wked = $last_worked_batch[$callsign]; + + // Validate and convert date safely to prevent epoch date (1970) issues + if (!empty($spot->last_wked->LAST_QSO)) { + $timestamp = strtotime($spot->last_wked->LAST_QSO); + // Check if strtotime succeeded and timestamp is valid (> 0) + if ($timestamp !== false && $timestamp > 0) { + $spot->last_wked->LAST_QSO = date($custom_date_format, $timestamp); + } else { + // Invalid date - remove last_wked to prevent displaying incorrect date + unset($spot->last_wked); + } + } else { + // Empty date - remove last_wked + unset($spot->last_wked); } + } } else { // Fallback for spots without status $spot->worked_dxcc = false; @@ -563,18 +577,35 @@ class Dxcluster_model extends CI_Model { // Contest detection - use class property instead of creating array each time if (!$spot->dxcc_spotted->isContest) { - // Check for contest keywords using optimized strpbrk-like approach + // More strict contest detection - require clear indicators + + // Method 1: Explicit contest keywords with word boundaries foreach ($this->contestIndicators as $indicator) { - if (strpos($upperMessage, $indicator) !== false) { + // Use word boundary to avoid matching "CQ DX" in "CQ DX Americas" (which is just a CQ call) + if (preg_match('/\b' . preg_quote($indicator, '/') . '\b/', $upperMessage)) { + // Additional check: avoid false positives from generic "CQ" messages + if ($indicator === 'DX CONTEST' && preg_match('/^CQ\s+DX\s+[A-Z]+$/i', trim($message))) { + continue; // Skip "CQ DX " patterns + } $spot->dxcc_spotted->isContest = true; - return $spot; // Early exit once contest detected + $spot->dxcc_spotted->contestName = $indicator; + return $spot; } } - // Additional heuristic: Check for typical contest exchange patterns - // Match RST + serial number patterns OR zone/state exchanges in single regex - if (preg_match('/\b(?:(?:599|59|5NN)\s+[0-9A-Z]{2,4}|CQ\s+[0-9A-Z]{1,3})\b/', $upperMessage)) { - $spot->dxcc_spotted->isContest = true; + // Method 2: Contest exchange pattern - must have RST AND serial AND no conversational words + // Exclude spots with conversational indicators (TU, TNX, 73, GL, etc.) + $conversational = '/\b(TU|TNX|THANKS|73|GL|HI|FB|CUL|HPE|PSE|DE)\b/'; + + if (!preg_match($conversational, $upperMessage)) { + // Look for typical contest exchange: RST + number (but not just any 599) + // Must be followed by more structured exchange (not just "ur 599") + if (preg_match('/\b(?:599|5NN)\s+(?:TU\s+)?[0-9]{2,4}\b/', $upperMessage) && + !preg_match('/\bUR\s+599\b/', $upperMessage)) { + $spot->dxcc_spotted->isContest = true; + $spot->dxcc_spotted->contestName = 'CONTEST'; + return $spot; + } } } diff --git a/application/views/bandmap/list.php b/application/views/bandmap/list.php index fbdc2fa4f..4d9e8dc95 100644 --- a/application/views/bandmap/list.php +++ b/application/views/bandmap/list.php @@ -1,135 +1,518 @@ - - - -
-
-
- - - -

- -
-
-
- - - - - - - - - - - + +
+ +
-
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
">"> [MHz]">">">">">">">">">">">">">
+
+
+
+
+ +
diff --git a/application/views/interface_assets/footer.php b/application/views/interface_assets/footer.php index 88c30b095..133b0a6a9 100644 --- a/application/views/interface_assets/footer.php +++ b/application/views/interface_assets/footer.php @@ -58,6 +58,7 @@ var lang_general_word_please_wait = ""; var lang_general_states_deprecated = ""; var lang_gen_hamradio_sat_info = ""; + var lang_notes_error_loading = ""; var lang_notes_sort = ""; var lang_notes_duplication_disabled = ""; @@ -113,7 +114,7 @@ var lang_qso_gridsquare_help = ""; var lang_cat_live = ""; var lang_cat_polling = ""; - var lang_cat_polling_tooltip = ""; + var lang_cat_polling_tooltip = ""; var lang_cat_tx = ""; var lang_cat_rx = ""; var lang_cat_tx_rx = ""; @@ -132,6 +133,11 @@ var lang_qso_location_is_fetched_from_provided_gridsquare = ""; var lang_qso_location_is_fetched_from_dxcc_coordinates = ""; + // CAT Offline Status Messages + var lang_cat_working_offline = ""; + var lang_cat_offline_cat_disabled = ""; + var lang_cat_offline_no_radio = ""; + // CAT Configuration var cat_timeout_minutes = Math.floor(optionslib->get_option('cat_timeout_interval'); ?> / 60); @@ -1463,6 +1469,15 @@ mymap.on('mousemove', onQsoMapMove); + +uri->segment(1) == "bandmap" && $this->uri->segment(2) == "list") { ?> + + + + + + + uri->segment(1) == "logbook" && $this->uri->segment(2) == "view") { ?>