// Callsign always has focus on load $("#callsign").focus(); var sessiondata = {}; $(document).ready(function () { (async function() { sessiondata = await getSession(); // save sessiondata global (we need it later, when adding qso) await restoreContestSession(sessiondata); // wait for restoring until finished setRst($("#mode").val()); setContestingTabOrder($("#exchangetype").val()); $("#callsign").focus().select(); })(); renderCallhistoryPanel([]); /* On Key up Calculate Bearing and Distance for Contest Gridsquare */ $(document).on('keyup', '#exch_gridsquare_r', function(){ calculateContestBearingDistance(); }); /* On Change also calculate Bearing and Distance for Contest Gridsquare */ $(document).on('change', '#exch_gridsquare_r', function(){ calculateContestBearingDistance(); }); }); function escapeHtml(unsafeText) { return String(unsafeText || '').replace(/[&<>"]/g, function (tag) { var replacements = { '&': '&', '<': '<', '>': '>', '"': '"' }; return replacements[tag] || tag; }); } function normalizeCallhistoryText(value) { return String(value || '').trim().toLowerCase(); } function renderCallhistoryPanel(matches) { var $card = $('#callhistory-info-panel'); var $panel = $('#callhistory-results'); if ($panel.length === 0 || $card.length === 0) { return; } if (!matches || matches.length === 0) { $card.hide(); return; } var html = ''; $panel.html(html); $card.show(); } function lookupCallhistory(call) { $.ajax({ url: base_url + 'index.php/callhistory/lookup', type: 'post', data: { callsign: call }, success: function (response) { if (!response || response.status !== 'ok') { renderCallhistoryPanel([]); return; } renderCallhistoryPanel(response.matches || []); }, error: function () { renderCallhistoryPanel([]); } }); } function setContestingTabOrder(exchangetype) { var orderedFieldIds = ['callsign', 'rst_sent']; switch (exchangetype) { case 'Exchange': orderedFieldIds.push('exch_sent'); orderedFieldIds.push('rst_rcvd'); orderedFieldIds.push('exch_rcvd'); break; case 'Gridsquare': orderedFieldIds.push('rst_rcvd'); orderedFieldIds.push('exch_gridsquare_r'); break; case 'Serial': orderedFieldIds.push('exch_serial_s'); orderedFieldIds.push('rst_rcvd'); orderedFieldIds.push('exch_serial_r'); break; case 'Serialexchange': orderedFieldIds.push('exch_serial_s'); orderedFieldIds.push('exch_sent'); orderedFieldIds.push('rst_rcvd'); orderedFieldIds.push('exch_serial_r'); orderedFieldIds.push('exch_rcvd'); break; case 'Serialgridsquare': orderedFieldIds.push('exch_serial_s'); orderedFieldIds.push('rst_rcvd'); orderedFieldIds.push('exch_serial_r'); orderedFieldIds.push('exch_gridsquare_r'); break; default: orderedFieldIds.push('rst_rcvd'); break; } orderedFieldIds.push('name'); orderedFieldIds.push('comment'); orderedFieldIds.push('save_qso'); $('#qso_input').find('input, select, button, textarea').attr('tabindex', '-1'); var tabindex = 1; orderedFieldIds.forEach(function (id) { var $field = $('#' + id); if ($field.length === 0 || $field.is(':disabled')) { return; } if (!$field.is(':visible')) { return; } $field.attr('tabindex', tabindex); tabindex += 1; }); } function calculateCallsignBearingDistance(callsign) { if (!callsign || callsign.length < 3) { return; } // Only proceed if we have a home gridsquare if (!my_gridsquare || my_gridsquare.length < 4) { return; } // Look up the callsign's QRA and get bearing/distance $.ajax({ url: base_url + 'index.php/logbook/contest_callsign_qra', type: 'post', data: { callsign: callsign, my_grid: my_gridsquare }, success: function(data) { if (data && (data.bearing !== '' || data.distance > 0)) { var unit = (measurement_base === 'M' ? ' mi' : measurement_base === 'N' ? ' nmi' : ' km'); // Display in the always-visible DXCC bearing area if (data.bearing !== '' && data.bearing !== undefined) { $('#locator_info_contest_dxcc').html(String(data.bearing) + '°'); $('#locator_info_contest_dxcc').show(); } if (data.distance && data.distance > 0) { $('#distance_contest_dxcc').text(parseFloat(data.distance).toFixed(0) + unit); $('#distance_contest_dxcc').show(); } } }, error: function(xhr, status, error) { console.log("Callsign QRA lookup error: " + error); }, }); } function calculateContestBearingDistance() { var received_grid = $("#exch_gridsquare_r").val(); if (!received_grid || received_grid.length < 4) { $('#locator_info_contest').text(""); $('#distance_contest').val(""); return; } // Only proceed if we have a home gridsquare if (!my_gridsquare || my_gridsquare.length < 4) { $('#locator_info_contest').text("No home grid"); return; } // Call backend to calculate bearing $.ajax({ url: base_url + 'index.php/logbook/contest_bearing', type: 'post', data: { grid: received_grid, my_grid: my_gridsquare }, success: function(data) { if (data && data.length > 0) { // Format bearing with degree symbol $('#locator_info_contest').html(data.trim() + '°'); } else { $('#locator_info_contest').text(""); } }, error: function(xhr, status, error) { console.log("Bearing error: " + error); }, }); // Call backend to calculate distance $.ajax({ url: base_url + 'index.php/logbook/contest_distance', type: 'post', data: { grid: received_grid, my_grid: my_gridsquare }, success: function(data) { if (data && data.length > 0 && !isNaN(data)) { // Format distance with unit based on user preference var distance_value = parseFloat(data).toFixed(2); var unit = ' km'; // Default if (measurement_base === 'M') { unit = ' mi'; } else if (measurement_base === 'N') { unit = ' nmi'; } $('#distance_contest').val(distance_value + unit); } else { $('#distance_contest').val(""); } }, error: function(xhr, status, error) { console.log("Distance error: " + error); }, }); } // Resets the logging form and deletes session from database function reset_contest_session() { $('#name').val(""); $('.callsign-suggestions').text(""); $('#callsign').val(""); $('#comment').val(""); $("#exch_serial_s").val("1"); $("#exch_serial_r").val(""); $('#exch_sent').val(""); $('#exch_rcvd').val(""); $("#exch_gridsquare_r").val(""); $('#locator_info_contest').text(""); $('#distance_contest').val(""); $('#locator_info_contest_dxcc').text(""); $('#distance_contest_dxcc').text(""); $('#locator_info_contest_dxcc').hide(); $('#distance_contest_dxcc').hide(); $("#callsign").focus(); setRst($("#mode").val()); $("#exchangetype").val("None"); setExchangetype("None"); $("#contestname").val("Other").change(); $(".contest_qso_table_contents").empty(); $('#copyexchangeto').val("None"); $.ajax({ url: base_url + 'index.php/contesting/deleteSession', type: 'post', success: function (data) { } }); } // Storing the contestid in contest session $('#contestname, #copyexchangeto').change(function () { var formdata = new FormData(document.getElementById("qso_input")); setSession(formdata); }); // Storing the exchange type in contest session $('#exchangetype').change(function () { var exchangetype = $("#exchangetype").val(); var formdata = new FormData(document.getElementById("qso_input")); setSession(formdata); setExchangetype(exchangetype); setContestingTabOrder(exchangetype); }); function setSession(formdata) { formdata.set('copyexchangeto',$("#copyexchangeto option:selected").index()); return $.ajax({ url: base_url + 'index.php/contesting/setSession', type: 'post', data: formdata, processData: false, contentType: false, }); } // realtime clock if (!manual) { $(function ($) { handleStart = setInterval(function () { getUTCTimeStamp($('.input_time')); }, 500); }); $(function ($) { handleDate = setInterval(function () { getUTCDateStamp($('.input_date')); }, 1000); }); } // We don't want spaces to be written in callsign // We don't want spaces to be written in exchange // We don't want spaces to be written in time :) $(function () { $('#callsign, #exch_rcvd, #start_time').on('keypress', function (e) { if (e.which == 32) { return false; } }); }); // We don't want anything but numbers to be written in serial $(function () { $('#exch_serial_r, #exch_serial_s').on('keypress', function (e) { if (e.key.charCodeAt(0) < 48 || e.key.charCodeAt(0) > 57) { return false; } }); }); // checked if worked before after blur $("#callsign").blur(function () { checkIfWorkedBefore(); // Restore full logbook table once user moves away from callsign field if ($.fn.DataTable.isDataTable('.qsotable')) { $('.qsotable').DataTable().search('').draw(); } }); // Here we capture keystrokes to execute functions document.onkeyup = function (e) { // ALT-W wipe if (e.altKey && e.which == 87) { reset_log_fields(); // CTRL-Enter logs QSO } else if ((e.keyCode == 10 || e.keyCode == 13) && (e.ctrlKey || e.metaKey)) { logQso(); // Enter in received exchange logs QSO } else if ((e.which == 13) && ( ($(document.activeElement).attr("id") == "exch_rcvd") || ($(document.activeElement).attr("id") == "exch_gridsquare_r") || ($(document.activeElement).attr("id") == "exch_serial_r") ) ) { logQso(); } else if (e.which == 27) { reset_log_fields(); // Space to jump to either callsign or the various exchanges } else if (e.which == 32) { var exchangetype = $("#exchangetype").val(); if (manual && $(document.activeElement).attr("id") == "start_time") { $("#callsign").focus(); return false; } if (exchangetype == 'Exchange') { if ($(document.activeElement).attr("id") == "callsign") { $("#exch_rcvd").focus(); return false; } else if ($(document.activeElement).attr("id") == "exch_rcvd") { $("#callsign").focus(); return false; } } else if (exchangetype == 'Serial') { if ($(document.activeElement).attr("id") == "callsign") { $("#exch_serial_r").focus(); return false; } else if ($(document.activeElement).attr("id") == "exch_serial_r") { $("#callsign").focus(); return false; } } else if (exchangetype == 'Serialexchange') { if ($(document.activeElement).attr("id") == "callsign") { $("#exch_serial_r").focus(); return false; } else if ($(document.activeElement).attr("id") == "exch_serial_r") { $("#exch_rcvd").focus(); return false; } else if ($(document.activeElement).attr("id") == "exch_rcvd") { $("#callsign").focus(); return false; } } else if (exchangetype == 'Serialgridsquare') { if ($(document.activeElement).attr("id") == "callsign") { $("#exch_serial_r").focus(); return false; } else if ($(document.activeElement).attr("id") == "exch_serial_r") { $("#exch_gridsquare_r").focus(); return false; } else if ($(document.activeElement).attr("id") == "exch_gridsquare_r") { $("#callsign").focus(); return false; } } else if (exchangetype == 'Gridsquare') { if ($(document.activeElement).attr("id") == "callsign") { $("#exch_gridsquare_r").focus(); return false; } else if ($(document.activeElement).attr("id") == "exch_gridsquare_r") { $("#callsign").focus(); return false; } } } }; /* time input shortcut */ $('#start_time').change(function () { var raw_time = $(this).val(); if (raw_time.match(/^\d\[0-6]d$/)) { raw_time = "0" + raw_time; } if (raw_time.match(/^[012]\d[0-5]\d$/)) { raw_time = raw_time.substring(0, 2) + ":" + raw_time.substring(2, 4); $('#start_time').val(raw_time); } }); /* date input shortcut */ $('#start_date').change(function () { raw_date = $(this).val(); if (raw_date.match(/^[12]\d{3}[01]\d[0123]\d$/)) { raw_date = raw_date.substring(0, 4) + "-" + raw_date.substring(4, 6) + "-" + raw_date.substring(6, 8); $('#start_date').val(raw_date); } }); // On Key up check and suggest callsigns var dupeCheckTimer = null; $("#callsign").keyup(function () { var call = $(this).val(); if (call.length >= 3) { $.ajax({ url: 'lookup/scp', method: 'POST', data: { callsign: $(this).val().toUpperCase() }, success: function (result) { if (result && result.trim() !== '') { $('.callsign-suggestions').text(result); highlight(call.toUpperCase()); $('.callsign-suggest').show(); } else { $('.callsign-suggestions').text(''); $('.callsign-suggest').hide(); } } }); // Debounced dupe check while typing clearTimeout(dupeCheckTimer); dupeCheckTimer = setTimeout(function() { checkIfWorkedBefore(); }, 400); var qTable = $('.qsotable').DataTable(); qTable.search(call).draw(); lookupCallhistory(call.toUpperCase()); } else if (call.length <= 2) { $('.callsign-suggestions').text(""); $('.callsign-suggest').hide(); $('#callsign').css({'border-color': '', 'box-shadow': ''}); $('#callsign_info').text("").removeClass('text-bg-danger text-bg-success'); renderCallhistoryPanel([]); if ($.fn.DataTable.isDataTable('.qsotable')) { $('.qsotable').DataTable().search('').draw(); } } }); function checkIfWorkedBefore() { var call = $("#callsign").val(); if (call.length >= 3) { $('#callsign_info').text(""); $.ajax({ url: base_url + 'index.php/contesting/checkIfWorkedBefore', type: 'post', data: { 'call': call, 'mode': $("#mode").val(), 'band': $("#band").val(), 'contest': $("#contestname").val() }, success: function (result) { if (result.message.substr(0, 6) == 'Worked') { $('#callsign_info').removeClass('text-bg-success'); $('#callsign_info').addClass('text-bg-danger'); $('#callsign_info').text(result.message); $('#callsign').css({'border-color': '#dc3545', 'box-shadow': '0 0 0 0.2rem rgba(220,53,69,.25)'}); } else if (result.message == "OKAY") { $('#callsign_info').removeClass('text-bg-danger'); $('#callsign_info').addClass('text-bg-success'); $('#callsign_info').text("Go Work Them!"); $('#callsign').css({'border-color': '#198754', 'box-shadow': '0 0 0 0.2rem rgba(25,135,84,.25)'}); } else { $('#callsign_info').text(""); $('#callsign').css({'border-color': '', 'box-shadow': ''}); } } }); // If gridsquare field is empty, try to get it from callsign lookup if ($("#exch_gridsquare_r").val().length === 0) { calculateCallsignBearingDistance(call); } } else { $('#callsign_info').text("").removeClass('text-bg-danger text-bg-success'); $('#callsign').css({'border-color': '', 'box-shadow': ''}); } } async function reset_log_fields() { $('#name').val(""); $('.callsign-suggestions').text(""); $('.callsign-suggest').hide(); $('#callsign').val("").css({'border-color': '', 'box-shadow': ''}); $('#comment').val(""); $('#exch_rcvd').val(""); $('#exch_serial_r').val(""); $('#exch_gridsquare_r').val(""); $('#locator_info_contest').text(""); $('#distance_contest').val(""); $('#locator_info_contest_dxcc').text(""); $('#distance_contest_dxcc').text(""); $('#locator_info_contest_dxcc').hide(); $('#distance_contest_dxcc').hide(); $("#callsign").focus(); setRst($("#mode").val()); $('#callsign_info').text("").removeClass('text-bg-danger text-bg-success'); renderCallhistoryPanel([]); sessiondata = await getSession(); await refresh_qso_table(sessiondata); var qTable = $('.qsotable').DataTable(); qTable.search('').draw(); } RegExp.escape = function (text) { return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); } function highlight(term, base) { if (!term) return; base = base || document.body; var re = new RegExp("(" + RegExp.escape(term) + ")", "gi"); var replacement = "" + term + ""; $(".callsign-suggestions", base).contents().each(function (i, el) { if (el.nodeType === 3) { var data = el.data; if (data = data.replace(re, replacement)) { var wrapper = $("").html(data); $(el).before(wrapper.contents()).remove(); } } }); } // Only set the frequency when not set by userdata/PHP. if ($('#frequency').val() == "") { $.get('qso/band_to_freq/' + $('#band').val() + '/' + $('.mode').val(), function (result) { $('#frequency').val(result); $('#frequency_rx').val(""); }); } /* on mode change */ $('#mode').change(function () { $.get('qso/band_to_freq/' + $('#band').val() + '/' + $('.mode').val(), function (result) { $('#frequency').val(result); $('#frequency_rx').val(""); }); setRst($("#mode").val()); checkIfWorkedBefore(); }); /* Calculate Frequency */ /* on band change */ $('#band').change(function () { $.get('qso/band_to_freq/' + $(this).val() + '/' + $('.mode').val(), function (result) { $('#frequency').val(result); $('#frequency_rx').val(""); }); checkIfWorkedBefore(); }); function setSerial(data) { var serialsent = 1; if (data.serialsent != "") { serialsent = parseInt(data.serialsent); } $("#exch_serial_s").val(serialsent); } function setExchangetype(exchangetype) { // Perhaps a better approach is to hide everything, then just enable the things you need $(".exchanger").hide(); $(".exchanges").hide(); $(".serials").hide(); $(".serialr").hide(); $(".gridsquarer").hide(); $(".gridsquares").hide(); if (exchangetype == 'Exchange') { $(".exchanger").show(); $(".exchanges").show(); } else if (exchangetype == 'Serial') { $(".serials").show(); $(".serialr").show(); } else if (exchangetype == 'Serialexchange') { $(".exchanger").show(); $(".exchanges").show(); $(".serials").show(); $(".serialr").show(); } else if (exchangetype == 'Serialgridsquare') { $(".serials").show(); $(".serialr").show(); $(".gridsquarer").show(); $(".gridsquares").show(); } else if (exchangetype == 'Gridsquare') { $(".gridsquarer").show(); $(".gridsquares").show(); } setContestingTabOrder(exchangetype); updateTableColumns(exchangetype); } /* Function: logQso Job: this handles the logging done in the contesting module. */ function logQso() { if ($("#callsign").val().length > 0) { $('.callsign-suggestions').text(""); var table = $('.qsotable').DataTable(); var exchangetype = $("#exchangetype").val(); var gridsquare = $("#exch_gridsquare_r").val(); var vucc = ''; if (gridsquare.indexOf(',') != -1) { vucc = gridsquare; gridsquare = ''; } var gridr = ''; var vuccr = ''; var exchsent = ''; var exchrcvd = ''; var serials = ''; var serialr = ''; switch (exchangetype) { case 'Exchange': exchsent = $("#exch_sent").val(); exchrcvd = $("#exch_rcvd").val(); break; case 'Gridsquare': gridr = gridsquare; vuccr = vucc; break; case 'Serial': serials = $("#exch_serial_s").val(); serialr = $("#exch_serial_r").val(); break; case 'Serialexchange': exchsent = $("#exch_sent").val(); exchrcvd = $("#exch_rcvd").val(); serials = $("#exch_serial_s").val(); serialr = $("#exch_serial_r").val(); break; case 'Serialgridsquare': gridr = gridsquare; vuccr = vucc; serials = $("#exch_serial_s").val(); serialr = $("#exch_serial_r").val(); break; } var data = [[ $("#start_date").val() + ' ' + $("#start_time").val(), $("#callsign").val($("#callsign").val().replace(/\s+/g, '').toUpperCase()), $("#band").val(), $("#mode").val(), $("#rst_sent").val(), $("#rst_rcvd").val(), exchsent, exchrcvd, serials, serialr, gridr, vuccr, ]]; var formdata = new FormData(document.getElementById("qso_input")); $.ajax({ url: base_url + 'index.php/qso/saveqso', type: 'post', data: formdata, processData: false, contentType: false, enctype: 'multipart/form-data', success: async function (html) { var exchangetype = $("#exchangetype").val(); if (exchangetype == "Serial" || exchangetype == 'Serialexchange' || exchangetype == 'Serialgridsquare') { $("#exch_serial_s").val(+$("#exch_serial_s").val() + 1); formdata.set('exch_serial_s', $("#exch_serial_s").val()); } $('#name').val(""); $('#callsign').val(""); $('#comment').val(""); $('#exch_rcvd').val(""); $('#exch_gridsquare_r').val(""); $('#exch_serial_r').val(""); $('.callsign-suggestions').text(""); $('.callsign-suggest').hide(); $('#callsign').css({'border-color': '', 'box-shadow': ''}); $('#callsign_info').text("").removeClass('text-bg-danger text-bg-success'); renderCallhistoryPanel([]); if (manual) { $("#start_time").focus().select(); } else { $("#callsign").focus(); } await setSession(formdata); // Re-fetch session so table shows all QSOs from session start, not just last minute sessiondata = await getSession(); await refresh_qso_table(sessiondata); } }); } } async function getSession() { return await $.ajax({ url: base_url + 'index.php/contesting/getSession', type: 'post', }); } async function restoreContestSession(data) { if (data) { if (data.copytodok != "") { $('#copyexchangeto option')[data.copytodok].selected = true; } if (data.contestid != "") { $("#contestname").val(data.contestid); } if (data.exchangetype != "") { $("#exchangetype").val(data.exchangetype); setExchangetype(data.exchangetype); setSerial(data); } if (data.exchangesent != "") { $("#exch_sent").val(data.exchangesent); } if (data.qso != "") { await refresh_qso_table(data); } } else { $("#exch_serial_s").val("1"); } } async function refresh_qso_table(data) { if (data && data.qso) { $.ajax({ url: base_url + 'index.php/contesting/getSessionQsos', type: 'post', data: { 'qso': data.qso, }, success: function (html) { // Destroy DataTables FIRST so DOM manipulation is clean if ($.fn.DataTable.isDataTable('.qsotable')) { $('.qsotable').DataTable().destroy(); } var mode = ''; $(".contest_qso_table_contents").empty(); var dupeCounts = {}; $.each(html, function () { var key = this.col_call + '|' + this.col_band + '|' + this.col_mode; dupeCounts[key] = (dupeCounts[key] || 0) + 1; }); $.each(html, function () { if (this.col_submode == null || this.col_submode == '') { mode = this.col_mode; } else { mode = this.col_submode; } var isDupe = dupeCounts[this.col_call + '|' + this.col_band + '|' + this.col_mode] > 1; $(".qsotable tbody").prepend('' + '' + this.col_time_on + '' + '' + this.col_call + '' + '' + this.col_band + '' + '' + mode + '' + '' + this.col_rst_sent + '' + '' + this.col_rst_rcvd + '' + '' + this.col_stx_string + '' + '' + this.col_srx_string + '' + '' + this.col_stx + '' + '' + this.col_srx + '' + '' + this.col_gridsquare + '' + '' + this.col_vucc_grids + '' + ''); }); $.fn.dataTable.moment('DD-MM-YYYY HH:mm:ss'); $('.qsotable').DataTable({ "pageLength": 25, responsive: false, "scrollY": "400px", "scrollCollapse": true, "paging": false, "scrollX": true, "dom": 'rt<"bottom"i>', "language": { url: getDataTablesLanguageUrl(), }, "search": { "search": $('#logbook-search').val() }, order: [0, 'desc'], "columnDefs": [ { "render": function (data, type, row) { return pad(row[8], 3); }, "targets": 8 }, { "render": function (data, type, row) { return pad(row[9], 3); }, "targets": 9 } ] }); $('#logbook-search').off('keyup.logbook').on('keyup.logbook', function () { $('.qsotable').DataTable().search(this.value).draw(); }); updateContestStats(html); updateTableColumns($('#exchangetype').val()); } }); } else { // Runs when no session is set usually when its a clean contest var selectElement = document.getElementById('contestname'); var selected_contest_id = selectElement.options[selectElement.selectedIndex].value; $.ajax({ url: base_url + 'index.php/contesting/getSessionFreshQsos', type: 'post', data: { 'contest_id': selected_contest_id }, success: function (html) { // Destroy DataTables FIRST so DOM manipulation is clean if ($.fn.DataTable.isDataTable('.qsotable')) { $('.qsotable').DataTable().destroy(); } var mode = ''; $(".contest_qso_table_contents").empty(); var dupeCounts = {}; $.each(html, function () { var key = this.col_call + '|' + this.col_band + '|' + this.col_mode; dupeCounts[key] = (dupeCounts[key] || 0) + 1; }); $.each(html, function () { if (this.col_submode == null || this.col_submode == '') { mode = this.col_mode; } else { mode = this.col_submode; } var isDupe = dupeCounts[this.col_call + '|' + this.col_band + '|' + this.col_mode] > 1; $(".qsotable tbody").prepend('' + '' + this.col_time_on + '' + '' + this.col_call + '' + '' + this.col_band + '' + '' + mode + '' + '' + this.col_rst_sent + '' + '' + this.col_rst_rcvd + '' + '' + this.col_stx_string + '' + '' + this.col_srx_string + '' + '' + this.col_stx + '' + '' + this.col_srx + '' + '' + this.col_gridsquare + '' + '' + this.col_vucc_grids + '' + ''); }); $.fn.dataTable.moment('DD-MM-YYYY HH:mm:ss'); $('.qsotable').DataTable({ "pageLength": 25, responsive: false, "scrollY": "400px", "scrollCollapse": true, "paging": false, "scrollX": true, "dom": 'rt<"bottom"i>', "language": { url: getDataTablesLanguageUrl(), }, "search": { "search": $('#logbook-search').val() }, order: [0, 'desc'], "columnDefs": [ { "render": function (data, type, row) { return pad(row[8], 3); }, "targets": 8 }, { "render": function (data, type, row) { return pad(row[9], 3); }, "targets": 9 } ] }); $('#logbook-search').off('keyup.logbook').on('keyup.logbook', function () { $('.qsotable').DataTable().search(this.value).draw(); }); updateContestStats(html); updateTableColumns($('#exchangetype').val()); } }); } } function updateTableColumns(exchangetype) { if (!$.fn.DataTable.isDataTable('.qsotable')) return; var table = $('.qsotable').DataTable(); var showExch = ['Exchange', 'Serialexchange'].indexOf(exchangetype) !== -1; var showSerial = ['Serial', 'Serialexchange', 'Serialgridsquare'].indexOf(exchangetype) !== -1; var showGrid = ['Gridsquare', 'Serialgridsquare'].indexOf(exchangetype) !== -1; table.column(6).visible(showExch, false); table.column(7).visible(showExch, false); table.column(8).visible(showSerial, false); table.column(9).visible(showSerial, false); table.column(10).visible(showGrid, false); table.column(11).visible(showGrid, false); table.draw(false); } function updateContestStats(qsoData) { if (!qsoData || qsoData.length === 0) { $('#contest-stats-card').hide(); return; } var bandOrder = ['160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m','4m','2m','70cm','23cm']; var bandCounts = {}; var now = new Date(); var cutoff = new Date(now.getTime() - 60 * 60 * 1000); var recentCount = 0; $.each(qsoData, function () { var band = this.col_band || 'Unknown'; bandCounts[band] = (bandCounts[band] || 0) + 1; // Parse col_time_on: format DD-MM-YYYY HH:mm:ss var parts = this.col_time_on.match(/(\d{2})-(\d{2})-(\d{4}) (\d{2}):(\d{2}):(\d{2})/); if (parts) { var qsoTime = new Date(Date.UTC( parseInt(parts[3]), parseInt(parts[2]) - 1, parseInt(parts[1]), parseInt(parts[4]), parseInt(parts[5]), parseInt(parts[6]) )); if (qsoTime >= cutoff) { recentCount++; } } }); var total = qsoData.length; $('#stats-total').text(total + (total === 1 ? ' QSO' : ' QSOs')); $('#stats-rate').text(recentCount + '/hr'); // Build per-band badges in canonical order, then remaining bands alphabetically var orderedBands = bandOrder.filter(function (b) { return bandCounts[b]; }); var extraBands = Object.keys(bandCounts).filter(function (b) { return bandOrder.indexOf(b) === -1; }).sort(); var allBands = orderedBands.concat(extraBands); var html = allBands.map(function (b) { return '' + b + ': ' + bandCounts[b] + ''; }).join(''); $('#stats-bands').html(html); $('#contest-stats-card').show(); } function pad(str, max) { str = str.toString(); return str.length < max ? pad("0" + str, max) : str; } function getUTCTimeStamp(el) { var now = new Date(); var localTime = now.getTime(); var utc = localTime + (now.getTimezoneOffset() * 60000); $(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 localTime = now.getTime(); var utc = localTime + (now.getTimezoneOffset() * 60000); $(el).attr('value', ("0" + now.getUTCDate()).slice(-2) + '-' + ("0" + (now.getUTCMonth() + 1)).slice(-2) + '-' + now.getUTCFullYear()); }