Migrate fully away from Jquery Stage 1

This commit is contained in:
johnnyq
2026-08-14 17:11:01 -04:00
parent dc6b191eed
commit 2354dd1511
9 changed files with 257 additions and 167 deletions

View File

@@ -1,14 +0,0 @@
$('#summaryModal').on('shown.bs.modal', function (e) {
// Perform AJAX request to get the summary
$.ajax({
url: 'post.php?ai_ticket_summary',
method: 'POST',
data: { ticket_id: <?php echo $ticket_id; ?> },
success: function(response) {
$('#summaryContent').html(response);
},
error: function() {
$('#summaryContent').html('Error generating summary.');
}
});
});

View File

@@ -1,53 +1,67 @@
$(document).ready(function() {
document.addEventListener('DOMContentLoaded', function () {
// Function to load contacts for a given client
function loadContacts(clientId) {
if (!clientId) return;
if (!clientId) {
return;
}
var $contactSelect = $('#contact_select');
$contactSelect.html('<option value="">Loading...</option>');
const contactSelect = document.getElementById('contact_select');
if (!contactSelect) {
return;
}
contactSelect.innerHTML = '<option value="">Loading...</option>';
$.ajax({
url: 'ajax.php',
type: 'GET',
dataType: 'json',
data: {
get_client_contacts: 1,
client_id: clientId
},
success: function(response) {
$contactSelect.empty();
const query = new URLSearchParams({
get_client_contacts: 1,
client_id: clientId
});
fetch('ajax.php?' + query.toString(), {
method: 'GET',
headers: { 'Accept': 'application/json' },
credentials: 'same-origin'
})
.then(function (res) {
if (!res.ok) {
throw new Error('HTTP ' + res.status);
}
return res.json();
})
.then(function (response) {
contactSelect.innerHTML = '';
if (response.contacts && response.contacts.length > 0) {
$contactSelect.append('<option value="">Select a contact</option>');
$.each(response.contacts, function(i, contact) {
$contactSelect.append(
$('<option>', {
value: contact.contact_id,
text: contact.contact_name
})
contactSelect.appendChild(new Option('Select a contact', ''));
response.contacts.forEach(function (contact) {
// new Option() sets text content, so contact names are
// never parsed as markup
contactSelect.appendChild(
new Option(contact.contact_name, contact.contact_id)
);
});
} else {
$contactSelect.append('<option value="">No contacts found</option>');
contactSelect.appendChild(new Option('No contacts found', ''));
}
// Refresh Select2
refreshTomSelect($contactSelect[0]);
},
error: function(xhr, status, error) {
// Let Tom Select re-read the replaced option list
refreshTomSelect(contactSelect);
})
.catch(function (error) {
console.error('AJAX Error:', error);
$contactSelect.html('<option value="">Failed to load contacts</option>');
}
});
contactSelect.innerHTML = '<option value="">Failed to load contacts</option>';
});
}
const clientSelect = document.getElementById('client_select');
if (!clientSelect) {
return;
}
// Load contacts for the currently selected client when modal opens
var initialClientId = $('#client_select').val();
loadContacts(initialClientId);
loadContacts(clientSelect.value);
// Load contacts when client changes
$('#client_select').on('change', function() {
var clientId = $(this).val();
loadContacts(clientId);
clientSelect.addEventListener('change', function () {
loadContacts(this.value);
});
});

View File

@@ -86,11 +86,11 @@
});
}
// jQuery parses a data-tasks attribute holding JSON into an array on its own,
// but hand it a string and it stays a string - so handle both
function readTemplateTasks($option) {
// dataset always gives a string; jQuery used to auto-parse JSON here, so
// parse it explicitly and still tolerate an already-parsed value
function readTemplateTasks(option) {
const tasks = $option.data('tasks');
const tasks = option.dataset.tasks;
if (!tasks) {
return [];
@@ -107,42 +107,56 @@
return tasks;
}
$(document).off('click.ticketTasks').on('click.ticketTasks', '#ticketTaskAdd', function () {
document.addEventListener('click', function (e) {
if (!e.target.closest('#ticketTaskAdd')) {
return;
}
addTaskRow('', '');
});
$(document).off('click.ticketTaskRemove').on('click.ticketTaskRemove', '.ticket-task-remove', function () {
$(this).closest('.ticket-task-row').remove();
document.addEventListener('click', function (e) {
const remove = e.target.closest('.ticket-task-remove');
if (!remove) {
return;
}
const row = remove.closest('.ticket-task-row');
if (row) {
row.remove();
}
});
// Ticket template picker - fills in the subject, details and task rows
$(document).off('change.ticketTemplate').on('change.ticketTemplate', '#ticket_template_select', function () {
const $option = $(this).find(':selected');
// Selecting "- No Template -" only unlinks the template - it must not wipe
// whatever the user has already written or added
if (!parseInt($option.val(), 10)) {
document.addEventListener('change', function (e) {
const select = e.target.closest('#ticket_template_select');
if (!select) {
return;
}
const templateSubject = $option.data('subject') || '';
const templateDetails = $option.data('details') || '';
const option = select.options[select.selectedIndex];
$('#subjectInput').val(templateSubject);
// Selecting "- No Template -" only unlinks the template - it must not wipe
// whatever the user has already written or added
if (!parseInt(option.value, 10)) {
return;
}
const templateSubject = option.dataset.subject || '';
const templateDetails = option.dataset.details || '';
document.getElementById('subjectInput').value = templateSubject;
if (window.tinymce) {
const editor = tinymce.get('detailsInput');
if (editor) {
editor.setContent(templateDetails);
} else {
$('#detailsInput').val(templateDetails);
document.getElementById('detailsInput').value = templateDetails;
}
} else {
$('#detailsInput').val(templateDetails);
document.getElementById('detailsInput').value = templateDetails;
}
setTaskRows(readTemplateTasks($option));
setTaskRows(readTemplateTasks(option));
});
})();

View File

@@ -21,14 +21,14 @@
// If the client selector is disabled, we must be on a client-specific page instead. Trigger the lists to update.
if (clientSelectDropdown.disabled) {
let client_id = $(clientSelectDropdown).find(':selected').val();
let client_id = clientSelectDropdown.value;
populateLists(client_id);
}
// Listener for client selection. Populate select lists when a client is selected
$(clientSelectDropdown).on('change', function (e) {
let client_id = $(this).find(':selected').val();
clientSelectDropdown.addEventListener('change', function () {
let client_id = this.value;
// Update the dependent dropdown lists
populateLists(client_id);
@@ -77,7 +77,7 @@
dropdown.innerHTML = '';
// A multi-select keeps showing its old selections until select2 is told the value changed
clearTomSelect(dropdown instanceof Element ? dropdown : $(dropdown)[0]);
clearTomSelect(dropdown);
if (placeholderLabel !== null) {
dropdown[dropdown.length] = new Option(placeholderLabel, placeholderValue);
@@ -89,7 +89,7 @@
// Redraws a select2 component after its options have been replaced
function refreshDropdown(dropdown) {
if (dropdown) {
refreshTomSelect(dropdown instanceof Element ? dropdown : $(dropdown)[0]);
refreshTomSelect(dropdown);
}
}

View File

@@ -1,4 +1,39 @@
$(document).ready(function () {
/**
* $.post replacement. jQuery serialised nested arrays/objects into PHP-style
* bracket params (positions[0][status_id]=...), which is what ajax.php parses,
* so that encoding is reproduced here rather than sending JSON.
*/
function itflowPostForm(url, data) {
const params = new URLSearchParams();
(function add(prefix, value) {
if (Array.isArray(value)) {
value.forEach(function (v, i) {
add(prefix + '[' + i + ']', v);
});
} else if (value !== null && typeof value === 'object') {
Object.keys(value).forEach(function (k) {
add(prefix ? prefix + '[' + k + ']' : k, value[k]);
});
} else {
params.append(prefix, value === true ? 'true' : String(value));
}
})('', data);
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'same-origin',
body: params.toString()
}).then(function (res) {
if (!res.ok) {
throw new Error('HTTP ' + res.status);
}
return res.text();
});
}
document.addEventListener('DOMContentLoaded', function () {
// -------------------------------
// Drag: Kanban Columns (Statuses)
// -------------------------------
@@ -8,16 +43,16 @@ $(document).ready(function () {
draggable: '.kanban-column',
onEnd: function () {
const columnPositions = Array.from(document.querySelectorAll('#kanban-board .kanban-column')).map((col, index) => ({
status_id: $(col).data('status-id'),
status_id: col.dataset.statusId,
status_kanban: index
}));
if (CONFIG_TICKET_MOVING_COLUMNS === 1) {
$.post('ajax.php', {
itflowPostForm('ajax.php', {
update_kanban_status_position: true,
positions: columnPositions
}).fail((xhr) => {
console.error('Error updating status order:', xhr.responseText);
}).catch((err) => {
console.error('Error updating status order:', err);
});
}
}
@@ -46,15 +81,15 @@ $(document).ready(function () {
return;
}
const columnId = $(target).data('status-id');
const columnId = target.dataset.statusId;
const positions = Array.from(target.querySelectorAll('.task')).map((card, index) => {
const ticketId = $(card).data('ticket-id');
const oldStatus = ticketId === $(movedEl).data('ticket-id')
? $(movedEl).data('ticket-status-id')
const ticketId = card.dataset.ticketId;
const oldStatus = ticketId === movedEl.dataset.ticketId
? movedEl.dataset.ticketStatusId
: false;
$(card).data('ticket-status-id', columnId); // update DOM
card.dataset.ticketStatusId = columnId; // update DOM
return {
ticket_id: ticketId,
@@ -64,11 +99,11 @@ $(document).ready(function () {
};
});
$.post('ajax.php', {
itflowPostForm('ajax.php', {
update_kanban_ticket: true,
positions: positions
}).fail((xhr) => {
console.error('Error updating ticket positions:', xhr.responseText);
}).catch((err) => {
console.error('Error updating ticket positions:', err);
});
// Refresh placeholders after update
@@ -81,7 +116,9 @@ $(document).ready(function () {
// 📱 Touch Support: Show drag handle on mobile
// -------------------------------
if (isTouchDevice()) {
$('.drag-handle-class').css('display', 'inline');
document.querySelectorAll('.drag-handle-class').forEach(function (el) {
el.style.display = 'inline';
});
}
// -------------------------------