var filters = {"order": "-edit_timestamp"}; global_loadingTickets = false; searchTimeout = null; pagination = {}; $(document).ready(function() { initSearchBar(); 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}'`); switch (state) { case "content": $("#ticketsContainer .none-found").hide(); $("#ticketsContainer .loading").hide(); break; case "loading": $("#ticketsContainer .content").empty(); $("#ticketsContainer .none-found").hide(); $("#ticketsContainer .loading").show(); break; case "no-content": $("#ticketsContainer .content").empty(); $("#ticketsContainer .none-found").show(); $("#ticketsContainer .loading").hide(); break; default: throw new Error(`Invalid Items State '${state}'`); } } function updateContentState(state) { console.debug(`updating items 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 Items 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]; } 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(); loadTicketContent($(this).data("uuid")); $(".ticket-item").removeClass("active"); $(this).addClass("active"); $('.email-app').removeClass('side-active'); $('.email-content').toggleClass('open'); }); } 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); } function loadTicketItems(page=1) { updateItemsState("loading"); filters["page"] = page; fetchTicketsPromise(filters).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 } // Iterate over and handle each ticket response.results.forEach(function(ticket) { // Create a formatted version of the ticket's timestamp const timestamp = new Date(ticket.timestamp); var formattedTime = timestampToHumanDate(timestamp, ticket.was_yesterday); // Mark the ticket if it is edited if (ticket.is_edited) formattedTime += " • edited"; // 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(formattedTime); template.find(".ticket-item-title").text(ticket.title); template.find(".ticket-item-desc").text(ticket.description); template.find(".ticket-item-icon").attr("src", ticket.author.icon); template.attr("data-uuid", ticket.uuid); // Add the content to the interface $("#ticketsContainer .content").append(template); }); // Make tickets clickable applyTicketClickFunction(); updateItemsState("content"); }); } function loadTicketContent(uuid) { updateContentState("loading"); if (global_loadingTickets) { console.debug("Spam prevention\nStopped loadTicketContent because already loading."); return; } $("#ticketContent .content").empty(); fetchTicketsPromise({uuid: uuid}).then((response) => { ticket = response.results[0]; // Create a formatted version of the ticket's timestamp const timestamp = new Date(ticket.timestamp); var formattedTime = timestampToHumanDate(timestamp, ticket.was_yesterday); // Mark the ticket if it is edited if (ticket.is_edited) formattedTime += " • edited"; // 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(formattedTime); template.find(".ticket-content-title").text(ticket.title); template.find(".ticket-content-desc").text(ticket.description); template.find(".ticket-content-icon").attr("src", ticket.author.icon); $("#ticketContent .content").append(template); updateContentState("content"); }); // $("#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); } }); }); }