All Jquery removed replaced with stand JS and non jqery dependent libs

This commit is contained in:
johnnyq
2026-08-14 17:28:06 -04:00
parent 2354dd1511
commit bbbfff7413
35 changed files with 753 additions and 328 deletions

View File

@@ -1352,16 +1352,24 @@ if (isset($_GET['asset_id'])) {
<!-- JavaScript to Show/Hide Password Form Group -->
<script>
$(document).ready(function() {
$('.authMethod').on('change', function() {
var $form = $(this).closest('.authForm');
if ($(this).val() === 'local') {
$form.find('.passwordGroup').show();
} else {
$form.find('.passwordGroup').hide();
document.addEventListener('DOMContentLoaded', function () {
function syncAuthForm(select) {
var form = select.closest('.authForm');
if (!form) {
return;
}
form.querySelectorAll('.passwordGroup').forEach(function (group) {
group.style.display = select.value === 'local' ? '' : 'none';
});
}
document.querySelectorAll('.authMethod').forEach(function (select) {
select.addEventListener('change', function () {
syncAuthForm(this);
});
syncAuthForm(select);
});
$('.authMethod').trigger('change');
});
});
</script>

View File

@@ -192,7 +192,7 @@ while ($row = mysqli_fetch_assoc($sql)) {
const allDayToggle = document.getElementById("event_add_all_day");
if (allDayToggle) {
allDayToggle.checked = true;
$(allDayToggle).trigger("change");
allDayToggle.dispatchEvent(new Event('change', { bubbles: true }));
}
bootstrap.Modal.getOrCreateInstance(document.getElementById('addCalendarEventModal')).show();
}
@@ -270,7 +270,7 @@ while ($row = mysqli_fetch_assoc($sql)) {
// Last, so the handler in app.js shows or hides the time row to match
if (allDayToggle) {
allDayToggle.checked = selectionInfo.allDay;
$(allDayToggle).trigger("change");
allDayToggle.dispatchEvent(new Event('change', { bubbles: true }));
}
calendar.unselect();
@@ -278,15 +278,14 @@ while ($row = mysqli_fetch_assoc($sql)) {
},
eventClick: function(editEvent) {
var eventId = editEvent.event.id;
var $link = $('<a>', {
href: '#',
'class': 'ajax-modal',
'data-modal-url': 'modals/calendar/calendar_event_edit.php?<?= $client_url ?>&id=' + eventId
});
var link = document.createElement('a');
link.href = '#';
link.className = 'ajax-modal';
link.dataset.modalUrl = 'modals/calendar/calendar_event_edit.php?<?= $client_url ?>&id=' + eventId;
$('body').append($link); // Append to the body
$link.trigger('click'); // Trigger the modal
$link.remove(); // Cleanup
document.body.appendChild(link); // Append to the body
link.click(); // Trigger the modal
link.remove(); // Cleanup
},
dayMaxEvents: true, // allow "more" link when too many events
views: {

View File

@@ -1228,16 +1228,24 @@ if (isset($_GET['contact_id'])) {
);
}
$(document).ready(function() {
$('.authMethod').on('change', function() {
var $form = $(this).closest('.authForm');
if ($(this).val() === 'local') {
$form.find('.passwordGroup').show();
} else {
$form.find('.passwordGroup').hide();
document.addEventListener('DOMContentLoaded', function () {
function syncAuthForm(select) {
var form = select.closest('.authForm');
if (!form) {
return;
}
form.querySelectorAll('.passwordGroup').forEach(function (group) {
group.style.display = select.value === 'local' ? '' : 'none';
});
}
document.querySelectorAll('.authMethod').forEach(function (select) {
select.addEventListener('change', function () {
syncAuthForm(this);
});
syncAuthForm(select);
});
$('.authMethod').trigger('change');
});
});
</script>

View File

@@ -747,78 +747,64 @@ require_once "../includes/footer.php";
?>
<!-- JSON Autocomplete / type ahead -->
<link rel="stylesheet" href="../libs/jquery-ui/jquery-ui.min.css">
<script src="../libs/jquery-ui/jquery-ui.min.js"></script>
<script>
$(function() {
document.addEventListener('DOMContentLoaded', function () {
var availableProducts = <?= $json_products ?? '[]' ?>;
$("#name").autocomplete({
var nameInput = document.getElementById('name');
if (!nameInput) {
return;
}
itflowAutocomplete(nameInput, {
minLength: 1,
delay: 0,
source: function(request, response) {
var term = $.ui.autocomplete.escapeRegex(request.term.toLowerCase());
var matcher = new RegExp(term, "i");
var matches = $.grep(availableProducts, function(item) {
return matcher.test(item.label || "") || matcher.test(item.product_name || "") || matcher.test(item.product_code || "");
});
response(matches);
source: availableProducts,
match: function (item, term) {
return String(item.label || '').toLowerCase().indexOf(term) !== -1
|| String(item.product_name || '').toLowerCase().indexOf(term) !== -1
|| String(item.product_code || '').toLowerCase().indexOf(term) !== -1;
},
select: function (event, ui) {
$("#name").val(ui.item.product_name);
$("#desc").val(ui.item.description);
$("#qty").val(1);
$("#price").val(ui.item.price);
$("#tax").val(ui.item.tax).trigger('change');
$("#product_id").val(ui.item.prod_id);
return false;
render: function (item) {
var esc = itflowEscapeHtml;
var typeText = item.type ? item.type.charAt(0).toUpperCase() + item.type.slice(1).toLowerCase() : "";
var showStock = (typeText.toLowerCase() !== "service");
var taxText = (item.tax_percent != null) ? (parseFloat(item.tax_percent) + "%") : "No tax";
var priceText = (item.price != null && item.price !== "") ? String(item.price) : "";
var stockText = (item.available_stock ?? 0);
return "<div class='d-flex justify-content-between align-items-start'>" +
"<div class='flex-fill pe-2'>" +
"<div class='fw-bold'>" + esc(item.label) +
(typeText ? " <small class='text-muted'>(" + esc(typeText) + ")</small>" : "") +
"</div>" +
"<div class='small text-muted'>" + esc(item.description) + "</div>" +
"<div class='mt-1'>" +
"<span class='badge bg-secondary me-1'>Tax: " + esc(taxText) + "</span>" +
(showStock ? "<span class='badge " + (stockText > 0 ? "bg-success" : "bg-danger") + "'>Stock: " + esc(stockText) + "</span>" : "") +
"</div>" +
"</div>" +
"<div class='text-end'>" +
"<div class='fw-bold'>" + esc(priceText) + "</div>" +
"</div>" +
"</div>";
},
onSelect: function (item) {
document.getElementById('name').value = item.product_name;
document.getElementById('desc').value = item.description;
document.getElementById('qty').value = 1;
document.getElementById('price').value = item.price;
setTomSelectValue(document.getElementById('tax'), item.tax);
document.getElementById('product_id').value = item.prod_id;
}
});
// Typing over the name by hand breaks the link to the product
$("#name").on("input", function() {
$("#product_id").val(0);
nameInput.addEventListener('input', function () {
document.getElementById('product_id').value = 0;
});
// Product names and descriptions are user supplied - escape before
// building markup, the default renderer uses .text() for this reason
function esc(value) {
return $("<div>").text(value == null ? "" : value).html();
}
// Keep it simple: default jQuery UI look, just richer content
$("#name").autocomplete("instance")._renderItem = function(ul, item) {
var typeText = item.type ? item.type.charAt(0).toUpperCase() + item.type.slice(1).toLowerCase() : "";
var showStock = (typeText.toLowerCase() !== "service");
var taxText = (item.tax_percent != null) ? (parseFloat(item.tax_percent) + "%") : "No tax";
var priceText = (item.price != null && item.price !== "") ? String(item.price) : "";
var stockText = (item.available_stock ?? 0);
var infoLeft =
"<div class='d-flex justify-content-between align-items-start'>" +
"<div class='flex-fill pe-2'>" +
"<div class='fw-bold'>" + esc(item.label) +
(typeText ? " <small class='text-muted'>(" + esc(typeText) + ")</small>" : "") +
"</div>" +
"<div class='small text-muted'>" + esc(item.description) + "</div>" +
"<div class='mt-1'>" +
"<span class='badge bg-secondary me-1'>Tax: " + esc(taxText) + "</span>" +
(showStock ? "<span class='badge " + (stockText > 0 ? "bg-success" : "bg-danger") + "'>Stock: " + esc(stockText) + "</span>" : "") +
"</div>" +
"</div>" +
"<div class='text-end'>" +
"<div class='fw-bold'>" + esc(priceText) + "</div>" +
"</div>" +
"</div>";
// Use the jQuery UI wrapper so default hover/focus styles apply
return $("<li>")
.append($("<div class='ui-menu-item-wrapper'>").append(infoLeft))
.appendTo(ul);
};
});
</script>
@@ -835,7 +821,7 @@ new Sortable(document.querySelector('table#items tbody'), {
order: index
}));
$.post('ajax.php', {
itflowPostForm('ajax.php', {
update_invoice_items_order: true,
csrf_token: '<?= $_SESSION['csrf_token'] ?>',
invoice_id: <?= $invoice_id ?>,

View File

@@ -1,38 +1,3 @@
/**
* $.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)

View File

@@ -460,18 +460,24 @@ ob_start();
</form>
<!-- JSON Autocomplete / type ahead -->
<link rel="stylesheet" href="/libs/jquery-ui/jquery-ui.min.css">
<script src="/libs/jquery-ui/jquery-ui.min.js"></script>
<script>
$(function() {
document.addEventListener('DOMContentLoaded', function () {
var operatingSystems = <?= $json_os ?>;
$("#os").autocomplete({
source: operatingSystems, // Should be an array of objects with 'label' and 'value'
select: function(event, ui) {
$("#os").val(ui.item.label); // Set the input field value to the selected label
return false;
var osInput = document.getElementById('os');
if (!osInput) {
return;
}
itflowAutocomplete(osInput, {
minLength: 1,
source: operatingSystems,
onSelect: function (item) {
osInput.value = item.label;
}
});
});
</script>

View File

@@ -296,17 +296,23 @@ function generatePassword() {
);
}
$(document).ready(function() {
$('.authMethod').on('change', function() {
var $form = $(this).closest('.authForm');
if ($(this).val() === 'local') {
$form.find('.passwordGroup').show();
} else {
$form.find('.passwordGroup').hide();
document.addEventListener('DOMContentLoaded', function () {
function syncAuthForm(select) {
var form = select.closest('.authForm');
if (!form) {
return;
}
});
$('.authMethod').trigger('change');
form.querySelectorAll('.passwordGroup').forEach(function (group) {
group.style.display = select.value === 'local' ? '' : 'none';
});
}
document.querySelectorAll('.authMethod').forEach(function (select) {
select.addEventListener('change', function () {
syncAuthForm(this);
});
syncAuthForm(select);
});
});
</script>

View File

@@ -336,17 +336,23 @@ function generatePassword() {
);
}
$(document).ready(function() {
$('.authMethod').on('change', function() {
var $form = $(this).closest('.authForm');
if ($(this).val() === 'local') {
$form.find('.passwordGroup').show();
} else {
$form.find('.passwordGroup').hide();
document.addEventListener('DOMContentLoaded', function () {
function syncAuthForm(select) {
var form = select.closest('.authForm');
if (!form) {
return;
}
});
$('.authMethod').trigger('change');
form.querySelectorAll('.passwordGroup').forEach(function (group) {
group.style.display = select.value === 'local' ? '' : 'none';
});
}
document.querySelectorAll('.authMethod').forEach(function (select) {
select.addEventListener('change', function () {
syncAuthForm(this);
});
syncAuthForm(select);
});
});
</script>

View File

@@ -248,8 +248,6 @@ ob_start();
<!-- Recurring Ticket Client/Contact JS -->
<link rel="stylesheet" href="/libs/jquery-ui/jquery-ui.min.css">
<script src="/libs/jquery-ui/jquery-ui.min.js"></script>
<script src="/agent/js/tickets_add_modal.js"></script>
<script src="/agent/js/ticket_tasks_modal.js"></script>

View File

@@ -285,8 +285,6 @@ ob_start();
<!-- Ticket Client/Contact JS -->
<link rel="stylesheet" href="/libs/jquery-ui/jquery-ui.min.css">
<script src="/libs/jquery-ui/jquery-ui.min.js"></script>
<script src="/agent/js/tickets_add_modal.js"></script>
<script src="/agent/js/ticket_tasks_modal.js"></script>

View File

@@ -19,18 +19,30 @@ ob_start();
</div>
<script>
$(function() {
$.ajax({
url: 'ajax.php?ai_ticket_summary',
document.addEventListener('DOMContentLoaded', function () {
var target = document.getElementById('summaryContent');
if (!target) {
return;
}
fetch('ajax.php?ai_ticket_summary', {
method: 'POST',
data: { ticket_id: <?= $ticket_id ?> },
success: function(response) {
$('#summaryContent').html(response);
},
error: function() {
$('#summaryContent').html('Error generating summary.');
}
});
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
credentials: 'same-origin',
body: new URLSearchParams({ ticket_id: '<?= $ticket_id ?>' }).toString()
})
.then(function (res) {
if (!res.ok) {
throw new Error('HTTP ' + res.status);
}
return res.text();
})
.then(function (html) {
target.innerHTML = html;
})
.catch(function () {
target.textContent = 'Error generating summary.';
});
});
</script>

View File

@@ -81,57 +81,93 @@ ob_start();
<!-- JS to make the correct boxes appear depending on if internal/client approval) -->
<script>
$('#approval_scope').on('change', function() {
const scope = $(this).val();
const typeSelect = $('#approval_type');
const wrapper = $('#approval_type_wrapper');
(function () {
var scopeSelect = document.getElementById('approval_scope');
var typeSelect = document.getElementById('approval_type');
var typeWrapper = document.getElementById('approval_type_wrapper');
var userWrapper = document.getElementById('specific_user_wrapper');
var userSelect = document.getElementById('specific_user_select');
typeSelect.empty();
$('#specific_user_wrapper').addClass('d-none');
if (!scope) {
wrapper.addClass('d-none');
if (!scopeSelect || !typeSelect) {
return;
}
wrapper.removeClass('d-none');
if (scope === 'internal') {
typeSelect.append('<option value="">Select...</option>');
typeSelect.append('<option value="any">Any internal reviewer</option>');
typeSelect.append('<option value="specific">Specific agent</option>');
}
if (scope === 'client') {
typeSelect.append('<option value="">Select...</option>');
typeSelect.append('<option value="any">Ticket contact</option>');
typeSelect.append('<option value="technical">Technical contacts</option>');
typeSelect.append('<option value="billing">Billing contacts</option>');
}
});
// Specific user (internal only for now)
$('#approval_type').on('change', function() {
const type = $(this).val();
const scope = $('#approval_scope').val();
const userSelect = $('#specific_user_select');
if (type !== 'specific' || scope !== 'internal') {
$('#specific_user_wrapper').addClass('d-none');
return;
}
$('#specific_user_wrapper').removeClass('d-none');
userSelect.empty().append('<option value="">Loading...</option>');
$.getJSON('ajax.php?get_internal_users=true', function(data) {
userSelect.empty().append('<option value="">Select user...</option>');
data.users.forEach(function(u) {
userSelect.append(`<option value="${u.user_id}">${u.user_name}</option>`);
function setOptions(select, pairs) {
select.innerHTML = '';
pairs.forEach(function (pair) {
// new Option() assigns text, so nothing here is parsed as markup
select.appendChild(new Option(pair[1], pair[0]));
});
});
});
// the selects are Tom Select enhanced, so it has to re-read them
refreshTomSelect(select);
}
scopeSelect.addEventListener('change', function () {
var scope = this.value;
setOptions(typeSelect, []);
userWrapper.classList.add('d-none');
if (!scope) {
typeWrapper.classList.add('d-none');
return;
}
typeWrapper.classList.remove('d-none');
if (scope === 'internal') {
setOptions(typeSelect, [
['', 'Select...'],
['any', 'Any internal reviewer'],
['specific', 'Specific agent']
]);
}
if (scope === 'client') {
setOptions(typeSelect, [
['', 'Select...'],
['any', 'Ticket contact'],
['technical', 'Technical contacts'],
['billing', 'Billing contacts']
]);
}
});
// Specific user (internal only for now)
typeSelect.addEventListener('change', function () {
var type = this.value;
var scope = scopeSelect.value;
if (type !== 'specific' || scope !== 'internal') {
userWrapper.classList.add('d-none');
return;
}
userWrapper.classList.remove('d-none');
setOptions(userSelect, [['', 'Loading...']]);
fetch('ajax.php?get_internal_users=true', {
headers: { 'Accept': 'application/json' },
credentials: 'same-origin'
})
.then(function (res) {
if (!res.ok) {
throw new Error('HTTP ' + res.status);
}
return res.json();
})
.then(function (data) {
var pairs = [['', 'Select user...']];
data.users.forEach(function (u) {
pairs.push([u.user_id, u.user_name]);
});
setOptions(userSelect, pairs);
})
.catch(function () {
setOptions(userSelect, [['', 'Failed to load users']]);
});
});
})();
</script>
<?php

View File

@@ -588,24 +588,66 @@ require_once "../includes/footer.php";
<!-- JSON Autocomplete / type ahead -->
<!-- //TODO: Move to js/ -->
<link rel="stylesheet" href="../libs/jquery-ui/jquery-ui.min.css">
<script src="../libs/jquery-ui/jquery-ui.min.js"></script>
<script>
$(function() {
var availableProducts = <?= $json_products ?? '[]' ?>;
$("#name").autocomplete({
source: availableProducts,
select: function(event, ui) {
$("#name").val(ui.item.label); // Product name field - this seemingly has to referenced as label
$("#desc").val(ui.item.description); // Product description field
$("#qty").val(1); // Product quantity field automatically make it a 1
$("#price").val(ui.item.price); // Product price field
setTomSelectValue(document.getElementById("tax"), ui.item.tax); // Tax field - setValue repaints the Tom Select widget
return false;
}
});
document.addEventListener('DOMContentLoaded', function () {
var availableProducts = <?= $json_products ?? '[]' ?>;
var nameInput = document.getElementById('name');
if (!nameInput) {
return;
}
itflowAutocomplete(nameInput, {
minLength: 1,
source: availableProducts,
match: function (item, term) {
return String(item.label || '').toLowerCase().indexOf(term) !== -1
|| String(item.product_name || '').toLowerCase().indexOf(term) !== -1
|| String(item.product_code || '').toLowerCase().indexOf(term) !== -1;
},
render: function (item) {
var esc = itflowEscapeHtml;
var typeText = item.type ? item.type.charAt(0).toUpperCase() + item.type.slice(1).toLowerCase() : "";
var showStock = (typeText.toLowerCase() !== "service");
var taxText = (item.tax_percent != null) ? (parseFloat(item.tax_percent) + "%") : "No tax";
var priceText = (item.price != null && item.price !== "") ? String(item.price) : "";
var stockText = (item.available_stock ?? 0);
return "<div class='d-flex justify-content-between align-items-start'>" +
"<div class='flex-fill pe-2'>" +
"<div class='fw-bold'>" + esc(item.label) +
(typeText ? " <small class='text-muted'>(" + esc(typeText) + ")</small>" : "") +
"</div>" +
"<div class='small text-muted'>" + esc(item.description) + "</div>" +
"<div class='mt-1'>" +
"<span class='badge bg-secondary me-1'>Tax: " + esc(taxText) + "</span>" +
(showStock ? "<span class='badge " + (stockText > 0 ? "bg-success" : "bg-danger") + "'>Stock: " + esc(stockText) + "</span>" : "") +
"</div>" +
"</div>" +
"<div class='text-end'>" +
"<div class='fw-bold'>" + esc(priceText) + "</div>" +
"</div>" +
"</div>";
},
onSelect: function (item) {
document.getElementById('name').value = item.product_name;
document.getElementById('desc').value = item.description;
document.getElementById('qty').value = 1;
document.getElementById('price').value = item.price;
setTomSelectValue(document.getElementById('tax'), item.tax);
document.getElementById('product_id').value = item.prod_id;
}
});
// Typing over the name by hand breaks the link to the product
nameInput.addEventListener('input', function () {
document.getElementById('product_id').value = 0;
});
});
</script>
<script src="../libs/SortableJS/Sortable.min.js"></script>
@@ -620,7 +662,7 @@ new Sortable(document.querySelector('table#items tbody'), {
order: index
}));
$.post('ajax.php', {
itflowPostForm('ajax.php', {
update_quote_items_order: true,
csrf_token: '<?= $_SESSION['csrf_token'] ?>',
quote_id: <?= $quote_id ?>,

View File

@@ -495,24 +495,66 @@ require_once "../includes/footer.php";
?>
<!-- JSON Autocomplete / type ahead -->
<link rel="stylesheet" href="../libs/jquery-ui/jquery-ui.min.css">
<script src="../libs/jquery-ui/jquery-ui.min.js"></script>
<script>
$(function() {
var availableProducts = <?= $json_products ?? '[]' ?>;
$("#name").autocomplete({
source: availableProducts,
select: function (event, ui) {
$("#name").val(ui.item.label); // Product name field - this seemingly has to referenced as label
$("#desc").val(ui.item.description); // Product description field
$("#qty").val(1); // Product quantity field automatically make it a 1
$("#price").val(ui.item.price); // Product price field
setTomSelectValue(document.getElementById("tax"), ui.item.tax); // Tax field - setValue repaints the Tom Select widget
return false;
}
});
document.addEventListener('DOMContentLoaded', function () {
var availableProducts = <?= $json_products ?? '[]' ?>;
var nameInput = document.getElementById('name');
if (!nameInput) {
return;
}
itflowAutocomplete(nameInput, {
minLength: 1,
source: availableProducts,
match: function (item, term) {
return String(item.label || '').toLowerCase().indexOf(term) !== -1
|| String(item.product_name || '').toLowerCase().indexOf(term) !== -1
|| String(item.product_code || '').toLowerCase().indexOf(term) !== -1;
},
render: function (item) {
var esc = itflowEscapeHtml;
var typeText = item.type ? item.type.charAt(0).toUpperCase() + item.type.slice(1).toLowerCase() : "";
var showStock = (typeText.toLowerCase() !== "service");
var taxText = (item.tax_percent != null) ? (parseFloat(item.tax_percent) + "%") : "No tax";
var priceText = (item.price != null && item.price !== "") ? String(item.price) : "";
var stockText = (item.available_stock ?? 0);
return "<div class='d-flex justify-content-between align-items-start'>" +
"<div class='flex-fill pe-2'>" +
"<div class='fw-bold'>" + esc(item.label) +
(typeText ? " <small class='text-muted'>(" + esc(typeText) + ")</small>" : "") +
"</div>" +
"<div class='small text-muted'>" + esc(item.description) + "</div>" +
"<div class='mt-1'>" +
"<span class='badge bg-secondary me-1'>Tax: " + esc(taxText) + "</span>" +
(showStock ? "<span class='badge " + (stockText > 0 ? "bg-success" : "bg-danger") + "'>Stock: " + esc(stockText) + "</span>" : "") +
"</div>" +
"</div>" +
"<div class='text-end'>" +
"<div class='fw-bold'>" + esc(priceText) + "</div>" +
"</div>" +
"</div>";
},
onSelect: function (item) {
document.getElementById('name').value = item.product_name;
document.getElementById('desc').value = item.description;
document.getElementById('qty').value = 1;
document.getElementById('price').value = item.price;
setTomSelectValue(document.getElementById('tax'), item.tax);
document.getElementById('product_id').value = item.prod_id;
}
});
// Typing over the name by hand breaks the link to the product
nameInput.addEventListener('input', function () {
document.getElementById('product_id').value = 0;
});
});
</script>
<script src="../libs/SortableJS/Sortable.min.js"></script>
@@ -527,7 +569,7 @@ new Sortable(document.querySelector('table#items tbody'), {
order: index
}));
$.post('ajax.php', {
itflowPostForm('ajax.php', {
update_recurring_invoice_items_order: true,
csrf_token: '<?= $_SESSION['csrf_token'] ?>',
recurring_invoice_id: <?= $recurring_invoice_id ?>,

View File

@@ -1414,7 +1414,7 @@ require_once "../includes/footer.php";
order: index
}));
$.post('ajax.php', {
itflowPostForm('ajax.php', {
update_ticket_tasks_order: true,
csrf_token: '<?= $_SESSION['csrf_token'] ?>',
ticket_id: <?= $ticket_id ?>,

View File

@@ -47,7 +47,6 @@ $data = "otpauth://totp/ITFlow:$session_email?secret=$token";
<link rel="stylesheet" href="../../libs/adminlte/css/adminlte.min.css">
<!-- jQuery -->
<script src="../../libs/jquery/jquery.min.js"></script>
</head>
<body class="hold-transition login-page">
@@ -103,10 +102,20 @@ $data = "otpauth://totp/ITFlow:$session_email?secret=$token";
<script>
// Slide alert up after 4 secs
$("#alert").fadeTo(5000, 500).slideUp(500, function(){
$("#alert").slideUp(500);
});
// Fade the alert out after 5s, then collapse it
(function () {
const alertEl = document.getElementById('alert');
if (!alertEl) {
return;
}
setTimeout(function () {
alertEl.style.transition = 'opacity .5s linear';
alertEl.style.opacity = '0';
setTimeout(function () {
alertEl.style.display = 'none';
}, 500);
}, 5000);
})();
// ClipboardJS
@@ -114,7 +123,7 @@ $data = "otpauth://totp/ITFlow:$session_email?secret=$token";
// on screen. This page is standalone and does not load js/app.js, so it
// carries its own copy of the helper.
function flashTooltip(button, message) {
const el = button instanceof Element ? button : $(button)[0];
const el = button instanceof Element ? button : document.querySelector(button);
if (!el) {
return;
}
@@ -144,10 +153,8 @@ $data = "otpauth://totp/ITFlow:$session_email?secret=$token";
});
// Enable Popovers
$(function () {
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) {
bootstrap.Popover.getOrCreateInstance(el);
})
document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) {
bootstrap.Popover.getOrCreateInstance(el);
});
</script>