443 lines
15 KiB
JavaScript
443 lines
15 KiB
JavaScript
var filters = {"ordering": "-edit_timestamp", "strict-tags": true};
|
|
global_loadingTickets = false;
|
|
searchTimeout = null;
|
|
pagination = {};
|
|
complexItems = false;
|
|
|
|
$(document).ready(function() {
|
|
initSearchBar();
|
|
toggleComplexItems(localStorage.getItem("hideComplexTickets") === "true");
|
|
|
|
setupFilter("#filterSidebar .filter-department", "author__department");
|
|
setupFilter("#filterSidebar .filter-tags", "tags");
|
|
setupFilter("#filterSidebar .filter-priority", "priority");
|
|
|
|
loadFilterCounts();
|
|
loadTicketItems();
|
|
});
|
|
|
|
function updateItemsState(state) {
|
|
console.debug(`updating items state to '${state}'`);
|
|
$("#ticketsContainer").scrollTop(0);
|
|
$("#ticketsContainer").trigger("updateScrollbar");
|
|
|
|
switch (state) {
|
|
case "content":
|
|
$("#ticketsContainer .none-found").hide();
|
|
$("#ticketsContainer .loading").hide();
|
|
$("#filterSidebar input").prop("disabled", false);
|
|
break;
|
|
case "loading":
|
|
$("#ticketsContainer .content").empty();
|
|
$("#ticketsContainer .none-found").hide();
|
|
$("#ticketsContainer .loading").show();
|
|
$("#filterSidebar input").prop("disabled", true);
|
|
break;
|
|
case "no-content":
|
|
$("#ticketsContainer .content").empty();
|
|
$("#ticketsContainer .none-found").show();
|
|
$("#ticketsContainer .loading").hide();
|
|
$("#filterSidebar input").prop("disabled", false);
|
|
break;
|
|
default:
|
|
throw new Error(`Invalid Items State '${state}'`);
|
|
}
|
|
}
|
|
|
|
function updateContentState(state) {
|
|
console.debug(`updating content state to '${state}'`);
|
|
switch (state) {
|
|
case "content":
|
|
$("#ticketContent .loading").hide();
|
|
break;
|
|
case "loading":
|
|
$("#ticketContent .content").empty();
|
|
$("#ticketContent .loading").show();
|
|
break;
|
|
default:
|
|
throw new Error(`Invalid Content State '${state}'`);
|
|
}
|
|
}
|
|
|
|
function initSearchBar() {
|
|
$("#searchTickets").keyup(() => {
|
|
updateItemsState("loading");
|
|
clearTimeout(searchTimeout);
|
|
searchTimeout = setTimeout(() => {
|
|
console.debug("searching");
|
|
value = $("#searchTickets").val();
|
|
if (value === "") {
|
|
console.debug("deleted search filters");
|
|
delete filters["search"];
|
|
}
|
|
else {
|
|
console.debug("updated search filters");
|
|
filters["search"] = value;
|
|
}
|
|
|
|
loadTicketItems();
|
|
}, 500);
|
|
})
|
|
}
|
|
|
|
function setupFilter(selector, key) {
|
|
$(selector).each(function () {
|
|
var input = $(this).find("input[type=checkbox], input[type=radio]");
|
|
var uuid = input.val();
|
|
|
|
input.on("change", function () {
|
|
if (input.is(":checkbox")) {
|
|
if ($(this).is(":checked")) {
|
|
filters[key] = filters[key] || [];
|
|
filters[key].push(uuid);
|
|
}
|
|
else {
|
|
filters[key] = filters[key].filter(id => id !== uuid);
|
|
if (filters[key].length === 0) delete filters[key];
|
|
}
|
|
}
|
|
else if (input.is(":radio") && input.is(":checked")) {
|
|
if (uuid === "all") delete filters[key];
|
|
else filters[key] = [uuid];
|
|
|
|
const deselectOption = $(`[name='${input.prop("name")}'].deselect-radio-filters`);
|
|
|
|
if (deselectOption.length) {
|
|
if (deselectOption.prop("checked")) deselectOption.parent().hide();
|
|
else deselectOption.parent().show();
|
|
}
|
|
}
|
|
|
|
console.debug(`Filter applied '${key}' as '${uuid}'`)
|
|
loadTicketItems();
|
|
});
|
|
});
|
|
}
|
|
|
|
function getOrdinalSuffix(day) {
|
|
if (day >= 11 && day <= 13) {
|
|
return day + 'th';
|
|
} else {
|
|
switch (day % 10) {
|
|
case 1: return day + 'st';
|
|
case 2: return day + 'nd';
|
|
case 3: return day + 'rd';
|
|
default: return day + 'th';
|
|
}
|
|
}
|
|
}
|
|
|
|
function timestampToHumanDate(timestamp, wasYesterday) {
|
|
if (wasYesterday) {
|
|
var day = getOrdinalSuffix(timestamp.getDate());
|
|
var month = timestamp.toLocaleString('en-GB', { month: 'short' });
|
|
var year = timestamp.toLocaleString('en-GB', { year: 'numeric' });
|
|
var time = timestamp.toLocaleString('en-GB', { hour: 'numeric', minute: 'numeric' });
|
|
return time + ', ' + day + ' ' + month + ' ' + year;
|
|
}
|
|
|
|
var hours = timestamp.getUTCHours();
|
|
var minutes = timestamp.getUTCMinutes();
|
|
return hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0');
|
|
}
|
|
|
|
function updateFilterCounts(filterType, data) {
|
|
$("#filterSidebar .filter-" + filterType).each(function() {
|
|
var uuid = $(this).find("input[type=checkbox],input[type=radio]").val();
|
|
var count = data[filterType][uuid];
|
|
$(this).find(".badge").text(count);
|
|
});
|
|
}
|
|
|
|
function loadFilterCounts() {
|
|
$.ajax({
|
|
url: URL_FilterCounts,
|
|
type: "GET",
|
|
success: function(data) {
|
|
updateFilterCounts('priority', data);
|
|
updateFilterCounts('tags', data);
|
|
updateFilterCounts('department', data);
|
|
|
|
// $("#filterPriorityAll .badge").text(data.tickets);
|
|
// $("#filterDepartmentAll .badge").text(data.tickets);
|
|
// $("#ticketCounts .total").text(data.tickets);
|
|
},
|
|
error: function(data) {
|
|
console.error(JSON.stringify(data, null, 4))
|
|
}
|
|
});
|
|
}
|
|
|
|
function applyTicketClickFunction() {
|
|
$(".ticket-item").on("click", function(e) {
|
|
e.preventDefault();
|
|
if ($(this).hasClass("active")) {
|
|
return;
|
|
}
|
|
|
|
loadTicketContent($(this).data("uuid"));
|
|
$(".ticket-item").removeClass("active");
|
|
$(this).addClass("active");
|
|
$('.email-app').removeClass('side-active');
|
|
$('.email-content').toggleClass('open');
|
|
});
|
|
}
|
|
|
|
$(".back-to-mailbox").on("click", function(e) {
|
|
e.preventDefault();
|
|
$(".ticket-item.active").removeClass("active");
|
|
});
|
|
|
|
function reloadCurrentTicket() {
|
|
loadTicketContent($(".ticket-item.active").data("uuid"));
|
|
}
|
|
|
|
function changeTicket(next=true) {
|
|
var selectedTicket = $(".ticket-item.active");
|
|
var uuid;
|
|
|
|
if (!selectedTicket.length) selectedTicket = $(".ticket-item").first();
|
|
else if (next) selectedTicket = selectedTicket.next();
|
|
else selectedTicket = selectedTicket.prev();
|
|
|
|
$(".ticket-item").removeClass("active");
|
|
selectedTicket.addClass("active");
|
|
uuid = selectedTicket.data("uuid");
|
|
loadTicketContent(uuid);
|
|
|
|
if (!$('.email-content').hasClass('open')) {
|
|
$('.email-content').addClass('open');
|
|
}
|
|
}
|
|
|
|
function changeItemsPage(next) {
|
|
$("#ticketItemsNextPage").prop("disabled", true);
|
|
$("#ticketItemsPrevPage").prop("disabled", true);
|
|
|
|
var page = pagination.page;
|
|
|
|
if (next && pagination.next) page ++;
|
|
else if (!next && pagination.prev) page --;
|
|
else return;
|
|
|
|
loadTicketItems(page);
|
|
}
|
|
|
|
$("#strictTags").on("change", function() {
|
|
const strictTags = $(this).prop("checked");
|
|
if (strictTags) filters["strict-tags"] = strictTags;
|
|
else delete filters["strict-tags"];
|
|
|
|
loadTicketItems();
|
|
});
|
|
|
|
|
|
/**
|
|
* Loads the list of ticket items using the current filters.
|
|
*
|
|
* @function loadTicketItems
|
|
* @param {Number} page For pagination, an invalid page will result in an error.
|
|
*/
|
|
function loadTicketItems(page=1) {
|
|
|
|
if (global_loadingTickets) {
|
|
alert("Spam prevention\nStopped loadTicketItems because already loading.");
|
|
return;
|
|
}
|
|
global_loadingTickets = true;
|
|
|
|
updateItemsState("loading");
|
|
filters["page"] = page;
|
|
|
|
var fetchFilters = { ...filters };
|
|
fetchFilters["only_fields"] = "uuid,title,short_description,author,priority,tags,timestamp,is_edited,author__forename,author__surname,author__department,author__icon,display_datetime";
|
|
|
|
fetchTicketsPromise(fetchFilters).then((response) => {
|
|
// Update the counts to show how many tickets were found
|
|
$("#ticketCounts .current").text(response.results.length);
|
|
$("#ticketCounts .total").text(response.count);
|
|
|
|
// If there are no tickets
|
|
if (!response.count) {
|
|
updateItemsState("no-content");
|
|
return;
|
|
}
|
|
|
|
const ticketHasNextPage = typeof response.next === "string";
|
|
const ticketHasPrevPage = typeof response.previous === "string";
|
|
$("#ticketItemsNextPage").prop("disabled", !ticketHasNextPage);
|
|
$("#ticketItemsPrevPage").prop("disabled", !ticketHasPrevPage);
|
|
const pageNumber = filters["page"] || 1; // If we haven't tracked the page, it's safe to assume it's 1,
|
|
pagination = { // because we always track it when it changes.
|
|
"page": pageNumber,
|
|
"next": ticketHasNextPage,
|
|
"prev": ticketHasPrevPage
|
|
}
|
|
|
|
// Update the pagination count with the current page
|
|
$("#paginationCounts .current").text(pageNumber);
|
|
|
|
// If we are on page one (which has been normalised to be the case in many intances)
|
|
// We can use the amount of results to calculate the total amount of pages.
|
|
if (pageNumber === 1) {
|
|
$("#paginationCounts .total").text(Math.ceil(response.count / response.results.length));
|
|
}
|
|
|
|
// Iterate over and handle each ticket
|
|
response.results.forEach(function(ticket) {
|
|
$("#ticketsContainer .content").append(createTicketItem(ticket));
|
|
});
|
|
|
|
// Make tickets clickable
|
|
applyTicketClickFunction();
|
|
|
|
updateItemsState("content");
|
|
global_loadingTickets = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a jquery object representing an element for a ticket item, constructed using the passed
|
|
* ticket and a predefined template.
|
|
*
|
|
* @function createTicketItem
|
|
* @param {Object} ticket An object representing a ticket.
|
|
* @return {jQuery} ticketElement to be appeneded as content.
|
|
*/
|
|
function createTicketItem(ticket) {
|
|
// Create a copy of the template using the ticket data
|
|
var template = $($("#ticketItemTemplate").html());
|
|
template.find(".ticket-item-author").text(`${ticket.author.forename} ${ticket.author.surname}`);
|
|
template.find(".ticket-item-datetime").text(ticket.display_datetime);
|
|
template.find(".ticket-item-title").text(ticket.title);
|
|
template.find(".ticket-item-desc").html(ticket.short_description);
|
|
template.find(".ticket-item-icon").attr("src", ticket.author.icon);
|
|
template.attr("data-uuid", ticket.uuid);
|
|
|
|
// Add tickets using the badge template
|
|
ticket.tags.forEach(function(tag) {
|
|
var tagTemplate = $($("#ticketContentBadgeTemplate").html());
|
|
tagTemplate.find(".ticket-content-badge-text").text(tag.title);
|
|
tagTemplate.css({ "color": tag.colour, "background-color": tag.backgroundcolour });
|
|
template.find(".ticket-item-tags").append(tagTemplate);
|
|
});
|
|
|
|
const priority = ticket.priority;
|
|
|
|
var priorityElem = template.find(".ticket-item-priority");
|
|
priorityElem.css("color", priority.colour);
|
|
priorityElem.css("background-color", priority.backgroundcolour);
|
|
priorityElem.attr("data-bs-title", priority.title + " Priority");
|
|
priorityElem.tooltip();
|
|
|
|
const department = ticket.author.department;
|
|
|
|
var departmentElem = template.find(".ticket-item-department");
|
|
if (department === null) {
|
|
departmentElem.hide();
|
|
}
|
|
else {
|
|
departmentElem.css("color", ticket.author.department.colour);
|
|
departmentElem.css("background-color", ticket.author.department.backgroundcolour);
|
|
departmentElem.attr("data-bs-title", ticket.author.department.title + " Department");
|
|
departmentElem.tooltip();
|
|
}
|
|
|
|
return template;
|
|
}
|
|
|
|
/**
|
|
* Load the content of a selected ticket.
|
|
*
|
|
* @function loadTicketContent
|
|
* @param {String} uuid A string representation of the ticket's UUID.
|
|
*/
|
|
function loadTicketContent(uuid) {
|
|
updateContentState("loading");
|
|
|
|
if (global_loadingTickets) {
|
|
console.debug("Spam prevention\nStopped loadTicketContent because already loading.");
|
|
return;
|
|
}
|
|
global_loadingTickets = true;
|
|
|
|
$("#ticketContent .content").empty();
|
|
|
|
fetchTicketsPromise({uuid: uuid}).then((response) => {
|
|
ticket = response.results[0];
|
|
$("#ticketContent .content").append(createTicketContent(ticket));
|
|
updateContentState("content");
|
|
global_loadingTickets = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns a jquery object representing an element for a ticket content, constructed using the
|
|
* passed ticket and a predefined template.
|
|
*
|
|
* @function createTicketItem
|
|
* @param {Object} ticket An object representing a ticket.
|
|
* @return {jQuery} ticketElement to be shown as content.
|
|
*/
|
|
function createTicketContent(ticket) {
|
|
// Create a copy of the template using the ticket data
|
|
var template = $($("#ticketContentTemplate").html());
|
|
template.find(".ticket-content-author").text(`${ticket.author.forename} ${ticket.author.surname}`);
|
|
template.find(".ticket-content-datetime").text(ticket.display_datetime);
|
|
template.find(".ticket-content-title").text(ticket.title);
|
|
template.find(".ticket-content-desc").html(ticket.description);
|
|
template.find(".ticket-content-icon").attr("src", ticket.author.icon);
|
|
console.debug(ticket.description);
|
|
|
|
ticket.tags.forEach(function(tag) {
|
|
var tagTemplate = $($("#ticketContentBadgeTemplate").html());
|
|
tagTemplate.find(".ticket-content-badge-text").text(tag.title);
|
|
tagTemplate.css({ "color": tag.colour, "background-color": tag.backgroundcolour });
|
|
template.find(".ticket-content-badges").append(tagTemplate);
|
|
});
|
|
|
|
return template;
|
|
}
|
|
|
|
function fetchTicketsPromise(queryFilters) {
|
|
return new Promise(function(resolve, reject) {
|
|
global_loadingTickets = true;
|
|
$.ajax({
|
|
url: URL_Tickets,
|
|
type: "GET",
|
|
dataType: "JSON",
|
|
data: $.param(queryFilters, true),
|
|
success: function(response) {
|
|
global_loadingTickets = false;
|
|
resolve(response);
|
|
},
|
|
error: function(response) {
|
|
global_loadingTickets = false;
|
|
if (response.status === 429) {
|
|
alert(`
|
|
HTTP ${response.status} - ${response.statusText}\n
|
|
${response.responseJSON.detail}
|
|
`);
|
|
}
|
|
reject(response);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Prevent certain dropdowns from closing when the user clicks.
|
|
$(".dropdown-menu.prevent-click-close").on("click", function(e) {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
function toggleComplexItems(hideComplex=null) {
|
|
if (hideComplex === null) {
|
|
hideComplex = !(localStorage.getItem("hideComplexTickets") === "true");
|
|
}
|
|
|
|
if (hideComplex) $("#ticketsContainer").removeClass("complex-items");
|
|
else $("#ticketsContainer").addClass("complex-items");
|
|
|
|
localStorage.setItem("hideComplexTickets", hideComplex);
|
|
}
|