Notes: basic support added to QSO page

This commit is contained in:
Szymon Porwolik
2025-10-07 23:03:20 +02:00
parent 0de317518e
commit a652b1d38c
4 changed files with 117 additions and 16 deletions

View File

@@ -2,7 +2,6 @@
// Notes controller: handles all note actions, with security and input validation
class Notes extends CI_Controller {
// API endpoint: check for duplicate note title in category for user
// Ensure only authorized users can access Notes controller
function __construct() {
parent::__construct();
@@ -41,19 +40,34 @@ class Notes extends CI_Controller {
function add() {
$this->load->model('note');
$this->load->library('form_validation');
$this->load->library('callbook'); // Used for callsign parsing
$this->load->library('callbook'); // Used for callsign parsing
// Support prefilled title/category from query string
$prefill_title = $this->input->get('title', TRUE);
$prefill_category = $this->input->get('category', TRUE);
$suggested_title = null;
// Validate form fields
$this->form_validation->set_rules('title', 'Note Title', 'required|callback_contacts_title_unique'); // Custom callback for Contacts category
$this->form_validation->set_rules('content', 'Content', 'required');
if ($this->form_validation->run() == FALSE) {
// Use POST if available, otherwise use prefill from query string
$category = $this->input->post('category', TRUE);
if ($category === 'Contacts') {
$suggested_title = strtoupper($this->callbook->get_plaincall($this->input->post('title', TRUE)));
if (empty($category) && !empty($prefill_category)) {
$category = $prefill_category;
}
if ($category === 'Contacts') {
$title_input = $this->input->post('title', TRUE);
if (empty($title_input) && !empty($prefill_title)) {
$title_input = $prefill_title;
}
$suggested_title = strtoupper($this->callbook->get_plaincall($title_input));
}
// Pass prefill values to view
$data['suggested_title'] = $suggested_title;
$data['prefill_title'] = $prefill_title;
$data['prefill_category'] = $prefill_category;
$data['category'] = $category;
$data['page_title'] = __("Add Notes");
$this->load->view('interface_assets/header', $data);
$this->load->view('notes/add');
@@ -241,26 +255,36 @@ class Notes extends CI_Controller {
$id = $this->input->get('id', TRUE); // Optional, for edit
$check_title = $title;
if ($category === 'Contacts') {
$check_title = strtoupper($title);
$this->load->library('callbook');
$check_title = strtoupper($this->callbook->get_plaincall($title));
}
$where = [
'category' => $category,
'cat' => $category,
'user_id' => $user_id,
'title' => $check_title
];
$query = $this->db->get_where('notes', $where);
$duplicate = false;
$exists = false;
$note_id = null;
if ($id) {
foreach ($query->result() as $note) {
if ($note->id != $id) {
$duplicate = true;
$exists = true;
$note_id = $note->id;
break;
}
}
} else {
$duplicate = $query->num_rows() > 0;
if ($query->num_rows() > 0) {
$exists = true;
$note_id = $query->row()->id;
}
}
$this->output->set_content_type('application/json')->set_output(json_encode(['duplicate' => $duplicate]));
$response = ['exists' => $exists];
if ($exists && $note_id) {
$response['id'] = $note_id;
}
$this->output->set_content_type('application/json')->set_output(json_encode($response));
}
// Form validation callback for add: unique Contacts note title for user, only core callsign

View File

@@ -29,7 +29,18 @@
<form method="post" action="<?php echo site_url('notes/add'); ?>" name="notes_add" id="notes_add">
<div class="mb-3">
<label for="inputTitle"><?= __("Title"); ?></label>
<input type="text" name="title" class="form-control" id="inputTitle" value="<?php echo isset($suggested_title) && $suggested_title ? $suggested_title : set_value('title'); ?>">
<input type="text" name="title" class="form-control" id="inputTitle" value="<?php
// Priority: suggested_title > POST value > prefill_title > ''
if (isset($suggested_title) && $suggested_title) {
echo $suggested_title;
} elseif (set_value('title')) {
echo set_value('title');
} elseif (isset($prefill_title) && $prefill_title) {
echo htmlspecialchars($prefill_title);
} else {
echo '';
}
?>">
</div>
<div class="mb-3">
<label for="catSelect">
@@ -39,8 +50,10 @@
</span>
</label>
<select name="category" class="form-select" id="catSelect">
<?php foreach (Note::get_possible_categories() as $category_key => $category_label): ?>
<option value="<?= htmlspecialchars($category_key) ?>"<?= (set_value('category', 'General') == $category_key ? ' selected="selected"' : '') ?>><?= $category_label ?></option>
<?php
$selected_category = set_value('category', isset($category) ? $category : (isset($prefill_category) ? $prefill_category : 'General'));
foreach (Note::get_possible_categories() as $category_key => $category_label): ?>
<option value="<?= htmlspecialchars($category_key) ?>"<?= ($selected_category == $category_key ? ' selected="selected"' : '') ?>><?= $category_label ?></option>
<?php endforeach; ?>
</select>
</div>

View File

@@ -154,7 +154,7 @@ switch ($date_format) {
<!-- Callsign Input -->
<div class="row">
<div class="mb-3 col-md-12">
<label for="callsign"><?= __("Callsign"); ?></label>&nbsp;<i id="check_cluster" data-bs-toggle="tooltip" title="<?= __("Search DXCluster for latest Spot"); ?>" class="fas fa-search"></i>
<label for="callsign"><?= __("Callsign"); ?></label>&nbsp;<i id="check_cluster" data-bs-toggle="tooltip" title="<?= __("Search DXCluster for latest Spot"); ?>" class="fas fa-search"></i></label>&nbsp;<i id="note_create_edit" data-bs-toggle="tooltip" title="<?= __("Create or edit note for this callsign"); ?>" class="fas fa-sticky-note text-secondary"></i>
<div class="input-group">
<input tabindex="7" type="text" class="form-control uppercase" id="callsign" name="callsign" autocomplete="off" required>
<span id="qrz_info" class="input-group-text btn-included-on-field d-none py-0"></span>

View File

@@ -73,6 +73,15 @@ function getUTCDateStamp(el) {
$(el).attr('value', formatted_date);
}
// Note icon state logic
function setNoteIconState(enabled) {
var $icon = $('#note_create_edit');
if (enabled) {
$icon.removeClass('text-secondary');
} else {
$icon.addClass('text-secondary');
}
}
$('#stationProfile').on('change', function () {
var stationProfile = $('#stationProfile').val();
@@ -399,7 +408,7 @@ function parseUserDate(user_provided_date) { // creates JS-Date out of user-prov
month = parseInt(parts[1], 10) - 1;
year = parseInt(parts[2], 10);
}
if (isNaN(day) || day < 1 || day > 31 || isNaN(month) || month < 0 || month > 11 || isNaN(year)) return null;
if (isNaN(day) || day < 1 || day > 31 || isNaN(month) || month < 0 || month > 11 || isNaN(year)) return null;
return new Date(year, month, day);
}
@@ -891,6 +900,7 @@ function reset_fields() {
clearTimeout();
set_timers();
resetTimers(qso_manual);
setNoteIconState(false); // Always gray out note icon on reset
}
$("#callsign").on("focusout", function () {
@@ -921,6 +931,25 @@ $("#callsign").on("focusout", function () {
find_callsign = find_callsign.replaceAll('Ø', '0');
const url = `${base_url}index.php/logbook/json/${find_callsign}/${json_band}/${json_mode}/${stationProfile}/${startDate}/${last_qsos_count}`;
// Check note existence for this callsign
$.get(
window.base_url + 'index.php/notes/check_duplicate',
{
category: 'Contacts',
title: callsign
},
function(data) {
if (typeof data === 'string') {
try { data = JSON.parse(data); } catch (e) { data = {}; }
}
if (data && data.exists === true) {
setNoteIconState(true);
} else {
setNoteIconState(false);
}
}
);
// Replace / in a callsign with - to stop urls breaking
lookupCall = $.getJSON(url, async function (result) {
@@ -2173,6 +2202,8 @@ $(document).ready(function () {
set_timers();
updateStateDropdown('#dxcc_id', '#stateInputLabel', '#location_us_county', '#stationCntyInputQso');
setNoteIconState(false); /// Grey-out note icon
// Clear the localStorage for the qrg units, except the quicklogCallsign and a possible backlog
clearQrgUnits();
set_qrg();
@@ -2400,6 +2431,39 @@ $(document).ready(function () {
});
}
// Note create/edit icon click handler
$('#note_create_edit').on('click', function() {
var callsign = $('#callsign').val().trim();
if (!callsign) {
alert('Please enter a callsign first.');
return;
}
// AJAX to check if note exists for this callsign in Contacts category
$.get(
window.base_url + 'index.php/notes/check_duplicate',
{
category: 'Contacts',
title: callsign
},
function(data) {
// Defensive: try to parse if string
if (typeof data === 'string') {
try { data = JSON.parse(data); } catch (e) { data = {}; }
}
if (data && data.exists === true && data.id) {
window.open(window.base_url + 'index.php/notes/edit/' + data.id, '_blank');
} else if (data && data.exists === false) {
// Open add with prefilled title and Contacts category
var url = window.base_url + 'index.php/notes/add?title=' + encodeURIComponent(callsign) + '&category=Contacts';
window.open(url, '_blank');
} else {
// Unexpected response, show error
alert('Could not check note existence. Please try again.');
}
}
);
});
// everything loaded and ready 2 go
bc.postMessage('ready');
});