functional filter + add modal

This commit is contained in:
Corban-Lee Jones 2024-01-10 23:15:43 +00:00
parent 4c7a81e076
commit 690bec9b24
3 changed files with 113 additions and 14 deletions

View File

@ -101,14 +101,23 @@ class Ticket(models.Model):
def clean_description(self): def clean_description(self):
cleaned_description = bleach.clean( cleaned_description = bleach.clean(
self.description, self.description,
tags=['b', 'i', 'u', 'p', 'br', 'a', 'h3', 'h4', 'h5', 'h6'], tags=[
'b', 'i', 'u', 'p', 'br', 'a', 'h3', 'h4', 'h5', 'h6', 'strong',
'figure', 'table', 'tbody', 'tr', 'td'
],
attributes={'a': ['href', 'title']} attributes={'a': ['href', 'title']}
) )
return cleaned_description return cleaned_description
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.description = self.clean_description() self.description = self.clean_description()
# self.edit_timestamp = timezone.now() ## TEMP COMMENT, UNCOMMENT LATER !!
# we must use the same datetime object, otherwise they wont match
now = timezone.now()
if self._state.adding: self.create_timestamp = now
self.edit_timestamp = now
super().save(*args, **kwargs) super().save(*args, **kwargs)
@property @property

View File

@ -10,6 +10,7 @@ from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from django.template import loader from django.template import loader
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from django.contrib.auth import get_user_model
from ..authentication.models import Department from ..authentication.models import Department
from .models import Ticket, TicketPriority, TicketTag from .models import Ticket, TicketPriority, TicketTag
@ -57,6 +58,7 @@ def get_tickets(request):
print(key, values) print(key, values)
for value in values: for value in values:
if value == "all": continue # don't apply a filter if we want all
queryset = queryset.filter(**{key: [value]}) queryset = queryset.filter(**{key: [value]})
tickets = queryset.order_by("-create_timestamp") tickets = queryset.order_by("-create_timestamp")
@ -65,6 +67,7 @@ def get_tickets(request):
return JsonResponse(data) return JsonResponse(data)
@login_required @login_required
@require_POST @require_POST
def get_filter_counts(request): def get_filter_counts(request):
@ -96,7 +99,34 @@ def get_filter_counts(request):
@login_required() @login_required()
@require_POST @require_POST
def new_ticket(request): def new_ticket(request):
return JsonResponse({"placeholder": "nothing here yet"}) print(request.POST)
get = lambda key: request.POST.get(key)
getlist = lambda key: request.POST.getlist(key)
title = get("title")
description = get("description")
author_id = get("author_id")
priority_id = get("priority_id")
tag_ids = getlist("tag_ids[]")
User = get_user_model()
author = User.objects.get(id=author_id)
priority = TicketPriority.objects.get(id=priority_id)
tags = [
tag for tag in TicketTag.objects.filter(id__in=tag_ids)
]
ticket = Ticket.objects.create(
title=title,
description=description,
author=author,
priority=priority,
)
ticket.tags.set(tags)
return JsonResponse({"success": "ticket created successfully"})
@login_required() @login_required()

View File

@ -75,7 +75,7 @@
<label for="filterTag-{{ tag.id }}" class="nav-link c-grey-800 cH-blue-500 actived"> <label for="filterTag-{{ tag.id }}" class="nav-link c-grey-800 cH-blue-500 actived">
<div class="peers ai-c jc-sb"> <div class="peers ai-c jc-sb">
<div class="peer peer-greed"> <div class="peer peer-greed">
<input type="checkbox" id="filterTag-{{ tag.id }}" class="form-check-input me-2" value="{{ priority.id }}"> <input type="checkbox" id="filterTag-{{ tag.id }}" class="form-check-input me-2" value="{{ tag.id }}">
<span>{{ tag.title }}</span> <span>{{ tag.title }}</span>
</div> </div>
<div class="peer"> <div class="peer">
@ -350,6 +350,20 @@
<script> <script>
var displayedTicketID = -1; var displayedTicketID = -1;
filters = {}; filters = {};
editor = null;
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() { $(document).ready(function() {
// $(".email-list-item").on("click", function() { // $(".email-list-item").on("click", function() {
@ -358,6 +372,9 @@
ClassicEditor ClassicEditor
.create( document.getElementById("newDesc"), {}) .create( document.getElementById("newDesc"), {})
.then( newEditor => {
editor = newEditor;
})
.catch( error => { .catch( error => {
console.error(error) console.error(error)
}); });
@ -377,8 +394,8 @@
function setupFilter(selector, key) { function setupFilter(selector, key) {
$(selector).each(function () { $(selector).each(function () {
var uuid = $(this).val();
var input = $(this).find("input[type=checkbox], input[type=radio]"); var input = $(this).find("input[type=checkbox], input[type=radio]");
var uuid = input.val();
input.on("change", function () { input.on("change", function () {
if (input.is(":checkbox")) { if (input.is(":checkbox")) {
@ -392,26 +409,64 @@
delete filters[key]; delete filters[key];
} }
} }
} else if (input.is(":radio") && input.is(":checked")) { }
else if (input.is(":radio") && input.is(":checked")) {
filters[key] = [uuid]; filters[key] = [uuid];
} }
loadAllTickets();
console.log(JSON.stringify(filters, null, 4)); 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) { $("#ticketModal form").on("submit", function(event) {
event.preventDefault(); event.preventDefault();
if (!validateForm()) {
return;
}
$.ajax({ $.ajax({
url: "{% url 'ticket-new' %}", url: "{% url 'ticket-new' %}",
type: "POST", type: "POST",
dataType: "json", dataType: "json",
data: { data: {
csrfmiddlewaretoken: "{{ csrf_token }}", csrfmiddlewaretoken: "{{ csrf_token }}",
title: $("#newTitle").val(),
description: editor.getData(),
author_id: "{{ request.user.id }}",
priority_id: $("#newPriority").val(),
tag_ids: $("#newTags").val()
},
success: function(data) {
loadAllTickets();
loadFilterCounts();
},
error: function(data) {
alert(JSON.stringify(data, null, 4))
} }
}); });
}); });
@ -431,7 +486,7 @@
function updateFilterCounts(filterType, data) { function updateFilterCounts(filterType, data) {
$("#filterSidebar .filter-" + filterType).each(function() { $("#filterSidebar .filter-" + filterType).each(function() {
var uuid = $(this).data("uuid"); var uuid = $(this).find("input[type=checkbox],input[type=radio]").val();
var count = data[filterType + '_counts'][uuid]; var count = data[filterType + '_counts'][uuid];
$(this).find(".badge").text(count); $(this).find(".badge").text(count);
}); });
@ -443,7 +498,7 @@
type: "POST", type: "POST",
dataType: "json", dataType: "json",
data: { data: {
csrfmiddlewaretoken: "{{ csrf_token }}", csrfmiddlewaretoken: "{{ csrf_token }}"
}, },
success: function(data) { success: function(data) {
console.log(JSON.stringify(data, null, 4)); console.log(JSON.stringify(data, null, 4));
@ -544,6 +599,15 @@
// displayTicket(ticketElement); // displayTicket(ticketElement);
// }); // });
if (displayedTicketID === ticketID) {
// displayedTicketID = -1;
return;
}
ticket.siblings().removeClass("bgc-grey-100");
ticket.addClass("bgc-grey-100");
$("#ticketTitle").text("") $("#ticketTitle").text("")
$("#ticketDesc").empty(); $("#ticketDesc").empty();
$("#ticketAuthor").text(""); $("#ticketAuthor").text("");
@ -553,10 +617,6 @@
$("#btnGroupDrop2").hide(); $("#btnGroupDrop2").hide();
$("#ticketBadges").empty().hide(); $("#ticketBadges").empty().hide();
if (displayedTicketID === ticketID) {
displayedTicketID = -1;
return;
}
displayedTicketID = ticketID; displayedTicketID = ticketID;