diff --git a/application/views/bandmap/list.php b/application/views/bandmap/list.php index e3cfd5ceb..3b05e73f6 100644 --- a/application/views/bandmap/list.php +++ b/application/views/bandmap/list.php @@ -113,6 +113,9 @@ var lang_bandmap_spot = ""; var lang_bandmap_spotters = ""; + // QSO preparation debounce messages + var lang_bandmap_please_wait = ""; + var lang_bandmap_wait_before_send = ""; // DataTables messages var lang_bandmap_loading_spots = ""; diff --git a/assets/js/sections/bandmap_list.js b/assets/js/sections/bandmap_list.js index 4e7f10df2..e37823aaf 100644 --- a/assets/js/sections/bandmap_list.js +++ b/assets/js/sections/bandmap_list.js @@ -16,6 +16,7 @@ // ======================================== const SPOT_REFRESH_INTERVAL = 60; // Auto-refresh interval in seconds +const QSO_SEND_DEBOUNCE_MS = 3000; // Debounce for sending callsign to QSO form (milliseconds) // Mode display capitalization lookup (API returns lowercase) const MODE_CAPITALIZATION = { 'phone': 'Phone', 'cw': 'CW', 'digi': 'Digi' }; @@ -1261,33 +1262,34 @@ $(function() { $(this).attr('title', lang_click_to_prepare_logging); }); - // Initialize tooltips with error handling - try { - $('[data-bs-toggle="tooltip"]').each(function() { - if (!this || !$(this).attr('title')) return; + // Initialize tooltips with error handling + try { + $('[data-bs-toggle="tooltip"]').each(function() { + if (!this || !$(this).attr('title')) return; - try { - // Dispose existing tooltip instance if it exists - const existingTooltip = bootstrap.Tooltip.getInstance(this); - if (existingTooltip) { - existingTooltip.dispose(); - } - - // Create new tooltip instance - new bootstrap.Tooltip(this, { - boundary: 'window', - trigger: 'hover', - sanitize: false - }); - } catch (err) { - // Skip if tooltip fails to initialize + try { + // Dispose existing tooltip instance if it exists + const existingTooltip = bootstrap.Tooltip.getInstance(this); + if (existingTooltip) { + existingTooltip.dispose(); } - }); - } catch (e) { - // Fallback if tooltip initialization fails - } - let displayedCount = spots2render || 0; + // Create new tooltip instance with proper configuration + new bootstrap.Tooltip(this, { + boundary: 'window', + trigger: 'hover', + sanitize: false, + html: false, + animation: true, + delay: { show: 100, hide: 100 } + }); + } catch (err) { + // Skip if tooltip fails to initialize + } + }); + } catch (e) { + // Fallback if tooltip initialization fails + } let displayedCount = spots2render || 0; // Update band count badges after rendering updateBandCountBadges(); @@ -2129,7 +2131,27 @@ $(function() { } } + // Track last QSO send time for debouncing + let lastQsoSendTime = 0; + function prepareLogging(call, qrg, mode, spotData) { + // Debounce check - prevent sending too quickly + const now = Date.now(); + const timeSinceLastSend = now - lastQsoSendTime; + + if (timeSinceLastSend < QSO_SEND_DEBOUNCE_MS) { + const remainingSeconds = Math.ceil((QSO_SEND_DEBOUNCE_MS - timeSinceLastSend) / 1000); + // Use translation with placeholder replacement + const message = lang_bandmap_wait_before_send.replace('%s', remainingSeconds); + if (typeof showToast === 'function') { + showToast(lang_bandmap_please_wait, message, 'bg-warning text-dark', 3000); + } + return; // Don't proceed with sending + } + + // Update last send time + lastQsoSendTime = now; + let ready_listener = true; // If CAT Control is enabled, tune the radio to the spot frequency diff --git a/assets/js/sections/qso.js b/assets/js/sections/qso.js index 0dc2da2d2..8f6b24bd4 100644 --- a/assets/js/sections/qso.js +++ b/assets/js/sections/qso.js @@ -664,23 +664,23 @@ var lastLookupCallsign = null; var lookupInProgress = false; // Helper function to populate reference fields after callsign lookup completes -// Uses Map-based storage with sequence validation to prevent race conditions +// Uses Map-based storage to prevent race conditions function populatePendingReferences(callsign, expectedSeq) { // Handle legacy call without parameters if (!callsign) { callsign = $('#callsign').val(); - expectedSeq = $('#callsign').data('expected-refs-seq'); } const entry = pendingReferencesMap.get(callsign); - + if (!entry) { - console.log('No pending references for callsign:', callsign); + // No references for this callsign - this is normal for non-POTA/SOTA/WWFF spots return; } - - // Validate sequence to prevent stale data - if (expectedSeq && entry.seq !== expectedSeq) { + + // Validate sequence only if expectedSeq was provided + // This prevents stale data from being populated + if (expectedSeq !== null && expectedSeq !== undefined && entry.seq !== expectedSeq) { console.warn('Sequence mismatch - ignoring stale references', { callsign: callsign, expected: expectedSeq, @@ -688,14 +688,13 @@ function populatePendingReferences(callsign, expectedSeq) { }); return; } - - // Mark as populated to prevent double-population + + // Check if already populated - prevent double-population for same instance if (entry.populated) { - console.warn('References already populated for', callsign); return; } entry.populated = true; - + const refs = entry.refs; // POTA - set without triggering change initially (silent = true) @@ -764,12 +763,10 @@ function populatePendingReferences(callsign, expectedSeq) { } else if (refs.wwff_ref && $('#wwff_ref').length) { $('#wwff_ref').trigger('change'); } - }, 100); // Small delay to let form settle - - // Cleanup after successful population - setTimeout(function() { + + // Cleanup immediately after triggering - we're done with these references pendingReferencesMap.delete(callsign); - }, 5000); // Keep for 5 seconds in case of retry + }, 100); // Small delay to let form settle } var bc = new BroadcastChannel('qso_wish'); @@ -827,10 +824,10 @@ bc.onmessage = function (ev) { if (ev.data.mode) { $("#mode").val(ev.data.mode); } - + // Store sequence for validation in populatePendingReferences $("#callsign").data('expected-refs-seq', seq); - + $("#callsign").val(callsign); $("#callsign").focusout(); $("#callsign").blur(); @@ -1267,13 +1264,10 @@ $("#callsign").on("focusout", function () { lastLookupCallsign = currentCallsign; lookupInProgress = true; - // Capture pendingReferences for THIS lookup (before it gets overwritten by another click) - // If pendingReferences exists, use it; otherwise set to null to prevent old references - // from being populated when user manually types a different callsign - var capturedReferences = pendingReferences ? Object.assign({}, pendingReferences) : null; - - // Clear pendingReferences immediately after capturing to prevent reuse on next manual lookup - pendingReferences = null; + // Check if we have pending references from bandmap for this callsign + // If yes, get the sequence; if no, we'll populate without sequence validation + var hasPendingRefs = pendingReferencesMap.has(currentCallsign); + var expectedSeq = hasPendingRefs ? pendingReferencesMap.get(currentCallsign).seq : null; // Disable Save QSO button and show fetch status $('#saveQso').prop('disabled', true); @@ -1833,9 +1827,9 @@ $("#callsign").on("focusout", function () { // Populate pending references from bandmap (after all lookup logic completes) // Small delay to ensure DOM is fully updated - // Use the captured references from when THIS lookup started + // Use the Map-based approach with the current callsign and expected sequence setTimeout(function() { - populatePendingReferences(capturedReferences); + populatePendingReferences(currentCallsign, expectedSeq); }, 100); }