diff --git a/application/controllers/User_options.php b/application/controllers/User_options.php
index 6e89a13c9..04ff09d76 100644
--- a/application/controllers/User_options.php
+++ b/application/controllers/User_options.php
@@ -40,7 +40,7 @@ class User_Options extends CI_Controller {
$obj = json_decode(file_get_contents("php://input"), true);
if ($obj['option_name'] ?? '' != '') {
$option_name=$this->security->xss_clean($obj['option_name']);
- $this->user_options_model->del_option('Favourite',$option_name);
+ $this->user_options_model->del_option('Favourite',$option_name);
}
$jsonout['success']=1;
header('Content-Type: application/json');
@@ -51,6 +51,71 @@ class User_Options extends CI_Controller {
$this->user_options_model->set_option('version_dialog', 'confirmed', array('boolean' => 'true'));
}
+ /**
+ * DX Cluster Filter Favorites
+ */
+ public function add_edit_dxcluster_fav() {
+ $obj = json_decode(file_get_contents("php://input"), true);
+ if (!$obj || !isset($obj['fav_name']) || trim($obj['fav_name']) === '') {
+ header('Content-Type: application/json');
+ echo json_encode(['success' => 0, 'error' => 'Invalid data']);
+ return;
+ }
+
+ // Sanitize all input
+ foreach($obj as $option_key => $option_value) {
+ if (is_array($option_value)) {
+ $obj[$option_key] = array_map([$this->security, 'xss_clean'], $option_value);
+ } else {
+ $obj[$option_key] = $this->security->xss_clean($option_value);
+ }
+ }
+
+ $option_name = $obj['fav_name'];
+ unset($obj['fav_name']); // Don't store the name as a value
+
+ // Convert arrays to JSON for storage
+ foreach($obj as $key => $value) {
+ if (is_array($value)) {
+ $obj[$key] = json_encode($value);
+ }
+ }
+
+ $this->user_options_model->set_option('DXClusterFavourite', $option_name, $obj);
+ $jsonout['success'] = 1;
+ header('Content-Type: application/json');
+ echo json_encode($jsonout);
+ }
+
+ public function get_dxcluster_fav() {
+ $result = $this->user_options_model->get_options('DXClusterFavourite');
+ $jsonout = [];
+ foreach($result->result() as $options) {
+ $value = $options->option_value;
+ // Try to decode JSON arrays - check if it looks like JSON first
+ if (is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) {
+ $decoded = json_decode($value, true);
+ if (json_last_error() === JSON_ERROR_NONE) {
+ $value = $decoded;
+ }
+ }
+ $jsonout[$options->option_name][$options->option_key] = $value;
+ }
+ header('Content-Type: application/json');
+ echo json_encode($jsonout);
+ }
+
+ public function del_dxcluster_fav() {
+ $obj = json_decode(file_get_contents("php://input"), true);
+ if ($obj['option_name'] ?? '' != '') {
+ $option_name = $this->security->xss_clean($obj['option_name']);
+ $this->user_options_model->del_option('DXClusterFavourite', $option_name);
+ }
+ $jsonout['success'] = 1;
+ header('Content-Type: application/json');
+ echo json_encode($jsonout);
+ }
+
public function get_qrg_units() {
$qrg_units = [];
diff --git a/application/views/bandmap/list.php b/application/views/bandmap/list.php
index bb88466ab..794f68a23 100644
--- a/application/views/bandmap/list.php
+++ b/application/views/bandmap/list.php
@@ -40,6 +40,17 @@
var lang_bandmap_modes_applied = "= __("Modes applied. Band filter preserved (CAT connection is active)"); ?>";
var lang_bandmap_favorites_applied = "= __("Applied your favorite bands and modes"); ?>";
+ // DX Cluster Filter Favorites translations
+ var lang_bandmap_filter_favorites = "= __("Favorites"); ?>";
+ var lang_bandmap_save_filters = "= __("Save Current Filters..."); ?>";
+ var lang_bandmap_filter_preset_name = "= __("Enter a name for this filter preset:"); ?>";
+ var lang_bandmap_filter_preset_saved = "= __("Filter preset saved"); ?>";
+ var lang_bandmap_filter_preset_loaded = "= __("Filter preset loaded"); ?>";
+ var lang_bandmap_filter_preset_deleted = "= __("Filter preset deleted"); ?>";
+ var lang_bandmap_delete_filter_confirm = "= __("Are you sure to delete this filter preset?"); ?>";
+ var lang_bandmap_no_filter_presets = "= __("No saved filter presets"); ?>";
+ var lang_bandmap_preset_limit_reached = "= __("Maximum of 20 filter presets reached. Please delete some before adding new ones."); ?>";
+
// Bandmap filter status messages
var lang_bandmap_loading_data = "= __("Loading data from DX Cluster"); ?>";
var lang_bandmap_last_fetched = "= __("Last fetched for"); ?>";
@@ -378,15 +389,26 @@
-
-
-
-
+
+
diff --git a/assets/js/sections/bandmap_list.js b/assets/js/sections/bandmap_list.js
index 24f2bdebf..36a25618e 100644
--- a/assets/js/sections/bandmap_list.js
+++ b/assets/js/sections/bandmap_list.js
@@ -382,9 +382,67 @@ $(function() {
});
}
+ // List of all filter select IDs
+ const FILTER_SELECT_IDS = ['cwnSelect', 'decontSelect', 'continentSelect', 'band', 'mode', 'additionalFlags', 'requiredFlags'];
+
+ // Map of storage keys to select IDs
+ const FILTER_KEY_TO_SELECT = {
+ cwn: 'cwnSelect',
+ deCont: 'decontSelect',
+ continent: 'continentSelect',
+ band: 'band',
+ mode: 'mode',
+ additionalFlags: 'additionalFlags',
+ requiredFlags: 'requiredFlags'
+ };
+
+ // Map currentFilters keys to storage keys
+ const CURRENT_TO_STORAGE_KEY = {
+ cwn: 'cwn',
+ deContinent: 'deCont',
+ spottedContinent: 'continent',
+ band: 'band',
+ mode: 'mode',
+ additionalFlags: 'additionalFlags',
+ requiredFlags: 'requiredFlags'
+ };
+
+ /**
+ * Build filter data object from currentFilters for storage
+ * @param {string} [favName] - Optional favorite name to include
+ * @returns {Object} Filter data with storage keys
+ */
+ function buildFilterDataFromCurrent(favName) {
+ let filterData = {};
+ if (favName) filterData.fav_name = favName;
+ Object.entries(CURRENT_TO_STORAGE_KEY).forEach(([currentKey, storageKey]) => {
+ filterData[storageKey] = currentFilters[currentKey];
+ });
+ return filterData;
+ }
+
+ /**
+ * Set all filter values from an object
+ * @param {Object} filterData - Object with filter keys (cwn, deCont, continent, band, mode, additionalFlags, requiredFlags)
+ */
+ function setAllFilterValues(filterData) {
+ Object.entries(FILTER_KEY_TO_SELECT).forEach(([key, selectId]) => {
+ if (filterData[key] !== undefined) {
+ $('#' + selectId).val(filterData[key]);
+ }
+ });
+ }
+
+ /**
+ * Update checkbox indicators for all filter selects
+ */
+ function updateAllSelectCheckboxes() {
+ FILTER_SELECT_IDS.forEach(selectId => updateSelectCheckboxes(selectId));
+ }
+
// Initialize checkbox indicators for all filter selects
function initFilterCheckboxes() {
- ['cwnSelect', 'decontSelect', 'continentSelect', 'band', 'mode', 'additionalFlags', 'requiredFlags'].forEach(selectId => {
+ FILTER_SELECT_IDS.forEach(selectId => {
updateSelectCheckboxes(selectId);
$(`#${selectId}`).on('change', () => updateSelectCheckboxes(selectId));
});
@@ -1901,22 +1959,18 @@ $(function() {
// Preserve current band selection if CAT Control is enabled
let currentBand = isCatTrackingEnabled ? $('#band').val() : null;
- $('#cwnSelect').val(['All']);
- $('#decontSelect').val(['Any']);
- $('#continentSelect').val(['Any']);
- $('#band').val(currentBand || ['All']); // Preserve band if CAT is enabled
- $('#mode').val(['All']);
- $('#additionalFlags').val(['All']);
- $('#requiredFlags').val([]);
+ setAllFilterValues({
+ cwn: ['All'],
+ deCont: ['Any'],
+ continent: ['Any'],
+ band: currentBand || ['All'],
+ mode: ['All'],
+ additionalFlags: ['All'],
+ requiredFlags: []
+ });
// Update checkbox indicators for all selects
- updateSelectCheckboxes('cwnSelect');
- updateSelectCheckboxes('decontSelect');
- updateSelectCheckboxes('continentSelect');
- updateSelectCheckboxes('band');
- updateSelectCheckboxes('mode');
- updateSelectCheckboxes('additionalFlags');
- updateSelectCheckboxes('requiredFlags');
+ updateAllSelectCheckboxes();
// Clear text search
$('#spotSearchInput').val('');
@@ -1965,6 +2019,136 @@ $(function() {
}
});
+ // ========================================
+ // DX CLUSTER FILTER FAVORITES
+ // ========================================
+
+ let dxclusterFavs = {};
+
+ /**
+ * Apply saved filter values to UI and trigger filter application
+ */
+ function applyDxClusterFilterValues(filterData) {
+ setAllFilterValues(filterData);
+ updateAllSelectCheckboxes();
+ syncQuickFilterButtons();
+ updateFilterIcon();
+ applyFilters(true);
+ }
+
+ function saveDxClusterFav() {
+ // Check preset limit (max 20)
+ if (Object.keys(dxclusterFavs).length >= 20) {
+ showToast && showToast(lang_bandmap_filter_favorites, lang_bandmap_preset_limit_reached, 'bg-warning text-dark', 4000);
+ return;
+ }
+
+ let favName = prompt(lang_bandmap_filter_preset_name);
+ if (!favName || favName.trim() === '') return;
+
+ // Build filter data from currentFilters using helper
+ let filterData = buildFilterDataFromCurrent(favName.trim());
+
+ $.ajax({
+ url: base_url + 'index.php/user_options/add_edit_dxcluster_fav',
+ method: 'POST',
+ dataType: 'json',
+ contentType: 'application/json; charset=utf-8',
+ data: JSON.stringify(filterData),
+ success: function(result) {
+ if (result.success) {
+ getDxClusterFavs();
+ showToast && showToast(lang_bandmap_filter_favorites, lang_bandmap_filter_preset_saved, 'bg-success text-white', 2000);
+ }
+ },
+ error: function() {
+ showToast && showToast(lang_bandmap_filter_favorites, lang_bandmap_favorites_failed, 'bg-danger text-white', 3000);
+ }
+ });
+ }
+
+ function getDxClusterFavs() {
+ $.ajax({
+ url: base_url + 'index.php/user_options/get_dxcluster_fav',
+ method: 'GET',
+ dataType: 'json',
+ success: function(result) {
+ dxclusterFavs = result;
+ renderDxClusterFavMenu();
+ }
+ });
+ }
+
+ function renderDxClusterFavMenu() {
+ let $menu = $('#dxcluster_fav_menu').empty();
+
+ let keys = Object.keys(dxclusterFavs);
+ if (keys.length === 0) {
+ $menu.append('
' + lang_bandmap_no_filter_presets + '');
+ return;
+ }
+
+ keys.forEach(function(key) {
+ // Build the menu item with data attribute on the parent div for easier click handling
+ let $item = $('
').attr('data-fav-name', key);
+ let $nameSpan = $('
').text(key);
+ let $deleteBtn = $('
').attr('data-fav-name', key);
+ $menu.append($item.append($nameSpan).append($deleteBtn));
+ });
+ }
+
+ function delDxClusterFav(name) {
+ if (!confirm(lang_bandmap_delete_filter_confirm)) return;
+
+ $.ajax({
+ url: base_url + 'index.php/user_options/del_dxcluster_fav',
+ method: 'POST',
+ dataType: 'json',
+ contentType: 'application/json; charset=utf-8',
+ data: JSON.stringify({ option_name: name }),
+ success: function(result) {
+ if (result.success) {
+ getDxClusterFavs();
+ showToast && showToast(lang_bandmap_filter_favorites, lang_bandmap_filter_preset_deleted, 'bg-info text-white', 2000);
+ }
+ }
+ });
+ }
+
+ // Event handlers
+ $('#dxcluster_fav_add').on('click', function(e) {
+ e.preventDefault();
+ saveDxClusterFav();
+ });
+
+ $(document).on('click', '.dxcluster_fav_del', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ delDxClusterFav($(this).data('fav-name'));
+ });
+
+ // Click on the entire favorite item row (but not the delete button)
+ $(document).on('click', '.dxcluster_fav_item', function(e) {
+ // Don't trigger if clicking the delete button
+ if ($(e.target).closest('.dxcluster_fav_del').length) return;
+
+ e.preventDefault();
+ let name = $(this).data('fav-name');
+ if (dxclusterFavs[name]) {
+ applyDxClusterFilterValues(dxclusterFavs[name]);
+ // Escape name for toast display (showToast uses innerHTML)
+ let safeName = $('
').text(name).html();
+ showToast && showToast(lang_bandmap_filter_favorites, lang_bandmap_filter_preset_loaded + ': ' + safeName, 'bg-success text-white', 2000);
+ }
+ });
+
+ // Load favorites on page load
+ getDxClusterFavs();
+
+ // ========================================
+ // END DX CLUSTER FILTER FAVORITES
+ // ========================================
+
// Sync button states when dropdown is shown
$('#filterDropdown').on('show.bs.dropdown', function() {
syncQuickFilterButtons();