这个提交包含在:
Peter Goodhall 2025-08-15 15:38:52 +01:00 提交者 GitHub
当前提交 4620cc3a72
找不到此签名对应的密钥
GPG 密钥 ID: B5690EEEBB952194
共有 35 个文件被更改,包括 3099 次插入886 次删除

查看文件

@ -24,5 +24,16 @@ RUN apt-get update && apt-get install -y \
&& docker-php-ext-install xml \
&& a2enmod rewrite
# Copy script.sh and make it executable
COPY script.sh /usr/local/bin/startup.sh
RUN sed -i 's/\r$//' /usr/local/bin/startup.sh && chmod +x /usr/local/bin/startup.sh
# Configure PHP for larger file uploads (30MB)
RUN echo "upload_max_filesize = 30M" >> /usr/local/etc/php/conf.d/uploads.ini \
&& echo "post_max_size = 35M" >> /usr/local/etc/php/conf.d/uploads.ini \
&& echo "memory_limit = 64M" >> /usr/local/etc/php/conf.d/uploads.ini \
&& echo "max_execution_time = 300" >> /usr/local/etc/php/conf.d/uploads.ini \
&& echo "max_input_time = 300" >> /usr/local/etc/php/conf.d/uploads.ini
# Expose port 80
EXPOSE 80

查看文件

@ -4,5 +4,9 @@ FROM mariadb:latest
# Add the install.sql file to the docker image
ADD install/assets/install.sql /docker-entrypoint-initdb.d
# Create a healthcheck script that uses mariadb-admin
RUN echo '#!/bin/bash\nmariadb-admin ping -h "localhost" --silent' > /usr/local/bin/healthcheck.sh \
&& chmod +x /usr/local/bin/healthcheck.sh
# Expose port 3306
EXPOSE 3306

查看文件

@ -22,7 +22,7 @@ $config['migration_enabled'] = TRUE;
|
*/
$config['migration_version'] = 204;
$config['migration_version'] = 205;
/*
|--------------------------------------------------------------------------

查看文件

@ -13,16 +13,6 @@ class adif extends CI_Controller {
if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); }
}
public function test() {
if(validateADIFDate('20120228') == true){
echo "valid date";
} else {
echo "date incorrect";
}
}
/* Shows Export Views */
public function export() {

查看文件

@ -3,6 +3,16 @@
class Dashboard extends CI_Controller
{
public function __construct()
{
parent::__construct();
// Load common models that are used across multiple methods
$this->load->model('user_model');
$this->load->model('logbook_model');
$this->load->model('logbooks_model');
}
public function index()
{
// If environment is set to development then show the debug toolbar
@ -13,10 +23,6 @@ class Dashboard extends CI_Controller
// Load language files
$this->lang->load('lotw');
// Database connections
$this->load->model('logbook_model');
$this->load->model('user_model');
// LoTW infos
$this->load->model('LotwCert');
@ -31,7 +37,6 @@ class Dashboard extends CI_Controller
redirect('user/login');
}
$this->load->model('logbooks_model');
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
/*
@ -63,9 +68,11 @@ class Dashboard extends CI_Controller
$this->load->model('stations');
$this->load->model('setup_model');
$data['countryCount'] = $this->setup_model->getCountryCount();
$data['logbookCount'] = $this->setup_model->getLogbookCount();
$data['locationCount'] = $this->setup_model->getLocationCount();
// Use consolidated setup counts instead of 3 separate queries
$setup_counts = $this->setup_model->getAllSetupCounts();
$data['countryCount'] = $setup_counts['country_count'];
$data['logbookCount'] = $setup_counts['logbook_count'];
$data['locationCount'] = $setup_counts['location_count'];
$data['current_active'] = $this->stations->find_active();
@ -85,19 +92,21 @@ class Dashboard extends CI_Controller
$data['radio_status'] = $this->cat->recent_status();
// Store info
$data['todays_qsos'] = $this->logbook_model->todays_qsos($logbooks_locations_array);
$data['total_qsos'] = $this->logbook_model->total_qsos($logbooks_locations_array);
$data['month_qsos'] = $this->logbook_model->month_qsos($logbooks_locations_array);
$data['year_qsos'] = $this->logbook_model->year_qsos($logbooks_locations_array);
// Store info - Use consolidated query for QSO statistics
$qso_stats = $this->logbook_model->get_qso_statistics_consolidated($logbooks_locations_array);
$data['todays_qsos'] = $qso_stats['todays_qsos'];
$data['total_qsos'] = $qso_stats['total_qsos'];
$data['month_qsos'] = $qso_stats['month_qsos'];
$data['year_qsos'] = $qso_stats['year_qsos'];
// Load Countries Breakdown data into array
$CountriesBreakdown = $this->logbook_model->total_countries_confirmed($logbooks_locations_array);
$data['total_countries'] = $CountriesBreakdown['Countries_Worked'];
$data['total_countries_confirmed_paper'] = $CountriesBreakdown['Countries_Worked_QSL'];
$data['total_countries_confirmed_eqsl'] = $CountriesBreakdown['Countries_Worked_EQSL'];
$data['total_countries_confirmed_lotw'] = $CountriesBreakdown['Countries_Worked_LOTW'];
// Use consolidated countries statistics instead of separate queries
$countries_stats = $this->logbook_model->get_countries_statistics_consolidated($logbooks_locations_array);
$data['total_countries'] = $countries_stats['Countries_Worked'];
$data['total_countries_confirmed_paper'] = $countries_stats['Countries_Worked_QSL'];
$data['total_countries_confirmed_eqsl'] = $countries_stats['Countries_Worked_EQSL'];
$data['total_countries_confirmed_lotw'] = $countries_stats['Countries_Worked_LOTW'];
$current_countries = $countries_stats['Countries_Current'];
$data['dashboard_upcoming_dx_card'] = false;
$data['dashboard_qslcard_card'] = false;
@ -107,53 +116,23 @@ class Dashboard extends CI_Controller
$dashboard_options = $this->user_options_model->get_options('dashboard')->result();
// Optimize options processing - convert to associative array for O(1) lookup
$options_map = array();
foreach ($dashboard_options as $item) {
$option_name = $item->option_name;
$option_key = $item->option_key;
$option_value = $item->option_value;
if ($option_name == 'dashboard_upcoming_dx_card' && $option_key == 'enabled') {
if($option_value == 'true') {
$data['dashboard_upcoming_dx_card'] = true;
} else {
$data['dashboard_upcoming_dx_card'] = false;
}
}
$options_map[$item->option_name][$item->option_key] = $item->option_value;
}
if ($option_name == 'dashboard_qslcards_card' && $option_key == 'enabled') {
if($item->option_value == 'true') {
$data['dashboard_qslcard_card'] = true;
} else {
$data['dashboard_qslcard_card'] = false;
}
}
// Quick lookups instead of nested loops
$data['dashboard_upcoming_dx_card'] = isset($options_map['dashboard_upcoming_dx_card']['enabled']) && $options_map['dashboard_upcoming_dx_card']['enabled'] == 'true';
$data['dashboard_qslcard_card'] = isset($options_map['dashboard_qslcards_card']['enabled']) && $options_map['dashboard_qslcards_card']['enabled'] == 'true';
$data['dashboard_eqslcard_card'] = isset($options_map['dashboard_eqslcards_card']['enabled']) && $options_map['dashboard_eqslcards_card']['enabled'] == 'true';
$data['dashboard_lotw_card'] = isset($options_map['dashboard_lotw_card']['enabled']) && $options_map['dashboard_lotw_card']['enabled'] == 'true';
$data['dashboard_vuccgrids_card'] = isset($options_map['dashboard_vuccgrids_card']['enabled']) && $options_map['dashboard_vuccgrids_card']['enabled'] == 'true';
if ($option_name == 'dashboard_eqslcards_card' && $option_key == 'enabled') {
if($item->option_value == 'true') {
$data['dashboard_eqslcard_card'] = true;
} else {
$data['dashboard_eqslcard_card'] = false;
}
}
if ($option_name == 'dashboard_lotw_card' && $option_key == 'enabled') {
if($item->option_value == 'true') {
$data['dashboard_lotw_card'] = true;
} else {
$data['dashboard_lotw_card'] = false;
}
}
if ($option_name == 'dashboard_vuccgrids_card' && $option_key == 'enabled') {
if($item->option_value == 'true') {
$data['dashboard_vuccgrids_card'] = true;
$data['vucc'] = $this->vucc->fetchVuccSummary();
$data['vuccSAT'] = $this->vucc->fetchVuccSummary('SAT');
} else {
$data['dashboard_vuccgrids_card'] = false;
}
}
// Only load VUCC data if the card is actually enabled
if ($data['dashboard_vuccgrids_card']) {
$data['vucc'] = $this->vucc->fetchVuccSummary();
$data['vuccSAT'] = $this->vucc->fetchVuccSummary('SAT');
}
@ -185,12 +164,10 @@ class Dashboard extends CI_Controller
$data['page_title'] = "Dashboard";
// Optimize DXCC calculation - get count directly instead of loading all records
$this->load->model('dxcc');
$dxcc = $this->dxcc->list_current();
$current = $this->logbook_model->total_countries_current($logbooks_locations_array);
$data['total_countries_needed'] = count($dxcc->result()) - $current;
$total_dxcc_count = $this->dxcc->get_total_dxcc_count();
$data['total_countries_needed'] = $total_dxcc_count - $current_countries;
$this->load->view('interface_assets/header', $data);
$this->load->view('dashboard/index');
@ -199,30 +176,23 @@ class Dashboard extends CI_Controller
}
public function todays_qso_component() {
$this->load->model('user_model');
if ($this->user_model->validate_session() == 0) {
// User is not logged in
} else {
$this->load->model('logbook_model');
$this->load->model('logbooks_model');
return;
}
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
$data['todays_qsos'] = $this->logbook_model->todays_qsos($logbooks_locations_array);
// Use consolidated query instead of individual todays_qsos call
$qso_stats = $this->logbook_model->get_qso_statistics_consolidated($logbooks_locations_array);
$data['todays_qsos'] = $qso_stats['todays_qsos'];
$this->load->view('components/dashboard_todays_qsos', $data);
}
public function logbook_display_component() {
$this->load->model('user_model');
if ($this->user_model->validate_session() == 0) {
// User is not logged in
} else {
$this->load->model('logbook_model');
$this->load->model('logbooks_model');
return;
}
// Get Logbook Locations

查看文件

@ -219,6 +219,16 @@ class Update extends CI_Controller {
$this->update_status("DONE");
}
public function get_status() {
$status_file = $this->make_update_path("status.html");
if (file_exists($status_file)) {
$content = file_get_contents($status_file);
echo $content;
} else {
echo "No status available";
}
}
public function update_status($done=""){
if ($done != "Downloading file"){
@ -234,7 +244,16 @@ class Update extends CI_Controller {
$html = $done."....<br/>";
}
file_put_contents($this->make_update_path("status.html"), $html);
$status_file = $this->make_update_path("status.html");
if (file_put_contents($status_file, $html) === FALSE) {
log_message('error', 'Failed to write status file: ' . $status_file);
// Try to create the directory if it doesn't exist
$dir = dirname($status_file);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
file_put_contents($status_file, $html);
}
}
}

查看文件

@ -29,70 +29,81 @@ class Workabledxcc extends CI_Controller
public function dxcclist()
{
$json = file_get_contents($this->optionslib->get_option('dxped_url'));
// Decode the JSON data into a PHP array
$dataResult = json_decode($json, true);
// Initialize an empty array to store the required data
$requiredData = array();
if (empty($dataResult)) {
$data['dxcclist'] = array();
$this->load->view('/workabledxcc/components/dxcclist', $data);
return;
}
// Get Date format
if ($this->session->userdata('user_date_format')) {
// If Logged in and session exists
$custom_date_format = $this->session->userdata('user_date_format');
} else {
// Get Default date format from /config/cloudlog.php
$custom_date_format = $this->config->item('qso_date_format');
}
// Iterate through the decoded JSON data
foreach ($dataResult as $item) {
// Create a new array with the required fields and add it to the main array
$oldStartDate = DateTime::createFromFormat('Y-m-d', $item['0']);
// Load models once
$this->load->model('logbook_model');
$this->load->model('Workabledxcc_model');
// Get all DXCC entities for all callsigns in one batch
$callsigns = array_column($dataResult, 'callsign');
$dates = array_column($dataResult, '0');
$dxccEntities = $this->Workabledxcc_model->batchDxccLookup($callsigns, $dates);
// Get worked/confirmed status for all entities in batch
$uniqueEntities = array_unique(array_filter($dxccEntities));
$dxccStatus = $this->Workabledxcc_model->batchDxccWorkedStatus($uniqueEntities);
// If JSON contains iota fields, batch process IOTA status
$iotas = [];
foreach ($dataResult as $item) {
if (!empty($item['iota'])) {
$iotas[] = $item['iota'];
}
}
$uniqueIotas = array_unique($iotas);
$iotaStatus = [];
if (!empty($uniqueIotas)) {
$iotaStatus = $this->Workabledxcc_model->batchIotaWorkedStatus($uniqueIotas);
}
// Process results
$requiredData = array();
foreach ($dataResult as $index => $item) {
$oldStartDate = DateTime::createFromFormat('Y-m-d', $item['0']);
$StartDate = $oldStartDate->format($custom_date_format);
$oldEndDate = DateTime::createFromFormat('Y-m-d', $item['1']);
$EndDate = $oldEndDate->format($custom_date_format);
$oldStartDate1 = DateTime::createFromFormat('Y-m-d', $item['0']);
$StartDate1 = $oldStartDate1->format('Y-m-d');
$this->load->model('logbook_model');
$dxccInfo = $this->logbook_model->dxcc_lookup($item['callsign'], $StartDate1);
// Call DXCC Worked function to check if the DXCC has been worked before
if (isset($dxccInfo['entity'])) {
$dxccWorked = $this->dxccWorked($dxccInfo['entity']);
} else {
// Handle the case where 'entity' is not set in $dxccInfo
$dxccWorked = array(
'workedBefore' => false,
'confirmed' => false,
);
}
// Get DXCC status for this callsign
$entity = $dxccEntities[$index] ?? null;
$worked = $entity && isset($dxccStatus[$entity]) ? $dxccStatus[$entity] : [
'workedBefore' => false,
'confirmed' => false,
'workedViaSatellite' => false
];
$requiredData[] = array(
'clean_date' => $item['0'],
'start_date' => $StartDate,
'end_date' => $EndDate,
'country' => $item['2'],
'iota' => isset($item['iota']) ? $item['iota'] : null,
'iota_status' => (isset($item['iota']) && isset($iotaStatus[$item['iota']])) ? $iotaStatus[$item['iota']] : null,
'notes' => $item['6'],
'callsign' => $item['callsign'],
'workedBefore' => $dxccWorked['workedBefore'],
'confirmed' => $dxccWorked['confirmed'],
'workedBefore' => $worked['workedBefore'],
'confirmed' => $worked['confirmed'],
'workedViaSatellite' => $worked['workedViaSatellite'],
);
}
$data['dxcclist'] = $requiredData;
// Return the array with the required data
$this->load->view('/workabledxcc/components/dxcclist', $data);
}
@ -102,6 +113,7 @@ class Workabledxcc extends CI_Controller
$return = [
"workedBefore" => false,
"confirmed" => false,
"workedViaSatellite" => false,
];
$user_default_confirmation = $this->session->userdata('user_default_confirmation');
@ -110,16 +122,28 @@ class Workabledxcc extends CI_Controller
$this->load->model('logbook_model');
if (!empty($logbooks_locations_array)) {
// Check terrestrial contacts
$this->db->where('COL_PROP_MODE !=', 'SAT');
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY', urldecode($country));
// Fix case sensitivity issue for DXCC country matching
$this->db->where('UPPER(COL_COUNTRY) = UPPER(?)', urldecode($country));
$query = $this->db->get($this->config->item('table_name'), 1, 0);
foreach ($query->result() as $workedBeforeRow) {
$return['workedBefore'] = true;
}
// Check satellite contacts
$this->db->where('COL_PROP_MODE', 'SAT');
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('UPPER(COL_COUNTRY) = UPPER(?)', urldecode($country));
$query = $this->db->get($this->config->item('table_name'), 1, 0);
foreach ($query->result() as $satelliteRow) {
$return['workedViaSatellite'] = true;
}
$extrawhere = '';
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'Q') !== false) {
$extrawhere = "COL_QSL_RCVD='Y'";
@ -155,7 +179,8 @@ class Workabledxcc extends CI_Controller
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY', urldecode($country));
// Fix case sensitivity issue for DXCC country matching
$this->db->where('UPPER(COL_COUNTRY) = UPPER(?)', urldecode($country));
$query = $this->db->get($this->config->item('table_name'), 1, 0);
foreach ($query->result() as $workedBeforeRow) {

查看文件

@ -1,4 +1,4 @@
<?php defined('BASEPATH') OR exit('No direct script access allowed');
<?php defined('BASEPATH') OR exit('No direct script access allowed');
class Migration_modify_eQSL_url extends CI_Migration {

查看文件

@ -0,0 +1,49 @@
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Migration_add_workable_dxcc_indexes extends CI_Migration {
public function up()
{
// Add composite index for workable DXCC queries
// This will greatly improve performance for COL_COUNTRY + station_id + COL_PROP_MODE queries
$this->db->db_debug = false;
// Check if index already exists
$index_exists = $this->db->query("SHOW INDEX FROM ".$this->config->item('table_name')." WHERE Key_name = 'idx_workable_dxcc'")->num_rows();
if ($index_exists == 0) {
$sql = "ALTER TABLE ".$this->config->item('table_name')." ADD INDEX `idx_workable_dxcc` (`COL_COUNTRY`, `station_id`, `COL_PROP_MODE`)";
$this->db->query($sql);
}
// Add index for confirmation status columns
$conf_index_exists = $this->db->query("SHOW INDEX FROM ".$this->config->item('table_name')." WHERE Key_name = 'idx_qsl_confirmations'")->num_rows();
if ($conf_index_exists == 0) {
$sql = "ALTER TABLE ".$this->config->item('table_name')." ADD INDEX `idx_qsl_confirmations` (`COL_QSL_RCVD`, `COL_LOTW_QSL_RCVD`, `COL_EQSL_QSL_RCVD`, `COL_QRZCOM_QSO_DOWNLOAD_STATUS`)";
$this->db->query($sql);
}
$this->db->db_debug = true;
}
public function down()
{
$this->db->db_debug = false;
// Drop the indexes if they exist
$index_exists = $this->db->query("SHOW INDEX FROM ".$this->config->item('table_name')." WHERE Key_name = 'idx_workable_dxcc'")->num_rows();
if ($index_exists > 0) {
$this->db->query("ALTER TABLE ".$this->config->item('table_name')." DROP INDEX `idx_workable_dxcc`");
}
$conf_index_exists = $this->db->query("SHOW INDEX FROM ".$this->config->item('table_name')." WHERE Key_name = 'idx_qsl_confirmations'")->num_rows();
if ($conf_index_exists > 0) {
$this->db->query("ALTER TABLE ".$this->config->item('table_name')." DROP INDEX `idx_qsl_confirmations`");
}
$this->db->db_debug = true;
}
}

查看文件

@ -58,6 +58,12 @@ class DXCC extends CI_Model {
return $this->db->get('dxcc_entities');
}
/* Optimized method to get count of current DXCC entities without loading all records */
function get_total_dxcc_count() {
$this->db->where('end', null);
return $this->db->count_all_results('dxcc_entities');
}
function get_dxcc_array($dxccArray, $bands, $postdata) {
$CI =& get_instance();
$CI->load->model('logbooks_model');

查看文件

@ -2079,7 +2079,7 @@ class Logbook_model extends CI_Model
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->group_start();
$this->db->like('SUBSTRING(COL_GRIDSQUARE, 1, 4)', substr($grid, 0, 4));
$this->db->or_like('SUBSTRING(COL_VUCC_GRIDS, 1, 4)', substr($grid, 0, 4));
$this->db->or_like('COL_VUCC_GRIDS', substr($grid, 0, 4));
$this->db->group_end();
if ($band != null && $band != 'SAT') {
@ -2621,95 +2621,99 @@ class Logbook_model extends CI_Model
}
if (!empty($logbooks_locations_array)) {
$this->db->select('
COUNT(IF(COL_QSL_SENT="Y",COL_QSL_SENT,null)) as QSL_Sent,
COUNT(IF(COL_QSL_RCVD="Y",COL_QSL_RCVD,null)) as QSL_Received,
COUNT(IF(COL_QSL_SENT IN("Q", "R") ,COL_QSL_SENT,null)) as QSL_Requested,
COUNT(IF(COL_EQSL_QSL_SENT="Y",COL_EQSL_QSL_SENT,null)) as eQSL_Sent,
COUNT(IF(COL_EQSL_QSL_RCVD="Y",COL_EQSL_QSL_RCVD,null)) as eQSL_Received,
COUNT(IF(COL_LOTW_QSL_SENT="Y",COL_LOTW_QSL_SENT,null)) as LoTW_Sent,
COUNT(IF(COL_LOTW_QSL_RCVD="Y",COL_LOTW_QSL_RCVD,null)) as LoTW_Received,
COUNT(IF(COL_QRZCOM_QSO_UPLOAD_STATUS="Y",COL_QRZCOM_QSO_UPLOAD_STATUS,null)) as QRZ_Sent,
COUNT(IF(COL_QRZCOM_QSO_DOWNLOAD_STATUS="Y",COL_QRZCOM_QSO_DOWNLOAD_STATUS,null)) as QRZ_Received,
COUNT(IF(COL_QSL_SENT="Y" and DATE(COL_QSLSDATE)=DATE(SYSDATE()),COL_QSL_SENT,null)) as QSL_Sent_today,
COUNT(IF(COL_QSL_RCVD="Y" and DATE(COL_QSLRDATE)=DATE(SYSDATE()),COL_QSL_RCVD,null)) as QSL_Received_today,
COUNT(IF(COL_QSL_SENT IN("Q", "R") and DATE(COL_QSLSDATE)=DATE(SYSDATE()) ,COL_QSL_SENT,null)) as QSL_Requested_today,
COUNT(IF(COL_EQSL_QSL_SENT="Y" and DATE(COL_EQSL_QSLSDATE)=DATE(SYSDATE()),COL_EQSL_QSL_SENT,null)) as eQSL_Sent_today,
COUNT(IF(COL_EQSL_QSL_RCVD="Y" and DATE(COL_EQSL_QSLRDATE)=DATE(SYSDATE()),COL_EQSL_QSL_RCVD,null)) as eQSL_Received_today,
COUNT(IF(COL_LOTW_QSL_SENT="Y" and DATE(COL_LOTW_QSLSDATE)=DATE(SYSDATE()),COL_LOTW_QSL_SENT,null)) as LoTW_Sent_today,
COUNT(IF(COL_LOTW_QSL_RCVD="Y" and DATE(COL_LOTW_QSLRDATE)=DATE(SYSDATE()),COL_LOTW_QSL_RCVD,null)) as LoTW_Received_today,
COUNT(IF(COL_QRZCOM_QSO_UPLOAD_STATUS="Y" and DATE(COL_QRZCOM_QSO_UPLOAD_DATE)=DATE(SYSDATE()),COL_QRZCOM_QSO_UPLOAD_STATUS,null)) as QRZ_Sent_today,
COUNT(IF(COL_QRZCOM_QSO_DOWNLOAD_STATUS="Y" and DATE(COL_QRZCOM_QSO_DOWNLOAD_DATE)=DATE(SYSDATE()),COL_QRZCOM_QSO_DOWNLOAD_STATUS,null)) as QRZ_Received_today
');
// Pre-calculate today's date for better performance
$today_date = date('Y-m-d');
$this->db->select("
COUNT(IF(COL_QSL_SENT='Y',COL_QSL_SENT,null)) as QSL_Sent,
COUNT(IF(COL_QSL_RCVD='Y',COL_QSL_RCVD,null)) as QSL_Received,
COUNT(IF(COL_QSL_SENT IN('Q', 'R') ,COL_QSL_SENT,null)) as QSL_Requested,
COUNT(IF(COL_EQSL_QSL_SENT='Y',COL_EQSL_QSL_SENT,null)) as eQSL_Sent,
COUNT(IF(COL_EQSL_QSL_RCVD='Y',COL_EQSL_QSL_RCVD,null)) as eQSL_Received,
COUNT(IF(COL_LOTW_QSL_SENT='Y',COL_LOTW_QSL_SENT,null)) as LoTW_Sent,
COUNT(IF(COL_LOTW_QSL_RCVD='Y',COL_LOTW_QSL_RCVD,null)) as LoTW_Received,
COUNT(IF(COL_QRZCOM_QSO_UPLOAD_STATUS='Y',COL_QRZCOM_QSO_UPLOAD_STATUS,null)) as QRZ_Sent,
COUNT(IF(COL_QRZCOM_QSO_DOWNLOAD_STATUS='Y',COL_QRZCOM_QSO_DOWNLOAD_STATUS,null)) as QRZ_Received,
COUNT(IF(COL_QSL_SENT='Y' and DATE(COL_QSLSDATE)='$today_date',COL_QSL_SENT,null)) as QSL_Sent_today,
COUNT(IF(COL_QSL_RCVD='Y' and DATE(COL_QSLRDATE)='$today_date',COL_QSL_RCVD,null)) as QSL_Received_today,
COUNT(IF(COL_QSL_SENT IN('Q', 'R') and DATE(COL_QSLSDATE)='$today_date' ,COL_QSL_SENT,null)) as QSL_Requested_today,
COUNT(IF(COL_EQSL_QSL_SENT='Y' and DATE(COL_EQSL_QSLSDATE)='$today_date',COL_EQSL_QSL_SENT,null)) as eQSL_Sent_today,
COUNT(IF(COL_EQSL_QSL_RCVD='Y' and DATE(COL_EQSL_QSLRDATE)='$today_date',COL_EQSL_QSL_RCVD,null)) as eQSL_Received_today,
COUNT(IF(COL_LOTW_QSL_SENT='Y' and DATE(COL_LOTW_QSLSDATE)='$today_date',COL_LOTW_QSL_SENT,null)) as LoTW_Sent_today,
COUNT(IF(COL_LOTW_QSL_RCVD='Y' and DATE(COL_LOTW_QSLRDATE)='$today_date',COL_LOTW_QSL_RCVD,null)) as LoTW_Received_today,
COUNT(IF(COL_QRZCOM_QSO_UPLOAD_STATUS='Y' and DATE(COL_QRZCOM_QSO_UPLOAD_DATE)='$today_date',COL_QRZCOM_QSO_UPLOAD_STATUS,null)) as QRZ_Sent_today,
COUNT(IF(COL_QRZCOM_QSO_DOWNLOAD_STATUS='Y' and DATE(COL_QRZCOM_QSO_DOWNLOAD_DATE)='$today_date',COL_QRZCOM_QSO_DOWNLOAD_STATUS,null)) as QRZ_Received_today
", FALSE);
$this->db->where_in('station_id', $logbooks_locations_array);
if ($query = $this->db->get($this->config->item('table_name'))) {
$this->db->last_query();
foreach ($query->result() as $row) {
$QSLBreakdown['QSL_Sent'] = $row->QSL_Sent;
$QSLBreakdown['QSL_Received'] = $row->QSL_Received;
$QSLBreakdown['QSL_Requested'] = $row->QSL_Requested;
$QSLBreakdown['eQSL_Sent'] = $row->eQSL_Sent;
$QSLBreakdown['eQSL_Received'] = $row->eQSL_Received;
$QSLBreakdown['LoTW_Sent'] = $row->LoTW_Sent;
$QSLBreakdown['LoTW_Received'] = $row->LoTW_Received;
$QSLBreakdown['QRZ_Sent'] = $row->QRZ_Sent;
$QSLBreakdown['QRZ_Received'] = $row->QRZ_Received;
$QSLBreakdown['QSL_Sent_today'] = $row->QSL_Sent_today;
$QSLBreakdown['QSL_Received_today'] = $row->QSL_Received_today;
$QSLBreakdown['QSL_Requested_today'] = $row->QSL_Requested_today;
$QSLBreakdown['eQSL_Sent_today'] = $row->eQSL_Sent_today;
$QSLBreakdown['eQSL_Received_today'] = $row->eQSL_Received_today;
$QSLBreakdown['LoTW_Sent_today'] = $row->LoTW_Sent_today;
$QSLBreakdown['LoTW_Received_today'] = $row->LoTW_Received_today;
$QSLBreakdown['QRZ_Sent_today'] = $row->QRZ_Sent_today;
$QSLBreakdown['QRZ_Received_today'] = $row->QRZ_Received_today;
if ($query->num_rows() > 0) {
$row = $query->row();
return array(
'QSL_Sent' => (int)$row->QSL_Sent,
'QSL_Received' => (int)$row->QSL_Received,
'QSL_Requested' => (int)$row->QSL_Requested,
'eQSL_Sent' => (int)$row->eQSL_Sent,
'eQSL_Received' => (int)$row->eQSL_Received,
'LoTW_Sent' => (int)$row->LoTW_Sent,
'LoTW_Received' => (int)$row->LoTW_Received,
'QRZ_Sent' => (int)$row->QRZ_Sent,
'QRZ_Received' => (int)$row->QRZ_Received,
'QSL_Sent_today' => (int)$row->QSL_Sent_today,
'QSL_Received_today' => (int)$row->QSL_Received_today,
'QSL_Requested_today' => (int)$row->QSL_Requested_today,
'eQSL_Sent_today' => (int)$row->eQSL_Sent_today,
'eQSL_Received_today' => (int)$row->eQSL_Received_today,
'LoTW_Sent_today' => (int)$row->LoTW_Sent_today,
'LoTW_Received_today' => (int)$row->LoTW_Received_today,
'QRZ_Sent_today' => (int)$row->QRZ_Sent_today,
'QRZ_Received_today' => (int)$row->QRZ_Received_today
);
}
return $QSLBreakdown;
} else {
$QSLBreakdown['QSL_Sent'] = 0;
$QSLBreakdown['QSL_Received'] = 0;
$QSLBreakdown['QSL_Requested'] = 0;
$QSLBreakdown['eQSL_Sent'] = 0;
$QSLBreakdown['eQSL_Received'] = 0;
$QSLBreakdown['LoTW_Sent'] = 0;
$QSLBreakdown['LoTW_Received'] = 0;
$QSLBreakdown['QRZ_Sent'] = 0;
$QSLBreakdown['QRZ_Received'] = 0;
$QSLBreakdown['QSL_Sent_today'] = 0;
$QSLBreakdown['QSL_Received_today'] = 0;
$QSLBreakdown['QSL_Requested_today'] = 0;
$QSLBreakdown['eQSL_Sent_today'] = 0;
$QSLBreakdown['eQSL_Received_today'] = 0;
$QSLBreakdown['LoTW_Sent_today'] = 0;
$QSLBreakdown['LoTW_Received_today'] = 0;
$QSLBreakdown['QRZ_Sent_today'] = 0;
$QSLBreakdown['QRZ_Received_today'] = 0;
return $QSLBreakdown;
}
} else {
$QSLBreakdown['QSL_Sent'] = 0;
$QSLBreakdown['QSL_Received'] = 0;
$QSLBreakdown['QSL_Requested'] = 0;
$QSLBreakdown['eQSL_Sent'] = 0;
$QSLBreakdown['eQSL_Received'] = 0;
$QSLBreakdown['LoTW_Sent'] = 0;
$QSLBreakdown['LoTW_Received'] = 0;
$QSLBreakdown['QRZ_Sent'] = 0;
$QSLBreakdown['QRZ_Received'] = 0;
$QSLBreakdown['QSL_Sent_today'] = 0;
$QSLBreakdown['QSL_Received_today'] = 0;
$QSLBreakdown['QSL_Requested_today'] = 0;
$QSLBreakdown['eQSL_Sent_today'] = 0;
$QSLBreakdown['eQSL_Received_today'] = 0;
$QSLBreakdown['LoTW_Sent_today'] = 0;
$QSLBreakdown['LoTW_Received_today'] = 0;
$QSLBreakdown['QRZ_Sent_today'] = 0;
$QSLBreakdown['QRZ_Received_today'] = 0;
return $QSLBreakdown;
// Return default values if no results
return array(
'QSL_Sent' => 0,
'QSL_Received' => 0,
'QSL_Requested' => 0,
'eQSL_Sent' => 0,
'eQSL_Received' => 0,
'LoTW_Sent' => 0,
'LoTW_Received' => 0,
'QRZ_Sent' => 0,
'QRZ_Received' => 0,
'QSL_Sent_today' => 0,
'QSL_Received_today' => 0,
'QSL_Requested_today' => 0,
'eQSL_Sent_today' => 0,
'eQSL_Received_today' => 0,
'LoTW_Sent_today' => 0,
'LoTW_Received_today' => 0,
'QRZ_Sent_today' => 0,
'QRZ_Received_today' => 0
);
} else {
return array(
'QSL_Sent' => 0,
'QSL_Received' => 0,
'QSL_Requested' => 0,
'eQSL_Sent' => 0,
'eQSL_Received' => 0,
'LoTW_Sent' => 0,
'LoTW_Received' => 0,
'QRZ_Sent' => 0,
'QRZ_Received' => 0,
'QSL_Sent_today' => 0,
'QSL_Received_today' => 0,
'QSL_Requested_today' => 0,
'eQSL_Sent_today' => 0,
'eQSL_Received_today' => 0,
'LoTW_Sent_today' => 0,
'LoTW_Received_today' => 0,
'QRZ_Sent_today' => 0,
'QRZ_Received_today' => 0
);
}
}
@ -2986,6 +2990,53 @@ class Logbook_model extends CI_Model
}
}
// Consolidated method to get all country statistics in one query
function get_countries_statistics_consolidated($StationLocationsArray = null) {
if ($StationLocationsArray == null) {
$CI = &get_instance();
$CI->load->model('logbooks_model');
$logbooks_locations_array = $CI->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
} else {
$logbooks_locations_array = $StationLocationsArray;
}
if (!empty($logbooks_locations_array)) {
// Get both confirmed countries and current countries in one query
$this->db->select('
COUNT(DISTINCT COL_COUNTRY) as Countries_Worked,
COUNT(DISTINCT IF(COL_QSL_RCVD = "Y", COL_COUNTRY, NULL)) as Countries_Worked_QSL,
COUNT(DISTINCT IF(COL_EQSL_QSL_RCVD = "Y", COL_COUNTRY, NULL)) as Countries_Worked_EQSL,
COUNT(DISTINCT IF(COL_LOTW_QSL_RCVD = "Y", COL_COUNTRY, NULL)) as Countries_Worked_LOTW,
COUNT(DISTINCT IF(dxcc_entities.end IS NULL, COL_COUNTRY, NULL)) as Countries_Current
');
$this->db->join('dxcc_entities', 'dxcc_entities.adif = ' . $this->config->item('table_name') . '.col_dxcc', 'left');
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY !=', 'Invalid');
$this->db->where('COL_DXCC >', '0');
if ($query = $this->db->get($this->config->item('table_name'))) {
if ($query->num_rows() > 0) {
$row = $query->row();
return array(
'Countries_Worked' => $row->Countries_Worked,
'Countries_Worked_QSL' => $row->Countries_Worked_QSL,
'Countries_Worked_EQSL' => $row->Countries_Worked_EQSL,
'Countries_Worked_LOTW' => $row->Countries_Worked_LOTW,
'Countries_Current' => $row->Countries_Current
);
}
}
}
return array(
'Countries_Worked' => 0,
'Countries_Worked_QSL' => 0,
'Countries_Worked_EQSL' => 0,
'Countries_Worked_LOTW' => 0,
'Countries_Current' => 0
);
}
/* Return total number of countries confirmed with paper QSL */
function total_countries_confirmed_paper()
{
@ -4806,6 +4857,7 @@ class Logbook_model extends CI_Model
$plot = array('lat' => 0, 'lng' => 0, 'html' => '', 'label' => '', 'flag' => '', 'confirmed' => 'N');
$plot['label'] = $row->COL_CALL;
$plot['callsign'] = $row->COL_CALL;
$flag = strtolower($CI->dxccflag->getISO($row->COL_DXCC));
$plot['flag'] = '<span data-bs-toggle="tooltip" title="' . ucwords(strtolower(($row->name==null?"- NONE -":$row->name))) . '"><span class="fi fi-' . $flag .'"></span></span> ';
$plot['html'] = ($row->COL_GRIDSQUARE != null ? "<b>Grid:</b> " . $row->COL_GRIDSQUARE . "<br />" : "");
@ -4980,6 +5032,63 @@ class Logbook_model extends CI_Model
// If all parsing fails, return the original input and let the database handle it
return $date_input;
}
/* Consolidated QSO Statistics - Get all basic counts in a single query */
function get_qso_statistics_consolidated($StationLocationsArray = null)
{
if ($StationLocationsArray == null) {
$this->load->model('logbooks_model');
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
} else {
$logbooks_locations_array = $StationLocationsArray;
}
if (!$logbooks_locations_array) {
return array(
'total_qsos' => 0,
'todays_qsos' => 0,
'month_qsos' => 0,
'year_qsos' => 0
);
}
// Calculate date ranges
$today_morning = date('Y-m-d 00:00:00');
$today_night = date('Y-m-d 23:59:59');
$month_morning = date('Y-m-01 00:00:00');
$date = new DateTime('now');
$date->modify('last day of this month');
$month_night = $date->format('Y-m-d') . " 23:59:59";
$year_morning = date('Y-01-01 00:00:00');
$year_night = date('Y-12-31 23:59:59');
// Build the consolidated query
$this->db->select("
COUNT(*) as total_qsos,
COUNT(CASE WHEN COL_TIME_ON >= '$today_morning' AND COL_TIME_ON <= '$today_night' THEN 1 END) as todays_qsos,
COUNT(CASE WHEN COL_TIME_ON >= '$month_morning' AND COL_TIME_ON <= '$month_night' THEN 1 END) as month_qsos,
COUNT(CASE WHEN COL_TIME_ON >= '$year_morning' AND COL_TIME_ON <= '$year_night' THEN 1 END) as year_qsos
", FALSE);
$this->db->where_in('station_id', $logbooks_locations_array);
$query = $this->db->get($this->config->item('table_name'));
if ($query->num_rows() > 0) {
$row = $query->row();
return array(
'total_qsos' => (int)$row->total_qsos,
'todays_qsos' => (int)$row->todays_qsos,
'month_qsos' => (int)$row->month_qsos,
'year_qsos' => (int)$row->year_qsos
);
}
return array(
'total_qsos' => 0,
'todays_qsos' => 0,
'month_qsos' => 0,
'year_qsos' => 0
);
}
}
// Function to validate ADIF date format

查看文件

@ -24,6 +24,25 @@ class Setup_model extends CI_Model {
return $query->row()->count;
}
// Consolidated method to get all setup counts in one query
function getAllSetupCounts() {
$userid = xss_clean($this->session->userdata('user_id'));
$sql = "SELECT
(SELECT COUNT(*) FROM dxcc_entities) as country_count,
(SELECT COUNT(*) FROM station_logbooks WHERE user_id = {$userid}) as logbook_count,
(SELECT COUNT(*) FROM station_profile WHERE user_id = {$userid}) as location_count";
$query = $this->db->query($sql);
$row = $query->row();
return array(
'country_count' => $row->country_count,
'logbook_count' => $row->logbook_count,
'location_count' => $row->location_count
);
}
}
?>

查看文件

@ -88,10 +88,20 @@
}
}
// Initialize bandcalls array with all bands set to 0
foreach ($bands as $band) {
$bandcalls[$band] = 0;
}
foreach ($bandunique as $band) {
$bandcalls[$band->band] = $band->calls;
}
// Initialize modecalls array with all modes set to 0
foreach ($modes as $mode) {
$modecalls[$mode] = 0;
}
foreach ($modeunique as $mode) {
//if ($mode->col_submode == null) {
if ($mode->col_submode == null || $mode->col_submode == "") {

查看文件

@ -2,87 +2,421 @@
class Workabledxcc_model extends CI_Model
{
// Cache for DXCC lookups to avoid repeated queries
private $dxccCache = array();
private $workedCache = array();
/**
* Batch DXCC lookup for multiple callsigns
* @param array $callsigns Array of callsigns
* @param array $dates Array of dates corresponding to callsigns
* @return array Array of DXCC entities indexed by callsign index
*/
public function batchDxccLookup($callsigns, $dates)
{
$this->load->model('logbook_model');
$entities = array();
foreach ($callsigns as $index => $callsign) {
$cacheKey = $callsign . '_' . $dates[$index];
if (!isset($this->dxccCache[$cacheKey])) {
$dxccInfo = $this->logbook_model->dxcc_lookup($callsign, $dates[$index]);
$this->dxccCache[$cacheKey] = isset($dxccInfo['entity']) ? $dxccInfo['entity'] : null;
}
$entities[$index] = $this->dxccCache[$cacheKey];
}
return $entities;
}
/**
* Batch check if DXCC entities have been worked/confirmed
* @param array $entities Array of unique DXCC entities
* @return array Array of worked/confirmed status indexed by entity
*/
public function batchDxccWorkedStatus($entities)
{
if (empty($entities)) {
return array();
}
$user_default_confirmation = $this->session->userdata('user_default_confirmation');
$this->load->model('logbooks_model');
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
if (empty($logbooks_locations_array)) {
return array_fill_keys($entities, [
'workedBefore' => false,
'confirmed' => false,
'workedViaSatellite' => false
]);
}
$results = array();
// Build confirmation criteria once
$confirmationCriteria = $this->buildConfirmationCriteria($user_default_confirmation);
// Debug: Log entities being checked
log_message('debug', 'Workable DXCC: Checking entities: ' . implode(', ', $entities));
// Batch query for worked status (terrestrial)
$workedResults = $this->batchWorkedQuery($entities, $logbooks_locations_array);
// Batch query for confirmed status (terrestrial)
$confirmedResults = $this->batchConfirmedQuery($entities, $logbooks_locations_array, $confirmationCriteria);
// Batch query for satellite contacts
$satelliteResults = $this->batchSatelliteQuery($entities, $logbooks_locations_array);
// Debug: Log results
log_message('debug', 'Workable DXCC: Worked results: ' . json_encode($workedResults));
log_message('debug', 'Workable DXCC: Confirmed results: ' . json_encode($confirmedResults));
log_message('debug', 'Workable DXCC: Satellite results: ' . json_encode($satelliteResults));
// Combine results
foreach ($entities as $entity) {
$results[$entity] = [
'workedBefore' => isset($workedResults[$entity]),
'confirmed' => isset($confirmedResults[$entity]),
'workedViaSatellite' => isset($satelliteResults[$entity])
];
}
return $results;
}
/**
* Batch check IOTA worked/confirmed status
* @param array $iotas Array of unique IOTA tags
* @return array Array indexed by IOTA tag with ['worked'=>bool,'confirmed'=>bool]
*/
public function batchIotaWorkedStatus($iotas)
{
if (empty($iotas)) {
return array();
}
$user_default_confirmation = $this->session->userdata('user_default_confirmation');
$this->load->model('logbooks_model');
$logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook'));
if (empty($logbooks_locations_array)) {
$out = [];
foreach ($iotas as $i) {
$out[$i] = ['worked' => false, 'confirmed' => false];
}
return $out;
}
// Build confirmation criteria once
$confirmationCriteria = $this->buildConfirmationCriteria($user_default_confirmation);
// Build case-insensitive WHERE conditions for COL_IOTA
$whereConditions = array();
foreach ($iotas as $iota) {
$whereConditions[] = "UPPER(COL_IOTA) = UPPER('" . $this->db->escape_str($iota) . "')";
}
if (empty($whereConditions)) {
return array();
}
$whereClause = '(' . implode(' OR ', $whereConditions) . ')';
// Worked query (any mode)
$this->db->select('COL_IOTA')
->distinct()
->from($this->config->item('table_name'))
->where_in('station_id', $logbooks_locations_array)
->where($whereClause);
$workedQuery = $this->db->get();
log_message('debug', 'Workable DXCC IOTA worked query: ' . $this->db->last_query());
$workedResults = array();
foreach ($workedQuery->result() as $row) {
foreach ($iotas as $iota) {
if (strtoupper($row->COL_IOTA) === strtoupper($iota)) {
$workedResults[$iota] = true;
break;
}
}
}
// Confirmed query (apply confirmation criteria, exclude satellite confirmations if desired)
$confirmedResults = array();
if ($confirmationCriteria !== '1=0') {
$this->db->select('COL_IOTA')
->distinct()
->from($this->config->item('table_name'))
->where($confirmationCriteria)
->where_in('station_id', $logbooks_locations_array)
->where($whereClause);
$confirmedQuery = $this->db->get();
foreach ($confirmedQuery->result() as $row) {
foreach ($iotas as $iota) {
if (strtoupper($row->COL_IOTA) === strtoupper($iota)) {
$confirmedResults[$iota] = true;
break;
}
}
}
}
$out = array();
foreach ($iotas as $iota) {
$out[$iota] = [
'worked' => isset($workedResults[$iota]),
'confirmed' => isset($confirmedResults[$iota])
];
}
// Debug
log_message('debug', 'Workable DXCC: IOTA worked results: ' . json_encode($workedResults));
log_message('debug', 'Workable DXCC: IOTA confirmed results: ' . json_encode($confirmedResults));
return $out;
}
/**
* Batch query to check which entities have been worked via satellite
*/
private function batchSatelliteQuery($entities, $logbooks_locations_array)
{
// Create case-insensitive matching for DXCC entities
$whereConditions = array();
foreach ($entities as $entity) {
$whereConditions[] = "UPPER(COL_COUNTRY) = UPPER('" . $this->db->escape_str($entity) . "')";
}
if (empty($whereConditions)) {
return array();
}
$whereClause = '(' . implode(' OR ', $whereConditions) . ')';
$this->db->select('COL_COUNTRY')
->distinct()
->from($this->config->item('table_name'))
->where('COL_PROP_MODE', 'SAT') // Only satellite contacts
->where_in('station_id', $logbooks_locations_array)
->where($whereClause);
$query = $this->db->get();
// Debug: Log the SQL query
log_message('debug', 'Workable DXCC satellite query: ' . $this->db->last_query());
$results = array();
foreach ($query->result() as $row) {
// Store with the original entity case for lookup
foreach ($entities as $entity) {
if (strtoupper($row->COL_COUNTRY) === strtoupper($entity)) {
$results[$entity] = true;
log_message('debug', 'Workable DXCC: Found satellite match: ' . $entity . ' matches ' . $row->COL_COUNTRY);
break;
}
}
}
return $results;
}
/**
* Build confirmation criteria SQL based on user preferences
*/
private function buildConfirmationCriteria($user_default_confirmation)
{
$criteria = array();
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'Q') !== false) {
$criteria[] = "COL_QSL_RCVD='Y'";
}
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'L') !== false) {
$criteria[] = "COL_LOTW_QSL_RCVD='Y'";
}
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'E') !== false) {
$criteria[] = "COL_EQSL_QSL_RCVD='Y'";
}
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'Z') !== false) {
$criteria[] = "COL_QRZCOM_QSO_DOWNLOAD_STATUS='Y'";
}
return empty($criteria) ? '1=0' : '(' . implode(' OR ', $criteria) . ')';
}
/**
* Batch query to check which entities have been worked
*/
private function batchWorkedQuery($entities, $logbooks_locations_array)
{
// Create case-insensitive matching for DXCC entities
$whereConditions = array();
foreach ($entities as $entity) {
$whereConditions[] = "UPPER(COL_COUNTRY) = UPPER('" . $this->db->escape_str($entity) . "')";
}
if (empty($whereConditions)) {
return array();
}
$whereClause = '(' . implode(' OR ', $whereConditions) . ')';
$this->db->select('COL_COUNTRY')
->distinct()
->from($this->config->item('table_name'))
->where('COL_PROP_MODE !=', 'SAT')
->where_in('station_id', $logbooks_locations_array)
->where($whereClause);
$query = $this->db->get();
// Debug: Log the SQL query
log_message('debug', 'Workable DXCC worked query: ' . $this->db->last_query());
$results = array();
foreach ($query->result() as $row) {
// Store with the original entity case for lookup
foreach ($entities as $entity) {
if (strtoupper($row->COL_COUNTRY) === strtoupper($entity)) {
$results[$entity] = true;
log_message('debug', 'Workable DXCC: Found worked match: ' . $entity . ' matches ' . $row->COL_COUNTRY);
break;
}
}
}
return $results;
}
/**
* Batch query to check which entities have been confirmed
*/
private function batchConfirmedQuery($entities, $logbooks_locations_array, $confirmationCriteria)
{
if ($confirmationCriteria === '1=0') {
return array();
}
// Create case-insensitive matching for DXCC entities
$whereConditions = array();
foreach ($entities as $entity) {
$whereConditions[] = "UPPER(COL_COUNTRY) = UPPER('" . $this->db->escape_str($entity) . "')";
}
if (empty($whereConditions)) {
return array();
}
$whereClause = '(' . implode(' OR ', $whereConditions) . ')';
$this->db->select('COL_COUNTRY')
->distinct()
->from($this->config->item('table_name'))
->where('COL_PROP_MODE !=', 'SAT')
->where($confirmationCriteria)
->where_in('station_id', $logbooks_locations_array)
->where($whereClause);
$query = $this->db->get();
$results = array();
foreach ($query->result() as $row) {
// Store with the original entity case for lookup
foreach ($entities as $entity) {
if (strtoupper($row->COL_COUNTRY) === strtoupper($entity)) {
$results[$entity] = true;
break;
}
}
}
return $results;
}
public function GetThisWeek()
{
$json = file_get_contents($this->optionslib->get_option('dxped_url'));
// Step 2: Convert the JSON data to an array.
$json = file_get_contents($this->optionslib->get_option('dxped_url'));
$data = json_decode($json, true);
// Step 3: Create a new array to hold the records for this week.
$thisWeekRecords = [];
if (empty($data)) {
return array();
}
// Get the start and end of this week.
$thisWeekRecords = [];
$startOfWeek = (new DateTime())->setISODate((new DateTime())->format('o'), (new DateTime())->format('W'), 1);
$endOfWeek = (clone $startOfWeek)->modify('+6 days');
// Step 4: Iterate over the array.
foreach ($data as $record) {
// Get Date format
if ($this->session->userdata('user_date_format')) {
$custom_date_format = $this->session->userdata('user_date_format');
} else {
$custom_date_format = $this->config->item('qso_date_format');
}
// Convert "0" and "1" to DateTime objects.
// First pass: filter records for this week
$weekRecords = array();
foreach ($data as $record) {
$startDate = new DateTime($record['0']);
$endDate = new DateTime($record['1']);
// Step 5: Check if the start date or end date is within this week.
if (($startDate >= $startOfWeek && $startDate <= $endOfWeek) || ($endDate >= $startOfWeek && $endDate <= $endOfWeek)) {
$endDate = new DateTime($record['1']);
$now = new DateTime();
$interval = $now->diff($endDate);
$daysLeft = $interval->days;
// If daysLeft is 0, set it to "Last day"
if ($daysLeft == 0) {
$daysLeft = "Last day";
} else {
$daysLeft = $daysLeft . " days left";
}
// Add daysLeft to record
$record['daysLeft'] = $daysLeft;
// Get Date format
if ($this->session->userdata('user_date_format')) {
// If Logged in and session exists
$custom_date_format = $this->session->userdata('user_date_format');
} else {
// Get Default date format from /config/cloudlog.php
$custom_date_format = $this->config->item('qso_date_format');
}
// Create a new array with the required fields and add it to the main array
$oldStartDate = DateTime::createFromFormat('Y-m-d', $record['0']);
$StartDate = $oldStartDate->format($custom_date_format);
$record['startDate'] = $StartDate;
$oldEndDate = DateTime::createFromFormat('Y-m-d', $record['1']);
$EndDate = $oldEndDate->format($custom_date_format);
$record['endDate'] = $EndDate;
$record['confirmed'] = true; // or false, depending on your logic
$CI = &get_instance();
$CI->load->model('logbook_model');
$dxccInfo = $CI->logbook_model->dxcc_lookup($record['callsign'], $startDate->format('Y-m-d'));
// Call DXCC Worked function to check if the DXCC has been worked before
if (isset($dxccInfo['entity'])) {
$dxccWorkedData = $this->dxccWorked($dxccInfo['entity']);
$record = array_merge($record, $dxccWorkedData);
} else {
// Handle the case where 'entity' is not set in $dxccInfo
$itemsToAdd = array(
'workedBefore' => false,
'confirmed' => false,
);
$record = array_merge($record, $itemsToAdd);
}
$thisWeekRecords[] = $record;
if (($startDate >= $startOfWeek && $startDate <= $endOfWeek) ||
($endDate >= $startOfWeek && $endDate <= $endOfWeek)) {
$weekRecords[] = $record;
}
}
return $thisWeekRecords;
if (empty($weekRecords)) {
return array();
}
// Batch process DXCC lookups
$callsigns = array_column($weekRecords, 'callsign');
$dates = array_column($weekRecords, '0');
$dxccEntities = $this->batchDxccLookup($callsigns, $dates);
// Get worked/confirmed status for all entities in batch
$uniqueEntities = array_unique(array_filter($dxccEntities));
$dxccStatus = $this->batchDxccWorkedStatus($uniqueEntities);
// Process results
foreach ($weekRecords as $index => $record) {
$endDate = new DateTime($record['1']);
$now = new DateTime();
$interval = $now->diff($endDate);
$daysLeft = $interval->days;
$daysLeft = ($daysLeft == 0) ? "Last day" : $daysLeft . " days left";
$record['daysLeft'] = $daysLeft;
$oldStartDate = DateTime::createFromFormat('Y-m-d', $record['0']);
$record['startDate'] = $oldStartDate->format($custom_date_format);
$oldEndDate = DateTime::createFromFormat('Y-m-d', $record['1']);
$record['endDate'] = $oldEndDate->format($custom_date_format);
// Get DXCC status for this callsign
$entity = $dxccEntities[$index] ?? null;
$worked = $entity && isset($dxccStatus[$entity]) ? $dxccStatus[$entity] : [
'workedBefore' => false,
'confirmed' => false,
'workedViaSatellite' => false
];
$record['workedBefore'] = $worked['workedBefore'];
$record['confirmed'] = $worked['confirmed'];
$record['workedViaSatellite'] = $worked['workedViaSatellite'];
$thisWeekRecords[] = $record;
}
return $thisWeekRecords;
}
function dxccWorked($country)
@ -91,6 +425,7 @@ class Workabledxcc_model extends CI_Model
$return = [
"workedBefore" => false,
"confirmed" => false,
"workedViaSatellite" => false,
];
$user_default_confirmation = $this->session->userdata('user_default_confirmation');
@ -99,16 +434,28 @@ class Workabledxcc_model extends CI_Model
$this->load->model('logbook_model');
if (!empty($logbooks_locations_array)) {
// Check for terrestrial contacts
$this->db->where('COL_PROP_MODE !=', 'SAT');
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY', urldecode($country));
// Fix case sensitivity issue for DXCC country matching
$this->db->where('UPPER(COL_COUNTRY) = UPPER(?)', urldecode($country));
$query = $this->db->get($this->config->item('table_name'), 1, 0);
foreach ($query->result() as $workedBeforeRow) {
$return['workedBefore'] = true;
}
// Check for satellite contacts
$this->db->where('COL_PROP_MODE', 'SAT');
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('UPPER(COL_COUNTRY) = UPPER(?)', urldecode($country));
$query = $this->db->get($this->config->item('table_name'), 1, 0);
foreach ($query->result() as $satelliteRow) {
$return['workedViaSatellite'] = true;
}
$extrawhere = '';
if (isset($user_default_confirmation) && strpos($user_default_confirmation, 'Q') !== false) {
$extrawhere = "COL_QSL_RCVD='Y'";
@ -144,7 +491,8 @@ class Workabledxcc_model extends CI_Model
$this->db->where_in('station_id', $logbooks_locations_array);
$this->db->where('COL_COUNTRY', urldecode($country));
// Fix case sensitivity issue for DXCC country matching
$this->db->where('UPPER(COL_COUNTRY) = UPPER(?)', urldecode($country));
$query = $this->db->get($this->config->item('table_name'), 1, 0);
foreach ($query->result() as $workedBeforeRow) {

查看文件

@ -1,6 +1,12 @@
<div class="container adif" id="adif_import">
<h2><?php echo $page_title; ?></h2>
<div class="row">
<div class="col-md-12">
<h2><i class="fas fa-file-import me-2"></i><?php echo $page_title; ?></h2>
<p class="text-muted mb-4">Import and export your amateur radio logs in ADIF format</p>
</div>
</div>
<?php
$showtab = '';
if (isset($tab)) {
@ -8,260 +14,420 @@
}
?>
<div class="card">
<style>
/* Fix text-muted visibility in dark themes */
.text-muted {
color: var(--bs-secondary-color) !important;
opacity: 0.8;
}
</style>
<div class="card shadow-sm">
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs pull-right" role="tablist">
<li class="nav-item">
<a class="nav-link <?php if ($showtab == '' || $showtab == 'adif') {
echo 'active';
} ?>" id="import-tab" data-bs-toggle="tab" href="#import" role="tab" aria-controls="import" aria-selected="<?php if ($showtab == '' || $showtab == 'adif') {
echo 'true';
} else {
echo 'false';
} ?>"><?php echo lang('adif_import') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" id="export-tab" data-bs-toggle="tab" href="#export" role="tab" aria-controls="export" aria-selected="false"><?php echo lang('adif_export') ?></a>
</li>
<li class="nav-item">
<a class="nav-link" id="lotw-tab" data-bs-toggle="tab" href="#lotw" role="tab" aria-controls="lotw" aria-selected="false"><?php echo lang('lotw_title') ?></a>
</li>
<li class="nav-item">
<a class="nav-link <?php if ($showtab == 'dcl') {
echo 'active';
} ?>" id="dcl-tab" data-bs-toggle="tab" href="#dcl" role="tab" aria-controls="dcl" aria-selected="<?php if ($showtab == 'dcl') {
echo 'true';
} else {
echo 'false';
} ?>"><?php echo lang('darc_dcl') ?></a>
</li>
</ul>
<nav class="card-header-tabs">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-item nav-link <?php if ($showtab == '' || $showtab == 'adif') echo 'active'; ?>"
id="import-tab" data-bs-toggle="tab" href="#import" role="tab" aria-controls="import"
aria-selected="<?php echo ($showtab == '' || $showtab == 'adif') ? 'true' : 'false'; ?>">
<i class="fas fa-upload me-1"></i><?php echo lang('adif_import') ?>
</a>
<a class="nav-item nav-link" id="export-tab" data-bs-toggle="tab" href="#export" role="tab"
aria-controls="export" aria-selected="false">
<i class="fas fa-download me-1"></i><?php echo lang('adif_export') ?>
</a>
<a class="nav-item nav-link" id="lotw-tab" data-bs-toggle="tab" href="#lotw" role="tab"
aria-controls="lotw" aria-selected="false">
<i class="fas fa-globe me-1"></i><?php echo lang('lotw_title') ?>
</a>
<a class="nav-item nav-link <?php if ($showtab == 'dcl') echo 'active'; ?>"
id="dcl-tab" data-bs-toggle="tab" href="#dcl" role="tab" aria-controls="dcl"
aria-selected="<?php echo ($showtab == 'dcl') ? 'true' : 'false'; ?>">
<i class="fas fa-flag me-1"></i><?php echo lang('darc_dcl') ?>
</a>
</div>
</nav>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane <?php if ($showtab == '' || $showtab == 'adif') {
echo 'active';
echo 'active show';
} else {
echo 'fade';
} ?>" id="import" role="tabpanel" aria-labelledby="home-tab">
} ?>" id="import" role="tabpanel" aria-labelledby="import-tab">
<?php if (isset($error) && ($showtab == '' || $showtab == 'adif')) { ?>
<div class="alert alert-danger" role="alert">
<?php echo $error; ?>
<i class="fas fa-exclamation-triangle me-2"></i><?php echo $error; ?>
</div>
<?php } ?>
<p><span class="badge text-bg-warning"><?php echo lang('general_word_important') ?></span> <?php echo lang('adif_alert_log_files_type') ?></p>
<p><span class="badge text-bg-warning"><?php echo lang('general_word_warning') ?></span> <?php echo lang('gen_max_file_upload_size') ?><?php echo $max_upload; ?>B.</p>
<!-- Important Notices -->
<div class="row mb-4">
<div class="col-md-12">
<div class="alert alert-warning d-flex align-items-center" role="alert">
<i class="fas fa-info-circle me-2"></i>
<div>
<strong><?php echo lang('general_word_important') ?>:</strong> <?php echo lang('adif_alert_log_files_type') ?><br>
<strong><?php echo lang('general_word_warning') ?>:</strong> <?php echo lang('gen_max_file_upload_size') ?><?php echo $max_upload; ?>B.
</div>
</div>
</div>
</div>
<form class="form" action="<?php echo site_url('adif/import'); ?>" method="post" enctype="multipart/form-data">
<select name="station_profile" class="form-select mb-2 me-sm-2" style="width: 20%;">
<option value="0"><?php echo lang('adif_select_stationlocation') ?></option>
<?php foreach ($station_profile->result() as $station) { ?>
<option value="<?php echo $station->station_id; ?>" <?php if ($station->station_id == $this->stations->find_active()) {
echo " selected =\"selected\"";
} ?>><?php echo lang('gen_hamradio_callsign') . ": " ?><?php echo $station->station_callsign; ?> (<?php echo $station->station_profile_name; ?>)</option>
<?php } ?>
</select>
<label class="visually-hidden" for="inlineFormInputName2"><?php echo lang('adif_file_label') ?></label>
<input class="file-input mb-2 me-sm-2" type="file" name="userfile" size="20" />
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="skipDuplicate" value="1" id="skipDuplicate">
<label class="form-check-label" for="skipDuplicate"><?php echo lang('adif_import_dup') ?></label>
<!-- Station Selection and File Upload -->
<div class="row mb-4">
<div class="col-md-6">
<h5 class="mb-3"><i class="fas fa-tower-broadcast me-2"></i>Station Selection</h5>
<div class="mb-3">
<label for="station_profile" class="form-label">Select Station Location</label>
<select name="station_profile" id="station_profile" class="form-select">
<option value="0"><?php echo lang('adif_select_stationlocation') ?></option>
<?php foreach ($station_profile->result() as $station) { ?>
<option value="<?php echo $station->station_id; ?>" <?php if ($station->station_id == $this->stations->find_active()) {
echo " selected =\"selected\"";
} ?>>
<?php echo lang('gen_hamradio_callsign') . ": " ?><?php echo $station->station_callsign; ?> (<?php echo $station->station_profile_name; ?>)
</option>
<?php } ?>
</select>
</div>
</div>
<div class="col-md-6">
<h5 class="mb-3"><i class="fas fa-file me-2"></i>File Upload</h5>
<div class="mb-3">
<label for="userfile" class="form-label">Choose ADIF File</label>
<input class="form-control" type="file" name="userfile" id="userfile" accept=".adi,.ADI,.adif,.ADIF" />
<div class="form-text">Supported formats: .adi, .adif</div>
</div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="markLotw" value="1" id="markLotwImport">
<label class="form-check-label" for="markLotwImport"><?php echo lang('adif_mark_imported_lotw') ?></label>
<!-- Import Options -->
<div class="row mb-4">
<div class="col-md-12">
<h5 class="mb-3"><i class="fas fa-cogs me-2"></i>Import Options</h5>
<!-- Basic Options -->
<div class="card mb-3">
<div class="card-header" style="background-color: var(--bs-secondary-bg); border-bottom: 1px solid var(--bs-border-color);">
<h6 class="mb-0">Basic Settings</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="skipDuplicate" value="1" id="skipDuplicate">
<label class="form-check-label" for="skipDuplicate">
<strong><?php echo lang('adif_import_dup') ?></strong>
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="dxccAdif" value="1" id="dxccAdif">
<label class="form-check-label" for="dxccAdif">
<strong><?php echo lang('adif_dxcc_from_adif') ?></strong>
<br><small class="text-muted"><?php echo lang('adif_dxcc_from_adif_hint') ?></small>
</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="operatorName" value="1" id="operatorName">
<label class="form-check-label" for="operatorName">
<strong><?php echo lang('adif_always_use_login_call_as_op') ?></strong>
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="skipStationCheck" value="1" id="skipStationCheck">
<label class="form-check-label" for="skipStationCheck">
<span class="badge bg-danger me-1"><?php echo lang('general_word_danger') ?></span>
<strong><?php echo lang('adif_ignore_station_call') ?></strong>
<br><small class="text-muted"><?php echo lang('adif_ignore_station_call_hint') ?></small>
</label>
</div>
</div>
</div>
</div>
</div>
<div class="small form-text text-muted"><?php echo lang('adif_hint_no_info_in_file') ?></div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="markHrd" value="1" id="markHrdImport">
<label class="form-check-label" for="markHrdImport"><?php echo lang('adif_mark_imported_hrdlog') ?></label>
</div>
<div class="small form-text text-muted"><?php echo lang('adif_hint_no_info_in_file') ?></div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="markQrz" value="1" id="markQrzImport">
<label class="form-check-label" for="markQrzImport"><?php echo lang('adif_mark_imported_qrz') ?></label>
</div>
<div class="small form-text text-muted"><?php echo lang('adif_hint_no_info_in_file') ?></div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="markClublog" value="1" id="markClublogImport">
<label class="form-check-label" for="markClublogImport"><?php echo lang('adif_mark_imported_clublog') ?></label>
</div>
<div class="small form-text text-muted"><?php echo lang('adif_hint_no_info_in_file') ?></div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="dxccAdif" value="1" id="dxccAdif">
<label class="form-check-label" for="dxccAdif"><?php echo lang('adif_dxcc_from_adif') ?></label>
</div>
<div class="small form-text text-muted"><?php echo lang('adif_dxcc_from_adif_hint') ?></div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="operatorName" value="1" id="operatorName">
<label class="form-check-label" for="operatorName"><?php echo lang('adif_always_use_login_call_as_op') ?></label>
<!-- Logbook Upload Markers -->
<div class="card">
<div class="card-header" style="background-color: var(--bs-secondary-bg); border-bottom: 1px solid var(--bs-border-color);">
<h6 class="mb-0">Mark as Uploaded to Online Logbooks</h6> </div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="markLotw" value="1" id="markLotwImport">
<label class="form-check-label" for="markLotwImport">
<strong><?php echo lang('adif_mark_imported_lotw') ?></strong>
<br><small class="text-muted"><?php echo lang('adif_hint_no_info_in_file') ?></small>
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="markHrd" value="1" id="markHrdImport">
<label class="form-check-label" for="markHrdImport">
<strong><?php echo lang('adif_mark_imported_hrdlog') ?></strong>
<br><small class="text-muted"><?php echo lang('adif_hint_no_info_in_file') ?></small>
</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="markQrz" value="1" id="markQrzImport">
<label class="form-check-label" for="markQrzImport">
<strong><?php echo lang('adif_mark_imported_qrz') ?></strong>
<br><small class="text-muted"><?php echo lang('adif_hint_no_info_in_file') ?></small>
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="markClublog" value="1" id="markClublogImport">
<label class="form-check-label" for="markClublogImport">
<strong><?php echo lang('adif_mark_imported_clublog') ?></strong>
<br><small class="text-muted"><?php echo lang('adif_hint_no_info_in_file') ?></small>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="skipStationCheck" value="1" id="skipStationCheck">
<label class="form-check-label" for="skipStationCheck"><span class="badge text-bg-warning"><?php echo lang('general_word_danger') ?></span> <?php echo lang('adif_ignore_station_call') ?></label>
</div>
<div class="small form-text text-muted"><?php echo lang('adif_ignore_station_call_hint') ?></div>
<!-- Submit Button -->
<div class="row">
<div class="col-md-12">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-upload me-2"></i><?php echo lang('adif_upload') ?>
</button>
</div>
</div>
<button type="submit" class="btn btn-sm btn-primary mb-2" value="Upload"><?php echo lang('adif_upload') ?></button>
</form>
</div>
<div class="tab-pane fade" id="export" role="tabpanel" aria-labelledby="home-tab">
<div class="tab-pane fade" id="export" role="tabpanel" aria-labelledby="export-tab">
<!-- Custom Export Section -->
<div class="row mb-4">
<div class="col-md-12">
<h5 class="mb-3"><i class="fas fa-download me-2"></i><?php echo lang('adif_export_take_it_anywhere') ?></h5>
<p class="text-muted mb-4"><?php echo lang('adif_export_take_it_anywhere_hint') ?></p>
<form class="form" action="<?php echo site_url('adif/export_custom'); ?>" method="post" enctype="multipart/form-data">
<div class="card">
<div class="card-header" style="background-color: var(--bs-secondary-bg); border-bottom: 1px solid var(--bs-border-color);">
<h6 class="mb-0">Export Settings</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="station_profile_export" class="form-label">Station Location</label>
<select name="station_profile" id="station_profile_export" class="form-select">
<option value="0"><?php echo lang('adif_select_stationlocation') ?></option>
<?php foreach ($station_profile->result() as $station) { ?>
<option value="<?php echo $station->station_id; ?>" <?php if ($station->station_id == $this->stations->find_active()) {
echo " selected =\"selected\"";
} ?>>
<?php echo lang('gen_hamradio_callsign') . ": " ?><?php echo $station->station_callsign; ?> (<?php echo $station->station_profile_name; ?>)
</option>
<?php } ?>
</select>
</div>
<div class="mb-3">
<label for="from_export" class="form-label"><?php echo lang('gen_from_date') ?></label>
<input name="from" id="from_export" type="date" class="form-control">
</div>
<div class="mb-3">
<label for="to_export" class="form-label"><?php echo lang('gen_to_date') ?></label>
<input name="to" id="to_export" type="date" class="form-control">
</div>
</div>
<div class="col-md-6">
<h6 class="mb-3">Export Options</h6>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="markLotw" value="1" id="markLotwExport">
<label class="form-check-label" for="markLotwExport">
<strong><?php echo lang('adif_mark_exported_lotw') ?></strong>
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="exportLotw" value="1" id="exportLotw">
<label class="form-check-label" for="exportLotw">
<strong><?php echo lang('adif_mark_exported_no_lotw') ?></strong>
</label>
</div>
</div>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary">
<i class="fas fa-download me-2"></i><?php echo lang('adif_export_qso') ?>
</button>
</div>
</div>
</form>
</div>
</div>
<form class="form" action="<?php echo site_url('adif/export_custom'); ?>" method="post" enctype="multipart/form-data">
<h5 class="card-title"><?php echo lang('adif_export_take_it_anywhere') ?> </h5>
<p class="card-text"><?php echo lang('adif_export_take_it_anywhere_hint') ?> </p>
<select name="station_profile" class="form-select mb-2 me-sm-2" style="width: 20%;">
<option value="0"><?php echo lang('adif_select_stationlocation') ?></option>
<?php foreach ($station_profile->result() as $station) { ?>
<option value="<?php echo $station->station_id; ?>" <?php if ($station->station_id == $this->stations->find_active()) {
echo " selected =\"selected\"";
} ?>><?php echo lang('gen_hamradio_callsign') . ": " ?><?php echo $station->station_callsign; ?> (<?php echo $station->station_profile_name; ?>)</option>
<?php } ?>
</select>
<br>
<label for="from"><?php echo lang('gen_from_date') . ": " ?></label>
<input name="from" id="from" type="date" class="form-control w-auto">
<br>
<label for="to"><?php echo lang('gen_to_date') . ": " ?></label>
<input name="to" id="to" type="date" class="form-control w-auto">
<br>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="markLotw" value="1" id="markLotwExport">
<label class="form-check-label" for="markLotwExport"><?php echo lang('adif_mark_exported_lotw') ?></label>
<!-- Satellite Export Section -->
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header" style="background-color: var(--bs-secondary-bg); border-bottom: 1px solid var(--bs-border-color);">
<h6 class="mb-0"><i class="fas fa-satellite me-2"></i><?php echo lang('adif_export_sat_only_qso') ?></h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p class="mb-3">Export all satellite contacts from your logbook</p>
<a href="<?php echo site_url('adif/exportsat'); ?>" title="Export All Satellite Contacts" target="_blank" class="btn btn-outline-primary">
<i class="fas fa-satellite me-2"></i><?php echo lang('adif_export_sat_only_qso_all') ?>
</a>
</div>
<div class="col-md-6">
<p class="mb-3">Export satellite contacts confirmed on LoTW</p>
<a href="<?php echo site_url('adif/exportsatlotw'); ?>" title="Export All Satellite QSOs Confirmed on LoTW" target="_blank" class="btn btn-outline-primary">
<i class="fas fa-check-circle me-2"></i><?php echo lang('adif_export_sat_only_qso_lotw') ?>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="exportLotw" value="1" id="exportLotw">
<label class="form-check-label" for="exportLotw"><?php echo lang('adif_mark_exported_no_lotw') ?></label>
</div>
</div>
</div>
<button type="submit" class="btn btn-sm btn-primary" value="Export"><?php echo lang('adif_export_qso') ?></button>
</form>
<br><br>
<h5><?php echo lang('adif_export_sat_only_qso') ?></h5>
<p><a href="<?php echo site_url('adif/exportsat'); ?>" title="Export All Satellite Contacts" target="_blank" class="btn btn-sm btn-primary"><?php echo lang('adif_export_sat_only_qso_all') ?></a></p>
<p><a href="<?php echo site_url('adif/exportsatlotw'); ?>" title="Export All Satellite QSOs Confirmed on LoTW" target="_blank" class="btn btn-sm btn-primary"><?php echo lang('adif_export_sat_only_qso_lotw') ?></a></p>
</div>
</div>
<div class="tab-pane fade" id="lotw" role="tabpanel" aria-labelledby="home-tab">
<form class="form" action="<?php echo site_url('adif/mark_lotw'); ?>" method="post" enctype="multipart/form-data">
<select name="station_profile" class="form-select mb-2 me-sm-2" style="width: 20%;">
<option value="0"><?php echo lang('adif_select_stationlocation') ?></option>
<?php foreach ($station_profile->result() as $station) { ?>
<option value="<?php echo $station->station_id; ?>"><?php echo lang('gen_hamradio_callsign') . ": " ?><?php echo $station->station_callsign; ?> (<?php echo $station->station_profile_name; ?>)</option>
<?php } ?>
</select>
<p><span class="badge text-bg-warning"><?php echo lang('general_word_warning') ?></span> <?php echo lang('adif_lotw_export_if_selected') ?></p>
<br>
<label for="from"><?php echo lang('gen_from_date') . ": " ?></label>
<input name="from" id="from" type="date" class="form-control w-auto">
<br>
<label for="to"><?php echo lang('gen_to_date') . ": " ?></label>
<input name="to" id="to" type="date" class="form-control w-auto">
<br>
<button type="button" class="btn btn-sm btn-primary" id="markExportedToLotw" value="Export"><?php echo lang('adif_mark_qso_as_exported_to_lotw') ?></button>
</form>
<div class="tab-pane fade" id="lotw" role="tabpanel" aria-labelledby="lotw-tab">
<div class="row">
<div class="col-md-12">
<h5 class="mb-3"><i class="fas fa-globe me-2"></i>LoTW Export Management</h5>
<div class="alert alert-warning d-flex align-items-center" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<div>
<strong><?php echo lang('general_word_warning') ?>:</strong> <?php echo lang('adif_lotw_export_if_selected') ?>
</div>
</div>
<form class="form" action="<?php echo site_url('adif/mark_lotw'); ?>" method="post" enctype="multipart/form-data">
<div class="card">
<div class="card-header" style="background-color: var(--bs-secondary-bg); border-bottom: 1px solid var(--bs-border-color);">
<h6 class="mb-0">Mark QSOs as Exported to LoTW</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="station_profile_lotw" class="form-label">Station Location</label>
<select name="station_profile" id="station_profile_lotw" class="form-select">
<option value="0"><?php echo lang('adif_select_stationlocation') ?></option>
<?php foreach ($station_profile->result() as $station) { ?>
<option value="<?php echo $station->station_id; ?>">
<?php echo lang('gen_hamradio_callsign') . ": " ?><?php echo $station->station_callsign; ?> (<?php echo $station->station_profile_name; ?>)
</option>
<?php } ?>
</select>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label for="from_lotw" class="form-label"><?php echo lang('gen_from_date') ?></label>
<input name="from" id="from_lotw" type="date" class="form-control">
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label for="to_lotw" class="form-label"><?php echo lang('gen_to_date') ?></label>
<input name="to" id="to_lotw" type="date" class="form-control">
</div>
</div>
</div>
</div>
<div class="card-footer">
<button type="button" class="btn btn-primary" id="markExportedToLotw">
<i class="fas fa-check me-2"></i><?php echo lang('adif_mark_qso_as_exported_to_lotw') ?>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="tab-pane <?php if ($showtab == 'dcl') {
echo 'active';
echo 'active show';
} else {
echo 'fade';
} ?>" id="dcl" role="tabpanel" aria-labelledby="home-tab">
} ?>" id="dcl" role="tabpanel" aria-labelledby="dcl-tab">
<?php if (isset($error) && $showtab == 'dcl') { ?>
<div class="alert alert-danger" role="alert">
<?php echo $error; ?>
<i class="fas fa-exclamation-triangle me-2"></i><?php echo $error; ?>
</div>
<?php } ?>
<p class="card-text"><?php echo lang('adif_dcl_text_pre') ?> <a href="http://dcl.darc.de/dml/export_adif_form.php" target="_blank"><?php echo lang('darc_dcl') ?></a> <?php echo lang('adif_dcl_text_post') ?></p>
<form class="form" action="<?php echo site_url('adif/dcl'); ?>" method="post" enctype="multipart/form-data">
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="onlyConfirmed" value="1" id="onlyConfirmed" checked>
<label class="form-check-label" for="onlyConfirmed"><?php echo lang('only_confirmed_qsos') ?></label>
<div class="row">
<div class="col-md-12">
<h5 class="mb-3"><i class="fas fa-flag me-2"></i>DARC DCL Import</h5>
<div class="alert alert-info d-flex align-items-center" role="alert">
<i class="fas fa-info-circle me-2"></i>
<div>
<?php echo lang('adif_dcl_text_pre') ?> <a href="http://dcl.darc.de/dml/export_adif_form.php" target="_blank" class="alert-link"><?php echo lang('darc_dcl') ?></a> <?php echo lang('adif_dcl_text_post') ?>
</div>
<div class="small form-text text-muted"><?php echo lang('only_confirmed_qsos_hint') ?></div>
</div>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="overwriteDok" value="1" id="overwriteDok">
<label class="form-check-label" for="overwriteDok"><span class="badge text-bg-warning"><?php echo lang('general_word_warning') ?></span> <?php echo lang('overwrite_by_dcl') ?></label>
<form class="form" action="<?php echo site_url('adif/dcl'); ?>" method="post" enctype="multipart/form-data">
<div class="card">
<div class="card-header" style="background-color: var(--bs-secondary-bg); border-bottom: 1px solid var(--bs-border-color);">
<h6 class="mb-0">DCL Import Settings</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="userfile_dcl" class="form-label">Choose DCL ADIF File</label>
<input class="form-control" type="file" name="userfile" id="userfile_dcl" />
</div>
</div>
<div class="col-md-6">
<h6 class="mb-3">Import Options</h6>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="onlyConfirmed" value="1" id="onlyConfirmed" checked>
<label class="form-check-label" for="onlyConfirmed">
<strong><?php echo lang('only_confirmed_qsos') ?></strong>
<br><small class="text-muted"><?php echo lang('only_confirmed_qsos_hint') ?></small>
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="overwriteDok" value="1" id="overwriteDok">
<label class="form-check-label" for="overwriteDok">
<span class="badge bg-warning text-dark me-1"><?php echo lang('general_word_warning') ?></span>
<strong><?php echo lang('overwrite_by_dcl') ?></strong>
<br><small class="text-muted"><?php echo lang('overwrite_by_dcl_hint') ?></small>
</label>
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" name="ignoreAmbiguous" value="1" id="ignoreAmbiguous" checked>
<label class="form-check-label" for="ignoreAmbiguous">
<strong><?php echo lang('ignore_ambiguous') ?></strong>
<br><small class="text-muted"><?php echo lang('ignore_ambiguous_hint') ?></small>
</label>
</div>
</div>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary">
<i class="fas fa-upload me-2"></i><?php echo lang('adif_upload') ?>
</button>
</div>
</div>
<div class="small form-text text-muted"><?php echo lang('overwrite_by_dcl_hint') ?></div>
</div>
</form>
</div>
<div class="mb-3 row">
<div class="col-md-10">
<div class="form-check-inline">
<input class="form-check-input" type="checkbox" name="ignoreAmbiguous" value="1" id="ignoreAmbiguous" checked>
<label class="form-check-label" for="ignoreAmbiguous"><?php echo lang('ignore_ambiguous') ?></label>
</div>
<div class="small form-text text-muted"><?php echo lang('ignore_ambiguous_hint') ?></div>
</div>
</div>
<input class="file-input mb-2 me-sm-2" type="file" name="userfile" size="20" />
<button type="submit" class="btn btn-sm btn-primary mb-2" value="Upload"><?php echo lang('adif_upload') ?></button>
</form>
</div>
</div>
</div>
</div>

查看文件

@ -23,39 +23,203 @@ $wwff = 0;
<h2><?php echo lang('options_bands'); ?></h2>
<!-- Info Card -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-info-circle me-2"></i><?php echo lang('options_bands'); ?> Information</h5>
</div>
<div class="card-body">
<div class="alert alert-primary alert-dismissible fade show" role="alert">
<strong><i class="fas fa-lightbulb"></i> Quick Guide:</strong>
The <strong>first column</strong> controls if a band appears in QSO entry. The <strong>award columns</strong> (CQ, DOK, DXCC, etc.) control which awards are tracked for that band. Use the <strong>master checkboxes</strong> at the bottom to toggle all bands for a specific award.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<p class="card-text mb-2">
<?php echo lang('options_bands_text_ln1'); ?>
</p>
<p class="card-text mb-0">
<?php echo lang('options_bands_text_ln2'); ?>
</p>
</div>
</div>
<!-- Controls Card -->
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-sliders-h me-2"></i>Band Management Controls</h5>
</div>
<div class="card-body">
<!-- Statistics -->
<div class="row mb-3">
<div class="col-md-8">
<div class="card">
<div class="card-body py-2">
<div class="row text-center">
<div class="col-md-6 col-6">
<strong id="activeBandsCount" class="text-primary">0</strong><br>
<small class="text-muted">Active for QSO Entry</small>
</div>
<div class="col-md-6 col-6">
<strong class="text-primary"><?php echo count($bands); ?></strong><br>
<small class="text-muted">Total Bands Configured</small>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="btn-group btn-group-sm w-100" role="group">
<button type="button" class="btn btn-outline-success" id="enableAllAwards" title="Enable all award tracking for all bands">
<i class="fas fa-check-double"></i> Enable All Awards
</button>
<button type="button" class="btn btn-outline-warning" id="resetAllAwards" title="Disable all award tracking">
<i class="fas fa-times"></i> Reset Awards
</button>
</div>
</div>
</div>
<!-- Search and Filter -->
<div class="row">
<div class="col-md-8">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="bandSearch" class="form-control" placeholder="Search bands, frequencies, or band groups... (Press / to focus)">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="clearSearch" title="Clear search">
<i class="fas fa-times"></i>
</button>
</div>
</div>
</div>
<div class="col-md-4">
<div class="btn-group btn-group-sm w-100" role="group">
<button type="button" class="btn btn-outline-secondary" id="showActiveOnly">
<i class="fas fa-eye"></i> Active Only
</button>
<button type="button" class="btn btn-outline-secondary" id="showAll">
<i class="fas fa-list"></i> Show All
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Bands Table Card -->
<div class="card">
<div class="card-header">
<?php echo lang('options_bands'); ?>
</div>
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="fas fa-table me-2"></i>Bands Configuration</h5>
<span class="badge bg-secondary" id="visibleRowsCount">Loading...</span>
</div>
</div>
<div class="card-body">
<p class="card-text">
<?php echo lang('options_bands_text_ln1'); ?>
</p>
<p class="card-text">
<?php echo lang('options_bands_text_ln2'); ?>
</p>
<div class="table-responsive">
<style>
.bandtable th, .bandtable td {
text-align: center;
vertical-align: middle;
white-space: nowrap;
padding: 8px 4px;
}
.bandtable th {
border-top: 2px solid #dee2e6;
background-color: var(--bs-secondary-bg, #f8f9fa);
}
.bandtable tfoot th {
border-top: 2px solid #adb5bd;
background-color: var(--bs-tertiary-bg, #e9ecef);
}
.bandtable th:first-child,
.bandtable td:first-child {
position: sticky;
left: 0;
z-index: 2;
box-shadow: 2px 0 2px rgba(0,0,0,0.1);
background-color: var(--bs-body-bg, #fff);
}
.bandtable th:nth-child(2),
.bandtable td:nth-child(2) {
position: sticky;
left: 40px;
z-index: 2;
box-shadow: 2px 0 2px rgba(0,0,0,0.1);
font-weight: bold;
background-color: var(--bs-body-bg, #fff);
}
.bandtable thead th:first-child,
.bandtable thead th:nth-child(2) {
z-index: 3;
}
.bandtable th:nth-child(3) {
border-left: 1px solid var(--bs-border-color, #dee2e6);
}
.bandtable input[type="checkbox"] {
transform: scale(1.2);
cursor: pointer;
}
.band-checkbox-cell {
background-color: #f8f9fa;
}
.frequency-cell {
font-family: monospace;
font-size: 0.9em;
}
.saving {
background-color: #fff3cd !important;
transition: background-color 0.3s ease;
}
.saved {
background-color: #d4edda !important;
transition: background-color 0.3s ease;
}
.updating {
background-color: #e2e3e5 !important;
transition: background-color 0.2s ease;
}
.updated {
background-color: #d1ecf1 !important;
transition: background-color 0.3s ease;
}
.band-actions {
white-space: nowrap;
}
.band-actions .btn {
margin: 0 2px;
}
@media (max-width: 768px) {
.bandtable th, .bandtable td {
padding: 4px 2px;
font-size: 0.85em;
}
}
</style>
<table style="width:100%" class="bandtable table table-sm table-striped">
<thead>
<tr>
<th></th>
<th title="Enable/disable band for QSO entry">Active</th>
<th><?php echo lang('gen_hamradio_band'); ?></th>
<th><?php echo lang('gen_hamradio_cq'); ?></th>
<th><?php echo lang('gen_hamradio_dok'); ?></th>
<th><?php echo lang('gen_hamradio_dxcc'); ?></th>
<th><?php echo lang('gen_hamradio_iota'); ?></th>
<th><?php echo lang('gen_hamradio_pota'); ?></th>
<th><?php echo lang('gen_hamradio_sig'); ?></th>
<th><?php echo lang('gen_hamradio_sota'); ?></th>
<th><?php echo lang('gen_hamradio_county_reference'); ?></th>
<th><?php echo lang('menu_vucc'); ?></th>
<th><?php echo lang('menu_was'); ?></th>
<th><?php echo lang('gen_hamradio_wwff'); ?></th>
<th><?php echo lang('gen_hamradio_bandgroup'); ?></th>
<th><?php echo lang('options_bands_ssb_qrg'); ?></th>
<th><?php echo lang('options_bands_data_qrg'); ?></th>
<th><?php echo lang('options_bands_cw_qrg'); ?></th>
<th title="CQ Magazine DX Awards">CQ</th>
<th title="Diplom Ortsverbände Kennung">DOK</th>
<th title="DX Century Club">DXCC</th>
<th title="Islands On The Air">IOTA</th>
<th title="Parks on the Air">POTA</th>
<th title="Special Interest Group">SIG</th>
<th title="Summits on the Air">SOTA</th>
<th title="US Counties">Counties</th>
<th title="VHF/UHF Century Club">VUCC</th>
<th title="Worked All States">WAS</th>
<th title="World Wide Flora and Fauna">WWFF</th>
<th title="Band group classification">Group</th>
<th title="SSB Operating Frequency">SSB</th>
<th title="Data Operating Frequency">Data</th>
<th title="CW Operating Frequency">CW</th>
<?php if($this->session->userdata('user_type') == '99') { ?>
<th></th>
<th></th>
@ -65,28 +229,28 @@ $wwff = 0;
<tbody>
<?php foreach ($bands as $band) { ?>
<tr>
<td style="text-align: center; vertical-align: middle;" class='band_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->active == 1) {echo 'checked';}?>></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo $band->band;?></td>
<td style="text-align: center; vertical-align: middle;" class='cq_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->cq == 1) {echo 'checked'; $cq++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='dok_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->dok == 1) {echo 'checked'; $dok++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='dxcc_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->dxcc == 1) {echo 'checked'; $dxcc++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='iota_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->iota == 1) {echo 'checked'; $iota++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='pota_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->iota == 1) {echo 'checked'; $pota++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='sig_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->sig == 1) {echo 'checked'; $sig++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='sota_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->sota == 1) {echo 'checked'; $sota++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='uscounties_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->uscounties == 1) {echo 'checked'; $uscounties++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='vucc_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->vucc == 1) {echo 'checked'; $vucc++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='was_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->was == 1) {echo 'checked'; $was++;}?>></td>
<td style="text-align: center; vertical-align: middle;" class='wwff_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->wwff == 1) {echo 'checked'; $wwff++;}?>></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo $band->bandgroup;?></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo $band->ssb;?></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo $band->data;?></td>
<td style="text-align: center; vertical-align: middle;" ><?php echo $band->cw;?></td>
<td class='band_<?php echo $band->id ?> band-checkbox-cell'><input type="checkbox" <?php if ($band->active == 1) {echo 'checked';}?>></td>
<td class="band-name"><?php echo $band->band;?></td>
<td class='cq_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->cq == 1) {echo 'checked'; $cq++;}?>></td>
<td class='dok_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->dok == 1) {echo 'checked'; $dok++;}?>></td>
<td class='dxcc_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->dxcc == 1) {echo 'checked'; $dxcc++;}?>></td>
<td class='iota_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->iota == 1) {echo 'checked'; $iota++;}?>></td>
<td class='pota_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->pota == 1) {echo 'checked'; $pota++;}?>></td>
<td class='sig_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->sig == 1) {echo 'checked'; $sig++;}?>></td>
<td class='sota_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->sota == 1) {echo 'checked'; $sota++;}?>></td>
<td class='uscounties_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->uscounties == 1) {echo 'checked'; $uscounties++;}?>></td>
<td class='vucc_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->vucc == 1) {echo 'checked'; $vucc++;}?>></td>
<td class='was_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->was == 1) {echo 'checked'; $was++;}?>></td>
<td class='wwff_<?php echo $band->id ?>'><input type="checkbox" <?php if ($band->wwff == 1) {echo 'checked'; $wwff++;}?>></td>
<td><?php echo $band->bandgroup;?></td>
<td class="frequency-cell"><?php echo $band->ssb;?></td>
<td class="frequency-cell"><?php echo $band->data;?></td>
<td class="frequency-cell"><?php echo $band->cw;?></td>
<?php if($this->session->userdata('user_type') == '99') { ?>
<td style="text-align: center; vertical-align: middle;" >
<td>
<a href="javascript:editBandDialog('<?php echo $band->bandid ?>');" class="btn btn-outline-primary btn-sm" title="Edit"><i class="fas fa-edit"></i></a>
</td>
<td style="text-align: center; vertical-align: middle;" >
<td>
<a href="javascript:deleteBand('<?php echo $band->id . '\',\'' . $band->band ?>');" class="btn btn-danger btn-sm" title="Delete"><i class="fas fa-trash-alt"></i></a>
</td>
<?php } ?>
@ -95,7 +259,7 @@ $wwff = 0;
<?php } ?>
</tbody>
<tfoot>
<th><?php echo lang('general_word_all'); ?></th>
<th>Toggle All</th>
<th></th>
<th class="master_cq"><input type="checkbox" <?php if ($cq > 0) echo 'checked';?>></th>
<th class="master_dok"><input type="checkbox" <?php if ($dok > 0) echo 'checked';?>></th>
@ -115,7 +279,7 @@ $wwff = 0;
<th></th>
<th></th>
</tfoot>
<table>
</table>
</div>
<br/>
<p>

查看文件

@ -784,6 +784,26 @@ if ($this->session->userdata('user_id') != null) {
});
// Form "submit" //
$('.custom-map-QSOs .btn_submit_map_custom').off('click').on('click', function() {
// Show loading spinner and disable button
const $button = $(this);
const $spinner = $('#load-spinner');
$button.prop('disabled', true);
$spinner.removeClass('d-none');
// Hide any previous alerts
$('.warningOnSubmit').hide();
$('#map-success-alert').addClass('d-none');
// Failsafe timeout to prevent stuck spinner (60 seconds)
const failsafeTimeout = setTimeout(function() {
console.warn('Map loading timed out - forcing spinner hide');
$button.prop('disabled', false);
$spinner.addClass('d-none');
$('.warningOnSubmit .warningOnSubmit_txt').text('Map loading timed out. Please try again.');
$('.warningOnSubmit').show();
}, 60000);
var customdata = {
'dataPost': {
'date_from': $('.custom-map-QSOs input[name="from"]').val(),
@ -795,6 +815,58 @@ if ($this->session->userdata('user_id') != null) {
},
'map_id': '#custommap'
};
// Add success and error callbacks to the customdata
customdata.onSuccess = function(plotjson) {
console.log('Map loading success callback called with data:', plotjson);
// Clear the failsafe timeout
clearTimeout(failsafeTimeout);
try {
// Update statistics
if (typeof updateMapStatistics === 'function') {
console.log('Calling updateMapStatistics function');
updateMapStatistics(plotjson);
} else {
console.warn('updateMapStatistics function not found');
}
// Show success message
const qsoCount = plotjson['markers'] ? plotjson['markers'].length : 0;
console.log('QSO count:', qsoCount);
$('#qso-count-display').text(`Loaded ${qsoCount} QSOs successfully`);
$('#map-success-alert').removeClass('d-none');
setTimeout(() => {
$('#map-success-alert').addClass('d-none');
}, 3000);
} catch (error) {
console.error('Error in success callback:', error);
$('.warningOnSubmit .warningOnSubmit_txt').text('Map loaded but encountered an error displaying statistics.');
$('.warningOnSubmit').show();
} finally {
// Always re-enable button and hide spinner
console.log('Re-enabling button and hiding spinner');
$button.prop('disabled', false);
$spinner.addClass('d-none');
}
};
customdata.onError = function() {
console.log('Map loading error callback called');
// Clear the failsafe timeout
clearTimeout(failsafeTimeout);
$('.warningOnSubmit .warningOnSubmit_txt').text('Failed to load map data. Please try again.');
$('.warningOnSubmit').show();
// Re-enable button and hide spinner
$button.prop('disabled', false);
$spinner.addClass('d-none');
};
initplot(qso_loc, customdata);
})
});
@ -806,6 +878,10 @@ if ($this->session->userdata('user_id') != null) {
success: function(data) {
document.getElementById('from').value = data;
document.getElementById('to').value = new Date().toISOString().split('T')[0];
// Update the date range display
if (typeof updateDateRangeDisplay === 'function') {
updateDateRangeDisplay();
}
},
error: function() {},
});
@ -1927,21 +2003,59 @@ if ($this->session->userdata('user_id') != null) {
<script>
$(document).ready(function() {
$('#btn_update_dxcc').bind('click', function() {
$('#dxcc_update_status').show();
// Show spinner and disable button
$('#dxcc_spinner').show();
$('#btn_text').text('Updating...');
$('#btn_update_dxcc').prop('disabled', true);
// Show initial status immediately
$('#dxcc_update_status').show().html('Starting update...<br/>');
$.ajax({
url: "update/dxcc"
});
setTimeout(update_stats, 5000);
// Start polling immediately, then every 2 seconds for more responsive updates
setTimeout(update_stats, 1000);
});
function update_stats() {
$('#dxcc_update_status').load('<?php echo base_url() ?>updates/status.html', function(val) {
$('#dxcc_update_staus').html(val);
if ((val === null) || (val.substring(0, 4) != "DONE")) {
setTimeout(update_stats, 5000);
}
});
console.log('Polling status at: ' + new Date().toLocaleTimeString());
$.get('<?php echo base_url() ?>index.php/update/get_status')
.done(function(response) {
console.log('Status response:', response);
// Always update the status display with the response
if (response && response.trim() !== '') {
$('#dxcc_update_status').html(response);
}
// Check if update is complete
if (response && typeof response === 'string' && response.substring(0, 4) === "DONE") {
console.log('Update completed!');
// Update is done - hide spinner and re-enable button
$('#dxcc_spinner').hide();
$('#btn_text').text('Update DXCC Data');
$('#btn_update_dxcc').prop('disabled', false);
} else {
// Continue polling if not done - reduced interval for better responsiveness
setTimeout(update_stats, 2000);
}
})
.fail(function(xhr, status, error) {
console.log('Error loading status: ' + status + ' - ' + error);
$('#dxcc_update_status').html('Error loading status... Retrying...<br/>');
// Reset button state on persistent error
if (xhr.status === 0 || xhr.status >= 500) {
$('#dxcc_spinner').hide();
$('#btn_text').text('Update DXCC Data');
$('#btn_update_dxcc').prop('disabled', false);
}
setTimeout(update_stats, 5000); // Retry in 5 seconds on error
});
}

查看文件

@ -200,8 +200,8 @@
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?php echo site_url('bandmap/list'); ?>" title="Bandmap"><i class="fa fa-bezier-curve"></i> <?php echo lang('menu_bandmap'); ?></a>
<?php } ?>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?php echo site_url('sattimers'); ?>" title="SAT Timers"><i class="fas fa-satellite"></i> SAT Timers</a>
<!-- <div class="dropdown-divider"></div> -->
<!-- a class="dropdown-item" href="<?php echo site_url('sattimers'); ?>" title="SAT Timers"><i class="fas fa-satellite"></i> SAT Timers</a> -->
</div>
</li>

查看文件

@ -1,87 +1,157 @@
<div class="container">
<div class="container logbooks-management">
<br>
<?php if($this->session->flashdata('message')) { ?>
<!-- Display Message -->
<div class="alert-message error">
<p><?php echo $this->session->flashdata('message'); ?></p>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle me-2"></i><?php echo $this->session->flashdata('message'); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php } ?>
<h2><?php echo $page_title; ?></h2>
<div class="card">
<div class="card-header">
<?php echo lang('station_logbooks_description_header')?>
</div>
<div class="card-body">
<p class="card-text"><?php echo lang('station_logbooks_description_text')?></p>
</div>
<!-- Page Header -->
<div class="row mb-4">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<h2><i class="fas fa-book me-2"></i><?php echo $page_title; ?></h2>
<p class="text-muted mb-0">Organize your QSOs into separate logbooks</p>
</div>
<a href="<?php echo site_url('logbooks/create'); ?>" class="btn btn-primary">
<i class="fas fa-plus me-2"></i><?php echo lang('station_logbooks_create')?></a>
</div>
</div>
</div>
<div class="card" style="margin-top: 20px;">
<div class="card-header">
<?php echo lang('station_logbooks')?> <a class="btn btn-primary float-end" href="<?php echo site_url('logbooks/create'); ?>"><i class="fas fa-plus"></i> <?php echo lang('station_logbooks_create')?></a>
</div>
<div class="card-body">
<div id="station_logbooks">
<?php if ($my_logbooks->num_rows() > 0) { ?>
<div class="table-responsive">
<table id="station_logbooks_table" class="table table-sm table-striped">
<thead>
<tr>
<th scope="col"><?php echo lang('general_word_name')?></th>
<th scope="col"><?php echo lang('station_logbooks_status')?></th>
<th scope="col"><?php echo lang('admin_edit')?></th>
<th scope="col"><?php echo lang('admin_delete')?></th>
<th scope="col"><?php echo lang('station_logbooks_link')?></th>
<th scope="col"><?php echo lang('station_logbooks_public_search')?></th>
</tr>
</thead>
<tbody>
<?php foreach ($my_logbooks->result() as $row) { ?>
<tr>
<td style="text-align: center; vertical-align: middle;">
<?php echo $row->logbook_name;?><br>
</td>
<td style="text-align: center; vertical-align: middle;">
<?php if($this->session->userdata('active_station_logbook') != $row->logbook_id) { ?>
<a href="<?php echo site_url('logbooks/set_active')."/".$row->logbook_id; ?>" class="btn btn-outline-primary btn-sm"><?php echo lang('station_logbooks_set_active')?></a>
<?php } else {
echo "<span class='badge text-bg-success'>" . lang('station_logbooks_active_logbook') . "</span>";
}?>
</td>
<td style="text-align: center; vertical-align: middle;">
<a href="<?php echo site_url('logbooks/edit')."/".$row->logbook_id; ?>" class="btn btn-outline-primary btn-sm"><i class="fas fa-edit" title="<?php echo lang('station_logbooks_edit_logbook') . ': ' . $row->logbook_name;?>"></i> </a>
</td>
<td style="text-align: center; vertical-align: middle;">
<?php if($this->session->userdata('active_station_logbook') != $row->logbook_id) { ?>
<a href="<?php echo site_url('Logbooks/delete')."/".$row->logbook_id; ?>" class="btn btn-danger btn-sm" onclick="return confirm('<?php echo lang('station_logbooks_confirm_delete') . $row->logbook_name; ?>');"><i class="fas fa-trash-alt"></i></a>
<?php } ?>
</td>
<td style="text-align: center; vertical-align: middle;">
<?php if($row->public_slug != '') { ?>
<a target="_blank" href="<?php echo site_url('visitor')."/".$row->public_slug; ?>" class="btn btn-outline-primary btn-sm" ><i class="fas fa-globe" title="<?php echo lang('station_logbooks_view_public') . $row->logbook_name;?>"></i> </a>
<?php } ?>
</td>
<td style="text-align: center; vertical-align: middle;">
<?php if ($row->public_search == 1) {
echo "<span class='badge text-bg-success'>" . lang('general_word_enabled') . "</span>";
} else {
echo "<span class='badge text-bg-dark'>" . lang('general_word_disabled') . "</span>";
} ?>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php } ?>
</div>
</div>
<!-- Information Card -->
<div class="row mb-4">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-info-circle me-2"></i><?php echo lang('station_logbooks_description_header')?></h5>
</div>
<div class="card-body">
<p class="mb-0"><?php echo lang('station_logbooks_description_text')?></p>
</div>
</div>
</div>
</div>
<!-- Station Logbooks Table -->
<?php if ($my_logbooks->num_rows() > 0) { ?>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-light">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="fas fa-list me-2"></i><?php echo lang('station_logbooks')?> (<?php echo $my_logbooks->num_rows(); ?>)</h5>
<small class="text-muted">Click to set a logbook as active</small>
</div>
</div>
<div class="card-body p-3">
<div class="table-responsive">
<table id="station_logbooks_table" class="table table-hover">
<thead class="table-light">
<tr>
<th scope="col"><i class="fas fa-tag me-1"></i><?php echo lang('general_word_name')?></th>
<th scope="col" class="text-center"><i class="fas fa-power-off me-1"></i><?php echo lang('station_logbooks_status')?></th>
<th scope="col" class="text-center"><i class="fas fa-globe me-1"></i><?php echo lang('station_logbooks_link')?></th>
<th scope="col" class="text-center"><i class="fas fa-search me-1"></i><?php echo lang('station_logbooks_public_search')?></th>
<th scope="col" class="text-center"><i class="fas fa-tools me-1"></i>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($my_logbooks->result() as $row) { ?>
<tr <?php if($this->session->userdata('active_station_logbook') == $row->logbook_id) { echo 'class="table-success"'; } ?>>
<td class="align-middle">
<div class="d-flex align-items-center">
<div>
<strong><?php echo $row->logbook_name;?></strong>
<?php if($this->session->userdata('active_station_logbook') == $row->logbook_id) { ?>
<span class="badge bg-success ms-2">
<i class="fas fa-check me-1"></i><?php echo lang('station_logbooks_active_logbook'); ?>
</span>
<?php } ?>
</div>
</div>
</td>
<td class="align-middle text-center">
<?php if($this->session->userdata('active_station_logbook') != $row->logbook_id) { ?>
<a href="<?php echo site_url('logbooks/set_active')."/".$row->logbook_id; ?>"
class="btn btn-outline-success btn-sm"
title="<?php echo lang('station_logbooks_set_active')?>">
<i class="fas fa-power-off me-1"></i><?php echo lang('station_logbooks_set_active')?>
</a>
<?php } else { ?>
<span class="badge bg-success">
<i class="fas fa-check me-1"></i><?php echo lang('station_logbooks_active_logbook'); ?>
</span>
<?php } ?>
</td>
<td class="align-middle text-center">
<?php if($row->public_slug != '') { ?>
<a target="_blank" href="<?php echo site_url('visitor')."/".$row->public_slug; ?>"
class="btn btn-info btn-sm"
title="<?php echo lang('station_logbooks_view_public') . $row->logbook_name;?>">
<i class="fas fa-globe me-1"></i>View Public
</a>
<?php } else { ?>
<span class="text-muted">-</span>
<?php } ?>
</td>
<td class="align-middle text-center">
<?php if ($row->public_search == 1) { ?>
<span class="badge bg-success">
<i class="fas fa-check me-1"></i><?php echo lang('general_word_enabled'); ?>
</span>
<?php } else { ?>
<span class="badge bg-secondary">
<i class="fas fa-times me-1"></i><?php echo lang('general_word_disabled'); ?>
</span>
<?php } ?>
</td>
<td class="align-middle text-center">
<div class="d-flex justify-content-center gap-1">
<a href="<?php echo site_url('logbooks/edit')."/".$row->logbook_id; ?>"
class="btn btn-primary btn-sm"
title="<?php echo lang('station_logbooks_edit_logbook') . ': ' . $row->logbook_name;?>">
<i class="fas fa-edit"></i>
</a>
<?php if($this->session->userdata('active_station_logbook') != $row->logbook_id) { ?>
<a href="<?php echo site_url('Logbooks/delete')."/".$row->logbook_id; ?>"
class="btn btn-danger btn-sm"
title="<?php echo lang('admin_delete')?>"
onclick="return confirm('<?php echo lang('station_logbooks_confirm_delete') . $row->logbook_name; ?>');">
<i class="fas fa-trash"></i>
</a>
<?php } ?>
</div>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php } else { ?>
<!-- Empty State -->
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-body text-center py-5">
<i class="fas fa-book text-muted mb-3" style="font-size: 3rem;"></i>
<h5 class="text-muted mb-3">No Logbooks</h5>
<p class="text-muted mb-4">You haven't created any logbooks yet. Create your first logbook to organize your QSOs.</p>
<a href="<?php echo site_url('logbooks/create'); ?>" class="btn btn-primary btn-lg">
<i class="fas fa-plus me-2"></i><?php echo lang('station_logbooks_create')?>
</a>
</div>
</div>
</div>
</div>
<?php } ?>
</div>

查看文件

@ -122,7 +122,11 @@
</div>
<div class="card-body">
<button class="btn btn-outline-success" hx-get="<?php echo site_url('lotw/lotw_upload'); ?>" hx-target="#lotw_manual_results">
<button id="manual-sync-btn" class="btn btn-outline-success"
hx-get="<?php echo site_url('lotw/lotw_upload'); ?>"
hx-target="#lotw_manual_results"
hx-indicator="#sync-spinner">
<span id="sync-spinner" class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true" style="display: none;"></span>
<?php echo lang('lotw_btn_manual_sync'); ?>
</button>
@ -131,3 +135,22 @@
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const syncBtn = document.getElementById('manual-sync-btn');
const spinner = document.getElementById('sync-spinner');
// Show spinner when htmx request starts
syncBtn.addEventListener('htmx:beforeRequest', function() {
spinner.style.display = 'inline-block';
syncBtn.disabled = true;
});
// Hide spinner when htmx request completes (success or error)
syncBtn.addEventListener('htmx:afterRequest', function() {
spinner.style.display = 'none';
syncBtn.disabled = false;
});
});
</script>

查看文件

@ -1,107 +1,557 @@
<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/map-enhancements.css">
<style>
.callsign-label-text {
background: rgba(0, 0, 0, 0.8) !important;
color: white !important;
font-family: Arial, sans-serif !important;
font-weight: bold !important;
padding: 1px 3px !important;
border-radius: 2px !important;
border: 1px solid #fff !important;
white-space: nowrap !important;
text-align: center !important;
box-shadow: 0 1px 2px rgba(0,0,0,0.5) !important;
display: inline-block !important;
line-height: 1 !important;
margin: 0 !important;
min-width: auto !important;
width: auto !important;
height: auto !important;
transform-origin: center !important;
font-size: 12px !important; /* Ensure consistent font size */
}
</style>
<div class="container custom-map-QSOs">
<br>
<h2><?php echo $logbook_name; echo strpos($logbook_name, 'logbook') ? '' : ' logbook'; ?> QSOs (Custom Dates)</h2>
<!-- Enhanced Header Section -->
<div class="row">
<div class="col-12">
<div class="card border-0 mb-4">
<div class="card-body rounded">
<div class="d-flex align-items-center justify-content-between">
<div>
<h2 class="mb-1">
<i class="fas fa-map-marked-alt me-2"></i>
<?php echo $logbook_name; echo strpos($logbook_name, 'logbook') ? '' : ' logbook'; ?> QSOs (Custom Dates)
</h2>
</div>
</div>
</div>
</div>
</div>
</div>
<?php if ($this->session->flashdata('notice')) { ?>
<div class="alert alert-info" role="alert">
<i class="fas fa-info-circle me-2"></i>
<?php echo $this->session->flashdata('notice'); ?>
</div>
<?php } ?>
<form method="post" action="<?php echo site_url('map/custom'); ?>">
<div class="row">
<div class="mb-3 col-md-3">
<label for="from"><?php echo lang('gen_from_date') . ": " ?></label>
<input name="from" id="from" type="date" class="form-control w-auto" value="<?php echo $date_from; ?>">
</div>
<div class="mb-3 col-md-3">
<label for="to"><?php echo lang('gen_to_date') . ": " ?></label>
<input name="to" id="to" type="date" class="form-control w-auto" value="<?php echo $date_to; ?>" max="<?php echo $date_to; ?>">
</div>
<div class="mb-3 col-md-3 d-flex align-items-end">
<input class="btn btn-secondary" type="button" value="<?php echo lang('set_log_to_full_dates'); ?>" onclick="get_oldest_qso_date();">
</div>
<!-- Filter Controls Section -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-filter me-2"></i>Filter Controls
</h5>
</div>
<div class="card-body">
<form method="post" action="<?php echo site_url('map/custom'); ?>">
<!-- Quick Action Buttons -->
<div class="row mb-4">
<div class="col-12">
<div class="card border-0">
<div class="card-body py-3">
<h6 class="card-title mb-3">
<i class="fas fa-bolt text-warning me-2"></i>Quick Filters
</h6>
<div class="row g-2">
<div class="col-6 col-md-2">
<button type="button" class="btn btn-outline-primary btn-sm w-100" onclick="setDateRange('today')">
<i class="fas fa-calendar-day me-1"></i>Today
</button>
</div>
<div class="col-6 col-md-2">
<button type="button" class="btn btn-outline-primary btn-sm w-100" onclick="setDateRange('week')">
<i class="fas fa-calendar-week me-1"></i>This Week
</button>
</div>
<div class="col-6 col-md-2">
<button type="button" class="btn btn-outline-primary btn-sm w-100" onclick="setDateRange('month')">
<i class="fas fa-calendar-alt me-1"></i>This Month
</button>
</div>
<div class="col-6 col-md-2">
<button type="button" class="btn btn-outline-primary btn-sm w-100" onclick="setDateRange('year')">
<i class="fas fa-calendar me-1"></i>This Year
</button>
</div>
<div class="col-6 col-md-2">
<button type="button" class="btn btn-outline-primary btn-sm w-100" onclick="get_oldest_qso_date()">
<i class="fas fa-history me-1"></i>All Time
</button>
</div>
<div class="col-6 col-md-2">
<button type="button" class="btn btn-outline-danger btn-sm w-100" onclick="clearFilters()">
<i class="fas fa-eraser me-1"></i>Reset
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="mb-3 col-md-3">
<label for="band">Band</label>
<select id="band2" name="band" class="form-select">
<option value="All" <?php if ($this->input->post('band') == "All" || $this->input->method() !== 'post') echo ' selected'; ?>>Every band</option>
<?php foreach ($worked_bands as $band) {
echo '<option value="' . $band . '"';
if ($this->input->post('band') == $band) echo ' selected';
echo '>' . $band . '</option>' . "\n";
} ?>
</select>
</div>
<!-- Date Range Section -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h6 class="card-title text-primary mb-3">
<i class="fas fa-calendar-range me-2"></i>Date Range
</h6>
<div class="row">
<div class="col-md-6">
<label for="from" class="form-label fw-semibold">
<i class="fas fa-play text-success me-1"></i><?php echo lang('gen_from_date') ?>
</label>
<input name="from" id="from" type="date" class="form-control" value="<?php echo $date_from; ?>">
</div>
<div class="col-md-6">
<label for="to" class="form-label fw-semibold">
<i class="fas fa-stop text-danger me-1"></i><?php echo lang('gen_to_date') ?>
</label>
<input name="to" id="to" type="date" class="form-control" value="<?php echo $date_to; ?>" max="<?php echo $date_to; ?>">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mb-3 col-md-3">
<label for="mode">Mode</label>
<select id="mode" name="mode" class="form-select">
<option value="All" <?php if ($this->input->post('mode') == "All" || $this->input->method() !== 'post') echo ' selected'; ?>><?php echo lang('general_word_all') ?></option>
<?php
foreach ($modes as $mode) {
if ($mode->submode ?? '' == '') {
echo '<option value="' . $mode . '">' . strtoupper($mode) . '</option>'."\n";
}
}
?>
</select>
</div>
<?php
// Sort translated propagation modes alphabetically
$prop_modes = ['AS' => lang('gen_hamradio_propagation_AS'),
'AUR' => lang('gen_hamradio_propagation_AUR'),
'AUE' => lang('gen_hamradio_propagation_AUE'),
'BS' => lang('gen_hamradio_propagation_BS'),
'ECH' => lang('gen_hamradio_propagation_ECH'),
'EME' => lang('gen_hamradio_propagation_EME'),
'ES' => lang('gen_hamradio_propagation_ES'),
'FAI' => lang('gen_hamradio_propagation_FAI'),
'F2' => lang('gen_hamradio_propagation_F2'),
'INTERNET' => lang('gen_hamradio_propagation_INTERNET'),
'ION' => lang('gen_hamradio_propagation_ION'),
'IRL' => lang('gen_hamradio_propagation_IRL'),
'MS' => lang('gen_hamradio_propagation_MS'),
'RPT' => lang('gen_hamradio_propagation_RPT'),
'RS' => lang('gen_hamradio_propagation_RS'),
'SAT' => lang('gen_hamradio_propagation_SAT'),
'TEP' => lang('gen_hamradio_propagation_TEP'),
'TR' => lang('gen_hamradio_propagation_TR')];
asort($prop_modes);
?>
<div class="mb-3 col-md-3">
<label for="selectPropagation">Propagation Mode</label>
<select class="form-select" id="selectPropagation" name="prop_mode">
<option value="All" <?php if ($this->input->post('prop_mode') == "All" || $this->input->method() !== 'post') echo ' selected'; ?>><?php echo lang('general_word_all') ?></option>
<option value="" <?php if ($this->input->post('prop_mode') == "" && $this->input->method() == 'post') echo ' selected'; ?>><?php echo lang('general_word_undefined') ?></option>
<?php
foreach($prop_modes as $key => $label) {
echo '<option value="' . $key . '">';
if ($this->input->post('prop_mode') == $key) echo ' selected';
echo $label . '</option>';
}
?>
</select>
</div>
<!-- Advanced Filters Section -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h6 class="card-title text-secondary mb-3">
<i class="fas fa-sliders-h me-2"></i>Advanced Filters
</h6>
<div class="row">
<div class="col-md-4 mb-3">
<label for="band2" class="form-label fw-semibold">
Band
</label>
<select id="band2" name="band" class="form-select">
<option value="All" <?php if ($this->input->post('band') == "All" || $this->input->method() !== 'post') echo ' selected'; ?>>
<i class="fas fa-asterisk"></i> Every band
</option>
<?php foreach ($worked_bands as $band) {
echo '<option value="' . $band . '"';
if ($this->input->post('band') == $band) echo ' selected';
echo '>' . $band . '</option>' . "\n";
} ?>
</select>
</div>
<div class="col-md-4 mb-3">
<label for="mode" class="form-label fw-semibold">
Mode
</label>
<select id="mode" name="mode" class="form-select">
<option value="All" <?php if ($this->input->post('mode') == "All" || $this->input->method() !== 'post') echo ' selected'; ?>>
<?php echo lang('general_word_all') ?>
</option>
<?php
foreach ($modes as $mode) {
if ($mode->submode ?? '' == '') {
echo '<option value="' . $mode . '">' . strtoupper($mode) . '</option>'."\n";
}
}
?>
</select>
</div>
<?php
// Sort translated propagation modes alphabetically
$prop_modes = ['AS' => lang('gen_hamradio_propagation_AS'),
'AUR' => lang('gen_hamradio_propagation_AUR'),
'AUE' => lang('gen_hamradio_propagation_AUE'),
'BS' => lang('gen_hamradio_propagation_BS'),
'ECH' => lang('gen_hamradio_propagation_ECH'),
'EME' => lang('gen_hamradio_propagation_EME'),
'ES' => lang('gen_hamradio_propagation_ES'),
'FAI' => lang('gen_hamradio_propagation_FAI'),
'F2' => lang('gen_hamradio_propagation_F2'),
'INTERNET' => lang('gen_hamradio_propagation_INTERNET'),
'ION' => lang('gen_hamradio_propagation_ION'),
'IRL' => lang('gen_hamradio_propagation_IRL'),
'MS' => lang('gen_hamradio_propagation_MS'),
'RPT' => lang('gen_hamradio_propagation_RPT'),
'RS' => lang('gen_hamradio_propagation_RS'),
'SAT' => lang('gen_hamradio_propagation_SAT'),
'TEP' => lang('gen_hamradio_propagation_TEP'),
'TR' => lang('gen_hamradio_propagation_TR')];
asort($prop_modes);
?>
<div class="col-md-4 mb-3">
<label for="selectPropagation" class="form-label fw-semibold">
Propagation Mode
</label>
<select class="form-select" id="selectPropagation" name="prop_mode">
<option value="All" <?php if ($this->input->post('prop_mode') == "All" || $this->input->method() !== 'post') echo ' selected'; ?>>
<?php echo lang('general_word_all') ?>
</option>
<option value="" <?php if ($this->input->post('prop_mode') == "" && $this->input->method() == 'post') echo ' selected'; ?>>
<?php echo lang('general_word_undefined') ?>
</option>
<?php
foreach($prop_modes as $key => $label) {
echo '<option value="' . $key . '"';
if ($this->input->post('prop_mode') == $key) echo ' selected';
echo '>' . $label . '</option>';
}
?>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Action Buttons and Status -->
<div class="row">
<div class="col-md-6">
<button class="btn btn-primary btn_submit_map_custom me-3" type="button">
<i class="fas fa-map-marker-alt me-2"></i>Load Map
<span class="spinner-border spinner-border-sm ms-2 d-none" id="load-spinner" role="status">
<span class="visually-hidden">Loading...</span>
</span>
</button>
<button class="btn btn-outline-info" type="button" onclick="exportMap()">
<i class="fas fa-download me-2"></i>Export Map
</button>
</div>
<div class="col-md-6">
<div class="alert alert-danger warningOnSubmit" style="display:none;">
<i class="fas fa-exclamation-triangle me-2"></i>
<span class="warningOnSubmit_txt">Error loading map data</span>
</div>
<div class="alert alert-success d-none" id="map-success-alert">
<i class="fas fa-check-circle me-2"></i>
<span id="qso-count-display">Map loaded successfully</span>
</div>
</div>
</div>
</form>
</div>
<div class="row">
<div class="mb-2 col-md-2">
<input class="btn btn-primary btn_submit_map_custom" type="button" value="Load Map">
</div>
<div class="mb-4 col-md-4">
<div class="alert alert-danger warningOnSubmit" style="display:none;"><span><i class="fas fa-times-circle"></i></span> <span class="warningOnSubmit_txt ms-1">Error</span></div>
</div>
</div>
</form>
</div>
</div>
<!-- Map -->
<div id="custommap" class="map-leaflet mt-2" style="width: 100%; height: 1000px;"></div>
<!-- Map Container -->
<div class="card">
<div class="card-header py-3">
<div class="row align-items-center">
<div class="col-md-4">
<h5 class="mb-0">
<i class="fas fa-globe-americas me-2"></i>World Map View
</h5>
<small class="text-muted">Showing: <strong id="active-logbook-display"><?php echo $logbook_name ?></strong></small>
</div>
<div class="col-md-4">
<div class="d-flex align-items-center justify-content-center">
<div class="me-4">
<small class="text-muted">QSOs Displayed:</small>
<span class="badge bg-primary ms-1" id="qso-count">0</span>
</div>
<div class="alert alert-success mb-0 py-1 px-2 d-none" id="map-status">
<small><i class="fas fa-check-circle me-1"></i>Map loaded successfully</small>
</div>
</div>
</div>
<div class="col-md-4 text-end">
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-outline-success" onclick="fitMapToMarkers()" title="Zoom to fit all QSOs">
<i class="fas fa-expand-arrows-alt"></i>
</button>
<button type="button" class="btn btn-outline-success" onclick="toggleGridSquares()" title="Toggle grid squares">
<i class="fas fa-th"></i>
</button>
<button type="button" class="btn btn-outline-success" onclick="toggleCallsignLabels()" title="Toggle callsign labels" id="callsign-labels-btn">
<i class="fas fa-tags"></i>
</button>
<button type="button" class="btn btn-outline-success" onclick="toggleFullscreen()" title="Toggle fullscreen">
<i class="fas fa-expand"></i>
</button>
</div>
</div>
</div>
</div>
<div class="card-body p-0">
<div id="custommap" class="map-leaflet" style="width: 100%; height: 1000px;"></div>
</div>
</div>
<div class="alert alert-success" role="alert">Showing QSOs for Custom Dates for Active Logbook <?php echo $logbook_name ?></div>
<!-- Map Legend -->
<div class="row mt-3">
<div class="col-12">
<div class="card border-0">
<div class="card-body py-2">
<div class="row align-items-center">
<div class="col-md-6">
<small class="text-muted fw-semibold me-3">Legend:</small>
<span class="me-3">
<i class="fas fa-dot-circle text-danger me-1"></i>
<small>QSO</small>
</span>
<span class="me-3">
<i class="fas fa-home text-primary me-1"></i>
<small>Home Station</small>
</span>
</div>
<div class="col-md-6 text-end">
<small class="text-muted">
<i class="fas fa-info-circle me-1"></i>
Click on markers for QSO details Use
<i class="fas fa-tags"></i> to toggle callsign labels
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Enhanced JavaScript functionality for improved UX
document.addEventListener('DOMContentLoaded', function() {
// Add event listeners for date changes - removed updateDateRangeDisplay calls
// since the date-range-display element doesn't exist
});
function setDateRange(period) {
const today = new Date();
const fromInput = document.getElementById('from');
const toInput = document.getElementById('to');
let fromDate, toDate = today;
switch (period) {
case 'today':
fromDate = new Date(today);
break;
case 'week':
fromDate = new Date(today);
fromDate.setDate(today.getDate() - 7);
break;
case 'month':
// Create first day of current month
fromDate = new Date(today.getFullYear(), today.getMonth(), 1);
console.log('Month calculation - Today:', today.toDateString(), 'First of month:', fromDate.toDateString());
break;
case 'year':
fromDate = new Date(today.getFullYear(), 0, 1);
break;
}
if (fromDate) {
// Use local date formatting to avoid timezone issues
const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
fromInput.value = formatDate(fromDate);
toInput.value = formatDate(today);
}
}
function clearFilters() {
document.getElementById('band2').selectedIndex = 0;
document.getElementById('mode').selectedIndex = 0;
document.getElementById('selectPropagation').selectedIndex = 0;
const today = new Date().toISOString().split('T')[0];
document.getElementById('from').value = today;
document.getElementById('to').value = today;
}
function exportMap() {
// This would integrate with the existing Leaflet print functionality
if (typeof map !== 'undefined' && map._container) {
// Trigger the existing print functionality
alert('Map export functionality - integrate with existing Leaflet print plugin');
}
}
function fitMapToMarkers() {
if (typeof map !== 'undefined' && plotlayers.length > 0) {
const group = new L.featureGroup(plotlayers);
map.fitBounds(group.getBounds().pad(0.1));
}
}
function toggleGridSquares() {
if (typeof maidenhead !== 'undefined') {
if (map.hasLayer(maidenhead)) {
map.removeLayer(maidenhead);
} else {
map.addLayer(maidenhead);
}
}
}
function toggleFullscreen() {
const mapContainer = document.getElementById('custommap');
if (!document.fullscreenElement) {
mapContainer.requestFullscreen().then(() => {
mapContainer.style.height = '100vh';
if (typeof map !== 'undefined') {
map.invalidateSize();
}
});
} else {
document.exitFullscreen().then(() => {
mapContainer.style.height = '1000px'; // Restore original height
if (typeof map !== 'undefined') {
map.invalidateSize();
}
});
}
}
// Global variable to track if callsign labels are shown
let callsignLabelsVisible = false;
let callsignLabels = [];
function clearCallsignLabels() {
// Remove all existing callsign labels from the map
callsignLabels.forEach(function(labelMarker) {
if (map.hasLayer(labelMarker)) {
map.removeLayer(labelMarker);
}
});
// Clear the labels array
callsignLabels = [];
// Reset the visibility state
callsignLabelsVisible = false;
// Reset button appearance
const button = document.getElementById('callsign-labels-btn');
if (button) {
button.classList.remove('btn-success');
button.classList.add('btn-outline-success');
}
}
function toggleCallsignLabels() {
const button = document.getElementById('callsign-labels-btn');
if (!callsignLabelsVisible) {
// Show callsign labels
if (typeof plotlayers !== 'undefined' && plotlayers.length > 0) {
plotlayers.forEach(function(marker) {
if (marker.data && marker.data.callsign) {
// Only create labels when callsign is specifically provided
const callsign = marker.data.callsign;
// Try a different approach - create a DivIcon instead of tooltip
const labelIcon = L.divIcon({
className: 'callsign-label-icon',
html: `<div class="callsign-label-text">${callsign}</div>`,
iconSize: [0, 0], // No fixed size, let CSS handle it
iconAnchor: [0, -5] // Position above the marker with minimal offset
});
// Create a separate marker for the label
const labelMarker = L.marker(marker.getLatLng(), {
icon: labelIcon,
interactive: false,
zIndexOffset: 1000
});
map.addLayer(labelMarker);
callsignLabels.push(labelMarker);
}
});
callsignLabelsVisible = true;
button.classList.remove('btn-outline-success');
button.classList.add('btn-success');
}
} else {
// Hide callsign labels
callsignLabels.forEach(function(labelMarker) {
map.removeLayer(labelMarker);
});
callsignLabels = [];
callsignLabelsVisible = false;
button.classList.remove('btn-success');
button.classList.add('btn-outline-success');
}
}
// Update statistics when map loads
function updateMapStatistics(plotjson) {
if (plotjson && plotjson.markers) {
const qsoCountElement = document.getElementById('qso-count');
if (qsoCountElement) {
qsoCountElement.textContent = plotjson.markers.length;
}
// Count unique countries and gridsquares
const countries = new Set();
const gridsquares = new Set();
plotjson.markers.forEach(marker => {
if (marker.country) countries.add(marker.country);
if (marker.gridsquare) gridsquares.add(marker.gridsquare);
});
// Only update if elements exist
const countryCountElement = document.getElementById('country-count');
if (countryCountElement) {
countryCountElement.textContent = countries.size;
}
const gridCountElement = document.getElementById('grid-count');
if (gridCountElement) {
gridCountElement.textContent = gridsquares.size;
}
// Show success message
const mapStatusElement = document.getElementById('map-status');
if (mapStatusElement) {
mapStatusElement.classList.remove('d-none');
setTimeout(() => {
mapStatusElement.classList.add('d-none');
}, 3000);
}
// Handle callsign labels when new data is loaded
const labelsWereVisible = callsignLabelsVisible;
// Clear any existing callsign labels before processing new data
clearCallsignLabels();
// Reapply callsign labels if they were previously enabled
if (labelsWereVisible) {
setTimeout(() => {
toggleCallsignLabels();
}, 100); // Small delay to ensure markers are fully rendered
}
}
}
</script>

查看文件

@ -1,99 +1,228 @@
<div class="container">
<div class="container station-management">
<br>
<?php if($this->session->flashdata('message')) { ?>
<!-- Display Message -->
<div class="alert-message error">
<p><?php echo $this->session->flashdata('message'); ?></p>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<i class="fas fa-check-circle me-2"></i><?php echo $this->session->flashdata('message'); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<?php } ?>
<div class="d-flex justify-content-between align-items-center mb-3">
<h2><?php echo $page_title; ?></h2>
<a href="<?php echo site_url('station/create'); ?>" class="btn btn-primary"><i class="fas fa-plus"></i> <?php echo lang('station_location_create'); ?></a>
<!-- Page Header -->
<div class="row mb-4">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center">
<div>
<h2><i class="fas fa-tower-broadcast me-2"></i><?php echo $page_title; ?></h2>
<p class="text-muted mb-0">Manage your station locations.</p>
</div>
<a href="<?php echo site_url('station/create'); ?>" class="btn btn-primary">
<i class="fas fa-plus me-2"></i><?php echo lang('station_location_create'); ?>
</a>
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<p class="card-text"><?php echo lang('station_location_header_ln1'); ?></p>
<p class="card-text"><?php echo lang('station_location_header_ln2'); ?></p>
<p class="card-text"><?php echo lang('station_location_header_ln3'); ?></p>
<?php if ($stations->num_rows() > 0) { ?>
<?php if($current_active == 0) { ?>
<div class="alert alert-danger" role="alert">
<?php echo lang('station_location_warning'); ?>
</div>
<?php } ?>
<?php if (($is_there_qsos_with_no_station_id >= 1) && ($is_admin)) { ?>
<div class="alert alert-danger" role="alert">
<span class="badge rounded-pill text-bg-warning"><?php echo lang('general_word_warning'); ?></span> <?php echo lang('station_location_warning_reassign'); ?> <br>
<?php echo lang('station_location_reassign_at'); ?> <a href="<?php echo site_url('maintenance/'); ?>" class="btn btn-warning"><i class="fas fa-sync"></i><?php echo lang('account_word_admin') . "/" . lang('general_word_maintenance'); ?></a>
</div>
<?php } ?>
<div class="table-responsive">
<table id="station_locations_table" class="table table-sm table-striped">
<thead>
<tr>
<th scope="col"><?php echo lang('station_location_name'); ?></th>
<th scope="col"><?php echo lang('station_location_callsign'); ?></th>
<th scope="col"><?php echo lang('general_word_country'); ?></th>
<th scope="col"><?php echo lang('gen_hamradio_gridsquare'); ?></th>
<th></th>
<th scope="col"><?php echo lang('admin_edit'); ?></th>
<th scope="col"><?php echo lang('admin_copy'); ?></th>
<th scope="col"><?php echo lang('station_location_emptylog'); ?></th>
<th scope="col"><?php echo lang('admin_delete'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($stations->result() as $row) { ?>
<tr>
<td style="text-align: center; vertical-align: middle;">
<?php echo $row->station_profile_name;?><br>
</td>
<td style="text-align: center; vertical-align: middle;"><?php echo $row->station_callsign;?></td>
<td style="text-align: center; vertical-align: middle;"><?php echo $row->station_country == '' ? '- NONE -' : $row->station_country; if ($row->dxcc_end != NULL) { echo ' <span class="badge text-bg-danger">'.lang('gen_hamradio_deleted_dxcc').'</span>'; } ?></td>
<td style="text-align: center; vertical-align: middle;"><?php echo $row->station_gridsquare;?></td>
<td style="text-align: center" data-order="<?php echo $row->station_id;?>">
<?php if($row->station_active != 1) { ?>
<a href="<?php echo site_url('station/set_active/').$current_active."/".$row->station_id; ?>" class="btn btn-outline-secondary btn-sm" onclick="return confirm('<?php echo lang('station_location_confirm_active'); ?> <?php echo $row->station_profile_name; ?>');"><?php echo lang('station_location_set_active'); ?></a>
<?php } else { ?>
<span class="badge text-bg-success"><?php echo lang('station_location_active'); ?></span>
<?php } ?>
<br>
<span class="badge text-bg-info">ID: <?php echo $row->station_id;?></span>
<span class="badge text-bg-light"><?php echo $row->qso_total;?> <?php echo lang('gen_hamradio_qso'); ?></span>
</td>
<td style="text-align: center; vertical-align: middle;">
<?php if($row->user_id == "") { ?>
<a href="<?php echo site_url('station/claim_user')."/".$row->station_id; ?>" class="btn btn-outline-primary btn-sm"><i class="fas fa-user-plus"></i> <?php echo lang('station_location_claim_ownership'); ?></a>
<?php } ?>
<a href="<?php echo site_url('station/edit')."/".$row->station_id; ?>" title="<?php echo lang('admin_edit'); ?>" class="btn btn-outline-primary btn-sm"><i class="fas fa-edit"></i></a>
</td>
<td style="text-align: center; vertical-align: middle;">
<a href="<?php echo site_url('station/copy')."/".$row->station_id; ?>" title="<?php echo lang('admin_copy'); ?>" class="btn btn-outline-primary btn-sm"><i class="fas fa-copy"></i></a>
</td>
<td style="text-align: center; vertical-align: middle;"> <a href="<?php echo site_url('station/deletelog')."/".$row->station_id; ?>" class="btn btn-danger btn-sm" title="<?php echo lang('station_location_emptylog'); ?>" onclick="return confirm('<?php echo lang('station_location_confirm_del_qso'); ?>');"><i class="fas fa-trash-alt"></i></a>
</td>
<td style="text-align: center; vertical-align: middle;">
<?php if($row->station_active != 1) { ?>
<a href="<?php echo site_url('station/delete')."/".$row->station_id; ?>" class="btn btn-danger btn-sm" title="<?php echo lang('admin_delete'); ?>" onclick="return confirm('<?php echo lang('station_location_confirm_del_stationlocation'); ?> <?php echo $row->station_profile_name; ?> <?php echo lang('station_location_confirm_del_stationlocation_qso'); ?>');"><i class="fas fa-trash-alt"></i></a>
<?php } ?>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php } ?>
</div>
<!-- Information Card -->
<div class="row mb-4">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-info-circle me-2"></i>About Station Locations</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="d-flex align-items-start mb-3">
<i class="fas fa-map-marker-alt text-primary me-2 mt-1"></i>
<div>
<h6 class="mb-1">Location Management</h6>
<p class="mb-0 text-muted small"><?php echo lang('station_location_header_ln1'); ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-start mb-3">
<i class="fas fa-radio text-primary me-2 mt-1"></i>
<div>
<h6 class="mb-1">Multiple Locations</h6>
<p class="mb-0 text-muted small"><?php echo lang('station_location_header_ln2'); ?></p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="d-flex align-items-start mb-3">
<i class="fas fa-cog text-primary me-2 mt-1"></i>
<div>
<h6 class="mb-1">Active Location</h6>
<p class="mb-0 text-muted small"><?php echo lang('station_location_header_ln3'); ?></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Warnings Section -->
<?php if($current_active == 0 || (($is_there_qsos_with_no_station_id >= 1) && ($is_admin))) { ?>
<div class="row mb-4">
<div class="col-md-12">
<?php if($current_active == 0) { ?>
<div class="alert alert-danger d-flex align-items-center" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<div><?php echo lang('station_location_warning'); ?></div>
</div>
<?php } ?>
<?php if (($is_there_qsos_with_no_station_id >= 1) && ($is_admin)) { ?>
<div class="alert alert-warning d-flex align-items-center" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i>
<div>
<strong><?php echo lang('general_word_warning'); ?>:</strong> <?php echo lang('station_location_warning_reassign'); ?><br>
<div class="mt-2">
<?php echo lang('station_location_reassign_at'); ?>
<a href="<?php echo site_url('maintenance/'); ?>" class="btn btn-warning btn-sm">
<i class="fas fa-sync me-1"></i><?php echo lang('account_word_admin') . "/" . lang('general_word_maintenance'); ?>
</a>
</div>
</div>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
<!-- Station Locations Table -->
<?php if ($stations->num_rows() > 0) { ?>
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header bg-light">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="fas fa-list me-2"></i>Station Locations (<?php echo $stations->num_rows(); ?>)</h5>
<small class="text-muted">Click on a station name to set it as active</small>
</div>
</div>
<div class="card-body p-3">
<div class="table-responsive">
<table id="station_locations_table" class="table table-hover">
<thead class="table-light">
<tr>
<th scope="col"><i class="fas fa-tag me-1"></i><?php echo lang('station_location_name'); ?></th>
<th scope="col"><i class="fas fa-microphone me-1"></i><?php echo lang('station_location_callsign'); ?></th>
<th scope="col"><i class="fas fa-flag me-1"></i><?php echo lang('general_word_country'); ?></th>
<th scope="col"><i class="fas fa-th me-1"></i><?php echo lang('gen_hamradio_gridsquare'); ?></th>
<th scope="col" class="text-center"><i class="fas fa-power-off me-1"></i>Status</th>
<th scope="col" class="text-center"><i class="fas fa-tools me-1"></i>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($stations->result() as $row) { ?>
<tr <?php if($row->station_active == 1) { echo 'class="table-success"'; } ?>>
<td class="align-middle">
<div class="d-flex align-items-center">
<div>
<strong><?php echo $row->station_profile_name;?></strong>
<?php if($row->station_active == 1) { ?>
<span class="badge bg-success ms-2">
<i class="fas fa-check me-1"></i><?php echo lang('station_location_active'); ?>
</span>
<?php } ?>
<br>
<small class="text-muted">
ID: <?php echo $row->station_id;?>
<?php echo $row->qso_total;?> <?php echo lang('gen_hamradio_qso'); ?>
</small>
</div>
</div>
</td>
<td class="align-middle">
<strong><?php echo $row->station_callsign;?></strong>
</td>
<td class="align-middle">
<?php echo $row->station_country == '' ? '<span class="text-muted">- NONE -</span>' : $row->station_country; ?>
<?php if ($row->dxcc_end != NULL) { echo '<br><span class="badge bg-danger">'.lang('gen_hamradio_deleted_dxcc').'</span>'; } ?>
</td>
<td class="align-middle">
<?php echo $row->station_gridsquare == '' ? '<span class="text-muted">-</span>' : $row->station_gridsquare;?>
</td>
<td class="align-middle text-center" data-order="<?php echo $row->station_id;?>">
<?php if($row->station_active != 1) { ?>
<a href="<?php echo site_url('station/set_active/').$current_active."/".$row->station_id; ?>"
class="btn btn-outline-success btn-sm"
onclick="return confirm('<?php echo lang('station_location_confirm_active'); ?> <?php echo $row->station_profile_name; ?>');",
title="<?php echo lang('station_location_set_active'); ?>">
<i class="fas fa-power-off me-1"></i><?php echo lang('station_location_set_active'); ?>
</a>
<?php } else { ?>
<span class="badge bg-success">
<i class="fas fa-check me-1"></i><?php echo lang('station_location_active'); ?>
</span>
<?php } ?>
</td>
<td class="align-middle text-center">
<div class="d-flex justify-content-center gap-1">
<?php if($row->user_id == "") { ?>
<a href="<?php echo site_url('station/claim_user')."/".$row->station_id; ?>"
class="btn btn-info btn-sm"
title="<?php echo lang('station_location_claim_ownership'); ?>">
<i class="fas fa-user-plus"></i>
</a>
<?php } ?>
<a href="<?php echo site_url('station/edit')."/".$row->station_id; ?>"
title="<?php echo lang('admin_edit'); ?>"
class="btn btn-primary btn-sm">
<i class="fas fa-edit"></i>
</a>
<a href="<?php echo site_url('station/copy')."/".$row->station_id; ?>"
title="<?php echo lang('admin_copy'); ?>"
class="btn btn-secondary btn-sm">
<i class="fas fa-copy"></i>
</a>
<a href="<?php echo site_url('station/deletelog')."/".$row->station_id; ?>"
class="btn btn-warning btn-sm"
title="<?php echo lang('station_location_emptylog'); ?>"
onclick="return confirm('<?php echo lang('station_location_confirm_del_qso'); ?>');">
<i class="fas fa-broom"></i>
</a>
<?php if($row->station_active != 1) { ?>
<a href="<?php echo site_url('station/delete')."/".$row->station_id; ?>"
class="btn btn-danger btn-sm"
title="<?php echo lang('admin_delete'); ?>"
onclick="return confirm('<?php echo lang('station_location_confirm_del_stationlocation'); ?> <?php echo $row->station_profile_name; ?> <?php echo lang('station_location_confirm_del_stationlocation_qso'); ?>');">
<i class="fas fa-trash"></i>
</a>
<?php } ?>
</div>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php } else { ?>
<!-- Empty State -->
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-body text-center py-5">
<i class="fas fa-tower-broadcast text-muted mb-3" style="font-size: 3rem;"></i>
<h5 class="text-muted mb-3">No Station Locations</h5>
<p class="text-muted mb-4">You haven't created any station locations yet. Create your first station location to get started.</p>
<a href="<?php echo site_url('station/create'); ?>" class="btn btn-primary btn-lg">
<i class="fas fa-plus me-2"></i><?php echo lang('station_location_create'); ?>
</a>
</div>
</div>
</div>
</div>
<?php } ?>
</div>

查看文件

@ -17,7 +17,7 @@ if ($qsoarray) {
foreach ($value as $key => $val) {
echo '<td>' . $val . '</td>';
}
echo '<th>' . $modetotal[$mode] . '</th>';
echo '<th>' . (isset($modetotal[$mode]) ? $modetotal[$mode] : 0) . '</th>';
echo '</tr>';
}
echo '</tbody><tfoot><tr><th>'.lang('statistics_total').'</th>';

查看文件

@ -17,13 +17,13 @@ if ($qsoarray) {
foreach ($value as $key => $val) {
echo '<td>' . $val . '</td>';
}
echo '<th>' . $modeunique[$mode] . '</th>';
echo '<th>' . (isset($modeunique[$mode]) ? $modeunique[$mode] : 0) . '</th>';
echo '</tr>';
}
echo '</tbody><tfoot><tr><th>'.lang('statistics_total').'</th>';
foreach($bands as $band) {
echo '<th>' . $bandunique[$band] . '</th>';
echo '<th>' . (isset($bandunique[$band]) ? $bandunique[$band] : 0) . '</th>';
}
echo '<th>' . $total->calls . '</th>';
echo '</tr></tfoot></table>';

查看文件

@ -25,9 +25,14 @@
</div>
<?php } else { ?>
<h5>Check for DXCC Data Updates</h5>
<input type="submit" class="btn btn-primary" id="btn_update_dxcc" value="Update DXCC Data" />
<button type="button" class="btn btn-primary" id="btn_update_dxcc">
<span class="spinner-border spinner-border-sm me-2" id="dxcc_spinner" role="status" aria-hidden="true" style="display: none;"></span>
<span id="btn_text">Update DXCC Data</span>
</button>
<div id="dxcc_update_status">Status:</br></div>
<div id="dxcc_update_status" class="mt-3 p-2 border rounded bg-light" style="min-height: 60px;">
<strong>Status:</strong> Ready to update
</div>
<br />
<br />
@ -45,11 +50,6 @@
This function can be used to update QSO continent information for all QSOs in Cloudlog missing that information.
</p>
<p><a class="btn btn-primary" href="<?php echo site_url('update/check_missing_continent'); ?>">Check QSOs missing continent data</a></p>
<style>
#dxcc_update_status {
display: None;
}
</style>
<?php } ?>
</div>
<div class="tab-pane fade" id="distance" role="tabpanel" aria-labelledby="distance-tab">

查看文件

@ -9,39 +9,7 @@
<script src="<?php echo base_url('assets/js/showdown.min.js'); ?>"></script>
<script>
function convertMarkdownToHTML() {
// Check if the required elements exist
var markdownDiv = document.getElementById('markdownDiv');
var formattedHTMLDiv = document.getElementById('formattedHTMLDiv');
if (!markdownDiv || !formattedHTMLDiv) {
console.warn('Required elements for markdown conversion not found');
return;
}
// Get the Markdown content from the div
var markdownContent = markdownDiv.innerText;
// Create a new Showdown Converter with simplifiedAutoLink option enabled
var converter = new showdown.Converter({
simplifiedAutoLink: true
});
// Convert Markdown to HTML
var html = converter.makeHtml(markdownContent);
// Set the HTML content of the div
formattedHTMLDiv.innerHTML = html;
}
// Only call the function if the DOM is ready and elements exist
document.addEventListener('DOMContentLoaded', function() {
convertMarkdownToHTML();
});
</script>
<?php
<?php
$versionDialogMode = isset($this->optionslib) ? $this->optionslib->get_option('version_dialog') : 'release_notes';
if ($versionDialogMode == 'custom_text' || $versionDialogMode == 'both') {
?>
@ -75,22 +43,72 @@
$data = json_decode($response, true);
$current_version = $this->optionslib->get_option('version');
if ($data !== null && !empty($data)) {
$firstRelease = null;
foreach ($data as $singledata) {
if ($singledata['tag_name'] == $current_version) {
$firstRelease = $singledata;
continue;
break;
}
}
$releaseBody = isset($firstRelease['body']) ? $firstRelease['body'] : 'No release information available';
$htmlReleaseBody = htmlspecialchars($releaseBody);
$htmlReleaseBodyWithLinks = preg_replace('/(https?:\/\/[^\s<]+)/', '<a href="$1" target="_blank">$1</a>', $htmlReleaseBody);
if ($firstRelease !== null) {
$releaseBody = isset($firstRelease['body']) ? $firstRelease['body'] : 'No release information available';
$releaseName = isset($firstRelease['name']) ? $firstRelease['name'] : 'No version name information available';
echo "<h4>v" . $releaseName . "</h4>";
echo "<div id='markdownDiv' style='display: none;'>" . $releaseBody . "</div>";
echo "<div id='formattedHTMLDiv'></div>";
$releaseName = isset($firstRelease['name']) ? $firstRelease['name'] : 'No version name information available';
echo "<h4>v" . $releaseName . "</h4>";
// Convert markdown to HTML using PHP
$htmlContent = $releaseBody;
// Escape HTML first to prevent issues
$htmlContent = htmlspecialchars($htmlContent);
// Convert headers
$htmlContent = preg_replace('/^## (.+)$/m', '<h3>$1</h3>', $htmlContent);
$htmlContent = preg_replace('/^# (.+)$/m', '<h2>$1</h2>', $htmlContent);
// Convert bullet points to list items
// First, find all bullet point sections and convert them to proper lists
$htmlContent = preg_replace_callback(
'/(?:^[ ]*\* .+(?:\r?\n|$))+/m',
function($matches) {
$listContent = $matches[0];
// Convert each bullet point to <li>, removing any trailing newlines
$listContent = preg_replace('/^[ ]*\* (.+?)(?:\r?\n|$)/m', '<li>$1</li>', $listContent);
// Wrap in <ul> tags
return '<ul>' . trim($listContent) . '</ul>';
},
$htmlContent
);
// Convert links (markdown style)
$htmlContent = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2" target="_blank">$1</a>', $htmlContent);
// Convert plain URLs to links
$htmlContent = preg_replace('/(https?:\/\/[^\s<]+)/', '<a href="$1" target="_blank">$1</a>', $htmlContent);
// Convert GitHub usernames (@username) to profile links
$htmlContent = preg_replace('/@([a-zA-Z0-9_-]+)/', '<a href="https://github.com/$1" target="_blank">@$1</a>', $htmlContent);
// Convert bold text
$htmlContent = preg_replace('/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $htmlContent);
// Convert line breaks to <br> tags
$htmlContent = nl2br($htmlContent);
// Clean up: remove <br> tags that appear right before or after list tags
$htmlContent = preg_replace('/<br\s*\/?>\s*(<\/?ul>)/', '$1', $htmlContent);
$htmlContent = preg_replace('/(<\/?ul>)\s*<br\s*\/?>/', '$1', $htmlContent);
$htmlContent = preg_replace('/<br\s*\/?>\s*(<\/?li>)/', '$1', $htmlContent);
$htmlContent = preg_replace('/(<\/?li>)\s*<br\s*\/?>/', '$1', $htmlContent);
echo "<div class='release-notes mt-3'>" . $htmlContent . "</div>";
} else {
echo '<h4>v' . $current_version . '</h4>';
echo '<p>No release information found for this version on GitHub.</p>';
}
} else {
echo 'Error decoding JSON data or received empty response.';
}

查看文件

@ -49,6 +49,36 @@ foreach ($grouped as $month => $dxccs) {
echo '<span class="badge bg-primary">Confirmed</span>';
}
// Add satellite badge if worked via satellite
if (isset($dxcc['workedViaSatellite']) && $dxcc['workedViaSatellite']) {
echo ' <span class="badge bg-info">Worked via Satellite</span>';
}
// IOTA handling: show badge if JSON contained an iota field
if (isset($dxcc['iota']) && !empty($dxcc['iota'])) {
$iotaTag = $dxcc['iota'];
$mapUrl = 'https://www.iota-world.org/iotamaps/?grpref=' . $iotaTag;
// Anchor inside badge should inherit readable text colour
$iotaAnchor = '<a href="' . $mapUrl . '" target="_blank" class="text-white text-decoration-none">' . $iotaTag . '</a>';
if (isset($dxcc['iota_status'])) {
$s = $dxcc['iota_status'];
if (!empty($s) && isset($s['worked']) && $s['worked']) {
echo ' <span class="badge bg-success">IOTA ' . $iotaAnchor . ' Worked</span>';
} else {
echo ' <span class="badge bg-danger">IOTA ' . $iotaAnchor . ' Needed</span>';
}
if (!empty($s) && isset($s['confirmed']) && $s['confirmed']) {
echo ' <span class="badge bg-primary">Confirmed</span>';
}
} else {
// No status; render a neutral badge containing the link
echo ' <span class="badge bg-secondary">' . $iotaAnchor . '</span>';
}
}
echo '</td>
<td>' . $dxcc['notes'] . '</td>
</tr>';

查看文件

@ -1,5 +1,16 @@
<div class="container">
<h2><?php echo $page_title; ?></h2>
<div id="dxcclist_display" hx-get="<?php echo site_url('workabledxcc/dxcclist'); ?>" hx-trigger="load"></div>
<div id="dxcclist_display" hx-get="<?php echo site_url('workabledxcc/dxcclist'); ?>" hx-trigger="load">
<!-- Loading spinner and message shown while HTMX loads content -->
<div class="d-flex justify-content-center align-items-center py-5">
<div class="text-center">
<div class="spinner-border text-primary mb-3" role="status" style="width: 3rem; height: 3rem;">
<span class="visually-hidden">Loading...</span>
</div>
<h5 class="text-muted">Generating Table</h5>
<p class="text-muted">Processing DXPedition data and checking your logbook...</p>
</div>
</div>
</div>
</div>

查看文件

@ -74,6 +74,200 @@ thead>tr>td {
margin-bottom: 0rem;
}
.adif .card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.adif .card-body {
padding: 1.5rem;
}
.adif .form-check {
padding-left: 0;
}
.adif .form-check-input {
margin-right: 0.5rem;
}
.adif .form-check-label {
font-size: 0.9rem;
line-height: 1.4;
}
.adif .badge {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
}
.adif .btn {
border-radius: 0.375rem;
font-weight: 500;
padding: 0.5rem 1rem;
}
.adif .btn-lg {
padding: 0.75rem 1.5rem;
font-size: 1.1rem;
}
.adif .form-control,
.adif .form-select {
border-radius: 0.375rem;
border-color: #ced4da;
}
.adif .form-control:focus,
.adif .form-select:focus {
border-color: #86b7fe;
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}
.adif .card .card-header h6 {
margin-bottom: 0;
font-weight: 600;
color: #495057;
}
.adif .alert {
border-radius: 0.5rem;
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
/* Station Management Page Styling */
.station-management {
padding-top: 15px;
}
.station-management .table-success {
background-color: rgba(25, 135, 84, 0.1);
}
.station-management .card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.station-management .card-header {
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.25rem;
}
.station-management .badge {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
}
.station-management .btn-sm {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
border-radius: 0.375rem;
}
/* DataTable styling within cards */
.station-management .dataTables_wrapper {
padding: 0;
}
.station-management .dataTables_length,
.station-management .dataTables_filter {
margin-bottom: 1rem;
}
.station-management .dataTables_info,
.station-management .dataTables_paginate {
margin-top: 1rem;
}
.station-management .dataTables_length label,
.station-management .dataTables_filter label {
font-weight: 500;
margin-bottom: 0;
}
.station-management .dataTables_filter input {
margin-left: 0.5rem;
border-radius: 0.375rem;
border: 1px solid #ced4da;
padding: 0.375rem 0.75rem;
}
.station-management .dataTables_length select {
margin: 0 0.5rem;
border-radius: 0.375rem;
border: 1px solid #ced4da;
padding: 0.375rem 0.75rem;
}
/* Logbooks Management Page Styling */
.logbooks-management {
padding-top: 15px;
}
.logbooks-management .table-success {
background-color: rgba(25, 135, 84, 0.1);
}
.logbooks-management .card {
border: none;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.logbooks-management .card-header {
background-color: #f8f9fa;
border-bottom: 1px solid #dee2e6;
padding: 1rem 1.25rem;
}
.logbooks-management .badge {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
}
.logbooks-management .btn-sm {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
border-radius: 0.375rem;
}
/* DataTable styling within cards */
.logbooks-management .dataTables_wrapper {
padding: 0;
}
.logbooks-management .dataTables_length,
.logbooks-management .dataTables_filter {
margin-bottom: 1rem;
}
.logbooks-management .dataTables_info,
.logbooks-management .dataTables_paginate {
margin-top: 1rem;
}
.logbooks-management .dataTables_length label,
.logbooks-management .dataTables_filter label {
font-weight: 500;
margin-bottom: 0;
}
.logbooks-management .dataTables_filter input {
margin-left: 0.5rem;
border-radius: 0.375rem;
border: 1px solid #ced4da;
padding: 0.375rem 0.75rem;
}
.logbooks-management .dataTables_length select {
margin: 0 0.5rem;
border-radius: 0.375rem;
border: 1px solid #ced4da;
padding: 0.375rem 0.75rem;
}
.api .alert p {
margin-bottom: 0rem;
}

查看文件

@ -0,0 +1,74 @@
/* Custom Map Interface Enhancements */
/* Ensure grid square labels stay together (fix for separated letters) */
.leaflet-marker-icon.my-div-icon,
.my-div-icon .grid-text,
.my-div-icon .grid-text font,
span.grid-text,
span.grid-text > font {
letter-spacing: 0 !important;
word-spacing: 0 !important;
white-space: nowrap !important;
font-family: monospace !important;
display: inline-block !important;
}
/* Map container enhancements */
#custommap {
border-radius: 0 0 0.375rem 0.375rem;
overflow: hidden;
transition: height 0.3s ease;
width: 100%;
height: 1000px !important; /* Maintain original screenshot-friendly height */
}
/* Fullscreen map styling */
#custommap:fullscreen {
border-radius: 0;
}
/* Statistics badges */
.custom-map-QSOs .badge {
font-size: 0.85em;
padding: 0.5em 0.75em;
}
/* Legend styling */
.custom-map-QSOs .fas {
width: 1em;
text-align: center;
}
/* Responsive improvements */
@media (max-width: 768px) {
.custom-map-QSOs .card-body {
padding: 1rem 0.5rem;
}
.custom-map-QSOs .btn-group .btn {
padding: 0.375rem 0.5rem;
font-size: 0.8rem;
}
/* Keep map height at 1000px even on mobile for screenshots */
#custommap {
height: 1000px !important;
min-height: 1000px !important;
}
}
/* Animation for statistics updates */
.custom-map-QSOs .badge {
transition: all 0.3s ease;
}
.custom-map-QSOs .badge.updated {
animation: pulse 0.5s ease-in-out;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}

查看文件

@ -65,12 +65,25 @@ function initplot(_url_qso, options={}) {
}
function askForPlots(_url_qso, options={}) {
console.log('askForPlots called with URL:', _url_qso, 'options:', options);
removeMarkers();
if (typeof options.dataPost !== "undefined") { _dataPost = options.dataPost; } else { _dataPost = {}; }
$.ajax({
url: _url_qso, type: 'POST', dataType: 'json', data: _dataPost,
error: function() { console.log('[ERROR] ajax askForPlots() function return error.'); },
url: _url_qso,
type: 'POST',
dataType: 'json',
data: _dataPost,
timeout: 50000, // 50 second timeout
error: function(xhr, status, error) {
console.log('[ERROR] ajax askForPlots() function return error:', status, error, xhr);
// Call custom error callback if provided
if (typeof options.onError === 'function') {
console.log('Calling custom error callback');
options.onError();
}
},
success: function(plotjson) {
console.log('askForPlots AJAX success, plotjson:', plotjson);
if ((typeof plotjson['markers'] !== "undefined")&&(plotjson['markers'].length>0)) {
for (i=0;i<plotjson['markers'].length;i++) { createPlots(plotjson['markers'][i]); }
}
@ -80,6 +93,14 @@ function askForPlots(_url_qso, options={}) {
$.each(iconsList, function(icon, data){
$(options.map_id+' .cspot_'+icon).addClass(data.icon).css("color",data.color);
});
// Call custom success callback if provided
if (typeof options.onSuccess === 'function') {
console.log('Calling custom success callback');
options.onSuccess(plotjson);
} else {
console.warn('No custom success callback provided');
}
}
});
}
@ -103,4 +124,9 @@ function removeMarkers() {
map.removeLayer(plotlayers[i]);
}
plotlayers=[];
// Clear callsign labels if the function exists (for custom map page)
if (typeof clearCallsignLabels === 'function') {
clearCallsignLabels();
}
}

查看文件

@ -1,16 +1,48 @@
$('.bandtable').on('click', 'input[type="checkbox"]', function() {
var clickedbandid = $(this).closest('td').attr("class");
var $checkbox = $(this);
var $cell = $checkbox.closest('td');
// Add visual feedback
$cell.addClass('saving');
$checkbox.prop('disabled', true);
var clickedbandid = $cell.attr("class");
clickedbandid = clickedbandid.match(/\d+/)[0];
saveBand(clickedbandid);
saveBand(clickedbandid, function() {
// Remove visual feedback on success
$cell.removeClass('saving');
$checkbox.prop('disabled', false);
// Add success flash
$cell.addClass('saved');
setTimeout(function() {
$cell.removeClass('saved');
}, 1000);
});
});
$('.bandtable tfoot').on('click', 'input[type="checkbox"]', function() {
var clickedaward = $(this).closest('th').attr("class");
var status = $(this).is(":checked");
var $masterCheckbox = $(this);
var clickedaward = $masterCheckbox.closest('th').attr("class");
var status = $masterCheckbox.is(":checked");
clickedaward = clickedaward.replace('master_', '');
$('[class^='+clickedaward+'_] input[type="checkbox').each(function() {
$(this).prop( "checked", status );
// Update all related checkboxes with animation
$('[class^='+clickedaward+'_] input[type="checkbox"]').each(function() {
var $checkbox = $(this);
var $cell = $checkbox.closest('td');
$cell.addClass('updating');
setTimeout(function() {
$checkbox.prop("checked", status);
$cell.removeClass('updating').addClass('updated');
setTimeout(function() {
$cell.removeClass('updated');
}, 500);
}, Math.random() * 200); // Stagger the updates
});
saveBandAward(clickedaward, status);
});
@ -34,8 +66,136 @@ $('.bandtable').DataTable({
"scrollCollapse": true,
"paging": false,
"scrollX": true,
"searching": true,
"language": {
url: getDataTablesLanguageUrl(),
},
"columnDefs": [
{
"targets": [0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // Checkbox columns
"orderable": false,
"className": "text-center"
},
{
"targets": [14, 15, 16], // Frequency columns
"className": "text-center frequency-cell"
}
],
"drawCallback": function() {
updateStatistics();
},
"initComplete": function() {
// Ensure statistics are updated when table is fully initialized
updateStatistics();
}
});
// Custom search functionality
$('#bandSearch').on('keyup', function() {
$('.bandtable').DataTable().search(this.value).draw();
updateStatistics();
});
// Clear search button
$('#clearSearch').on('click', function() {
$('#bandSearch').val('');
$('.bandtable').DataTable().search('').draw();
updateStatistics();
});
// Keyboard shortcut to focus search (like GitHub)
$(document).on('keydown', function(e) {
if (e.key === '/' && !$(e.target).is('input, textarea')) {
e.preventDefault();
$('#bandSearch').focus();
}
if (e.key === 'Escape' && $(e.target).is('#bandSearch')) {
$('#bandSearch').blur().val('');
$('.bandtable').DataTable().search('').draw();
}
});
// Filter buttons
$('#showActiveOnly').on('click', function() {
$('.bandtable tbody tr').each(function() {
var $row = $(this);
var isActive = $row.find('.band-checkbox-cell input[type="checkbox"]').is(':checked');
if (!isActive) {
$row.hide();
} else {
$row.show();
}
});
$(this).addClass('active').siblings().removeClass('active');
updateStatistics();
});
$('#showAll').on('click', function() {
$('.bandtable tbody tr').show();
$(this).addClass('active').siblings().removeClass('active');
updateStatistics();
});
// Initialize with "Show All" active
$('#showAll').addClass('active');
// Update statistics
function updateStatistics() {
var activeBands = $('.band-checkbox-cell input[type="checkbox"]:checked').length;
// Fallback: if the class-based selector doesn't work, try alternative selectors
if (activeBands === 0) {
// Try finding by column position (first column checkboxes)
activeBands = $('.bandtable tbody tr td:first-child input[type="checkbox"]:checked').length;
}
$('#activeBandsCount').text(activeBands);
// Update visible rows count
var visibleRows = $('.bandtable tbody tr:visible').length;
var totalRows = $('.bandtable tbody tr').length;
$('#visibleRowsCount').text(visibleRows + ' of ' + totalRows + ' bands');
}
// Update statistics on page load
$(document).ready(function() {
// Wait for table to be fully rendered before calculating stats
setTimeout(function() {
updateStatistics();
}, 500);
});
// Update statistics when band status changes
$('.bandtable').on('change', '.band-checkbox-cell input[type="checkbox"]', function() {
updateStatistics();
});
// Bulk action buttons
$('#enableAllAwards').on('click', function() {
if (confirm('This will enable ALL award tracking (DXCC, IOTA, SOTA, WWFF, POTA, etc.) for ALL bands. Continue?')) {
$('.bandtable tbody tr').each(function() {
var $row = $(this);
// Check all award checkboxes except the first (active) column
$row.find('input[type="checkbox"]').not('.band-checkbox-cell input').each(function() {
if (!$(this).is(':checked')) {
$(this).prop('checked', true).trigger('change');
}
});
});
}
});
$('#resetAllAwards').on('click', function() {
if (confirm('This will disable ALL award tracking for ALL bands (bands will remain active for QSO entry). Continue?')) {
$('.bandtable tbody tr').each(function() {
var $row = $(this);
// Uncheck all award checkboxes except the first (active) column
$row.find('input[type="checkbox"]').not('.band-checkbox-cell input').each(function() {
if ($(this).is(':checked')) {
$(this).prop('checked', false).trigger('change');
}
});
});
}
});
@ -201,7 +361,7 @@ function deactivateAllBands() {
});
}
function saveBand(id) {
function saveBand(id, callback) {
$.ajax({
url: base_url + 'index.php/band/saveBand',
type: 'post',
@ -220,6 +380,11 @@ function saveBand(id) {
'vucc': $(".vucc_"+id+" input[type='checkbox']").is(":checked")
},
success: function (html) {
if (callback) callback();
},
error: function() {
// Show error state
if (callback) callback();
}
});
}

查看文件

@ -1,5 +1,3 @@
version: "3.8"
networks:
mynet:
@ -12,9 +10,10 @@ services:
- "80:80"
volumes:
- ./:/var/www/html:rw
command: ["./script.sh"]
command: ["/usr/local/bin/startup.sh"]
depends_on:
- db
db:
condition: service_healthy
networks:
- mynet
restart: on-failure
@ -30,11 +29,11 @@ services:
networks:
- mynet
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
timeout: 30s
retries: 20
interval: 15s
start_period: 60s
test: ["CMD", "/usr/local/bin/healthcheck.sh"]
timeout: 20s
retries: 10
interval: 10s
start_period: 40s
volumes:
db_data: {}

7
package-lock.json 自动生成的
查看文件

@ -1923,10 +1923,11 @@
"license": "MIT"
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz",
"integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.14"
}

查看文件

@ -60,32 +60,51 @@ if [ ! -d "$DEST_DIR" ]; then
mkdir -p $DEST_DIR
fi
# Check if configuration has already been processed
if [ -f "$DEST_DIR/database.php" ] && [ -f "$DEST_DIR/config.php" ]; then
echo "Configuration files already exist, skipping processing..."
else
echo "Processing configuration files..."
# Use sed with a different delimiter (`|`) to avoid conflicts with special characters
# Strip any trailing whitespace/newlines from variables before using them
CLEAN_DATABASE=$(echo "${MYSQL_DATABASE}" | tr -d '\r\n')
CLEAN_USER=$(echo "${MYSQL_USER}" | tr -d '\r\n')
CLEAN_PASSWORD=$(echo "${MYSQL_PASSWORD}" | tr -d '\r\n')
CLEAN_HOST=$(echo "${MYSQL_HOST}" | tr -d '\r\n')
CLEAN_LOCATOR=$(echo "${BASE_LOCATOR}" | tr -d '\r\n')
CLEAN_URL=$(echo "${WEBSITE_URL}" | tr -d '\r\n')
CLEAN_DIRECTORY=$(echo "${DIRECTORY}" | tr -d '\r\n')
sed -i "s|%DATABASE%|${CLEAN_DATABASE}|g" $DATABASE_FILE
sed -i "s|%USERNAME%|${CLEAN_USER}|g" $DATABASE_FILE
sed -i "s|%PASSWORD%|${CLEAN_PASSWORD}|g" $DATABASE_FILE
sed -i "s|%HOSTNAME%|${CLEAN_HOST}|g" $DATABASE_FILE
sed -i "s|%baselocator%|${CLEAN_LOCATOR}|g" $CONFIG_FILE
sed -i "s|%websiteurl%|${CLEAN_URL}|g" $CONFIG_FILE
sed -i "s|%directory%|${CLEAN_DIRECTORY}|g" $CONFIG_FILE
# Use sed with a different delimiter (`|`) to avoid conflicts with special characters
sed -i "s|%DATABASE%|${MYSQL_DATABASE}|g" $DATABASE_FILE
sed -i "s|%USERNAME%|${MYSQL_USER}|g" $DATABASE_FILE
sed -i "s|%PASSWORD%|${MYSQL_PASSWORD}|g" $DATABASE_FILE
sed -i "s|%HOSTNAME%|${MYSQL_HOST}|g" $DATABASE_FILE
sed -i "s|%baselocator%|${BASE_LOCATOR}|g" $CONFIG_FILE
sed -i "s|%websiteurl%|${WEBSITE_URL}|g" $CONFIG_FILE
sed -i "s|%directory%|${DIRECTORY}|g" $CONFIG_FILE
# Move the files to the destination directory
mv $CONFIG_FILE $DEST_DIR
mv $DATABASE_FILE $DEST_DIR
# Move the files to the destination directory
mv $CONFIG_FILE $DEST_DIR
mv $DATABASE_FILE $DEST_DIR
# Delete the /install directory
rm -rf /install
# Delete the /install directory
rm -rf /install
fi
echo "Replacement complete."
# Wait for database to be ready
echo "Waiting for database to be ready..."
until mysql -h"$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -e "SELECT 1" > /dev/null 2>&1; do
echo "Database is not ready yet. Waiting 5 seconds..."
sleep 5
done
echo "Database is ready!"
echo "Connecting to: Host=$MYSQL_HOST, User=$MYSQL_USER, Database=$MYSQL_DATABASE"
# Give the database a moment, then test connection once
sleep 2
if mariadb -h"$MYSQL_HOST" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" -D"$MYSQL_DATABASE" -e "SELECT 1;" >/dev/null 2>&1; then
echo "Database is ready!"
else
echo "Database connection failed, but continuing anyway since healthcheck passed..."
fi
# Set Permissions
chown -R root:www-data /var/www/html/application/config/