From 73eeedbb367ab577da8c8d72c5ff3c2a4a07baa1 Mon Sep 17 00:00:00 2001 From: corbz Date: Mon, 15 Jan 2024 00:27:39 +0000 Subject: [PATCH] tickets API changes & search box function --- apps/api/urls.py | 11 +- apps/api/views.py | 21 +- apps/fixtures/home/defaulttickets.json | 10 +- apps/static/assets/js/tickets.js | 343 +++++++++++++++++++++++++ apps/templates/home/tickets.html | 335 +----------------------- core/urls.py | 3 +- 6 files changed, 378 insertions(+), 345 deletions(-) create mode 100644 apps/static/assets/js/tickets.js diff --git a/apps/api/urls.py b/apps/api/urls.py index d54b9bf..fb1b7d3 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -1,10 +1,13 @@ # -*- encoding: utf-8 -*- -from django.urls import path +from django.urls import path, include + +from . import views -from .views import TicketListApiView, FilterCountApiView urlpatterns = [ - path("ticket/", TicketListApiView.as_view(), name="ticket"), - path("filter-counts/", FilterCountApiView.as_view(), name="filter-counts"), + path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), + + path("tickets/", views.TicketListApiView.as_view(), name="tickets"), + path("filter-counts/", views.FilterCountApiView.as_view(), name="filter-counts"), ] \ No newline at end of file diff --git a/apps/api/views.py b/apps/api/views.py index 6a624fe..f951ca0 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -41,7 +41,11 @@ class TicketListApiView(APIView): permission_classes = [permissions.IsAuthenticated] pagination_class = TicketPaginiation - ALLOWED_FILTERS = ("uuid__in", "priority__in", "tags__in", "author__department__in") + ALLOWED_FILTERS = ("uuid__in", "priority__in", "tags__in", "author__department__in", "title__contains") + MATCHER_MAP = { + "contains": lambda k, v: {k: v[0]}, + "in": lambda k, v: {k: v} + } # @method_decorator(cache_page(60 * 5)) def get(self, request: HttpRequest) -> Response: @@ -68,7 +72,6 @@ class TicketListApiView(APIView): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - def _get_tickets(self, request: HttpRequest) -> Response: """Returns a response containing tickets matching the given filters. @@ -92,16 +95,20 @@ class TicketListApiView(APIView): queryset = Ticket.objects.all() - for key in request.GET.keys(): - values = request.GET.getlist(key) if key.endswith("[]") else [request.GET.get(key)] + for key, values in request.GET.lists(): key = key.removesuffix("[]") if key not in self.ALLOWED_FILTERS: raise KeyError(key) - for value in values: - if value == "all": continue - queryset = queryset.filter(Q(**{key: [value]})) + if "all" in values: + continue + + if not key.endswith("__in"): + values = values[0] + + filter_kwargs = {key: values} + queryset = queryset.filter(Q(**filter_kwargs)) tickets = queryset.order_by("-create_timestamp") serializer = TicketSerializer(tickets, many=True) diff --git a/apps/fixtures/home/defaulttickets.json b/apps/fixtures/home/defaulttickets.json index 838523f..6cae925 100644 --- a/apps/fixtures/home/defaulttickets.json +++ b/apps/fixtures/home/defaulttickets.json @@ -22,7 +22,7 @@ "fields": { "title": "Security Patch Installation", "description": "There's a critical security patch that needs to be installed on all workstations to address recent vulnerabilities. Attempted to deploy the patch, but facing challenges on certain machines. Need high-priority assistance to ensure all systems are promptly updated for enhanced security measures.", - "author": "291cc4c1-3bb3-4417-aaa6-748606fede77", + "author": "4965745e-b82a-4496-80e1-055217a780b0", "priority": "a680328f-0680-456c-8e26-f594e05989ad", "create_timestamp": "2024-01-09T00:09:20Z", "edit_timestamp": "2024-01-09T00:09:20Z", @@ -92,7 +92,7 @@ "fields": { "title": "Hardware Malfunction", "description": "The printer on the third floor is not responding. Checked cables and power source, but issue persists. Need assistance to fix the hardware problem.", - "author": "291cc4c1-3bb3-4417-aaa6-748606fede77", + "author": "4965745e-b82a-4496-80e1-055217a780b0", "priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8", "create_timestamp": "2024-01-09T00:06:58Z", "edit_timestamp": "2024-01-09T00:44:49Z", @@ -126,7 +126,7 @@ "fields": { "title": "VPN Connection Issue", "description": "Remote team members are encountering difficulties establishing a VPN connection. This is hindering their ability to access essential resources. Providing detailed information on the error messages received and troubleshooting steps taken so far. Requesting immediate attention to restore seamless VPN functionality.", - "author": "291cc4c1-3bb3-4417-aaa6-748606fede77", + "author": "4965745e-b82a-4496-80e1-055217a780b0", "priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8", "create_timestamp": "2024-01-09T00:09:39Z", "edit_timestamp": "2024-01-09T00:13:27Z", @@ -143,7 +143,7 @@ "fields": { "title": "IT Training Request", "description": "Requesting IT training sessions for the marketing team to enhance their proficiency in utilizing specific software tools. Providing a detailed outline of the desired training topics and the anticipated benefits for the team. Seeking assistance in scheduling and conducting the training sessions.", - "author": "291cc4c1-3bb3-4417-aaa6-748606fede77", + "author": "4965745e-b82a-4496-80e1-055217a780b0", "priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43", "create_timestamp": "2024-01-09T00:12:17Z", "edit_timestamp": "2024-01-09T00:12:17Z", @@ -190,7 +190,7 @@ "fields": { "title": "Software Update Problem", "description": "Unable to install the latest software update on my workstation. Getting error code XYZ. Detailed steps attempted are listed in the description.", - "author": "291cc4c1-3bb3-4417-aaa6-748606fede77", + "author": "4965745e-b82a-4496-80e1-055217a780b0", "priority": "a680328f-0680-456c-8e26-f594e05989ad", "create_timestamp": "2024-01-09T00:06:32Z", "edit_timestamp": "2024-01-09T00:06:32Z", diff --git a/apps/static/assets/js/tickets.js b/apps/static/assets/js/tickets.js new file mode 100644 index 0000000..8b65eea --- /dev/null +++ b/apps/static/assets/js/tickets.js @@ -0,0 +1,343 @@ +var displayedTicketID = -1; + filters = {}; + editor = null; + searchTimeout = null; + loadingTickets = false; + +const formControls = [ + { + id: "newTitle", + validation: function(element) { + const value = element.val(); + return (!element.attr("required") || value.trim() !== "") + }, + errorMessage: function(element) { + return "This field is required." + } + } +]; + +$(document).ready(function() { + // $(".email-list-item").on("click", function() { + // displayTicket(this); + // }); + + ClassicEditor + .create( document.getElementById("newDesc"), {}) + .then( newEditor => { + editor = newEditor; + }) + .catch( error => { + console.error(error) + }); + + $("#searchTickets").keyup(() => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + value = $("#searchTickets").val(); + if (value === "") { + delete filters["title__contains"]; + return; + } + + filters["title__contains"] = value; + loadAllTickets(); + }, 500); + }) + + setupFilter("#filterSidebar .filter-department", "author__department__in"); + setupFilter("#filterSidebar .filter-tag", "tags__in") + setupFilter("#filterSidebar .filter-priority", "priority__in") + + loadFilterCounts(); + loadAllTickets(); +}); + +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].splice(filters[key].indexOf(uuid), 1); + filters[key] = filters[key].filter(id => id !== uuid); + if (filters[key].length === 0) { + delete filters[key]; + } + } + } + else if (input.is(":radio") && input.is(":checked")) { + filters[key] = [uuid]; + } + + console.log(JSON.stringify(filters, null, 4)); + loadAllTickets(); + }); + }); +} + +function validateForm() { + + $("#ticketModal form").find(".form-control,.form-select").removeClass("is-valid is-invalid"); + $("#ticketModal form .invalid-feedback").text(""); + + var valid = true; + + formControls.forEach(function(control) { + var element = $("#" + control.id); + if (!control.validation(element)) { + element.addClass("is-invalid"); + element.siblings(".invalid-feedback").text(control.errorMessage(element)); + valid = false; + } + else { + element.addClass("is-valid"); + } + }); + + return valid; +} + +$("#ticketModal form").on("submit", function(event) { + event.preventDefault(); + + if (!validateForm()) { + return; + } + + $.ajax({ + url: URL_NewTicket, + type: "POST", + dataType: "json", + data: { + csrfmiddlewaretoken: CSRFMiddlewareToken, + title: $("#newTitle").val(), + description: editor.getData(), + author_id: CurrentUserID, + priority_id: $("#newPriority").val(), + tag_ids: $("#newTags").val() + }, + success: function(data) { + loadAllTickets(); + loadFilterCounts(); + }, + error: function(data) { + alert(JSON.stringify(data, null, 4)) + } + }); +}); + +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 updateFilterCounts(filterType, data) { + $("#filterSidebar .filter-" + filterType).each(function() { + var uuid = $(this).find("input[type=checkbox],input[type=radio]").val(); + var count = data[filterType + '_counts'][uuid]; + $(this).find(".badge").text(count); + }); +} + +function loadFilterCounts() { + $.ajax({ + url: URL_FilterCounts, + type: "GET", + success: function(data) { + console.log(JSON.stringify(data, null, 4)); + + updateFilterCounts('priority', data); + updateFilterCounts('tag', data); + updateFilterCounts('department', data); + $("#filterPriorityAll .badge").text(data.ticket_count); + $("#ticketCount").text(data.ticket_count) + + }, + error: function(data) { + alert(JSON.stringify(data, null, 4)) + } + }); +} + +function loadAllTickets() { + if (loadingTickets === true) { + return; + } + + $("#ticketsContainer").empty(); + loadingTickets = true; + // alert(JSON.stringify(filters, null, 4)); + + $.ajax({ + url: URL_Tickets, + type: "GET", + dataType: "json", + data: filters, + success: function(data) { + loadingTickets = false; + console.log(JSON.stringify(data, null, 4)) + + data.forEach(function(ticket) { + var timestamp = new Date(ticket.timestamp); + var formattedTime; + + if (ticket.was_yesterday) { + 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' }); + + // Formatting the final result + var formattedTime = time + ', ' + day + ' ' + month + ' ' + year; + } + else { + var hours = timestamp.getUTCHours(); + var minutes = timestamp.getUTCMinutes(); + formattedTime = hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0'); + } + + if (ticket.is_edited) { + formattedTime += " • edited"; + } + + var item = $(` +
+
+ +
+
+
+
+
${ticket.author.forename} ${ticket.author.surname}
+
+
+ ${formattedTime} +
+
+
${ticket.title}
+ ${ticket.description} +
+
+ `); + + + $("#ticketsContainer").append(item); + }); + + $(".email-list-item").on("click", function(e) { + e.preventDefault(); + displayTicket(this); + $('.email-content').toggleClass('open'); + }); + }, + error: function(data) { + loadingTickets = false; + alert(JSON.stringify(data, null, 4)); + console.error(`${data.responseJSON.error}\n${data.responseJSON.detail}`); + } + }); +} + +function displayTicket(ticketElement) { + ticket = $(ticketElement); + ticketID = ticket.data("ticket-id"); + + // $(".back-to-mailbox").off("click").on("click", function(event) { + // event.preventDefault(); + // $('.email-content').toggleClass('open'); + // displayTicket(ticketElement); + // }); + + + if (displayedTicketID === ticketID) { + // displayedTicketID = -1; + return; + } + + ticket.siblings().removeClass("bgc-grey-100"); + ticket.addClass("bgc-grey-100"); + + $("#ticketTitle").text("") + $("#ticketDesc").empty(); + $("#ticketAuthor").text(""); + $("#ticketAuthorImg").hide(); + $("#ticketAuthorImg").prop("src", ""); + $("#ticketTimestamp").text(""); + $("#btnGroupDrop2").hide(); + $("#ticketBadges").empty().hide(); + + + displayedTicketID = ticketID; + + $.ajax({ + url: URL_Tickets, + type: 'get', + dataType: 'json', + data: { + uuid__in: [ticketID] + }, + success: function (data) { + console.log(JSON.stringify(data, null, 4)); + + var ticket = data[0]; + var author = ticket.author; + var department = author.department; + var priority = ticket.priority; + + $("#ticketTitle").text(ticket.title); + $("#ticketDesc").append($(`
${ticket.description}
`)); + $("#ticketAuthor").text(`${author.forename} ${author.surname}`); + $("#ticketAuthorImg").show(); + $("#ticketAuthorImg").prop("src", author.icon); + $("#btnGroupDrop2").show(); + $("#ticketBadges").show(); + + $("#ticketBadges").append($(`
${priority.title} Priority
`)); + + if (department != null) { + $("#ticketBadges").append($(`
${department.title}
`)); + } + + ticket.tags.forEach(function(tag) { + $("#ticketBadges").append($(`
${tag.title}
`)); + }); + + // timestamp + var timestamp = new Date(ticket.timestamp); + var formattedTime; + + if (ticket.was_yesterday) { + var options = { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' }; + formattedTime = timestamp.toLocaleDateString('en-GB', options); + } + else { + var hours = timestamp.getUTCHours(); + var minutes = timestamp.getUTCMinutes(); + formattedTime = hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0'); + } + + if (ticket.is_edited) { + formattedTime += " • edited"; + } + + $("#ticketTimestamp").text(formattedTime); + }, + error: function(message) { + alert(JSON.stringify(message, null, 4)); + } + }); +} \ No newline at end of file diff --git a/apps/templates/home/tickets.html b/apps/templates/home/tickets.html index 49165c7..c2adce1 100644 --- a/apps/templates/home/tickets.html +++ b/apps/templates/home/tickets.html @@ -151,7 +151,7 @@
- +
@@ -296,333 +296,14 @@ {% endblock content %} - {% block javascripts %} + + {% endblock javascripts %} diff --git a/core/urls.py b/core/urls.py index 423bed2..5a1c7bb 100644 --- a/core/urls.py +++ b/core/urls.py @@ -6,8 +6,7 @@ from django.conf.urls.static import static from django.urls import path, include # add this urlpatterns = [ - path('admin/', admin.site.urls), # Django admin route - path("api-auth/", include("rest_framework.urls")), + path('admin/', admin.site.urls), path("api/", include(("apps.api.urls", "apps.api"), namespace="api")), # ADD NEW Routes HERE