mirror of
https://github.com/itflow-org/itflow
synced 2026-08-31 11:55:11 +00:00
All Jquery removed replaced with stand JS and non jqery dependent libs
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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 ?>,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ?>,
|
||||
|
||||
@@ -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 ?>,
|
||||
|
||||
@@ -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 ?>,
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user