mirror of
https://github.com/magicbug/Cloudlog
synced 2026-08-13 17:49:35 -04:00
1033 lines
44 KiB
PHP
1033 lines
44 KiB
PHP
|
|
<style>
|
|
#dxccmap {
|
|
height: calc(100vh) !important;
|
|
max-height: 900px !important;
|
|
}
|
|
/*Legend specific*/
|
|
.legend {
|
|
padding: 6px 8px;
|
|
font: 14px Arial, Helvetica, sans-serif;
|
|
background: white;
|
|
background: rgba(255, 255, 255, 0.8);
|
|
line-height: 24px;
|
|
color: #555;
|
|
}
|
|
.legend h4 {
|
|
text-align: center;
|
|
font-size: 16px;
|
|
margin: 2px 12px 8px;
|
|
color: #777;
|
|
}
|
|
.legend span {
|
|
position: relative;
|
|
bottom: 3px;
|
|
}
|
|
.legend i {
|
|
width: 18px;
|
|
height: 18px;
|
|
float: left;
|
|
margin: 0 8px 0 0;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.stats-card {
|
|
border-left: 4px solid #007bff;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
background-color: #f8f9fa;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.stats-card.milestone {
|
|
border-left-color: #28a745;
|
|
}
|
|
|
|
.stats-card.milestone.achieved {
|
|
background-color: #d4edda;
|
|
}
|
|
|
|
.stats-card h5 {
|
|
margin: 0 0 10px 0;
|
|
color: #333;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.stats-number {
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: #007bff;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
.milestone.achieved .stats-number {
|
|
color: #28a745;
|
|
}
|
|
|
|
.progress {
|
|
height: 24px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.progress-label {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.filter-collapse {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.preset-filters {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.preset-btn {
|
|
margin-right: 5px;
|
|
margin-bottom: 5px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.table-search {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.sortable {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.sortable::after {
|
|
content: ' ↕';
|
|
font-size: 12px;
|
|
color: #999;
|
|
}
|
|
|
|
.sortable.asc::after {
|
|
content: ' ↑';
|
|
color: #007bff;
|
|
}
|
|
|
|
.sortable.desc::after {
|
|
content: ' ↓';
|
|
color: #007bff;
|
|
}
|
|
|
|
.dxcc-link {
|
|
color: #007bff;
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.dxcc-link:hover {
|
|
color: #0056b3;
|
|
}
|
|
|
|
.continent-card {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.continent-card:hover {
|
|
background-color: #e9ecef;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.tab-pane {
|
|
animation: fadeIn 0.3s;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
</style>
|
|
|
|
<div class="container">
|
|
<!-- Award Info Box -->
|
|
<br>
|
|
<div id="awardInfoButton">
|
|
<script>
|
|
var lang_awards_info_button = "<?php echo lang('awards_info_button'); ?>";
|
|
var lang_award_info_ln1 = "<?php echo lang('awards_dxcc_description_ln1'); ?>";
|
|
var lang_award_info_ln2 = "<?php echo lang('awards_dxcc_description_ln2'); ?>";
|
|
var lang_award_info_ln3 = "<?php echo lang('awards_dxcc_description_ln3'); ?>";
|
|
var lang_award_info_ln4 = "<?php echo lang('awards_dxcc_description_ln4'); ?>";
|
|
</script>
|
|
<h2><?php echo $page_title; ?></h2>
|
|
<button type="button" class="btn btn-sm btn-primary me-1" id="displayAwardInfo"><?php echo lang('awards_info_button'); ?></button>
|
|
</div>
|
|
<!-- End of Award Info Box -->
|
|
|
|
<!-- Filters Section (moved to top) -->
|
|
<div class="card mb-4" style="margin-top: 20px;">
|
|
<div class="card-header filter-collapse" data-bs-toggle="collapse" data-bs-target="#filterPanel" style="cursor: pointer;">
|
|
<i class="fas fa-filter me-2"></i><strong>Filters</strong>
|
|
<span class="float-end"><i class="fas fa-chevron-down"></i></span>
|
|
</div>
|
|
<div id="filterPanel" class="collapse show">
|
|
<div class="card-body">
|
|
<!-- Preset Filters -->
|
|
<div class="preset-filters mb-3">
|
|
<label class="form-label">Quick Presets:</label>
|
|
<button type="button" class="btn preset-btn btn-outline-primary btn-sm" onclick="applyPreset('confirmed')">Show Confirmed Only</button>
|
|
<button type="button" class="btn preset-btn btn-outline-primary btn-sm" onclick="applyPreset('worked')">Show Worked Only</button>
|
|
<button type="button" class="btn preset-btn btn-outline-primary btn-sm" onclick="applyPreset('unworked')">Show Unworked</button>
|
|
<button type="button" class="btn preset-btn btn-outline-primary btn-sm" onclick="applyPreset('lotwonly')">LoTW Only</button>
|
|
<button type="button" class="btn preset-btn btn-outline-secondary btn-sm" onclick="resetFilters()">Reset All</button>
|
|
</div>
|
|
|
|
<form class="form" action="<?php echo site_url('awards/dxcc'); ?>" method="post" enctype="multipart/form-data" id="dxccForm">
|
|
|
|
<div class="mb-3 row">
|
|
<div class="col-md-2 control-label" for="checkboxes">Deleted DXCC</div>
|
|
<div class="col-md-10">
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="includedeleted" id="includedeleted" value="1" <?php if ($this->input->post('includedeleted')) echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="includedeleted">Include deleted</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Multiple Checkboxes (inline) -->
|
|
<div class="mb-3 row">
|
|
<div class="col-md-2" for="checkboxes">Worked / Confirmed</div>
|
|
<div class="col-md-10">
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="worked" id="worked" value="1" <?php if ($this->input->post('worked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="worked">Show worked</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="confirmed" id="confirmed" value="1" <?php if ($this->input->post('confirmed') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="confirmed">Show confirmed</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="notworked" id="notworked" value="1" <?php if ($this->input->post('notworked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="notworked">Show not worked</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3 row">
|
|
<div class="col-md-2">QSL Type</div>
|
|
<div class="col-md-10">
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="qsl" value="1" id="qsl" <?php if ($this->input->post('qsl') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="qsl">QSL</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="lotw" value="1" id="lotw" <?php if ($this->input->post('lotw') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="lotw">LoTW</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="eqsl" value="1" id="eqsl" <?php if ($this->input->post('eqsl')) echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="eqsl">eQSL</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3 row">
|
|
<div class="col-md-2">Continents</div>
|
|
<div class="col-md-10">
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="Antarctica" id="Antarctica" value="1" <?php if ($this->input->post('Antarctica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="Antarctica">Antarctica</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="Africa" id="Africa" value="1" <?php if ($this->input->post('Africa') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="Africa">Africa</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="Asia" id="Asia" value="1" <?php if ($this->input->post('Asia') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="Asia">Asia</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="Europe" id="Europe" value="1" <?php if ($this->input->post('Europe') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="Europe">Europe</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="NorthAmerica" id="NorthAmerica" value="1" <?php if ($this->input->post('NorthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="NorthAmerica">North America</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="SouthAmerica" id="SouthAmerica" value="1" <?php if ($this->input->post('SouthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="SouthAmerica">South America</label>
|
|
</div>
|
|
<div class="form-check-inline">
|
|
<input class="form-check-input" type="checkbox" name="Oceania" id="Oceania" value="1" <?php if ($this->input->post('Oceania') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="Oceania">Oceania</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3 row">
|
|
<label class="col-md-2 control-label">Bands</label>
|
|
<div class="col-md-10">
|
|
<div class="mb-2">
|
|
<button type="button" class="btn preset-btn btn-outline-primary btn-sm" onclick="setBandGroup('hf')" data-bs-toggle="tooltip" data-bs-placement="top" data-dxcc-band-tooltip="1" title="Selects HF bands only (160m to 10m)">HF Only</button>
|
|
<button type="button" class="btn preset-btn btn-outline-primary btn-sm" onclick="setBandGroup('vhfuhf')" data-bs-toggle="tooltip" data-bs-placement="top" data-dxcc-band-tooltip="1" title="Selects all non-HF bands (VHF/UHF and above)">VHF/UHF+</button>
|
|
<?php if (in_array('SAT', $worked_bands, true)) { ?>
|
|
<button type="button" class="btn preset-btn btn-outline-primary btn-sm" onclick="setBandGroup('sat')" data-bs-toggle="tooltip" data-bs-placement="top" data-dxcc-band-tooltip="1" title="Selects SAT entries only">SAT Only</button>
|
|
<?php } ?>
|
|
<?php if (in_array('EME', $worked_bands, true)) { ?>
|
|
<button type="button" class="btn preset-btn btn-outline-primary btn-sm" onclick="setBandGroup('eme')" data-bs-toggle="tooltip" data-bs-placement="top" data-dxcc-band-tooltip="1" title="Selects EME entries only">EME Only</button>
|
|
<?php } ?>
|
|
<button type="button" class="btn preset-btn btn-outline-secondary btn-sm" onclick="setBandGroup('all')">All Bands</button>
|
|
<button type="button" class="btn preset-btn btn-outline-secondary btn-sm" onclick="setBandGroup('none')">Clear Bands</button>
|
|
</div>
|
|
<?php foreach($worked_bands as $band) {
|
|
$bandId = 'band_' . preg_replace('/[^A-Za-z0-9]+/', '_', $band);
|
|
?>
|
|
<div class="form-check form-check-inline mb-1">
|
|
<input class="form-check-input" type="checkbox" name="bands[]" id="<?php echo $bandId; ?>" value="<?php echo $band; ?>" <?php if ($this->input->method() !== 'post' || in_array($band, $bands)) echo ' checked="checked"'; ?> >
|
|
<label class="form-check-label" for="<?php echo $bandId; ?>"><?php echo $band; ?></label>
|
|
</div>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3 row">
|
|
<label class="col-md-2 control-label" for="mode">Mode</label>
|
|
<div class="col-md-2">
|
|
<select id="mode" name="mode" class="form-select form-select-sm">
|
|
<option value="All" <?php if ($this->input->post('mode') == "All" || $this->input->method() !== 'post') echo ' selected'; ?>>All</option>
|
|
<?php
|
|
foreach($modes->result() as $mode){
|
|
if ($mode->submode == null) {
|
|
echo '<option value="' . $mode->mode . '"';
|
|
if ($this->input->post('mode') == $mode->mode) echo ' selected';
|
|
echo '>'. $mode->mode . '</option>'."\n";
|
|
} else {
|
|
echo '<option value="' . $mode->submode . '"';
|
|
if ($this->input->post('mode') == $mode->submode) echo ' selected';
|
|
echo '>' . $mode->submode . '</option>'."\n";
|
|
}
|
|
}
|
|
?>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3 row">
|
|
<label class="col-md-2 control-label" for="year">Year</label>
|
|
<div class="col-md-2">
|
|
<select id="year" name="year" class="form-select form-select-sm">
|
|
<option value="All" <?php if ($this->input->post('year') == 'All' || $this->input->method() !== 'post') echo ' selected'; ?>>All years</option>
|
|
<?php foreach ($worked_years as $yr): ?>
|
|
<option value="<?php echo $yr; ?>" <?php if ($this->input->post('year') == $yr) echo ' selected'; ?>><?php echo $yr; ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3 row">
|
|
<label class="col-md-2 control-label" for="button1id"></label>
|
|
<div class="col-md-10">
|
|
<button id="button2id" type="reset" name="button2id" class="btn btn-sm btn-warning">Reset</button>
|
|
<button id="button1id" type="submit" name="button1id" class="btn btn-sm btn-primary">Show</button>
|
|
<?php if ($dxcc_array) {
|
|
?><button type="button" onclick="load_dxcc_map();" class="btn btn-info btn-sm"><i class="fas fa-globe-americas"></i> Show DXCC Map</button>
|
|
<button type="button" onclick="exportTableToCSV('dxcc_table.csv');" class="btn btn-success btn-sm"><i class="fas fa-download"></i> Export CSV</button>
|
|
<?php }?>
|
|
</div>
|
|
</div>
|
|
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Summary Section -->
|
|
<?php if ($dxcc_array): ?>
|
|
<div class="row mt-4 mb-4">
|
|
<!-- Worked vs Confirmed -->
|
|
<div class="col-md-6">
|
|
<div class="stats-card">
|
|
<h5>DXCC Progress</h5>
|
|
<div class="row">
|
|
<div class="col-6">
|
|
<div class="progress-label">Total Worked</div>
|
|
<div class="stats-number"><?php echo $dxcc_statistics['total_worked']; ?></div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="progress-label">Total Confirmed</div>
|
|
<div class="stats-number" style="color: #28a745;"><?php echo $dxcc_statistics['total_confirmed']; ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Milestones -->
|
|
<div class="col-md-6">
|
|
<div class="stats-card milestone <?php echo $dxcc_statistics['milestones'][100] ? 'achieved' : ''; ?>">
|
|
<h5>🏆 Milestone Progress</h5>
|
|
<div class="row text-center">
|
|
<div class="col-4">
|
|
<div style="font-size: 14px;">100</div>
|
|
<div style="font-size: 20px; margin-top: 5px;">
|
|
<?php echo $dxcc_statistics['milestones'][100] ? '✓' : '✗'; ?>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<div style="font-size: 14px;">200</div>
|
|
<div style="font-size: 20px; margin-top: 5px;">
|
|
<?php echo $dxcc_statistics['milestones'][200] ? '✓' : '✗'; ?>
|
|
</div>
|
|
</div>
|
|
<div class="col-4">
|
|
<div style="font-size: 14px;">300</div>
|
|
<div style="font-size: 20px; margin-top: 5px;">
|
|
<?php echo $dxcc_statistics['milestones'][300] ? '✓' : '✗'; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Continent Breakdown -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="stats-card">
|
|
<h5>By Continent</h5>
|
|
<div class="row">
|
|
<?php
|
|
$continents = array(
|
|
'Africa' => 'Africa',
|
|
'Antarctica' => 'Antarctica',
|
|
'Asia' => 'Asia',
|
|
'Europe' => 'Europe',
|
|
'NorthAmerica' => 'North America',
|
|
'SouthAmerica' => 'South America',
|
|
'Oceania' => 'Oceania'
|
|
);
|
|
|
|
$continentCodes = array(
|
|
'Africa' => 'AF',
|
|
'Antarctica' => 'AN',
|
|
'Asia' => 'AS',
|
|
'Europe' => 'EU',
|
|
'NorthAmerica' => 'NA',
|
|
'SouthAmerica' => 'SA',
|
|
'Oceania' => 'OC'
|
|
);
|
|
|
|
foreach ($continents as $key => $label):
|
|
$worked = isset($dxcc_statistics['worked_by_continent'][$key]) ? $dxcc_statistics['worked_by_continent'][$key] : 0;
|
|
$confirmed = isset($dxcc_statistics['confirmed_by_continent'][$key]) ? $dxcc_statistics['confirmed_by_continent'][$key] : 0;
|
|
$total = isset($continent_breakdown[$key]) ? $continent_breakdown[$key] : 0;
|
|
$percentage = $total > 0 ? round(($confirmed / $total) * 100) : 0;
|
|
$continentCode = isset($continentCodes[$key]) ? $continentCodes[$key] : '';
|
|
?>
|
|
<div class="col-md-6 col-lg-3 mb-3 continent-card" onclick="viewContinentQsos('<?php echo $continentCode; ?>', '<?php echo htmlspecialchars($label); ?>')">
|
|
<div class="progress-label"><?php echo $label; ?></div>
|
|
<div style="font-size: 16px; margin-bottom: 5px;">
|
|
<strong><?php echo $confirmed; ?>/<?php echo $total; ?></strong>
|
|
</div>
|
|
<div class="progress">
|
|
<div class="progress-bar" role="progressbar" style="width: <?php echo $percentage; ?>%" aria-valuenow="<?php echo $percentage; ?>" aria-valuemin="0" aria-valuemax="100"></div>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
|
<li class="nav-item">
|
|
<a class="nav-link active" id="table-tab" data-bs-toggle="tab" href="#table" role="tab" aria-controls="table" aria-selected="true">Table</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<a class="nav-link" id="map-tab" onclick="load_dxcc_map();" data-bs-toggle="tab" href="#dxccmaptab" role="tab" aria-controls="home" aria-selected="false">Map</a>
|
|
</li>
|
|
</ul>
|
|
<br />
|
|
|
|
<div class="tab-content" id="myTabContent">
|
|
<div class="tab-pane fade" id="dxccmaptab" role="tabpanel" aria-labelledby="home-tab">
|
|
<br />
|
|
|
|
<div id="dxccmap" class="map-leaflet" ></div>
|
|
|
|
</div>
|
|
|
|
<!-- Table Search Box -->
|
|
<?php if ($dxcc_array): ?>
|
|
<div class="table-search">
|
|
<input type="text" id="searchInput" class="form-control form-control-sm" placeholder="Search DXCC by name or prefix...">
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php
|
|
$i = 1;
|
|
if ($dxcc_array) {
|
|
echo '
|
|
<table style="width:100%" class="table-sm table tabledxcc table-bordered table-hover table-striped table-condensed text-center" id="dxcc_table">
|
|
<thead>
|
|
<tr>
|
|
<td class="sortable" data-column="0">#</td>
|
|
<td class="sortable" data-column="1">DXCC Name</td>
|
|
<td class="sortable" data-column="2">Prefix</td>';
|
|
foreach($bands as $band) {
|
|
echo '<td class="sortable" data-column="' . (3 + array_search($band, $bands)) . '">' . $band . '</td>';
|
|
}
|
|
echo '</tr>
|
|
</thead>
|
|
<tbody>';
|
|
foreach ($dxcc_array as $dxcc => $value) { // Fills the table with the data
|
|
echo '<tr>
|
|
<td>'. $i++ .'</td>';
|
|
foreach ($value as $name => $key) {
|
|
if (isset($value['Deleted']) && $value['Deleted'] == 1 && $name == "name") {
|
|
echo '<td><span class="dxcc-link" onclick="viewDxccQsos(\'' . htmlspecialchars($dxcc) . '\');">' . $key . '</span> <span class="badge text-bg-danger">'.lang('gen_hamradio_deleted_dxcc').'</span></td>';
|
|
} else if ($name == "Deleted") {
|
|
continue;
|
|
} else if ($name == "name") {
|
|
echo '<td><span class="dxcc-link" onclick="viewDxccQsos(\'' . htmlspecialchars($dxcc) . '\');">' . $key . '</span></td>';
|
|
} else {
|
|
echo '<td style="text-align: center">' . $key . '</td>';
|
|
}
|
|
}
|
|
echo '</tr>';
|
|
}
|
|
echo '</table>
|
|
<h2>Summary</h2>
|
|
|
|
<table class="table-sm tablesummary table table-bordered table-hover table-striped table-condensed text-center">
|
|
<thead>
|
|
<tr><td></td>';
|
|
|
|
foreach($bands as $band) {
|
|
echo '<td>' . $band . '</td>';
|
|
}
|
|
echo '<td>Total</td>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
|
|
<tr><td>Total worked</td>';
|
|
|
|
foreach ($dxcc_summary['worked'] as $dxcc) { // Fills the table with the data
|
|
echo '<td style="text-align: center">' . $dxcc . '</td>';
|
|
}
|
|
|
|
echo '</tr><tr>
|
|
<td>Total confirmed</td>';
|
|
foreach ($dxcc_summary['confirmed'] as $dxcc) { // Fills the table with the data
|
|
echo '<td style="text-align: center">' . $dxcc . '</td>';
|
|
}
|
|
|
|
echo '</tr>
|
|
</table>
|
|
</div>';
|
|
|
|
}
|
|
else {
|
|
echo '<div class="alert alert-danger" role="alert">Nothing found!</div>';
|
|
}
|
|
?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- QSO Modal -->
|
|
<div class="modal fade" id="dxccQsoModal" tabindex="-1" role="dialog" aria-labelledby="dxccQsoModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="dxccQsoModalLabel"><span id="dxccModalTitle">QSOs</span> - <span id="dxccEntityName"></span></h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="qsoLoading" style="text-align: center; display: none;">
|
|
<div class="spinner-border" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
</div>
|
|
<table id="qsoTable" class="table table-sm table-striped" style="display: none;">
|
|
<thead id="qsoTableHeader">
|
|
<tr>
|
|
<th>Date/Time</th>
|
|
<th>Call</th>
|
|
<th>Band</th>
|
|
<th>Mode</th>
|
|
<th>RST Sent</th>
|
|
<th>RST Rcvd</th>
|
|
<th>QSL</th>
|
|
<th>LoTW</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="qsoTableBody">
|
|
</tbody>
|
|
</table>
|
|
<div id="noQsos" style="text-align: center; display: none;" class="alert alert-info">
|
|
No QSOs found for this entity.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<br>
|
|
|
|
<script>
|
|
let currentSort = { column: null, direction: 'asc' };
|
|
let currentModalType = 'qso'; // 'qso' or 'continent'
|
|
|
|
document.querySelectorAll('[data-dxcc-band-tooltip="1"]').forEach(el => {
|
|
new bootstrap.Tooltip(el);
|
|
});
|
|
|
|
// Table sorting functionality
|
|
document.querySelectorAll('.sortable').forEach(header => {
|
|
header.addEventListener('click', function() {
|
|
const column = parseInt(this.dataset.column);
|
|
const table = document.getElementById('dxcc_table');
|
|
const tbody = table.querySelector('tbody');
|
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
|
|
// Toggle sort direction
|
|
if (currentSort.column === column) {
|
|
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
currentSort.column = column;
|
|
currentSort.direction = 'asc';
|
|
}
|
|
|
|
// Update header indicators
|
|
document.querySelectorAll('.sortable').forEach(h => h.classList.remove('asc', 'desc'));
|
|
this.classList.add(currentSort.direction);
|
|
|
|
// Sort rows
|
|
rows.sort((a, b) => {
|
|
const aValue = a.querySelectorAll('td')[column].textContent.trim();
|
|
const bValue = b.querySelectorAll('td')[column].textContent.trim();
|
|
|
|
let comparison = 0;
|
|
if (!isNaN(aValue) && !isNaN(bValue)) {
|
|
comparison = parseInt(aValue) - parseInt(bValue);
|
|
} else {
|
|
comparison = aValue.localeCompare(bValue);
|
|
}
|
|
|
|
return currentSort.direction === 'asc' ? comparison : -comparison;
|
|
});
|
|
|
|
// Reorder rows
|
|
rows.forEach(row => tbody.appendChild(row));
|
|
});
|
|
});
|
|
|
|
// Table search functionality
|
|
const searchInput = document.getElementById('searchInput');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('keyup', function() {
|
|
const searchTerm = this.value.toLowerCase();
|
|
const table = document.getElementById('dxcc_table');
|
|
const rows = table.querySelectorAll('tbody tr');
|
|
|
|
rows.forEach(row => {
|
|
const name = row.querySelectorAll('td')[1].textContent.toLowerCase();
|
|
const prefix = row.querySelectorAll('td')[2].textContent.toLowerCase();
|
|
|
|
if (name.includes(searchTerm) || prefix.includes(searchTerm)) {
|
|
row.style.display = '';
|
|
} else {
|
|
row.style.display = 'none';
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Preset filter functions
|
|
function applyPreset(preset) {
|
|
const form = document.getElementById('dxccForm');
|
|
|
|
// Reset all checkboxes first
|
|
form.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = false);
|
|
|
|
// All QSL types
|
|
const allQslTypes = ['qsl', 'lotw', 'eqsl'];
|
|
|
|
// All continents
|
|
const allContinents = ['Antarctica', 'Africa', 'Asia', 'Europe', 'NorthAmerica', 'SouthAmerica', 'Oceania'];
|
|
|
|
switch(preset) {
|
|
case 'confirmed':
|
|
document.getElementById('confirmed').checked = true;
|
|
document.getElementById('qsl').checked = true;
|
|
document.getElementById('lotw').checked = true;
|
|
allContinents.forEach(cont => {
|
|
const el = document.getElementById(cont);
|
|
if (el) el.checked = true;
|
|
});
|
|
break;
|
|
case 'worked':
|
|
document.getElementById('worked').checked = true;
|
|
document.getElementById('confirmed').checked = true;
|
|
document.getElementById('qsl').checked = true;
|
|
document.getElementById('lotw').checked = true;
|
|
allContinents.forEach(cont => {
|
|
const el = document.getElementById(cont);
|
|
if (el) el.checked = true;
|
|
});
|
|
break;
|
|
case 'unworked':
|
|
document.getElementById('notworked').checked = true;
|
|
document.getElementById('qsl').checked = true;
|
|
document.getElementById('lotw').checked = true;
|
|
allContinents.forEach(cont => {
|
|
const el = document.getElementById(cont);
|
|
if (el) el.checked = true;
|
|
});
|
|
break;
|
|
case 'lotwonly':
|
|
document.getElementById('confirmed').checked = true;
|
|
document.getElementById('lotw').checked = true;
|
|
// LoTW only means QSL and eQSL are NOT checked (already unchecked from reset)
|
|
allContinents.forEach(cont => {
|
|
const el = document.getElementById(cont);
|
|
if (el) el.checked = true;
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
function setBandGroup(group) {
|
|
const hfBands = ['160m', '80m', '60m', '40m', '30m', '20m', '17m', '15m', '12m', '10m'];
|
|
const bandCheckboxes = document.querySelectorAll('input[name="bands[]"]');
|
|
|
|
if (group === 'all') {
|
|
bandCheckboxes.forEach(cb => cb.checked = true);
|
|
return;
|
|
}
|
|
|
|
if (group === 'none') {
|
|
bandCheckboxes.forEach(cb => cb.checked = false);
|
|
return;
|
|
}
|
|
|
|
if (group === 'hf') {
|
|
bandCheckboxes.forEach(cb => {
|
|
cb.checked = hfBands.includes(cb.value);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (group === 'vhfuhf') {
|
|
bandCheckboxes.forEach(cb => {
|
|
cb.checked = !hfBands.includes(cb.value);
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (group === 'sat') {
|
|
bandCheckboxes.forEach(cb => {
|
|
cb.checked = cb.value === 'SAT';
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (group === 'eme') {
|
|
bandCheckboxes.forEach(cb => {
|
|
cb.checked = cb.value === 'EME';
|
|
});
|
|
}
|
|
}
|
|
|
|
function resetFilters() {
|
|
const form = document.getElementById('dxccForm');
|
|
form.reset();
|
|
form.querySelectorAll('input[name="bands[]"]').forEach(cb => cb.checked = true);
|
|
}
|
|
|
|
// View QSOs for specific DXCC filtered by status
|
|
function viewDxccQsosByStatus(dxcc, status) {
|
|
const modal = new bootstrap.Modal(document.getElementById('dxccQsoModal'));
|
|
currentModalType = 'qso';
|
|
|
|
// Set headers for QSO table
|
|
document.getElementById('qsoTableHeader').innerHTML = `
|
|
<tr>
|
|
<th>Date/Time</th>
|
|
<th>Call</th>
|
|
<th>Band</th>
|
|
<th>Mode</th>
|
|
<th>RST Sent</th>
|
|
<th>RST Rcvd</th>
|
|
<th>QSL</th>
|
|
<th>LoTW</th>
|
|
</tr>
|
|
`;
|
|
document.getElementById('dxccModalTitle').textContent = status === 'C' ? 'Confirmed QSOs' : 'Worked QSOs';
|
|
|
|
// Show loading state
|
|
document.getElementById('qsoLoading').style.display = 'block';
|
|
document.getElementById('qsoTable').style.display = 'none';
|
|
document.getElementById('noQsos').style.display = 'none';
|
|
const statusLabel = status === 'C' ? 'Confirmed' : (status === 'W' ? 'Worked Only' : '');
|
|
document.getElementById('dxccEntityName').textContent = 'Loading... (' + statusLabel + ')';
|
|
|
|
modal.show();
|
|
|
|
// Fetch QSOs via AJAX
|
|
$.ajax({
|
|
url: base_url + 'index.php/awards/get_dxcc_qsos_by_status',
|
|
type: 'post',
|
|
data: {
|
|
dxcc_id: dxcc,
|
|
status: status,
|
|
limit: 100
|
|
},
|
|
dataType: 'json',
|
|
success: function(data) {
|
|
document.getElementById('qsoLoading').style.display = 'none';
|
|
|
|
if (data.error) {
|
|
document.getElementById('noQsos').style.display = 'block';
|
|
document.getElementById('noQsos').innerHTML = '<div class="alert alert-danger">Error: ' + data.error + '</div>';
|
|
document.getElementById('dxccEntityName').textContent = statusLabel + ' - Error';
|
|
return;
|
|
}
|
|
|
|
// Get DXCC name from dxcc_id (we'll use a placeholder since we're filtering by status)
|
|
document.getElementById('dxccEntityName').textContent = (status === 'C' ? 'Confirmed' : 'Worked Only') + ' QSOs';
|
|
|
|
if (data.count > 0) {
|
|
document.getElementById('qsoTable').style.display = 'table';
|
|
const tbody = document.getElementById('qsoTableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.qsos.forEach(qso => {
|
|
const row = document.createElement('tr');
|
|
const datetime = new Date(qso.col_time_on).toLocaleString();
|
|
row.innerHTML = `
|
|
<td>${datetime}</td>
|
|
<td>${qso.col_call}</td>
|
|
<td>${qso.col_band}</td>
|
|
<td>${qso.col_mode}</td>
|
|
<td>${qso.col_rst_sent || '-'}</td>
|
|
<td>${qso.col_rst_rcvd || '-'}</td>
|
|
<td>${qso.col_qsl_rcvd ? '✓' : '-'}</td>
|
|
<td>${qso.COL_LOTW_QSL_RCVD === 'Y' ? '✓' : '-'}</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
});
|
|
} else {
|
|
document.getElementById('noQsos').style.display = 'block';
|
|
document.getElementById('noQsos').innerHTML = '<div class="alert alert-info">No ' + (status === 'C' ? 'confirmed' : 'worked only') + ' QSOs found.</div>';
|
|
document.getElementById('qsoTable').style.display = 'none';
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
document.getElementById('qsoLoading').style.display = 'none';
|
|
console.error('AJAX Error:', error, xhr.responseText);
|
|
document.getElementById('noQsos').style.display = 'block';
|
|
document.getElementById('noQsos').innerHTML = '<div class="alert alert-danger">Error loading QSOs: ' + error + '</div>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// View QSOs for specific continent
|
|
function viewContinentQsos(continentCode, continentName) {
|
|
const modal = new bootstrap.Modal(document.getElementById('dxccQsoModal'));
|
|
currentModalType = 'continent';
|
|
|
|
// Set headers for continent table (countries)
|
|
document.getElementById('qsoTableHeader').innerHTML = `
|
|
<tr>
|
|
<th>Country</th>
|
|
</tr>
|
|
`;
|
|
document.getElementById('dxccModalTitle').textContent = 'Countries';
|
|
|
|
// Show loading state
|
|
document.getElementById('qsoLoading').style.display = 'block';
|
|
document.getElementById('qsoTable').style.display = 'none';
|
|
document.getElementById('noQsos').style.display = 'none';
|
|
document.getElementById('dxccEntityName').textContent = 'Loading... (' + continentName + ')';
|
|
|
|
modal.show();
|
|
|
|
// Fetch DXCC entities for this continent via AJAX
|
|
$.ajax({
|
|
url: base_url + 'index.php/awards/get_continent_qsos',
|
|
type: 'post',
|
|
data: {
|
|
continent_code: continentCode,
|
|
limit: 100
|
|
},
|
|
dataType: 'json',
|
|
success: function(data) {
|
|
document.getElementById('qsoLoading').style.display = 'none';
|
|
|
|
if (data.error) {
|
|
document.getElementById('noQsos').style.display = 'block';
|
|
document.getElementById('noQsos').innerHTML = '<div class="alert alert-danger">Error: ' + data.error + '</div>';
|
|
document.getElementById('dxccEntityName').textContent = continentName + ' - Error';
|
|
return;
|
|
}
|
|
|
|
document.getElementById('dxccEntityName').textContent = continentName + ' Countries';
|
|
|
|
if (data.count > 0) {
|
|
document.getElementById('qsoTable').style.display = 'table';
|
|
const tbody = document.getElementById('qsoTableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.entities.forEach(entity => {
|
|
const row = document.createElement('tr');
|
|
let statusBadge = '';
|
|
let statusColor = '';
|
|
|
|
if (entity.status === 'confirmed') {
|
|
statusBadge = '<span class="badge bg-success">✓ Confirmed</span>';
|
|
statusColor = 'bg-success';
|
|
} else if (entity.status === 'worked') {
|
|
statusBadge = '<span class="badge bg-warning">W Worked</span>';
|
|
statusColor = 'bg-danger';
|
|
} else {
|
|
statusBadge = '<span class="badge bg-secondary">- Not Worked</span>';
|
|
statusColor = '';
|
|
}
|
|
|
|
row.innerHTML = `
|
|
<td>
|
|
<strong><a href="javascript:viewDxccQsos(${entity.adif})" style="color: #007bff; cursor: pointer;">${entity.name}</a></strong>
|
|
<small style="margin-left: 10px;">${entity.prefix}</small>
|
|
<span style="float: right;">${statusBadge}</span>
|
|
</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
});
|
|
} else {
|
|
document.getElementById('noQsos').style.display = 'block';
|
|
document.getElementById('noQsos').innerHTML = '<div class="alert alert-info">No countries found for ' + continentName + '.</div>';
|
|
document.getElementById('qsoTable').style.display = 'none';
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
document.getElementById('qsoLoading').style.display = 'none';
|
|
console.error('AJAX Error:', error, xhr.responseText);
|
|
document.getElementById('noQsos').style.display = 'block';
|
|
document.getElementById('noQsos').innerHTML = '<div class="alert alert-danger">Error loading countries: ' + error + '</div>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// View QSOs for specific DXCC
|
|
function viewDxccQsos(dxcc) {
|
|
const modal = new bootstrap.Modal(document.getElementById('dxccQsoModal'));
|
|
currentModalType = 'qso';
|
|
|
|
// Set headers for QSO table
|
|
document.getElementById('qsoTableHeader').innerHTML = `
|
|
<tr>
|
|
<th>Date/Time</th>
|
|
<th>Call</th>
|
|
<th>Band</th>
|
|
<th>Mode</th>
|
|
<th>RST Sent</th>
|
|
<th>RST Rcvd</th>
|
|
<th>QSL</th>
|
|
<th>LoTW</th>
|
|
</tr>
|
|
`;
|
|
document.getElementById('dxccModalTitle').textContent = 'QSOs';
|
|
|
|
// Show loading state
|
|
document.getElementById('qsoLoading').style.display = 'block';
|
|
document.getElementById('qsoTable').style.display = 'none';
|
|
document.getElementById('noQsos').style.display = 'none';
|
|
document.getElementById('dxccEntityName').textContent = 'Loading...';
|
|
|
|
modal.show();
|
|
|
|
// Fetch QSOs via AJAX
|
|
$.ajax({
|
|
url: base_url + 'index.php/awards/get_dxcc_qsos',
|
|
type: 'post',
|
|
data: {
|
|
dxcc_id: dxcc,
|
|
limit: 100
|
|
},
|
|
dataType: 'json',
|
|
success: function(data) {
|
|
document.getElementById('qsoLoading').style.display = 'none';
|
|
|
|
// Get DXCC name from the table
|
|
const dxccName = document.querySelector('[onclick="viewDxccQsos(\'' + dxcc + '\')"]')?.textContent || 'Entity ' + dxcc;
|
|
document.getElementById('dxccEntityName').textContent = dxccName;
|
|
|
|
if (data.count > 0) {
|
|
document.getElementById('qsoTable').style.display = 'table';
|
|
const tbody = document.getElementById('qsoTableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.qsos.forEach(qso => {
|
|
const row = document.createElement('tr');
|
|
const datetime = new Date(qso.col_time_on).toLocaleString();
|
|
row.innerHTML = `
|
|
<td>${datetime}</td>
|
|
<td>${qso.col_call}</td>
|
|
<td>${qso.col_band}</td>
|
|
<td>${qso.col_mode}</td>
|
|
<td>${qso.col_rst_sent || '-'}</td>
|
|
<td>${qso.col_rst_rcvd || '-'}</td>
|
|
<td>${qso.col_qsl_rcvd ? '✓' : '-'}</td>
|
|
<td>${qso.COL_LOTW_QSL_RCVD === 'Y' ? '✓' : '-'}</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
});
|
|
} else {
|
|
document.getElementById('noQsos').style.display = 'block';
|
|
document.getElementById('qsoTable').style.display = 'none';
|
|
}
|
|
},
|
|
error: function(xhr, status, error) {
|
|
document.getElementById('qsoLoading').style.display = 'none';
|
|
console.error('AJAX Error:', error, xhr.responseText);
|
|
document.getElementById('noQsos').style.display = 'block';
|
|
document.getElementById('noQsos').innerHTML = '<div class="alert alert-danger">Error loading QSOs: ' + error + '</div>';
|
|
}
|
|
});
|
|
}
|
|
|
|
// CSV Export functionality
|
|
function exportTableToCSV(filename) {
|
|
const table = document.getElementById('dxcc_table');
|
|
let csv = [];
|
|
|
|
// Add header row
|
|
const headers = Array.from(table.querySelectorAll('thead tr td')).map(h => h.textContent.trim());
|
|
csv.push(headers.join(','));
|
|
|
|
// Add data rows
|
|
const rows = table.querySelectorAll('tbody tr');
|
|
rows.forEach(row => {
|
|
if (row.style.display !== 'none') { // Only export visible rows
|
|
const cells = Array.from(row.querySelectorAll('td')).map(cell => {
|
|
let text = cell.textContent.trim();
|
|
// Escape quotes and wrap in quotes if contains comma
|
|
text = text.replace(/"/g, '""');
|
|
return '"' + text + '"';
|
|
});
|
|
csv.push(cells.join(','));
|
|
}
|
|
});
|
|
|
|
// Create blob and download
|
|
const blob = new Blob([csv.join('\n')], { type: 'text/csv' });
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
}
|
|
</script>
|
|
|