diff --git a/application/controllers/Awards.php b/application/controllers/Awards.php index 596dcc439..9af3993bc 100644 --- a/application/controllers/Awards.php +++ b/application/controllers/Awards.php @@ -160,6 +160,10 @@ class Awards extends CI_Controller $dxcclist = $this->dxcc->fetchdxcc($postdata); $data['dxcc_array'] = $this->dxcc->get_dxcc_array($dxcclist, $bands, $postdata); $data['dxcc_summary'] = $this->dxcc->get_dxcc_summary($bands, $postdata); + + // Calculate continent breakdown and totals + $data['continent_breakdown'] = $this->dxcc->get_continent_breakdown($dxcclist); + $data['dxcc_statistics'] = $this->dxcc->get_dxcc_statistics($data['dxcc_array'], $postdata); // Render Page $data['page_title'] = "Awards - DXCC"; @@ -1290,4 +1294,216 @@ class Awards extends CI_Controller } } } -} + + /* + * Get QSOs for a specific DXCC entity + */ + public function get_dxcc_qsos() + { + $this->load->model('logbooks_model'); + + $dxcc_id = $this->security->xss_clean($this->input->post('dxcc_id')); + $limit = $this->security->xss_clean($this->input->post('limit')) ?: 20; + + if (!$dxcc_id || !is_numeric($dxcc_id)) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'Invalid DXCC ID')); + return; + } + + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + + if (!$logbooks_locations_array) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'No logbook data')); + return; + } + + $location_list = "'" . implode("','", $logbooks_locations_array) . "'"; + + try { + // Get QSOs for this DXCC + $query = $this->db->query(" + SELECT + col_time_on, + col_call, + col_band, + col_mode, + col_rst_sent, + col_rst_rcvd, + col_qsl_sent, + col_qsl_rcvd, + COL_LOTW_QSL_SENT, + COL_LOTW_QSL_RCVD + FROM " . $this->config->item('table_name') . " + WHERE station_id IN (" . $location_list . ") + AND col_dxcc = " . $dxcc_id . " + ORDER BY col_time_on DESC + LIMIT " . intval($limit) + ); + + if ($query->num_rows() > 0) { + $qsos = $query->result_array(); + header('Content-Type: application/json'); + echo json_encode(array('qsos' => $qsos, 'count' => $query->num_rows())); + } else { + header('Content-Type: application/json'); + echo json_encode(array('qsos' => array(), 'count' => 0)); + } + } catch (Exception $e) { + header('Content-Type: application/json'); + echo json_encode(array('error' => $e->getMessage())); + } + } + + /* + * Get QSOs for a specific DXCC entity filtered by status (Confirmed or Worked) + */ + public function get_dxcc_qsos_by_status() + { + $this->load->model('logbooks_model'); + + $dxcc_id = $this->security->xss_clean($this->input->post('dxcc_id')); + $status = $this->security->xss_clean($this->input->post('status')); + $limit = $this->security->xss_clean($this->input->post('limit')) ?: 100; + + if (!$dxcc_id || !is_numeric($dxcc_id) || !$status) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'Invalid parameters', 'count' => 0, 'qsos' => array())); + return; + } + + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + + if (!$logbooks_locations_array) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'No logbook data', 'count' => 0, 'qsos' => array())); + return; + } + + $location_list = "'" . implode("','", $logbooks_locations_array) . "'"; + + try { + // Build WHERE clause for status filter + $status_where = ''; + if ($status === 'C') { + // Confirmed: either QSL received or LoTW received + $status_where = " AND (col_qsl_rcvd = 1 OR COL_LOTW_QSL_RCVD = 'Y')"; + } elseif ($status === 'W') { + // Worked but not confirmed: neither QSL nor LoTW received + $status_where = " AND (col_qsl_rcvd IS NULL OR col_qsl_rcvd != 1) AND (COL_LOTW_QSL_RCVD IS NULL OR COL_LOTW_QSL_RCVD != 'Y')"; + } + + // Get QSOs for this DXCC filtered by status + $query = $this->db->query(" + SELECT + col_time_on, + col_call, + col_band, + col_mode, + col_rst_sent, + col_rst_rcvd, + col_qsl_sent, + col_qsl_rcvd, + COL_LOTW_QSL_SENT, + COL_LOTW_QSL_RCVD + FROM " . $this->config->item('table_name') . " + WHERE station_id IN (" . $location_list . ") + AND col_dxcc = " . $dxcc_id . $status_where . " + ORDER BY col_time_on DESC + LIMIT " . intval($limit) + ); + + if ($query->num_rows() > 0) { + $qsos = $query->result_array(); + header('Content-Type: application/json'); + echo json_encode(array('qsos' => $qsos, 'count' => $query->num_rows())); + } else { + header('Content-Type: application/json'); + echo json_encode(array('qsos' => array(), 'count' => 0)); + } + } catch (Exception $e) { + header('Content-Type: application/json'); + echo json_encode(array('error' => $e->getMessage(), 'count' => 0, 'qsos' => array())); + } + } + + /* + * Get DXCC entities for a specific continent with their status + */ + public function get_continent_qsos() + { + $this->load->model('logbooks_model'); + $this->load->model('dxcc'); + + $continent_code = $this->security->xss_clean($this->input->post('continent_code')); + + if (!$continent_code) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'Invalid continent code', 'count' => 0, 'entities' => array())); + return; + } + + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + + if (!$logbooks_locations_array) { + header('Content-Type: application/json'); + echo json_encode(array('error' => 'No logbook data', 'count' => 0, 'entities' => array())); + return; + } + + $location_list = "'" . implode("','", $logbooks_locations_array) . "'"; + + try { + // Get all DXCC entities for this continent + $query = $this->db->query(" + SELECT adif, name, prefix, cont + FROM dxcc_entities + WHERE cont = '" . $this->db->escape_like_str($continent_code) . "' + ORDER BY name ASC + "); + + $entities = array(); + if ($query->num_rows() > 0) { + foreach ($query->result() as $entity) { + // Check if worked + $worked_query = $this->db->query(" + SELECT COUNT(*) as count FROM " . $this->config->item('table_name') . " + WHERE station_id IN (" . $location_list . ") + AND col_dxcc = " . $entity->adif + ); + $worked = $worked_query->row()->count > 0 ? true : false; + + // Check if confirmed + $confirmed_query = $this->db->query(" + SELECT COUNT(*) as count FROM " . $this->config->item('table_name') . " + WHERE station_id IN (" . $location_list . ") + AND col_dxcc = " . $entity->adif . " + AND (col_qsl_rcvd = 1 OR COL_LOTW_QSL_RCVD = 'Y') + "); + $confirmed = $confirmed_query->row()->count > 0 ? true : false; + + $status = 'unworked'; + if ($confirmed) { + $status = 'confirmed'; + } elseif ($worked) { + $status = 'worked'; + } + + $entities[] = array( + 'adif' => $entity->adif, + 'name' => $entity->name, + 'prefix' => $entity->prefix, + 'status' => $status + ); + } + } + + header('Content-Type: application/json'); + echo json_encode(array('entities' => $entities, 'count' => count($entities))); + } catch (Exception $e) { + header('Content-Type: application/json'); + echo json_encode(array('error' => $e->getMessage(), 'count' => 0, 'entities' => array())); + } + } +} \ No newline at end of file diff --git a/application/models/Dxcc.php b/application/models/Dxcc.php index ef02fea57..3ad61cfe9 100644 --- a/application/models/Dxcc.php +++ b/application/models/Dxcc.php @@ -101,7 +101,7 @@ class DXCC extends CI_Model { if ($postdata['worked'] != NULL) { $workedDXCC = $this->getDxccBandWorked($location_list, $band, $postdata); foreach ($workedDXCC as $wdxcc) { - $dxccMatrix[$wdxcc->dxcc][$band] = '
name).'","'. $band . '","'. $postdata['mode'] . '","DXCC", "")\'>W
'; + $dxccMatrix[$wdxcc->dxcc][$band] = '
dxcc . ', "W")\'>W
'; } } @@ -109,7 +109,7 @@ class DXCC extends CI_Model { if ($postdata['confirmed'] != NULL) { $confirmedDXCC = $this->getDxccBandConfirmed($location_list, $band, $postdata); foreach ($confirmedDXCC as $cdxcc) { - $dxccMatrix[$cdxcc->dxcc][$band] = '
name).'","'. $band . '","'. $postdata['mode'] . '","DXCC","'.$qsl.'")\'>C
'; + $dxccMatrix[$cdxcc->dxcc][$band] = '
dxcc . ', "C")\'>C
'; } } } @@ -222,7 +222,7 @@ class DXCC extends CI_Model { $location_list = "'".implode("','",$logbooks_locations_array)."'"; - $sql = "select adif, prefix, name, date(end) Enddate, date(start) Startdate, lat, `long` + $sql = "select adif, prefix, name, cont, date(end) Enddate, date(start) Startdate, lat, `long` from dxcc_entities"; if ($postdata['notworked'] == NULL) { @@ -507,5 +507,152 @@ class DXCC extends CI_Model { return $query->row(); } + + /* + * Get continent breakdown for DXCC statistics + */ + function get_continent_breakdown($dxccArray) { + $continents = array( + 'Africa' => 0, + 'Antarctica' => 0, + 'Asia' => 0, + 'Europe' => 0, + 'NorthAmerica' => 0, + 'SouthAmerica' => 0, + 'Oceania' => 0 + ); + + if (!$dxccArray) { + return $continents; + } + + // Map continent codes to names + $continentMap = array( + 'AF' => 'Africa', + 'AN' => 'Antarctica', + 'AS' => 'Asia', + 'EU' => 'Europe', + 'NA' => 'NorthAmerica', + 'SA' => 'SouthAmerica', + 'OC' => 'Oceania' + ); + + foreach ($dxccArray as $dxcc) { + if (isset($dxcc->cont) && isset($continentMap[$dxcc->cont])) { + $continentName = $continentMap[$dxcc->cont]; + $continents[$continentName]++; + } + } + + return $continents; + } + + /* + * Get overall DXCC statistics + */ + function get_dxcc_statistics($dxcc_array, $postdata) { + $stats = array( + 'total_worked' => 0, + 'total_confirmed' => 0, + 'worked_by_continent' => array( + 'Africa' => 0, + 'Antarctica' => 0, + 'Asia' => 0, + 'Europe' => 0, + 'NorthAmerica' => 0, + 'SouthAmerica' => 0, + 'Oceania' => 0 + ), + 'confirmed_by_continent' => array( + 'Africa' => 0, + 'Antarctica' => 0, + 'Asia' => 0, + 'Europe' => 0, + 'NorthAmerica' => 0, + 'SouthAmerica' => 0, + 'Oceania' => 0 + ), + 'milestones' => array( + 100 => false, + 200 => false, + 300 => false + ) + ); + + if (!$dxcc_array) { + return $stats; + } + + // Get DXCC entities to map continents + $dxcc_result = $this->db->get('dxcc_entities')->result_array(); + $all_dxcc = array(); + foreach ($dxcc_result as $row) { + $all_dxcc[$row['adif']] = $row; + } + + // Map continent codes to names + $continentMap = array( + 'AF' => 'Africa', + 'AN' => 'Antarctica', + 'AS' => 'Asia', + 'EU' => 'Europe', + 'NA' => 'NorthAmerica', + 'SA' => 'SouthAmerica', + 'OC' => 'Oceania' + ); + + // Count worked DXCC from array + $worked_count = 0; + $confirmed_count = 0; + + foreach ($dxcc_array as $adif => $entity) { + // Check if this DXCC has any confirmed contacts (any cell with 'bg-success') + $is_worked = false; + $is_confirmed = false; + + foreach ($entity as $key => $value) { + if (is_string($value) && strpos($value, 'bg-success') !== false) { + $is_confirmed = true; + $is_worked = true; + break; + } + if (is_string($value) && strpos($value, 'bg-danger') !== false) { + $is_worked = true; + } + } + + if ($is_worked) { + $worked_count++; + } + if ($is_confirmed) { + $confirmed_count++; + } + + // Map continent stats + if (isset($all_dxcc[$adif]) && isset($all_dxcc[$adif]['cont'])) { + $cont_code = $all_dxcc[$adif]['cont']; + $continent = isset($continentMap[$cont_code]) ? $continentMap[$cont_code] : null; + + if ($continent) { + if ($is_confirmed) { + $stats['confirmed_by_continent'][$continent]++; + } + if ($is_worked) { + $stats['worked_by_continent'][$continent]++; + } + } + } + } + + $stats['total_worked'] = $worked_count; + $stats['total_confirmed'] = $confirmed_count; + + // Check milestone achievements + $stats['milestones'][100] = $stats['total_worked'] >= 100; + $stats['milestones'][200] = $stats['total_worked'] >= 200; + $stats['milestones'][300] = $stats['total_worked'] >= 300; + + return $stats; + } } ?> diff --git a/application/views/awards/dxcc/index.php b/application/views/awards/dxcc/index.php index 0ad0a0b44..ccb4d6707 100644 --- a/application/views/awards/dxcc/index.php +++ b/application/views/awards/dxcc/index.php @@ -30,7 +30,121 @@ 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; } +} +

@@ -47,139 +161,257 @@
-
-
- -
-
Deleted DXCC
-
-
- input->post('includedeleted')) echo ' checked="checked"'; ?> > - + + +
+ +
+
+
DXCC Progress
+
+
+
Total Worked
+
+
+
+
Total Confirmed
+
+
- -
-
Worked / Confirmed
-
-
- input->post('worked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - + +
+
+
🏆 Milestone Progress
+
+
+
100
+
+ +
-
- input->post('confirmed') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - +
+
200
+
+ +
-
- input->post('notworked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - +
+
300
+
+ +
+
+
-
-
QSL Type
-
-
- input->post('qsl') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('lotw') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('eqsl')) echo ' checked="checked"'; ?> > - -
-
-
- -
-
Continents
-
-
- input->post('Antarctica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('Africa') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('Asia') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('Europe') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('NorthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('SouthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
- input->post('Oceania') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > - -
-
-
- -
- -
- -
-
- -
- -
- +
+
+
+ / +
+
+
+
+
+
+
+
+ -
- -
- - - - + +
+
+ Filters + +
+
+
+ +
+ + + + + +
-
-
-
+
+ +
+
Deleted DXCC
+
+
+ input->post('includedeleted')) echo ' checked="checked"'; ?> > + +
+
+
+ + +
+
Worked / Confirmed
+
+
+ input->post('worked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('confirmed') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('notworked') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+
+ +
+
QSL Type
+
+
+ input->post('qsl') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('lotw') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('eqsl')) echo ' checked="checked"'; ?> > + +
+
+
+ +
+
Continents
+
+
+ input->post('Antarctica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('Africa') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('Asia') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('Europe') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('NorthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('SouthAmerica') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+ input->post('Oceania') || $this->input->method() !== 'post') echo ' checked="checked"'; ?> > + +
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + + + + +
+
+ +
+ + +