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(); }); 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) { // 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); }); var priorityElem = template.find(".ticket-item-priority"); priorityElem.css("color", ticket.priority.colour); priorityElem.css("background-color", ticket.priority.backgroundcolour); // priorityElem.attr("data-bs-title", ticket.priority.title); // priorityElem.attr("data-bs-toggle", "tooltip"); // priorityElem.tooltip(); var departmentElem = template.find(".ticket-item-department"); if (ticket.author.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); // departmentElem.attr("data-bs-toggle", "tooltip"); // departmentElem.tooltip(); // Add the content to the interface $("#ticketsContainer .content").append(template); }); $("body").tooltip(); // Make tickets clickable applyTicketClickFunction(); updateItemsState("content"); global_loadingTickets = false; }); } 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]; // 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); }); $("#ticketContent .content").append(template); updateContentState("content"); global_loadingTickets = false; }); // $("#ticketContent").empty(); // updateInterfaceState("showing-content"); } 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); }