Profile information, translation and bug with the callsign information fetch

This commit is contained in:
Szymon Porwolik
2025-10-28 23:10:43 +01:00
parent 344365fd8b
commit 16b2e9763e
6 changed files with 370 additions and 59 deletions

View File

@@ -162,21 +162,25 @@ class Logbook extends CI_Controller {
$measurement_base = $this->session->userdata('user_measurement_base');
}
$return['callsign_name'] = $this->nval($callbook['name'] ?? '', $this->logbook_model->call_name($callsign));
$return['callsign_qra'] = $this->nval($callbook['gridsquare'] ?? '', $this->logbook_model->call_qra($callsign));
// Fill in the return array with callbook data, using database values as fallback
// Callbook data takes precedence over database values
// Logic updated to use nval() function to prioritize callbook data
// Added callsign_email field
$return['callsign_name'] = $this->nval($this->logbook_model->call_name($callsign), $callbook['name'] ?? '');
$return['callsign_qra'] = $this->nval($this->logbook_model->call_qra($callsign), $callbook['gridsquare'] ?? '');
$return['callsign_geoloc'] = $callbook['geoloc'] ?? '';
$return['callsign_distance'] = $this->distance($return['callsign_qra'], $station_id);
$return['callsign_qth'] = $this->nval($callbook['city'] ?? '', $this->logbook_model->call_qth($callsign));
$return['callsign_iota'] = $this->nval($callbook['iota'] ?? '', $this->logbook_model->call_iota($callsign));
$return['callsign_email'] = $this->nval($callbook['email'] ?? '', $this->logbook_model->call_email($callsign));
$return['qsl_manager'] = $this->nval($callbook['qslmgr'] ?? '', $this->logbook_model->call_qslvia($callsign));
$return['callsign_state'] = $this->nval($callbook['state'] ?? '', $this->logbook_model->call_state($callsign));
$return['callsign_us_county'] = $this->nval($callbook['us_county'] ?? '', $this->logbook_model->call_us_county($callsign));
$return['callsign_ituz'] = $this->nval($callbook['ituz'] ?? '', $this->logbook_model->call_ituzone($callsign));
$return['callsign_cqz'] = $this->nval($callbook['cqz'] ?? '', $this->logbook_model->call_cqzone($callsign));
$return['callsign_qth'] = $this->nval($this->logbook_model->call_qth($callsign), $callbook['city'] ?? '');
$return['callsign_iota'] = $this->nval($this->logbook_model->call_iota($callsign), $callbook['iota'] ?? '');
$return['callsign_email'] = $this->nval($this->logbook_model->call_email($callsign), $callbook['email'] ?? '');
$return['qsl_manager'] = $this->nval($this->logbook_model->call_qslvia($callsign), $callbook['qslmgr'] ?? '');
$return['callsign_state'] = $this->nval($this->logbook_model->call_state($callsign), $callbook['state'] ?? '');
$return['callsign_us_county'] = $this->nval($this->logbook_model->call_us_county($callsign), $callbook['us_county'] ?? '');
$return['callsign_ituz'] = $this->nval($this->logbook_model->call_ituzone($callsign), $callbook['ituz'] ?? '');
$return['callsign_cqz'] = $this->nval($this->logbook_model->call_cqzone($callsign), $callbook['cqz'] ?? '');
$return['workedBefore'] = $this->worked_grid_before($return['callsign_qra'], $band, $mode);
$return['confirmed'] = $this->confirmed_grid_before($return['callsign_qra'], $band, $mode);
$return['timesWorked'] = $this->logbook_model->times_worked($lookupcall);
$return['confirmed'] = $this->confirmed_grid_before($return['callsign_qra'], $band, $mode);
$return['timesWorked'] = $this->logbook_model->times_worked($lookupcall);
if ($this->session->userdata('user_show_profile_image')) {
if (isset($callbook) && isset($callbook['image'])) {
@@ -188,6 +192,31 @@ class Logbook extends CI_Controller {
} else {
$return['image'] = "n/a";
}
// Additional profile information from QRZ
$return['profile_url'] = $callbook['url'] ?? '';
$return['profile_class'] = $callbook['class'] ?? '';
$return['profile_born'] = $callbook['born'] ?? '';
$return['profile_eqsl'] = $callbook['eqsl'] ?? '';
$return['profile_lotw'] = $callbook['lotw'] ?? '';
$return['profile_mqsl'] = $callbook['mqsl'] ?? '';
$return['profile_fname'] = $callbook['fname'] ?? '';
$return['profile_name_last'] = $callbook['name_last'] ?? '';
$return['profile_nickname'] = $callbook['nickname'] ?? '';
$return['profile_aliases'] = $callbook['aliases'] ?? '';
$return['profile_p_call'] = $callbook['p_call'] ?? '';
$return['profile_addr1'] = $callbook['addr1'] ?? '';
$return['profile_addr2'] = $callbook['addr2'] ?? '';
$return['profile_state'] = $callbook['state'] ?? '';
$return['profile_zip'] = $callbook['zip'] ?? '';
$return['profile_country'] = $callbook['country'] ?? '';
$return['profile_dxcc'] = $callbook['dxcc'] ?? '';
$return['profile_lat'] = $callbook['lat'] ?? '';
$return['profile_lon'] = $callbook['lon'] ?? '';
$return['profile_efdate'] = $callbook['efdate'] ?? '';
$return['profile_expdate'] = $callbook['expdate'] ?? '';
$return['profile_GMTOffset'] = $callbook['GMTOffset'] ?? '';
$return['profile_qslmgr'] = $callbook['qslmgr'] ?? '';
}
if ($return['callsign_qra'] != "" || $return['callsign_qra'] != null) {

View File

@@ -91,60 +91,99 @@ class Qrz {
return $data['error'] = $xml->Session->Error;
}
// Return Required Fields
// Map all QRZ XML fields according to API specification
$data['callsign'] = (string)$xml->Callsign->call;
if ($use_fullname === true) {
$data['name'] = (string)$xml->Callsign->fname. ' ' . (string)$xml->Callsign->name;
} else {
$data['name'] = (string)$xml->Callsign->fname;
}
$data['xref'] = (string)$xml->Callsign->xref;
$data['aliases'] = (string)$xml->Callsign->aliases;
$data['dxcc'] = (string)$xml->Callsign->dxcc;
$data['fname'] = (string)$xml->Callsign->fname;
$data['name_last'] = (string)$xml->Callsign->name;
$data['addr1'] = (string)$xml->Callsign->addr1;
$data['addr2'] = (string)$xml->Callsign->addr2;
$data['state'] = (string)$xml->Callsign->state;
$data['zip'] = (string)$xml->Callsign->zip;
$data['country'] = (string)$xml->Callsign->country;
$data['ccode'] = (string)$xml->Callsign->ccode;
$data['lat'] = (string)$xml->Callsign->lat;
$data['lon'] = (string)$xml->Callsign->lon;
$data['grid'] = (string)$xml->Callsign->grid;
$data['county'] = (string)$xml->Callsign->county;
$data['fips'] = (string)$xml->Callsign->fips;
$data['land'] = (string)$xml->Callsign->land;
$data['efdate'] = (string)$xml->Callsign->efdate;
$data['expdate'] = (string)$xml->Callsign->expdate;
$data['p_call'] = (string)$xml->Callsign->p_call;
$data['class'] = (string)$xml->Callsign->class;
$data['codes'] = (string)$xml->Callsign->codes;
$data['qslmgr'] = (string)$xml->Callsign->qslmgr;
$data['email'] = (string)$xml->Callsign->email;
$data['url'] = (string)$xml->Callsign->url;
$data['u_views'] = (string)$xml->Callsign->u_views;
$data['bio'] = (string)$xml->Callsign->bio;
$data['biodate'] = (string)$xml->Callsign->biodate;
$data['image'] = (string)$xml->Callsign->image;
$data['imageinfo'] = (string)$xml->Callsign->imageinfo;
$data['serial'] = (string)$xml->Callsign->serial;
$data['moddate'] = (string)$xml->Callsign->moddate;
$data['MSA'] = (string)$xml->Callsign->MSA;
$data['AreaCode'] = (string)$xml->Callsign->AreaCode;
$data['TimeZone'] = (string)$xml->Callsign->TimeZone;
$data['GMTOffset'] = (string)$xml->Callsign->GMTOffset;
$data['DST'] = (string)$xml->Callsign->DST;
$data['eqsl'] = (string)$xml->Callsign->eqsl;
$data['mqsl'] = (string)$xml->Callsign->mqsl;
$data['cqzone'] = (string)$xml->Callsign->cqzone;
$data['ituzone'] = (string)$xml->Callsign->ituzone;
$data['born'] = (string)$xml->Callsign->born;
$data['user'] = (string)$xml->Callsign->user;
$data['lotw'] = (string)$xml->Callsign->lotw;
$data['iota'] = (string)$xml->Callsign->iota;
$data['geoloc'] = (string)$xml->Callsign->geoloc;
$data['attn'] = (string)$xml->Callsign->attn;
$data['nickname'] = (string)$xml->Callsign->nickname;
$data['name_fmt'] = (string)$xml->Callsign->name_fmt;
// Build legacy 'name' field for backward compatibility
if ($use_fullname === true) {
$data['name'] = $data['fname']. ' ' . $data['name_last'];
} else {
$data['name'] = $data['fname'];
}
// we always give back the name, no matter if reduced data or not
$data['name'] = trim($data['name']);
// Sanitize gridsquare to allow only up to 8 characters
$unclean_gridsquare = (string)$xml->Callsign->grid; // Get the gridsquare from QRZ convert to string
$clean_gridsquare = strlen($unclean_gridsquare) > 8 ? substr($unclean_gridsquare,0,8) : $unclean_gridsquare; // Trim gridsquare to 8 characters max
$unclean_gridsquare = $data['grid'];
$clean_gridsquare = strlen($unclean_gridsquare) > 8 ? substr($unclean_gridsquare,0,8) : $unclean_gridsquare;
if ($reduced == false) {
$data['gridsquare'] = $clean_gridsquare;
$data['geoloc'] = (string)$xml->Callsign->geoloc;
$data['city'] = (string)$xml->Callsign->addr2;
$data['lat'] = (string)$xml->Callsign->lat;
$data['long'] = (string)$xml->Callsign->lon;
$data['dxcc'] = (string)$xml->Callsign->dxcc;
$data['state'] = (string)$xml->Callsign->state;
$data['iota'] = (string)$xml->Callsign->iota;
$data['qslmgr'] = (string)$xml->Callsign->qslmgr;
$data['image'] = (string)$xml->Callsign->image;
$data['ituz'] = (string)$xml->Callsign->ituzone;
$data['cqz'] = (string)$xml->Callsign->cqzone;
if ($xml->Callsign->country == "United States") {
$data['us_county'] = (string)$xml->Callsign->county;
} else {
$data['us_county'] = null;
}
// Map fields for backward compatibility with existing code
$data['gridsquare'] = $clean_gridsquare;
$data['city'] = $data['addr2'];
$data['long'] = $data['lon'];
$data['ituz'] = $data['ituzone'];
$data['cqz'] = $data['cqzone'];
if ($data['country'] == "United States") {
$data['us_county'] = $data['county'];
} else {
$data['us_county'] = null;
}
if ($reduced == true) {
// Clear location-specific fields for reduced mode
$data['gridsquare'] = '';
$data['city'] = '';
$data['lat'] = '';
$data['long'] = '';
$data['lon'] = '';
$data['dxcc'] = '';
$data['state'] = '';
$data['iota'] = '';
$data['qslmgr'] = (string)$xml->Callsign->qslmgr;
$data['image'] = (string)$xml->Callsign->image;
$data['us_county'] = '';
$data['ituz'] = '';
$data['cqz'] = '';
$data['ituzone'] = '';
$data['cqzone'] = '';
}
} finally {

View File

@@ -93,6 +93,23 @@
var lang_qso_lookup_summit_info = "<?= __("Lookup %s summit info on %s"); ?>";
var lang_qso_lookup_reference_info = "<?= __("Lookup %s reference info on %s"); ?>";
var lang_qso_error_loading_bearing = "<?= __("Error loading bearing!"); ?>";
var lang_qso_profile_aliases = "<?= __("Aliases"); ?>";
var lang_qso_profile_previously = "<?= __("Previously"); ?>";
var lang_qso_profile_born = "<?= __("Born"); ?>";
var lang_qso_profile_years_old = "<?= __("years old"); ?>";
var lang_qso_profile_license = "<?= __("License"); ?>";
var lang_qso_profile_from = "<?= __("from"); ?>";
var lang_qso_profile_years = "<?= __("years"); ?>";
var lang_qso_profile_expired_on = "<?= __("expired on"); ?>";
var lang_qso_profile_website = "<?= __("Website"); ?>";
var lang_qso_profile_local_time = "<?= __("Local time"); ?>";
var lang_qso_profile_qsl = "<?= __("QSL"); ?>";
var lang_qso_profile_view_location_maps = "<?= __("View location on Google Maps (Satellite)"); ?>";
var lang_qso_profile_license_novice = "<?= __("Novice"); ?>";
var lang_qso_profile_license_technician = "<?= __("Technician"); ?>";
var lang_qso_profile_license_general = "<?= __("General"); ?>";
var lang_qso_profile_license_advanced = "<?= __("Advanced"); ?>";
var lang_qso_profile_license_extra = "<?= __("Extra"); ?>";
var lang_qso_gridsquare_formatting = "<?= __("Gridsquare Formatting"); ?>";
var lang_qso_gridsquare_help = "<?= __("Enter multiple (4-digit) grids separated with commas. For example: IO77,IO78"); ?>";
</script>

View File

@@ -828,10 +828,19 @@ switch ($date_format) {
<?php if ($this->session->userdata('user_show_profile_image')) { ?>
<div class="card callsign-image" id="callsign-image" style="display: none;">
<div class="card-header"><h4 style="font-size: 16px; font-weight: bold;" class="card-title"><?= __("Profile Picture"); ?></h4></div>
<div class="card-header">
<h4 style="font-size: 16px; font-weight: bold;" class="card-title mb-0">
<?= __("QSO Partner's Profile"); ?>
<span class="ms-1" data-bs-toggle="tooltip" title="<?= __("Profile picture and data fetched from third-party services. This information is not stored on your Wavelog instance.") ?>">
<i class="fa fa-question-circle"></i>
</span>
</h4>
</div>
<div class="card-body callsign-image">
<div class="callsign-image-content" id="callsign-image-content">
<div class="card-body callsign-image d-flex gap-3">
<div class="callsign-image-content" id="callsign-image-content" style="flex-shrink: 0; max-width: 100%;">
</div>
<div class="callsign-image-info" id="callsign-image-info" style="flex-grow: 1; min-width: 0; display: none;">
</div>
</div>
</div>

View File

@@ -643,6 +643,9 @@ div#station_logbooks_linked_table_paginate {
.callsign-image-pic {
max-height: 250px;
max-width: 100%;
border: 1px solid var(--bs-card-cap-bg);
border-radius: 0.375rem;
display: block;
}
#mapactivators {

View File

@@ -5,6 +5,52 @@ var scps = [];
let lookupCall = null;
let preventLookup = false;
// 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();
});
// 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');
@@ -957,6 +1003,8 @@ function reset_fields() {
$('#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("");
@@ -1308,19 +1356,29 @@ $("#callsign").on("focusout", function () {
$('#name').val(result.callsign_name);
}
/* Find Operators E-mail */
if ($('#email').val() == "") {
/* 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() !== "") {
// 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('<a href="mailto:' + result.callsign_email + '" style="color: inherit; text-decoration: none;"><i class="fas fa-envelope" style="font-size: 20px;"></i></a>');
$('#email_info').attr('title', lang_qso_send_email_to.replace('%s', result.callsign_email)).removeClass('d-none');
$('#email_info').show();
}
if ($('#continent').val() == "") {
} if ($('#continent').val() == "") {
$('#continent').val(result.dxcc.cont);
}
@@ -1328,13 +1386,169 @@ $("#callsign").on("focusout", function () {
$('#qth').val(result.callsign_qth);
}
/* Find link to qrz.com picture */
if (result.image != "n/a") {
$('#callsign-image-content').html('<img class="callsign-image-pic" href="' + result.image + '" data-fancybox="images" src="' + result.image + '" style="cursor: pointer;">');
$('#callsign-image').attr('style', 'display: true;');
/* 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('<img class="callsign-image-pic" href="' + result.image + '" data-fancybox="images" src="' + result.image + '" style="cursor: pointer;">');
// Store which callsign this profile data belongs to
$('#callsign-image').attr('data-profile-callsign', resultCallsign);
// Build comprehensive profile information
let profileInfo = ''; // Name line: Name Nickname Lastname
let nameParts = [];
if (result.profile_fname) nameParts.push(result.profile_fname);
if (result.profile_nickname) nameParts.push('"' + result.profile_nickname + '"');
if (result.profile_name_last) nameParts.push(result.profile_name_last);
if (nameParts.length > 0) {
profileInfo += '<p class="mb-1"><strong>' + nameParts.join(' ') + '</strong></p>';
}
// Aliases
if (result.profile_aliases) {
profileInfo += '<p class="mb-1 text-muted" style="font-size: 0.85rem;">' + lang_qso_profile_aliases + ': ' + result.profile_aliases + '</p>';
}
// Previous call
if (result.profile_p_call) {
profileInfo += '<p class="mb-1 text-muted" style="font-size: 0.85rem;">' + lang_qso_profile_previously + ': ' + result.profile_p_call + '</p>';
}
// 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 += '<p class="mb-1" style="font-size: 0.875rem;"><i class="fas fa-map-marker-alt me-1"></i>' + 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 += ' <a href="' + mapsUrl + '" target="_blank" title="' + lang_qso_profile_view_location_maps + '"><i class="fas fa-map"></i></a>';
}
profileInfo += '</p>';
}
// Born (with age calculation)
if (result.profile_born) {
let currentYear = new Date().getFullYear();
let age = currentYear - parseInt(result.profile_born);
profileInfo += '<p class="mb-1" style="font-size: 0.875rem;"><i class="fas fa-birthday-cake me-1"></i>' + lang_qso_profile_born + ': ' + result.profile_born + ' (' + age + ' ' + lang_qso_profile_years_old + ')</p>';
}
// License information
if (result.profile_class || result.profile_efdate || result.profile_expdate) {
let licenseText = '<i class="fas fa-certificate me-1"></i>';
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 += ' <span class="text-danger">' + lang_qso_profile_expired_on + ' ' + expYear + '</span>';
}
}
profileInfo += '<p class="mb-1" style="font-size: 0.875rem;">' + licenseText + '</p>';
}
// Website link
if (result.profile_url) {
profileInfo += '<p class="mb-1" style="font-size: 0.875rem;"><i class="fas fa-globe me-1"></i><a href="' + result.profile_url + '" target="_blank">' + lang_qso_profile_website + '</a></p>';
}
// Local time (will be auto-updated)
if (result.profile_GMTOffset) {
let offsetHours = parseFloat(result.profile_GMTOffset);
let localTime = calculateLocalTime(offsetHours);
profileInfo += '<p class="mb-1" id="profile-local-time" style="font-size: 0.875rem;"><i class="fas fa-clock me-1"></i>' + lang_qso_profile_local_time + ': ' + localTime + '</p>';
// Set up auto-update every minute
setInterval(function() {
let updatedTime = calculateLocalTime(offsetHours);
$('#profile-local-time').html('<i class="fas fa-clock me-1"></i>' + lang_qso_profile_local_time + ': ' + updatedTime);
}, 60000);
}
// QSL information
let qslInfo = '<i class="fas fa-envelope me-1"></i>' + 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' ? '<i class="fas fa-check-circle text-success"></i>' :
result.profile_eqsl == '0' ? '<i class="fas fa-times-circle text-danger"></i>' :
'<i class="fas fa-question-circle text-warning"></i>';
let lotwIcon = result.profile_lotw == '1' ? '<i class="fas fa-check-circle text-success"></i>' :
result.profile_lotw == '0' ? '<i class="fas fa-times-circle text-danger"></i>' :
'<i class="fas fa-question-circle text-warning"></i>';
let mqslIcon = result.profile_mqsl == '1' ? '<i class="fas fa-check-circle text-success"></i>' :
result.profile_mqsl == '0' ? '<i class="fas fa-times-circle text-danger"></i>' :
'<i class="fas fa-question-circle text-warning"></i>';
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 += '<br><span style="font-size: 0.85rem;">(' + qslMethodsIcons.join(', ') + ')</span>';
} else {
qslInfo += qslMethodsIcons.join(', ');
}
profileInfo += '<p class="mb-0" style="font-size: 0.875rem;">' + qslInfo + '</p>'; $('#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() == "") {