var favs = {};
var selected_sat;
var selected_sat_mode;
var scps = [];
let lookupCall = null;
let preventLookup = false;
var submitTimeout = null; // Debounce timer for QSO submission
// Calculate local time based on GMT offset
function calculateLocalTime(gmtOffset) {
let now = new Date();
let utcTime = now.getTime() + (now.getTimezoneOffset() * 60000);
let localTime = new Date(utcTime + (3600000 * gmtOffset));
let hours = ("0" + localTime.getHours()).slice(-2);
let minutes = ("0" + localTime.getMinutes()).slice(-2);
return hours + ':' + minutes;
}
// Check and update profile info visibility based on available width
function checkProfileInfoVisibility() {
if ($('#callsign-image').is(':visible') && $('#callsign-image-info').html().trim() !== '') {
// Additional validation: check if profile data matches current callsign
let currentCallsign = $('#callsign').val().toUpperCase().replaceAll('Ø', '0');
let profileCallsign = $('#callsign-image').attr('data-profile-callsign') || '';
if (currentCallsign !== profileCallsign) {
// Callsign mismatch, hide the profile panel
console.log('Profile callsign mismatch during visibility check - hiding panel');
$('#callsign-image').attr('style', 'display: none;');
$('#callsign-image-info').html("");
$('#callsign-image-info').hide();
return;
}
let cardBody = $('.card-body.callsign-image');
let imageWidth = $('#callsign-image-content').outerWidth() || 0;
let cardWidth = cardBody.width() || 0;
let availableWidth = cardWidth - imageWidth - 24; // Subtract gap (24px for gap-3)
if (availableWidth >= 200) {
$('#callsign-image-info').show();
} else {
$('#callsign-image-info').hide();
}
}
}
// Attach resize listener for profile info visibility
$(window).on('resize', function() {
checkProfileInfoVisibility();
});
// Create and add banner control
window.mapBanner = L.control({ position: "bottomleft" }); // You can change position: "topleft", "bottomleft", etc.
window.mapBanner.onAdd = function () {
const div = L.DomUtil.create("div", "info legend");
div.style.background = "rgba(0, 0, 0, 0.7)";
div.style.color = "white";
div.style.padding = "8px 12px";
div.style.borderRadius = "8px";
div.style.fontSize = "13px";
div.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)";
div.innerHTML = bannerText;
return div;
};
// if the dxcc id changes we need to update the state dropdown and clear the county value to avoid wrong data
$("#dxcc_id").on('change', function () {
updateStateDropdown('#dxcc_id', '#stateInputLabel', '#location_us_county', '#stationCntyInputQso');
$('#stationCntyInputQso').val('');
$('#dxcc_id').multiselect('refresh');
});
function resetTimers(qso_manual) {
if (typeof qso_manual !== 'undefined' && qso_manual != 1) {
handleStart = setInterval(function () { getUTCTimeStamp($('.input_start_time')); }, 500);
handleEnd = setInterval(function () { getUTCTimeStamp($('.input_end_time')); }, 500);
handleDate = setInterval(function () { getUTCDateStamp($('.input_date')); }, 1000);
}
}
function getUTCTimeStamp(el) {
var now = new Date();
$(el).attr('value', ("0" + now.getUTCHours()).slice(-2) + ':' + ("0" + now.getUTCMinutes()).slice(-2) + ':' + ("0" + now.getUTCSeconds()).slice(-2));
}
function getUTCDateStamp(el) {
var now = new Date();
var day = ("0" + now.getUTCDate()).slice(-2);
var month = ("0" + (now.getUTCMonth() + 1)).slice(-2);
var year = now.getUTCFullYear();
var short_year = year.toString().slice(-2);
// Format the date based on user_date_format passed from PHP
var formatted_date;
switch (user_date_format) {
case "d/m/y":
formatted_date = day + "/" + month + "/" + short_year;
break;
case "d/m/Y":
formatted_date = day + "/" + month + "/" + year;
break;
case "m/d/y":
formatted_date = month + "/" + day + "/" + short_year;
break;
case "m/d/Y":
formatted_date = month + "/" + day + "/" + year;
break;
case "d.m.Y":
formatted_date = day + "." + month + "." + year;
break;
case "y/m/d":
formatted_date = short_year + "/" + month + "/" + day;
break;
case "Y-m-d":
formatted_date = year + "-" + month + "-" + day;
break;
case "M d, Y":
// Need to get the month name abbreviation
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
formatted_date = monthNames[now.getUTCMonth()] + " " + parseInt(day) + ", " + year;
break;
case "M d, y":
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
formatted_date = monthNames[now.getUTCMonth()] + " " + parseInt(day) + ", " + short_year;
break;
case "d M y":
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
formatted_date = parseInt(day) + " " + monthNames[now.getUTCMonth()] + " " + short_year;
break;
default:
// Default to d-m-Y format as shown in the PHP code
formatted_date = day + "-" + month + "-" + year;
}
$(el).attr('value', formatted_date);
}
// Note card state logic including EasyMDE initialization and handling
function setNotesVisibility(state, noteText = "",show_notes = user_show_notes) {
var $noteCard = $('#callsign-notes');
var $saveBtn = $('#callsign-note-save-btn');
var $editorElem = $('#callsign_note_content');
var noteEditor = $editorElem.data('easymde');
var $editBtn = $('#callsign-note-edit-btn');
// Do nothing if user preference is to hide notes
if (!show_notes) {
$noteCard.hide();
return;
}
// Initialize EasyMDE if not already done
if (!noteEditor && typeof EasyMDE !== 'undefined') {
noteEditor = new EasyMDE({
element: $editorElem[0],
spellChecker: false,
toolbar: [
"bold", "italic", "heading", "|","preview", "|",
"quote", "unordered-list", "ordered-list", "|",
"link", "image", "|",
"guide"
],
forceSync: true,
status: false,
maxHeight: '150px',
autoDownloadFontAwesome: false,
autoRefresh: { delay: 250 },
});
$editorElem.data('easymde', noteEditor);
}
if (state === 0) {
// No callsign - Hide note card
$noteCard.hide();
$('#callsign-notes-body').removeClass('show');
} else if (state === 1) {
// Callsign, no note yet - show note card with message
$noteCard.show();
$('#callsign-notes-body').removeClass('show');
// Hide editor toolbar, set value and show preview
document.querySelector('.EasyMDEContainer .editor-toolbar').style.display = 'none';
noteEditor.value(lang_qso_note_missing);
noteEditor.togglePreview();
noteEditor.codemirror.setOption('readOnly', true);
} else if (state === 2) {
// Callsign with existing notes - show note card with notes
$noteCard.show();
// Automatically expand the panel when note exists
$('#callsign-notes-body').addClass('show');
// Hide editor toolbar, set value and show preview
document.querySelector('.EasyMDEContainer .editor-toolbar').style.display = 'none';
noteEditor.value(noteText);
noteEditor.togglePreview();
noteEditor.codemirror.setOption('readOnly', true);
}
// Hide buttons per default here
$saveBtn.addClass('d-none').hide();
$editBtn.addClass('d-none').hide();
// Show Edit button for states 1 and 2
if (state === 1 || state === 2) {
$editBtn.removeClass('d-none').show();
} else {
$editBtn.addClass('d-none').hide();
}
}
$('#stationProfile').on('change', function () {
var stationProfile = $('#stationProfile').val();
$.ajax({
url: base_url + 'index.php/qso/get_station_power',
type: 'post',
data: { 'stationProfile': stationProfile },
success: function (res) {
$('#transmit_power').val(res.station_power);
latlng=[res.lat,res.lng];
station_callsign = res.station_callsign;
$("#sat_name").change();
},
error: function () {
$('#transmit_power').val('');
},
});
// [eQSL default msg] change value on change station profle //
qso_set_eqsl_qslmsg(stationProfile, false, '.qso_panel');
});
// [eQSL default msg] change value on clic //
$('.qso_panel .qso_eqsl_qslmsg_update').off('click').on('click', function () {
qso_set_eqsl_qslmsg($('.qso_panel #stationProfile').val(), true, '.qso_panel');
$('#charsLeft').text(" ");
});
$("#callsign").on("compositionstart", function(){ this.isComposing = true; });
$("#callsign").on("compositionend", function(e){
this.isComposing = false;
$(this).trigger("input");
});
$(document).on("keydown", function (e) {
if (e.key === "Escape" && $('#callsign').val() != '') { // escape key maps to keycode `27`
preventLookup = true;
if (lookupCall) {
lookupCall.abort();
}
reset_fields();
// make sure the focusout event is finished before we allow a new lookup
setTimeout(() => {
preventLookup = false;
}, 100);
// console.log("Escape key pressed");
$('#callsign').trigger("focus");
}
});
// Sanitize some input data
$('#callsign').on('input', function () {
// Prevent checking when the user's composing in IME
if (this.isComposing) return;
$(this).val($(this).val().replace(/\s/g, ''));
$(this).val($(this).val().replace(/0/g, 'Ø'));
$(this).val($(this).val().replace(/\./g, '/P'));
$(this).val($(this).val().replace(/\ /g, ''));
});
$('#locator').on('input', function () {
$(this).val($(this).val().replace(/\s/g, ''));
});
$("#check_cluster").on("click", function () {
if ($("#callsign").val().trim() == '') {
$.ajax({
url: dxcluster_provider + "/qrg_lookup/" + $("#frequency").val() / 1000,
cache: false,
dataType: "json",
success: function (dxspot) {
if ((dxspot.spotted ?? '') != '') {
reset_fields();
$("#callsign").val(dxspot.spotted);
$("#callsign").trigger("blur");
} else {
showToast(lang_general_word_info, lang_qso_no_spots_found, 'bg-info text-dark', 2000);
}
}
});
} else {
showToast(lang_general_word_info, lang_qso_you_already_filled_an_qso, 'bg-info text-dark', 2000);
}
});
function set_timers() {
setTimeout(function () {
var callsignValue = localStorage.getItem("quicklogCallsign");
if (callsignValue !== null && callsignValue !== undefined) {
$("#callsign").val(callsignValue);
$("#mode").trigger("focus");
localStorage.removeItem("quicklogCallsign");
}
}, 100);
}
function invalidAntEl() {
var saveQsoButtonText = $("#saveQso").html();
showToast(lang_general_word_warning, lang_invalid_ant_el+" "+parseFloat($("#ant_el").val()).toFixed(1), 'bg-warning text-dark', 5000);
$("#saveQso").html(saveQsoButtonText).prop("disabled", false);
}
$("#qso_input").off('submit').on('submit', function (e) {
e.preventDefault();
// Check for rapid submission attempts (debounce)
if (submitTimeout) {
showToast(lang_general_word_warning, lang_qso_wait_before_saving, 'bg-info text-dark', 3000);
return false;
}
// Prevent submission if Save button is disabled (fetch in progress)
if ($('#saveQso').prop('disabled')) {
return false;
}
var _submit = true;
if ((typeof qso_manual !== "undefined") && (qso_manual == "1")) {
if ($('#qso_input input[name="end_time"]').length == 1) { _submit = testTimeOffConsistency(); }
}
if (_submit) {
// Set debounce timer (1 second)
submitTimeout = setTimeout(function() {
submitTimeout = null;
}, 3000);
var saveQsoButtonText = $("#saveQso").html();
$("#saveQso").html(' ' + saveQsoButtonText + '...').prop('disabled', true);
manual_addon = '?manual=' + qso_manual;
// Capture form data before AJAX call for WebSocket transmission
var formDataObj = {};
$("#qso_input").serializeArray().map(function(x) {
formDataObj[x.name] = x.value;
});
$.ajax({
url: base_url + 'index.php/qso' + manual_addon,
method: 'POST',
type: 'post',
timeout: 10000,
data: $(this).serialize(),
dataType: 'json',
success: function (result) {
if (result.message == 'success') {
activeStationId = result.activeStationId;
activeStationOP = result.activeStationOP;
activeStationTXPower = result.activeStationTXPower;
// Build dynamic success message
var contactCallsign = $("#callsign").val().toUpperCase();
var operatorCallsign = activeStationOP || station_callsign;
var successMessage = lang_qso_added
.replace('%s', contactCallsign)
.replace('%s', operatorCallsign);
showToast(lang_general_word_success, successMessage, 'bg-success text-white', 5000);
// Send QSO data via WebSocket if CAT is enabled via WebSocket
if (typeof sendQSOViaWebSocket === 'function') {
// Add additional context to captured form data
formDataObj.station_id = activeStationId;
formDataObj.operator_callsign = operatorCallsign;
formDataObj.timestamp = new Date().toISOString();
// Include ADIF if available
if (result.adif) {
formDataObj.adif = result.adif;
}
// Send via WebSocket (function checks if WS is connected)
var wsSent = sendQSOViaWebSocket(formDataObj);
if (wsSent) {
console.log('QSO sent via WebSocket with ADIF');
}
}
prepare_next_qso(saveQsoButtonText);
processBacklog(); // If we have success with the live-QSO, we could also process the backlog
// Clear debounce timer on success to allow immediate next submission
if (submitTimeout) {
clearTimeout(submitTimeout);
submitTimeout = null;
}
} else {
showToast(lang_general_word_error, result.errors, 'bg-danger text-white', 5000);
$("#saveQso").html(saveQsoButtonText).prop("disabled", false);
// Clear debounce timer on error to allow retry
if (submitTimeout) {
clearTimeout(submitTimeout);
submitTimeout = null;
}
}
},
error: function () {
saveToBacklog(JSON.stringify(this.data),manual_addon);
prepare_next_qso(saveQsoButtonText);
showToast(lang_general_word_info, lang_qso_added_to_backlog, 'bg-info text-dark', 5000);
// Clear debounce timer on error to allow retry
if (submitTimeout) {
clearTimeout(submitTimeout);
submitTimeout = null;
}
}
});
}
return false;
});
function prepare_next_qso(saveQsoButtonText) {
reset_fields();
htmx.trigger("#qso-last-table", "qso_event")
$("#saveQso").html(saveQsoButtonText).prop("disabled", false);
$("#callsign").val("");
var triggerEl = document.querySelector('#myTab a[href="#qso"]')
bootstrap.Tab.getInstance(triggerEl).show() // Select tab by name
$("#callsign").trigger("focus");
}
var processingBL=false;
async function processBacklog() {
if (!processingBL) {
processingBL=true;
const Qsobacklog = JSON.parse(localStorage.getItem('qso-backlog')) || [];
for (const entry of [...Qsobacklog]) {
try {
await $.ajax({url: base_url + 'index.php/qso' + entry.manual_addon, method: 'POST', type: 'post', data: JSON.parse(entry.data),
success: function(resdata) {
// Send QSO data via WebSocket if CAT is enabled via WebSocket
if (typeof sendQSOViaWebSocket === 'function' && resdata) {
try {
const result = JSON.parse(resdata);
if (result.message === 'success') {
const qsoData = JSON.parse(entry.data);
// Add additional context
qsoData.station_id = result.activeStationId;
qsoData.operator_callsign = result.activeStationOP || station_callsign;
qsoData.timestamp = new Date().toISOString();
qsoData.backlog_processed = true;
sendQSOViaWebSocket(qsoData);
}
} catch (e) {
// Ignore JSON parse errors
}
}
Qsobacklog.splice(Qsobacklog.findIndex(e => e.id === entry.id), 1);
},
error: function() {
entry.attempts++;
}});
} catch (error) {
entry.attempts++;
}
}
localStorage.setItem('qso-backlog', JSON.stringify(Qsobacklog));
processingBL=false;
}
}
function saveToBacklog(formData,manual_addon) {
const backlog = JSON.parse(localStorage.getItem('qso-backlog')) || [];
const entry = {
id: Date.now(),
timestamp: new Date().toISOString(),
data: formData,
manual_addon: manual_addon,
attempts: 0
};
backlog.push(entry);
localStorage.setItem('qso-backlog', JSON.stringify(backlog));
}
window.addEventListener('beforeunload', processBacklog()); // process possible QSO-Backlog on unload of page
window.addEventListener('pagehide', processBacklog()); // process possible QSO-Backlog on Hide of page (Mobile-Browsers)
$('#reset_time').on("click", function () {
var now = new Date();
$('#start_time').attr('value', ("0" + now.getUTCHours()).slice(-2) + ':' + ("0" + now.getUTCMinutes()).slice(-2) + ':' + ("0" + now.getUTCSeconds()).slice(-2));
$("[id='start_time']").each(function () {
$(this).attr("value", ("0" + now.getUTCHours()).slice(-2) + ':' + ("0" + now.getUTCMinutes()).slice(-2) + ':' + ("0" + now.getUTCSeconds()).slice(-2));
});
});
// Function to format the current time as HH:MM or HH:MM:SS
function formatTime(date, includeSeconds) {
let time = ("0" + date.getUTCHours()).slice(-2) + ":" + ("0" + date.getUTCMinutes()).slice(-2);
if (includeSeconds) {
time += ":" + ("0" + date.getUTCSeconds()).slice(-2);
}
return time;
}
// Event listener for resetting start time
$("#reset_start_time").on("click", function () {
var now = new Date();
// Format start and end times
let startTime = formatTime(now, qso_manual != 1);
let endTime = formatTime(now, qso_manual != 1);
// Update all elements with id 'start_time'
$("[id='start_time']").each(function () {
$(this).val(startTime);
});
// Update all elements with id 'end_time'
$("[id='end_time']").each(function () {
$(this).val(endTime);
});
// Update the start date
var day = ("0" + now.getUTCDate()).slice(-2);
var month = ("0" + (now.getUTCMonth() + 1)).slice(-2);
var year = now.getUTCFullYear();
var short_year = year.toString().slice(-2);
var formatted_date;
switch (user_date_format) {
case "d/m/y":
formatted_date = day + "/" + month + "/" + short_year;
break;
case "d/m/Y":
formatted_date = day + "/" + month + "/" + year;
break;
case "m/d/y":
formatted_date = month + "/" + day + "/" + short_year;
break;
case "m/d/Y":
formatted_date = month + "/" + day + "/" + year;
break;
case "d.m.Y":
formatted_date = day + "." + month + "." + year;
break;
case "y/m/d":
formatted_date = short_year + "/" + month + "/" + day;
break;
case "Y-m-d":
formatted_date = year + "-" + month + "-" + day;
break;
case "M d, Y":
// Need to get the month name abbreviation
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
formatted_date = monthNames[now.getUTCMonth()] + " " + parseInt(day) + ", " + year;
break;
case "M d, y":
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
formatted_date = monthNames[now.getUTCMonth()] + " " + parseInt(day) + ", " + short_year;
break;
case "d M y":
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
formatted_date = parseInt(day) + " " + monthNames[now.getUTCMonth()] + " " + short_year;
break;
default:
// Default to d-m-Y format as shown in the PHP code
formatted_date = day + "-" + month + "-" + year;
}
$("#start_date").val(formatted_date);
});
function parseUserDate(user_provided_date) { // creates JS-Date out of user-provided date with user_date_format
var parts, day, month, year;
var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
switch (user_date_format) {
case "d/m/y":
parts = user_provided_date.split("/");
day = parseInt(parts[0], 10);
month = parseInt(parts[1], 10) - 1;
year = 2000 + parseInt(parts[2], 10);
break;
case "d/m/Y":
parts = user_provided_date.split("/");
day = parseInt(parts[0], 10);
month = parseInt(parts[1], 10) - 1;
year = parseInt(parts[2], 10);
break;
case "m/d/y":
parts = user_provided_date.split("/");
month = parseInt(parts[0], 10) - 1;
day = parseInt(parts[1], 10);
year = 2000 + parseInt(parts[2], 10);
break;
case "m/d/Y":
parts = user_provided_date.split("/");
month = parseInt(parts[0], 10) - 1;
day = parseInt(parts[1], 10);
year = parseInt(parts[2], 10);
break;
case "d.m.Y":
parts = user_provided_date.split(".");
day = parseInt(parts[0], 10);
month = parseInt(parts[1], 10) - 1;
year = parseInt(parts[2], 10);
break;
case "y/m/d":
parts = user_provided_date.split("/");
year = 2000 + parseInt(parts[0], 10);
month = parseInt(parts[1], 10) - 1;
day = parseInt(parts[2], 10);
break;
case "Y-m-d":
parts = user_provided_date.split("-");
year = parseInt(parts[0], 10);
month = parseInt(parts[1], 10) - 1;
day = parseInt(parts[2], 10);
break;
case "M d, Y":
// Example: Jul 28, 2025
parts = user_provided_date.replace(',', '').split(' ');
month = monthNames.indexOf(parts[0]);
if (month === -1) return null;
day = parseInt(parts[1], 10);
year = parseInt(parts[2], 10);
break;
case "M d, y":
// Example: Jul 28, 25
parts = user_provided_date.replace(',', '').split(' ');
month = monthNames.indexOf(parts[0]);
if (month === -1) return null;
day = parseInt(parts[1], 10);
year = 2000 + parseInt(parts[2], 10);
break;
case "d M y":
// Example: 28 Jul 25
parts = user_provided_date.split(' ');
day = parseInt(parts[0], 10);
month = monthNames.indexOf(parts[1]);
if (month === -1) return null;
year = 2000 + parseInt(parts[2], 10);
break;
default: // fallback "d-m-Y"
parts = user_provided_date.split("-");
day = parseInt(parts[0], 10);
month = parseInt(parts[1], 10) - 1;
year = parseInt(parts[2], 10);
}
if (isNaN(day) || day < 1 || day > 31 || isNaN(month) || month < 0 || month > 11 || isNaN(year)) return null;
return new Date(year, month, day);
}
// Event listener for resetting end time
$("#reset_end_time").on("click", function () {
var now = new Date();
// Format end time
let endTime = formatTime(now, qso_manual != 1);
// Update all elements with id 'end_time'
$("[id='end_time']").each(function () {
$(this).val(endTime);
});
});
$('#fav_add').on("click", function (event) {
save_fav();
});
$(document).on("click", "#fav_del", function (event) {
del_fav($(this).attr('name'));
});
$(document).on("click", "#fav_recall", function (event) {
$('#sat_name').val(favs[this.innerText].sat_name);
if (favs[this.innerText].sat_name) {
$("#sat_name").change();
}
$('#sat_mode').val(favs[this.innerText].sat_mode);
$('#band_rx').val(favs[this.innerText].band_rx);
$('#band').val(favs[this.innerText].band);
$('#frequency_rx').val(favs[this.innerText].frequency_rx);
$('#frequency').val(favs[this.innerText].frequency).trigger("change");
$('#selectPropagation').val(favs[this.innerText].prop_mode);
$('#mode').val(favs[this.innerText].mode).on("change");
setRst($('.mode').val());
});
function del_fav(name) {
if (confirm(lang_qso_delete_fav_confirm)) {
$.ajax({
url: base_url + 'index.php/user_options/del_fav',
method: 'POST',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ "option_name": name }),
success: function (result) {
get_fav();
}
});
}
}
function get_fav() {
$.ajax({
url: base_url + 'index.php/user_options/get_fav',
method: 'GET',
dataType: 'json',
contentType: "application/json; charset=utf-8",
success: function (result) {
$("#fav_menu").empty();
for (const key in result) {
$("#fav_menu").append('');
}
favs = result;
}
});
}
function save_fav() {
var payload = {};
payload.sat_name = $('#sat_name').val();
payload.sat_mode = $('#sat_mode').val();
payload.band_rx = $('#band_rx').val();
payload.band = $('#band').val();
payload.frequency_rx = $('#frequency_rx').val();
payload.frequency = $('#frequency').val();
payload.prop_mode = $('#selectPropagation').val();
payload.mode = $('#mode').val();
$.ajax({
url: base_url + 'index.php/user_options/add_edit_fav',
method: 'POST',
dataType: 'json',
contentType: "application/json; charset=utf-8",
data: JSON.stringify(payload),
success: function (result) {
get_fav();
}
});
}
if (qso_manual == 0) {
var bc_bandmap = new BroadcastChannel('qso_window');
bc_bandmap.onmessage = function (ev) {
// respond ONLY to ping if we've an open live-window
// Otherwise a spot will be filled accidently into an open POST-QSO Window
if (ev.data == 'ping') {
bc_bandmap.postMessage('pong');
}
}
}
// Store pending references from bandmap to populate AFTER callsign lookup completes
// Map structure: callsign -> {seq, refs, timestamp, populated}
var pendingReferencesMap = new Map();
var referenceSequence = 0;
// Track last lookup to prevent duplicate calls
var lastLookupCallsign = null;
var lookupInProgress = false;
// Helper function to populate reference fields after callsign lookup completes
// Uses Map-based storage to prevent race conditions
function populatePendingReferences(callsign, expectedSeq) {
// Handle legacy call without parameters
if (!callsign) {
callsign = $('#callsign').val();
}
const entry = pendingReferencesMap.get(callsign);
if (!entry) {
// No references for this callsign - this is normal for non-POTA/SOTA/WWFF spots
return;
}
// 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,
actual: entry.seq
});
return;
}
// Check if already populated - prevent double-population for same instance
if (entry.populated) {
return;
}
entry.populated = true;
const refs = entry.refs;
// POTA - set without triggering change initially (silent = true)
if (refs.pota_ref && $('#pota_ref').length) {
try {
var $select = $('#pota_ref').selectize();
if ($select.length && $select[0].selectize) {
var selectize = $select[0].selectize;
selectize.addOption({name: refs.pota_ref});
selectize.setValue(refs.pota_ref, true); // Silent = true
// Manually show icon since onChange doesn't fire in silent mode
if (refs.pota_ref.indexOf(',') === -1) {
$('#pota_info').show();
$('#pota_info').html('
');
$('#pota_info').attr('title', lang_qso_lookup_reference_info.replace('%s', refs.pota_ref).replace('%s', 'pota.co'));
}
}
} catch (e) {
console.warn('Could not set POTA reference:', e);
}
}
// SOTA - set without triggering change initially (silent = true)
if (refs.sota_ref && $('#sota_ref').length) {
try {
var $select = $('#sota_ref').selectize();
if ($select.length && $select[0].selectize) {
var selectize = $select[0].selectize;
selectize.addOption({name: refs.sota_ref});
selectize.setValue(refs.sota_ref, true); // Silent = true
// Manually show icon since onChange doesn't fire in silent mode
$('#sota_info').show();
$('#sota_info').html('
');
$('#sota_info').attr('title', lang_qso_lookup_summit_info.replace('%s', refs.sota_ref).replace('%s', 'sota.org.uk'));
}
} catch (e) {
console.warn('Could not set SOTA reference:', e);
}
}
// WWFF - set without triggering change initially (silent = true)
if (refs.wwff_ref && $('#wwff_ref').length) {
try {
var $select = $('#wwff_ref').selectize();
if ($select.length && $select[0].selectize) {
var selectize = $select[0].selectize;
selectize.addOption({name: refs.wwff_ref});
selectize.setValue(refs.wwff_ref, true); // Silent = true
// Manually show icon since onChange doesn't fire in silent mode
$('#wwff_info').show();
$('#wwff_info').html('
');
$('#wwff_info').attr('title', lang_qso_lookup_reference_info.replace('%s', refs.wwff_ref).replace('%s', 'cqgma.org'));
}
} catch (e) {
console.warn('Could not set WWFF reference:', e);
}
}
// IOTA - set silently (no change trigger yet)
if (refs.iota_ref && $('#iota_ref').length) {
try {
let $iotaSelect = $('#iota_ref');
if ($iotaSelect.find('option[value="' + refs.iota_ref + '"]').length === 0) {
$iotaSelect.append(new Option(refs.iota_ref, refs.iota_ref));
}
$iotaSelect.val(refs.iota_ref); // Don't trigger change yet
} catch (e) {
console.warn('Could not set IOTA reference:', e);
}
}
// NOW trigger gridsquare lookup ONLY ONCE for the highest priority reference
// Priority: POTA > SOTA > WWFF (most commonly used)
// This prevents multiple simultaneous AJAX gridsquare lookups that can race
setTimeout(function() {
if (refs.pota_ref && $('#pota_ref').length) {
$('#pota_ref').trigger('change');
} else if (refs.sota_ref && $('#sota_ref').length) {
$('#sota_ref').trigger('change');
} else if (refs.wwff_ref && $('#wwff_ref').length) {
$('#wwff_ref').trigger('change');
}
// Cleanup immediately after triggering - we're done with these references
pendingReferencesMap.delete(callsign);
}, 100); // Small delay to let form settle
}
if (qso_manual == 0) {
var bc = new BroadcastChannel('qso_wish');
bc.onmessage = function (ev) {
// Handle ping/pong only when manual mode is disabled (qso_manual == 0)
if (ev.data.ping) {
if (qso_manual == 0) {
let message = {};
message.pong = true;
bc.postMessage(message);
}
} else {
// Always process frequency, callsign, and reference data from bandmap
// (regardless of manual mode - bandmap should control the form)
const callsign = ev.data.call;
const seq = ++referenceSequence;
let delay = 0;
// Only reset if callsign is different from what we're about to set
if ($("#callsign").val() != "" && $("#callsign").val() != callsign) {
reset_fields();
delay = 600;
}
// Store references with metadata in Map (prevents race conditions)
pendingReferencesMap.set(callsign, {
seq: seq,
refs: {
pota_ref: ev.data.pota_ref,
sota_ref: ev.data.sota_ref,
wwff_ref: ev.data.wwff_ref,
iota_ref: ev.data.iota_ref
},
timestamp: Date.now(),
populated: false
});
// Cleanup old entries (> 30 seconds)
for (let [key, value] of pendingReferencesMap) {
if (Date.now() - value.timestamp > 30000) {
pendingReferencesMap.delete(key);
}
}
setTimeout(() => {
if (ev.data.frequency != null) {
$('#frequency').val(ev.data.frequency).trigger("change");
$("#band").val(frequencyToBand(ev.data.frequency));
}
if (ev.data.frequency_rx != "") {
$('#frequency_rx').val(ev.data.frequency_rx);
$("#band_rx").val(frequencyToBand(ev.data.frequency_rx));
}
// Set mode if provided (backward compatible - optional field)
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();
}, delay);
}
} /* receive */
}
$("#sat_name").on('change', function () {
var sat = $("#sat_name").val();
if (sat == "") {
$("#sat_mode").val("");
$("#selectPropagation").val("");
stop_az_ele_ticker();
} else {
$('#lotw_support').text("");
$('#lotw_support').removeClass();
get_sat_info();
}
});
$("#sat_name").on('focusout', function () {
if ($(this).val().length == 0) {
$('#lotw_support').text("");
$('#lotw_support').removeClass();
}
});
var satupdater;
function stop_az_ele_ticker() {
if (satupdater) {
clearInterval(satupdater);
}
$("#ant_az").val('');
$("#ant_el").val('');
}
function start_az_ele_ticker(tle) {
const lines = tle.tle.trim().split('\n');
// Initialize a satellite record
var satrec = satellite.twoline2satrec(lines[0], lines[1]);
// Define the observer's location in radians
var observerGd = {
longitude: satellite.degreesToRadians(latlng[1]),
latitude: satellite.degreesToRadians(latlng[0]),
height: 0.370
};
function updateAzEl() {
let dateParts=parseUserDate($('#start_date').val());
let timeParts=$("#start_time").val().split(":");
try {
var time = new Date(Date.UTC(
dateParts.getFullYear(),dateParts.getMonth(),dateParts.getDate(),
parseInt(timeParts[0]),parseInt(timeParts[1]),(parseInt(timeParts[2] ?? 0))
));
if (isNaN(time.getTime())) {
throw new Error("Invalid date");
}
var positionAndVelocity = satellite.propagate(satrec, time);
var gmst = satellite.gstime(time);
var positionEcf = satellite.eciToEcf(positionAndVelocity.position, gmst);
var observerEcf = satellite.geodeticToEcf(observerGd);
var lookAngles = satellite.ecfToLookAngles(observerGd, positionEcf);
let az=(satellite.radiansToDegrees(lookAngles.azimuth).toFixed(2));
let el=(satellite.radiansToDegrees(lookAngles.elevation).toFixed(2));
$("#ant_az").val(parseFloat(az).toFixed(1));
$("#ant_el").val(parseFloat(el).toFixed(1));
// Send real-time azimuth/elevation via WebSocket if using WebSocket CAT and working satellite
if (typeof sendSatellitePositionViaWebSocket === 'function') {
var satName = $("#sat_name").val();
if (satName && satName !== '') {
sendSatellitePositionViaWebSocket(satName, parseFloat(az).toFixed(1), parseFloat(el).toFixed(1));
}
}
} catch(e) {
$("#ant_az").val('');
$("#ant_el").val('');
}
}
satupdater=setInterval(updateAzEl, 1000);
}
function get_sat_info() {
stop_az_ele_ticker();
$.ajax({
url: base_url + 'index.php/satellite/get_sat_info',
type: 'post',
data: {
sat: $("#sat_name").val(),
},
success: function (data) {
if (data !== null) {
if (data.tle) {
start_az_ele_ticker(data);
}
if (data.lotw_support == 'Y') {
$('#lotw_support').html(lang_qso_sat_lotw_supported).fadeIn("slow");
$('#lotw_support').addClass('badge bg-success');
} else if (data.lotw_support == 'N') {
$('#lotw_support').html(lang_qso_sat_lotw_not_supported).fadeIn("slow");
$('#lotw_support').addClass('badge bg-danger');
}
} else {
$('#lotw_support').html(lang_qso_sat_lotw_support_not_found).fadeIn("slow");
$('#lotw_support').addClass('badge bg-warning');
}
},
error: function (data) {
console.log('Something went wrong while trying to fetch info for sat: '+$("#sat_name"));
},
});
}
if ($("#sat_name").val() !== '') {
get_sat_info();
}
$('#stateDropdown').on('change', function () {
var state = $("#stateDropdown option:selected").text();
var dxcc = $("#dxcc_id option:selected").val();
if (state != "") {
switch (dxcc) {
case '6':
case '110':
case '291':
$("#stationCntyInputQso").prop('disabled', false);
selectize_usa_county('#stateDropdown', '#stationCntyInputQso');
break;
case '15':
case '54':
case '61':
case '126':
case '151':
case '288':
case '339':
case '170':
case '21':
case '29':
case '32':
case '281':
$("#stationCntyInputQso").prop('disabled', false);
break;
default:
$("#stationCntyInputQso").prop('disabled', true);
}
} else {
$("#stationCntyInputQso").prop('disabled', true);
//$('#stationCntyInputQso')[0].selectize.destroy();
$("#stationCntyInputQso").val("");
}
});
$(document).on('change', 'input', function () {
var optionslist = $('.satellite_modes_list')[0].options;
var value = $(this).val();
for (var x = 0; x < optionslist.length; x++) {
if (optionslist[x].value === value) {
// Store selected sat mode
selected_sat_mode = value;
// get Json file
$.getJSON(site_url + "/satellite/satellite_data", function (data) {
// Build the options array
var sat_modes = [];
$.each(data, function (key, val) {
if (key == selected_sat) {
$.each(val.Modes, function (key1, val2) {
if (key1 == selected_sat_mode) {
if ((val2[0].Downlink_Mode == "LSB" && val2[0].Uplink_Mode == "USB") || (val2[0].Downlink_Mode == "USB" && val2[0].Uplink_Mode == "LSB")) { // inverting Transponder? set to SSB
$("#mode").val("SSB");
} else {
$("#mode").val(val2[0].Uplink_Mode);
}
$("#band").val(frequencyToBand(val2[0].Uplink_Freq));
$("#band_rx").val(frequencyToBand(val2[0].Downlink_Freq));
$("#frequency").val(val2[0].Uplink_Freq).trigger("change");
$("#frequency_rx").val(val2[0].Downlink_Freq);
$("#selectPropagation").val('SAT');
}
});
}
});
});
}
}
});
$(document).on('change', 'input', function () {
var optionslist = $('.satellite_names_list')[0].options;
var value = $(this).val();
for (var x = 0; x < optionslist.length; x++) {
if (optionslist[x].value === value) {
$("#sat_mode").val("");
$('.satellite_modes_list').find('option').remove().end();
selected_sat = value;
// get Json file
$.getJSON(site_url + "/satellite/satellite_data", function (data) {
// Build the options array
var sat_modes = [];
$.each(data, function (key, val) {
if (key == value) {
$.each(val.Modes, function (key1, val2) {
//console.log (key1);
sat_modes.push('');
});
}
});
// Add to the datalist
$('.satellite_modes_list').append(sat_modes.join(""));
});
}
}
});
function changebadge(entityval) {
if ($("#sat_name").val() != "") {
$.getJSON(base_url + 'index.php/logbook/jsonlookupdxcc/' + entityval + '/SAT/0/0', function (result) {
$('#callsign_info').removeClass("lotw_info_orange");
$('#callsign_info').removeClass("text-bg-secondary");
$('#callsign_info').removeClass("text-bg-success");
$('#callsign_info').removeClass("text-bg-danger");
$('#callsign_info').attr('title', '');
if (result.confirmed) {
$('#callsign_info').addClass("text-bg-success");
$('#callsign_info').attr('title', decodeHtml(lang_qso_dxcc_confirmed));
} else if (result.workedBefore) {
$('#callsign_info').addClass("text-bg-success");
$('#callsign_info').addClass("lotw_info_orange");
$('#callsign_info').attr('title', decodeHtml(lang_qso_dxcc_worked));
} else {
$('#callsign_info').addClass("text-bg-danger");
$('#callsign_info').attr('title', decodeHtml(lang_qso_dxcc_new));
}
})
} else {
$.getJSON(base_url + 'index.php/logbook/jsonlookupdxcc/' + entityval + '/0/' + $("#band").val() + '/' + $("#mode").val(), function (result) {
// Reset CSS values before updating
$('#callsign_info').removeClass("lotw_info_orange");
$('#callsign_info').removeClass("text-bg-secondary");
$('#callsign_info').removeClass("text-bg-success");
$('#callsign_info').removeClass("text-bg-danger");
$('#callsign_info').attr('title', '');
if (result.confirmed) {
$('#callsign_info').addClass("text-bg-success");
$('#callsign_info').attr('title', decodeHtml(lang_qso_dxcc_confirmed));
} else if (result.workedBefore) {
$('#callsign_info').addClass("text-bg-success");
$('#callsign_info').addClass("lotw_info_orange");
$('#callsign_info').attr('title', decodeHtml(lang_qso_dxcc_worked));
} else {
$('#callsign_info').addClass("text-bg-danger");
$('#callsign_info').attr('title', decodeHtml(lang_qso_dxcc_new));
}
})
}
}
$('#btn_reset').on("click", function () {
preventLookup = true;
if (lookupCall) {
lookupCall.abort();
}
reset_fields();
// make sure the focusout event is finished before we allow a new lookup
setTimeout(() => {
preventLookup = false;
}, 100);
});
$('#btn_fullreset').on("click", function () {
reset_to_default();
});
function reset_to_default() {
reset_fields();
panMap(activeStationId);
$("#stationProfile").val(activeStationId);
$("#selectPropagation").val("");
$("#frequency_rx").val("");
$("#band_rx").val("");
$("#transmit_power").val(activeStationTXPower);
$("#sat_name").val("");
$("#sat_mode").val("");
$("#ant_az").val("");
$("#ant_el").val("");
$("#distance").val("");
stop_az_ele_ticker();
}
/* Function: reset_fields is used to reset the fields on the QSO page */
function reset_fields() {
// Clear all pending references to avoid they get prefilled in the next QSO after clear
// we do this first to avoid race conditions for slow javascript
pendingReferencesMap.clear();
$('#locator_info').text("");
$('#lotw_support').text("");
$('#lotw_support').removeClass();
$('#comment').val("");
$('#country').val("");
$('#continent').val("");
$('#email').val("");
$('#region').val("");
$('#ham_of_note_line').empty().hide();
$('#lotw_info').text("");
$('#lotw_info').attr('data-bs-original-title', "");
$('#lotw_info').removeClass("lotw_info_red");
$('#lotw_info').removeClass("lotw_info_yellow");
$('#lotw_info').removeClass("lotw_info_orange");
$('#qrz_info').text("").hide();
$('#hamqth_info').text("").hide();
$('#email_info').html("").addClass('d-none').hide();
$('#dxcc_id').val("").multiselect('refresh');
$('#cqz').val("");
$('#ituz').val("");
$('#name').val("");
$('#qth').val("");
$('#locator').val("");
$('#ant_path').val("");
$('#iota_ref').val("");
$("#locator").removeClass("confirmedGrid");
$("#locator").removeClass("workedGrid");
$("#locator").removeClass("newGrid");
$('#locator').attr('title', '');
$("#callsign").val("");
$("#callsign").removeClass("confirmedGrid");
$("#callsign").removeClass("workedGrid");
$("#callsign").removeClass("newGrid");
$('#callsign').attr('title', '');
$('#callsign_info').removeClass("text-bg-secondary");
$('#callsign_info').removeClass("text-bg-success");
$('#callsign_info').removeClass("text-bg-danger");
$('#callsign-image').attr('style', 'display: none;');
$('#callsign-image-content').text("");
$('#callsign-image-info').html("");
$('#callsign-image-info').hide();
$("#operator_callsign").val(activeStationOP);
$('#qsl_via').val("");
$('#callsign_info').text("");
$('#stateDropdown').val("");
$('#qso-last-table').show();
$('#partial_view').hide();
$('.callsign-suggest').hide();
$("#distance").val("");
setRst($(".mode").val());
var $select = $('#sota_ref').selectize();
var selectize = $select[0].selectize;
selectize.clear();
var $select = $('#wwff_ref').selectize();
var selectize = $select[0].selectize;
selectize.clear();
var $select = $('#pota_ref').selectize();
var selectize = $select[0].selectize;
selectize.clear();
var $select = $('#darc_dok').selectize();
var selectize = $select[0].selectize;
selectize.clear();
$('#stationCntyInputQso').val("");
$select = $('#stationCntyInputQso').selectize();
selectize = $select[0].selectize;
selectize.clear();
var $select = $('#sota_ref').selectize();
var selectize = $select[0].selectize;
selectize.clear();
$('#notes').val("");
$('#sig').val("");
$('#sig_info').val("");
$('#sent').val("N");
$('#sent-method').val("");
$('#qsl_via').val("");
mymap.setView(pos, 12);
mymap.removeLayer(markers);
if (window.mapBanner) {
mymap.removeControl(window.mapBanner);
}
$('.callsign-suggest').hide();
$('.awardpane').remove();
$('#timesWorked').html(lang_qso_title_previous_contacts);
updateStateDropdown('#dxcc_id', '#stateInputLabel', '#location_us_county', '#stationCntyInputEdit');
clearTimeout();
set_timers();
resetTimers(qso_manual);
setNotesVisibility(0); // Set note card to hidden
}
// Get status of notes for this callsign
function get_note_status(callsign){
$.get(
window.base_url + 'index.php/notes/check_duplicate',
{
category: 'Contacts',
title: callsign
},
function(data) {
if (typeof data === 'string') {
try { data = JSON.parse(data); } catch (e) { data = {}; }
}
if (data && data.exists === true && data.id) {
// Get the note content using the note ID
$.get(
window.base_url + 'index.php/notes/get/' + data.id,
function(noteData) {
if (typeof noteData === 'string') {
try { noteData = JSON.parse(noteData); } catch (e) { noteData = {}; }
}
if (noteData && noteData.content) {
$('#callsign-note-id').val(data.id);
setNotesVisibility(2, noteData.content);
} else {
$('#callsign-note-id').val('');
setNotesVisibility(2, lang_general_word_error);
}
}
).fail(function() {
$('#callsign-note-id').val('');
setNotesVisibility(2, lang_general_word_error);
});
} else {
$('#callsign-note-id').val('');
setNotesVisibility(1);
}
}
);
}
// Lookup callsign on focusout - if the callsign is 3 chars or longer
$("#callsign").on("focusout", function () {
if ($(this).val().length >= 3 && preventLookup == false) {
var currentCallsign = $(this).val().toUpperCase().replaceAll('Ø', '0');
// Prevent duplicate lookups for the same callsign if already in progress
if (lookupInProgress && lastLookupCallsign === currentCallsign) {
return;
}
// If callsign changed, allow new lookup even if one is in progress
lastLookupCallsign = currentCallsign;
lookupInProgress = true;
// 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);
$('#fetch_status').show();
// Set timeout to unlock form after 10 seconds
var fetchTimeout = setTimeout(function() {
$('#saveQso').prop('disabled', false);
$('#fetch_status').hide();
}, 10000);
/* Find and populate DXCC */
$('.callsign-suggest').hide();
if ($("#sat_name").val() != "") {
var json_band = "SAT";
} else {
var json_band = $("#band").val();
}
const json_mode = $("#mode").val();
let find_callsign = $(this).val().toUpperCase();
let callsign = find_callsign;
let startDate = $('#start_date').val();
// Characters '/' and ',' are not URL safe, so we replace
// them with '_' and '%'.
if (startDate.includes('/')) {
startDate = startDate.replaceAll('/', '_');
}
if (startDate.includes(',')) {
startDate = startDate.replaceAll(',', '%');
}
startDate = encodeURIComponent(startDate);
const stationProfile = $('#stationProfile').val();
find_callsign = find_callsign.replace(/\//g, "-");
find_callsign = find_callsign.replaceAll('Ø', '0');
const url = `${base_url}index.php/logbook/json/${find_callsign}/${json_band}/${json_mode}/${stationProfile}/${startDate}/${last_qsos_count}`;
// Replace / in a callsign with - to stop urls breaking
lookupCall = $.getJSON(url, async function (result) {
// Make sure the typed callsign and json result match
if ($('#callsign').val().toUpperCase().replaceAll('Ø', '0') == result.callsign) {
// Reset QSO fields
resetDefaultQSOFields();
// --- Added for WebSocket Integration (Rotators / External Displays) ---
if (typeof window.broadcastLookupResult === 'function') {
const broadcastData = {
callsign: result.callsign,
dxcc_id: result.dxcc.adif,
name: result.callsign_name,
gridsquare: result.callsign_qra,
city: result.callsign_qth,
iota: result.callsign_iota,
state: result.callsign_state,
us_county: result.callsign_us_county,
bearing: result.bearing,
distance: result.callsign_distance,
lotw_member: result.lotw_member,
lotw_days: result.lotw_days,
eqsl_member: result.eqsl_member,
qsl_manager: result.qsl_manager,
slot_confirmed: result.dxcc_confirmed_on_band_mode,
darc_dok: result.callsign_darc_dok
};
window.broadcastLookupResult(broadcastData);
}
// ---------------------------------------------------------
// Set qso icon
get_note_status(result.callsign);
if (result.dxcc.entity != undefined) {
$('#country').val(convert_case(result.dxcc.entity));
$('#callsign_info').text(convert_case(result.dxcc.entity));
if ($("#sat_name").val() != "") {
//logbook/jsonlookupgrid/io77/SAT/0/0
await $.getJSON(base_url + 'index.php/logbook/jsonlookupcallsign/' + find_callsign + '/SAT/0/0', function (result) {
// Reset CSS values before updating
$('#callsign').removeClass("workedGrid");
$('#callsign').removeClass("confirmedGrid");
$('#callsign').removeClass("newGrid");
$('#callsign').attr('title', '');
$('#ham_of_note_line').empty().hide();
if (result.confirmed) {
$('#callsign').addClass("confirmedGrid");
$('#callsign').attr('title', lang_qso_callsign_confirmed);
} else if (result.workedBefore) {
$('#callsign').addClass("workedGrid");
$('#callsign').attr('title', lang_qso_callsign_worked);
}
else {
$('#callsign').addClass("newGrid");
$('#callsign').attr('title', lang_qso_callsign_new);
}
})
} else {
await $.getJSON(base_url + 'index.php/logbook/jsonlookupcallsign/' + find_callsign + '/0/' + $("#band").val() + '/' + $("#mode").val(), function (result) {
// Reset CSS values before updating
$('#callsign').removeClass("confirmedGrid");
$('#callsign').removeClass("workedGrid");
$('#callsign').removeClass("newGrid");
$('#callsign').attr('title', '');
$('#ham_of_note_line').empty().hide();
if (result.confirmed) {
$('#callsign').addClass("confirmedGrid");
$('#callsign').attr('title', lang_qso_callsign_confirmed);
} else if (result.workedBefore) {
$('#callsign').addClass("workedGrid");
$('#callsign').attr('title', lang_qso_callsign_worked);
} else {
$('#callsign').addClass("newGrid");
$('#callsign').attr('title', lang_qso_callsign_new);
}
})
}
changebadge(result.dxcc.adif);
// Reload DXCC summary table if it was already loaded
let $targetPane = $('#dxcc-summary');
if ($targetPane.data("loaded")) {
$targetPane.data("loaded", false);
getDxccResult(result.dxcc.adif, convert_case(result.dxcc.entity));
}
}
if (result.lotw_member == "active") {
$('#lotw_info').text("LoTW");
if (result.lotw_days > 365) {
$('#lotw_info').addClass('lotw_info_red');
} else if (result.lotw_days > 30) {
$('#lotw_info').addClass('lotw_info_orange');
$lotw_hint = ' lotw_info_orange';
} else if (result.lotw_days > 7) {
$('#lotw_info').addClass('lotw_info_yellow');
}
$('#lotw_link').attr('href', "https://lotw.arrl.org/lotwuser/act?act=" + callsign.replace('Ø', '0'));
$('#lotw_link').attr('target', "_blank");
$('#lotw_info').attr('data-bs-toggle', "tooltip");
if (result.lotw_days == 1) {
$('#lotw_info').attr('data-bs-original-title', decodeHtml(lang_lotw_upload_day_ago));
} else {
$('#lotw_info').attr('data-bs-original-title', decodeHtml(lang_lotw_upload_days_ago.replace('%x', result.lotw_days)));
}
$('[data-bs-toggle="tooltip"]').tooltip();
}
$('#qrz_info').html('
');
$('#qrz_info').attr('title', decodeHtml(lang_qso_lookup_info.replace('%s', callsign).replace('%s', 'qrz.com'))).removeClass('d-none');
$('#qrz_info').show();
$('#hamqth_info').html('
');
$('#hamqth_info').attr('title', decodeHtml(lang_qso_lookup_info.replace('%s', callsign).replace('%s', 'hamqth.com'))).removeClass('d-none');
$('#hamqth_info').show();
var $dok_select = $('#darc_dok').selectize();
var dok_selectize = $dok_select[0].selectize;
if ((result.dxcc.adif == '230') && (($("#callsign").val().trim().length) > 0)) {
if (result.callsign_darc_dok != '') {
dok_selectize.addOption({ name: result.callsign_darc_dok });
dok_selectize.setValue(result.callsign_darc_dok, false);
} else {
dok_selectize.clear();
}
} else {
dok_selectize.clear();
}
$.getJSON(base_url + 'index.php/lookup/ham_of_note/' + $('#callsign').val().toUpperCase().replaceAll('Ø', '0').replaceAll('/','-'), function (result) {
if (result && result.length > 0) {
var html = '';
$.each(result, function(i, entry) {
var linkHtml = '';
if (entry.link != null) {
linkHtml = ' ' + entry.linkname + '';
}
html += '
' + entry.description + '' + linkHtml + '
'; }); $('#ham_of_note_line').html(html).show("slow"); var minimized_elements = $('#ham_of_note_line span.minimize'); var maxlen = 50; minimized_elements.each(function(){ var t = $(this).text(); if(t.length < maxlen) return; $(this).html( t.slice(0,maxlen)+'... '+lang_qso_more+'' ); }); $('a.more', minimized_elements).click(function(event){ event.preventDefault(); $(this).hide().prev().hide(); $(this).next().show(); }); $('a.less', minimized_elements).click(function(event){ event.preventDefault(); $(this).parent().hide().prev().show().prev().show(); }); } }); $('#dxcc_id').val(result.dxcc.adif).multiselect('refresh'); await updateStateDropdown('#dxcc_id', '#stateInputLabel', '#location_us_county', '#stationCntyInputEdit'); if (result.callsign_cqz != '' && (result.callsign_cqz >= 1 && result.callsign_cqz <= 40)) { $('#cqz').val(result.callsign_cqz); } else { $('#cqz').val(result.dxcc.cqz); } if (result.callsign_ituz != '') { $('#ituz').val(result.callsign_ituz); } else { $('#ituz').val(result.dxcc.ituz); } var redIcon = L.icon({ iconUrl: icon_dot_url, iconSize: [18, 18], // size of the icon }); // Set Map to Lat/Long markers.clearLayers(); mymap.setZoom(8); // Remove previous banner (if any) if (window.mapBanner) { mymap.removeControl(window.mapBanner); } if (typeof result.latlng !== "undefined" && result.latlng !== false) { var marker = L.marker([result.latlng[0], result.latlng[1]], { icon: redIcon }); mymap.panTo([result.latlng[0], result.latlng[1]]); mymap.setView([result.latlng[0], result.latlng[1]], 8); bannerText = "📡 "+lang_qso_location_is_fetched_from_provided_gridsquare+": " + result.callsign_qra.toUpperCase(); markers.addLayer(marker).addTo(mymap); } else { mymap.panTo([result.dxcc.lat, result.dxcc.long]); mymap.setView([result.dxcc.lat, result.dxcc.long], 8); bannerText = "🌍 "+lang_qso_location_is_fetched_from_dxcc_coordinates+": " + $('#dxcc_id option:selected').text(); } // Create and add banner control window.mapBanner = L.control({ position: "bottomleft" }); // You can change position: "topleft", "bottomleft", etc. window.mapBanner.onAdd = function () { const div = L.DomUtil.create("div", "info legend"); div.style.background = "rgba(0, 0, 0, 0.7)"; div.style.color = "white"; div.style.padding = "8px 12px"; div.style.borderRadius = "8px"; div.style.fontSize = "13px"; div.style.boxShadow = "0 2px 6px rgba(0,0,0,0.3)"; div.innerHTML = bannerText; return div; }; window.mapBanner.addTo(mymap); /* Find Locator if the field is empty */ if ($('#locator').val() == "") { if (result.callsign_geoloc != 'grid' || result.timesWorked > 0) { $('#locator').val(result.callsign_qra); $('#locator_info').html(result.bearing); } if (result.callsign_distance != "" && result.callsign_distance != 0) { document.getElementById("distance").value = result.callsign_distance; } if (result.callsign_qra != "" && (result.callsign_geoloc != 'grid' || result.timesWorked > 0)) { if (result.confirmed) { $('#locator').addClass("confirmedGrid"); $('#locator').attr('title', lang_qso_grid_confirmed); } else if (result.workedBefore) { $('#locator').addClass("workedGrid"); $('#locator').attr('title', lang_qso_grid_worked); } else { $('#locator').addClass("newGrid"); $('#locator').attr('title', lang_qso_grid_new); } } else { $('#locator').removeClass("workedGrid"); $('#locator').removeClass("confirmedGrid"); $('#locator').removeClass("newGrid"); $('#locator').attr('title', ''); } } /* Find Operators Name */ if ($('#qsl_via').val() == "") { $('#qsl_via').val(result.qsl_manager); } /* Find Operators Name */ if ($('#name').val() == "") { $('#name').val(result.callsign_name); } /* Find Operators E-mail */ if ($('#email').val() == "") { // Validate that we're setting email for the correct callsign let currentCallsign = $('#callsign').val().toUpperCase().replaceAll('Ø', '0'); let resultCallsign = result.callsign.toUpperCase(); if (currentCallsign === resultCallsign) { $('#email').val(result.callsign_email); } } // Show email icon if email is available if (result.callsign_email && result.callsign_email.trim() !== "") { // Validate callsign match before showing email icon let currentCallsign = $('#callsign').val().toUpperCase().replaceAll('Ø', '0'); let resultCallsign = result.callsign.toUpperCase(); if (currentCallsign === resultCallsign) { $('#email_info').html(''); $('#email_info').attr('title', lang_qso_send_email_to.replace('%s', result.callsign_email)).removeClass('d-none'); $('#email_info').show(); } } if ($('#continent').val() == "") { $('#continent').val(result.dxcc.cont); } if ($('#qth').val() == "") { $('#qth').val(result.callsign_qth); } /* Find link to qrz.com picture */ if (result.image != "n/a") { // Verify that the result still matches the current callsign to prevent stale data let currentCallsign = $('#callsign').val().toUpperCase().replaceAll('Ø', '0'); let resultCallsign = result.callsign.toUpperCase(); if (currentCallsign !== resultCallsign) { // Callsign changed, don't display stale profile data return; } $('#callsign-image-content').html('' + nameParts.join(' ') + '
'; } // Aliases if (result.profile_aliases) { profileInfo += '' + lang_qso_profile_aliases + ': ' + result.profile_aliases + '
'; } // Previous call if (result.profile_p_call) { profileInfo += '' + lang_qso_profile_previously + ': ' + result.profile_p_call + '
'; } // Address information let addressParts = []; if (result.profile_addr1) addressParts.push(result.profile_addr1); // Zip code before city (addr2), no comma after zip if (result.profile_zip && result.profile_addr2) { addressParts.push(result.profile_zip + ' ' + result.profile_addr2); } else { if (result.profile_zip) addressParts.push(result.profile_zip); if (result.profile_addr2) addressParts.push(result.profile_addr2); } if (result.profile_state) addressParts.push(result.profile_state); if (result.profile_country) addressParts.push(result.profile_country); if (addressParts.length > 0) { let addressText = addressParts.join(', '); profileInfo += '' + addressText; // Google Maps link if coordinates available with pin marker if (result.profile_lat && result.profile_lon) { let mapsUrl = 'https://www.google.com/maps/place/' + result.profile_lat + ',' + result.profile_lon + '/@' + result.profile_lat + ',' + result.profile_lon + ',15z/data=!3m1!1e3'; profileInfo += ' '; } profileInfo += '
'; } // Email information if (result.callsign_email) { profileInfo += ''; } // Born (with age calculation) if (result.profile_born) { let currentYear = new Date().getFullYear(); let age = currentYear - parseInt(result.profile_born); profileInfo += '' + lang_qso_profile_born + ': ' + result.profile_born + ' (' + age + ' ' + lang_qso_profile_years_old + ')
'; } // License information if (result.profile_class || result.profile_efdate || result.profile_expdate) { let licenseText = ''; if (result.profile_class) { // Map common license class codes to readable names let licenseMap = { '1': lang_qso_profile_license_novice, '2': lang_qso_profile_license_technician, '3': lang_qso_profile_license_general, '4': lang_qso_profile_license_advanced, '5': lang_qso_profile_license_extra, 'E': lang_qso_profile_license_extra, 'A': lang_qso_profile_license_advanced, 'G': lang_qso_profile_license_general, 'T': lang_qso_profile_license_technician, 'N': lang_qso_profile_license_novice }; let licenseDisplay = licenseMap[result.profile_class] || result.profile_class; licenseText += lang_qso_profile_license + ': ' + licenseDisplay; } if (result.profile_efdate) { let efYear = result.profile_efdate.substring(0, 4); let yearsLicensed = new Date().getFullYear() - parseInt(efYear); licenseText += ' ' + lang_qso_profile_from + ' ' + efYear + ' (' + yearsLicensed + ' ' + lang_qso_profile_years + ')'; } if (result.profile_expdate) { let expYear = result.profile_expdate.substring(0, 4); let currentYear = new Date().getFullYear(); if (parseInt(expYear) < currentYear) { licenseText += ' ' + lang_qso_profile_expired_on + ' ' + expYear + ''; } } profileInfo += '' + licenseText + '
'; } // Website link if (result.profile_url) { profileInfo += '' + lang_qso_profile_website + '
'; } // Local time (will be auto-updated) if (result.profile_GMTOffset) { let offsetHours = parseFloat(result.profile_GMTOffset); let localTime = calculateLocalTime(offsetHours); profileInfo += '' + lang_qso_profile_local_time + ': ' + localTime + '
'; // Set up auto-update every minute setInterval(function() { let updatedTime = calculateLocalTime(offsetHours); $('#profile-local-time').html('' + lang_qso_profile_local_time + ': ' + updatedTime); }, 60000); } // QSL information let qslInfo = '' + lang_qso_profile_qsl + ': '; let qslMethodsIcons = []; // Build QSL methods icons list // Green checkmark for 1, red cross for 0, question mark for empty let eqslIcon = result.profile_eqsl == '1' ? '' : result.profile_eqsl == '0' ? '' : ''; let lotwIcon = result.profile_lotw == '1' ? '' : result.profile_lotw == '0' ? '' : ''; let mqslIcon = result.profile_mqsl == '1' ? '' : result.profile_mqsl == '0' ? '' : ''; qslMethodsIcons.push('QSL: ' + mqslIcon); qslMethodsIcons.push('LoTW: ' + lotwIcon); qslMethodsIcons.push('eQSL: ' + eqslIcon); // Display manager info as-is from QRZ (e.g., "QSL only via LOTW and QRZ.com") if (result.profile_qslmgr) { qslInfo += result.profile_qslmgr; qslInfo += '' + qslInfo + '
'; // Email information if (result.callbook_source) { profileInfo += ''+result.callbook_source+'
'; } $('#callsign-image-info').html(profileInfo); // Show the panel first so we can measure it $('#callsign-image').attr('style', 'display: true;'); // Wait for next frame to ensure rendering, then check available width setTimeout(function() { checkProfileInfoVisibility(); }, 10); } /* * Update state with returned value */ if ($("#stateDropdown").val() == "") { $("#stateDropdown").val(result.callsign_state); } /* * Update county with returned value for USA only for now * and make sure control is enabled for others * with cnty info */ var dxcc = $('#dxcc_id').val(); switch (dxcc) { case '6': case '110': case '291': selectize_usa_county('#stateDropdown', '#stationCntyInputQso'); if ($('#stationCntyInputQso').has('option').length == 0 && result.callsign_us_county != "") { var county_select = $('#stationCntyInputQso').selectize(); var county_selectize = county_select[0].selectize; county_selectize.addOption({ name: result.callsign_us_county }); county_selectize.setValue(result.callsign_us_county, false); } break; case '15': case '54': case '61': case '126': case '151': case '288': case '339': case '170': case '21': case '29': case '32': case '281': if (result.callsign_state == "") { $("#stationCntyInputQso").prop('disabled', true); } else { $("#stationCntyInputQso").prop('disabled', false); $("#stationCntyInputQso").val(result.callsign_us_county); } break; default: $("#stationCntyInputQso").prop('disabled', false); } if (result.timesWorked != "") { if (result.timesWorked == '0') { $('#timesWorked').html(lang_qso_title_not_worked_before); } else { $('#timesWorked').html(result.timesWorked + ' ' + lang_qso_title_times_worked_before); } } else { $('#timesWorked').html(lang_qso_title_previous_contacts); } if ($('#iota_ref').val() == "") { $('#iota_ref').val(result.callsign_iota); } // Hide the last QSO table $('#qso-last-table').hide(); $('#partial_view').show(); /* display past QSOs */ $('#partial_view').html(result.partial); // Get DXCC Summary loadAwardTabs(function() { getDxccResult(result.dxcc.adif, convert_case(result.dxcc.entity)); }); // Re-enable Save QSO button and hide fetch status clearTimeout(fetchTimeout); $('#saveQso').prop('disabled', false); $('#fetch_status').hide(); // Populate pending references from bandmap (after all lookup logic completes) // Small delay to ensure DOM is fully updated // Use the Map-based approach with the current callsign and expected sequence setTimeout(function() { populatePendingReferences(currentCallsign, expectedSeq); }, 100); } // Trigger custom event to notify that callsign lookup is complete $(document).trigger('callsignLookupComplete'); // else { // console.log("Callsigns do not match, skipping lookup"); // console.log("Typed Callsign: " + $('#callsign').val()); // console.log("Returned Callsign: " + result.callsign); // } }).always(function() { // Always re-enable button even if there's an error clearTimeout(fetchTimeout); // Add short delay to ensure multiselect and all fields are properly populated setTimeout(function() { $('#saveQso').prop('disabled', false); $('#fetch_status').hide(); }, 300); // Reset lookup in progress flag lookupInProgress = false; }); } else { // Reset QSO fields resetDefaultQSOFields(); } }) // This function executes the call to the backend for fetching cq summary and inserted table below qso entry function getCqResult() { satOrBand = $('#band').val(); if ($('#selectPropagation').val() == 'SAT') { satOrBand = 'SAT'; } $.ajax({ url: base_url + 'index.php/lookup/search', type: 'post', data: { type: 'cq', cqz: $('#cqz').val(), reduced_mode: true, current_band: satOrBand, current_mode: $('#mode').val(), }, success: function (html) { $('#cq-summary').empty(); $('#cq-summary').append(lang_summary_cq + ' ' + $('#cqz').val() + '.'); $('#cq-summary').append(html); } }); } // This function executes the call to the backend for fetching was summary and inserted table below qso entry function getWasResult() { $('#state-summary').empty(); if ($('#stateDropdown').val() === '') { $('#state-summary').append(lang_summary_warning_empty_state); return; } let dxccid = $('#dxcc_id').val(); if (!['291', '6', '110'].includes(dxccid)) { $('#state-summary').append(lang_summary_state_valid); return; } satOrBand = $('#band').val(); if ($('#selectPropagation').val() == 'SAT') { satOrBand = 'SAT'; } $.ajax({ url: base_url + 'index.php/lookup/search', type: 'post', data: { type: 'was', was: $('#stateDropdown').val(), reduced_mode: true, current_band: satOrBand, current_mode: $('#mode').val(), }, success: function (html) { $('#state-summary').append(lang_summary_state + ' ' + $('#stateDropdown').val() + '.'); $('#state-summary').append(html); } }); } // This function executes the call to the backend for fetching sota summary and inserted table below qso entry function getSotaResult() { $('#sota-summary').empty(); if ($('#sota_ref').val() === '') { $('#sota-summary').append(lang_summary_warning_empty_sota); return; } satOrBand = $('#band').val(); if ($('#selectPropagation').val() == 'SAT') { satOrBand = 'SAT'; } $.ajax({ url: base_url + 'index.php/lookup/search', type: 'post', data: { type: 'sota', sota: $('#sota_ref').val(), reduced_mode: true, current_band: satOrBand, current_mode: $('#mode').val(), }, success: function (html) { $('#sota-summary').append(lang_summary_sota + ' ' + $('#sota_ref').val() + '.'); $('#sota-summary').append(html); } }); } // This function executes the call to the backend for fetching pota summary and inserted table below qso entry function getPotaResult() { let potaref = $('#pota_ref').val(); $('#pota-summary').empty(); if (potaref === '') { $('#pota-summary').append(lang_summary_warning_empty_pota); return; } satOrBand = $('#band').val(); if ($('#selectPropagation').val() == 'SAT') { satOrBand = 'SAT'; } if (potaref.includes(',')) { let values = potaref.split(',').map(function(v) { return v.trim(); }).filter(function(v) { return v; }); let tabContent = $('#pota-summary'); // Tab content container tabContent.append('