Ticket Item Changes & Ticket.string_datetime

Added string datetime and indicators for priority and departments.

Also added a toggle for the indicators/departments to be shown.
This commit is contained in:
Corban-Lee Jones 2024-01-22 01:05:31 +00:00
parent 4344dfb271
commit 90abffac05
5 changed files with 123 additions and 74 deletions

View File

@ -152,5 +152,5 @@ class TicketSerializer(DynamicModelSerializer):
fields = (
"uuid", "title", "description", "author", "create_timestamp",
"edit_timestamp", "is_edited", "was_yesterday", "is_older_than_day",
"timestamp", "priority", "tags", "short_description"
"timestamp", "priority", "tags", "short_description", "string_datetime"
)

View File

@ -1,5 +1,6 @@
# -*- encoding: utf-8 -*-
import logging
import bleach
from uuid import uuid4
from datetime import timedelta, datetime
@ -9,6 +10,8 @@ from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
log = logging.getLogger(__name__)
class TicketPriority(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
@ -152,7 +155,7 @@ class Ticket(models.Model):
@property
def was_yesterday(self) -> bool:
"""_summary_
"""Returns a boolean dependent on if `self.timestamp` is from before midnight yesterday.
Returns
-------
@ -165,6 +168,45 @@ class Ticket(models.Model):
return self.timestamp < midnight_today
@property
def string_datetime(self) -> str:
"""Provides a human readable string representation of `self.timestamp` that should be displayed
to represent the ticket's age.
Returns
-------
str
The string representation of `self.timestamp`.
"""
difference = timezone.now() - self.timestamp
days = difference.days
hours = difference.total_seconds() // 3600
minutes = difference.total_seconds() // 60
seconds = difference.total_seconds()
hours, minutes, seconds = map(int, (hours, minutes, seconds))
if seconds < 60:
value, unit = seconds, "second"
elif minutes < 60:
value, unit = minutes, "minute"
elif hours < 24:
value, unit = hours, "hour"
elif days < 7:
value, unit = days, "day"
else:
return self.timestamp.strftime("%Y-%m-%d")
if value > 1:
unit += "s"
return f"{value} {unit} ago"
@property
def timestamp(self) -> datetime:

View File

@ -113,10 +113,21 @@ body {
.ticket-item:hover {
background-color: var(--bs-tertiary-bg);
}
/*
.ticket-item .ticket-item-indicator {
width: 5px;
height: 100%;
writing-mode: vertical-rl;
text-orientation: upright;
} */
.ticket-item .ticket-item-complex {
transition: all 0.3s ease;
overflow: hidden;
width: 2rem;
}
#ticketsContainer:not(.complex-items) .ticket-item .ticket-item-complex {
margin: 0 !important;
width: 0 !important;
}
.ticket-item .ticket-item-icon {

View File

@ -1,7 +1,8 @@
var filters = {"order": "-edit_timestamp"};
var filters = {"ordering": "-edit_timestamp"};
global_loadingTickets = false;
searchTimeout = null;
pagination = {};
complexItems = false;
$(document).ready(function() {
initSearchBar();
@ -220,7 +221,7 @@ function loadTicketItems(page=1) {
filters["page"] = page;
var fetchFilters = { ...filters };
fetchFilters["only_fields"] = "uuid,title,short_description,author,priority,tags,timestamp,was_yesterday,is_edited,author__forename,author__surname,author__department,author__icon";
fetchFilters["only_fields"] = "uuid,title,short_description,author,priority,tags,timestamp,is_edited,author__forename,author__surname,author__department,author__icon,string_datetime";
fetchTicketsPromise(fetchFilters).then((response) => {
// Update the counts to show how many tickets were found
@ -256,17 +257,10 @@ function loadTicketItems(page=1) {
// 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-datetime").text(ticket.string_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);
@ -280,16 +274,20 @@ function loadTicketItems(page=1) {
template.find(".ticket-item-tags").append(tagTemplate);
});
var priorityElem = template.find(".ticket-item-indicator");
var priorityElem = template.find(".ticket-item-priority");
// priorityElem.text(ticket.priority.title);
priorityElem.css("color", ticket.priority.colour);
priorityElem.css("background-color", ticket.priority.backgroundcolour);
priorityElem.attr("data-title", ticket.priority.title + " Priority");
// Add the priority using the badge template
var priorityTemplate = $($("#ticketContentBadgeTemplate").html());
priorityTemplate.find(".ticket-content-badge-text").text(ticket.priority.title + " Priority");
priorityTemplate.css({ "color": ticket.priority.colour, "background-color": ticket.priority.backgroundcolour });
priorityTemplate.removeClass("rounded-pill").addClass("rounded-1");
template.find(".ticket-item-priority").append(priorityTemplate);
var departmentElem = template.find(".ticket-item-department");
departmentElem.addClass("bgc-orange-100 c-orange-700")
// // Add the priority using the badge template
// var priorityTemplate = $($("#ticketContentBadgeTemplate").html());
// priorityTemplate.find(".ticket-content-badge-text").text(ticket.priority.title + " Priority");
// priorityTemplate.css({ "color": ticket.priority.colour, "background-color": ticket.priority.backgroundcolour });
// priorityTemplate.removeClass("rounded-pill").addClass("rounded-1");
// template.find(".ticket-item-priority").append(priorityTemplate);
// Add the content to the interface
$("#ticketsContainer .content").append(template);
@ -315,17 +313,10 @@ function loadTicketContent(uuid) {
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-datetime").text(ticket.string_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);
@ -377,4 +368,15 @@ function fetchTicketsPromise(queryFilters) {
// Prevent certain dropdowns from closing when the user clicks.
$(".dropdown-menu.prevent-click-close").on("click", function(e) {
e.stopPropagation();
});
});
function toggleComplexItems() {
complexItems = !complexItems;
if (complexItems) {
$("#ticketsContainer").addClass("complex-items");
}
else {
$("#ticketsContainer").removeClass("complex-items");
}
}

View File

@ -77,25 +77,6 @@
<input type="checkbox" name="strictTags" id="strictTags" class="form-check-input" checked="checked">
</div>
</li>
<!-- <li>
<span class="dropdown-item-text">Tag Filtering</span>
</li>
<li>
<label for="ticketTags-AND" class="dropdown-item">
<div class="form-check form-check-switch">
<span class="form-check-label">Strict</span>
<input type="radio" name="ticketTags" id="ticketTags-AND" class="form-check-input" checked="checked" value="AND">
</div>
</label>
</li>
<li>
<label for="ticketTags-OR" class="dropdown-item">
<div class="form-check form-check-switch">
<span class="form-check-label">Loose</span>
<input type="radio" name="ticketTags" id="ticketTags-OR" class="form-check-input" value="OR">
</div>
</label>
</li> -->
</div>
</div>
</h6>
@ -167,7 +148,7 @@
<div class="email-list h-100 layers">
<div class="layer w-100">
<div class="bg-body-tertiary peers ai-c p-20 fxw-nw">
<div class="peer me-auto">
<div class="peer me-2">
<div class="btn-group" role="group">
<button type="button" class="email-side-toggle d-n@md+ btn bg-body bdrs-2 mR-3">
<i class="ti-menu"></i>
@ -178,9 +159,17 @@
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:loadTicketItems();">
<i class="ti-reload"></i>
</button>
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript: toggleComplexItems();">
<i class="ti-layout-media-left"></i>
</button>
</div>
</div>
<div class="peer">
<!-- <div class="peer me-2">
<div class="btn-group" role="group">
</div>
</div> -->
<div class="peer ms-auto">
<div class="btn-group bg-body mX-3" role="group">
<button type="button" id="ticketItemsPrevPage" class="btn bg-body bdrs-2" onclick="javascript: changeItemsPage(false);" disabled>
<i class="ti-angle-left"></i>
@ -417,30 +406,30 @@
{% block javascripts %}
<!-- Ticket Item Template -->
<script id="ticketItemTemplate" type="text/template">
<div class="ticket-item peers fxw-nw bdB" data-uuid="-1">
<div class="peer align-self-stretch">
<div class="ticket-item-indicator"></div>
</div>
<div class="peer peer-greed peers fxw-nw p-20 w-100">
<div class="peer mR-10">
<img src="" alt="" class="ticket-item-icon me-2">
<div class="ticket-item fxw-nw bdB peers fxw-nw p-20 w-100" data-uuid="-1">
<div class="ticket-item-complex peer mR-20 d-flex flex-column align-self-stretch">
<img src="" alt="" class="ticket-item-icon">
<div class="ticket-item-department badge rounded mt-auto mx-auto">
<i class="fa fa-users"></i>
</div>
<div class="peer peer-greed ov-h">
<div class="peers ai-c mb-2">
<div class="peer peer-greed">
<h6 class="ticket-item-author"></h6>
</div>
<div class="peer">
<small class="ticket-item-datetime"></small>
</div>
<div class="ticket-item-priority badge rounded mt-2 mx-auto">
<i class="fa fa-folder"></i>
</div>
</div>
<div class="peer peer-greed ov-h">
<div class="peers ai-c mb-2">
<div class="peer peer-greed">
<h6 class="ticket-item-author"></h6>
</div>
<h5 class="ticket-item-title mb-0"></h5>
<div class="ticket-item-desc mt-2"></div>
<div class="peers">
<!-- <div class="ticket-item-priority pe-5 peer peer-greed"></div> -->
<div class="ticket-item-tags peer d-flex flex-wrap mw-100" data-bs-toggle="tooltip"></div>
<div class="peer">
<small class="ticket-item-datetime"></small>
</div>
</div>
<h5 class="ticket-item-title mb-0"></h5>
<div class="ticket-item-desc mt-2"></div>
<div class="peers">
<div class="ticket-item-tags peer d-flex flex-wrap mw-100" data-bs-toggle="tooltip"></div>
</div>
</div>
</div>
</script>
@ -455,8 +444,13 @@
<img class="ticket-content-icon" src="" alt="">
</div>
<div class="peer">
<small class="ticket-content-datetime"></small>
<!-- <small class="ticket-content-datetime"></small> -->
<h5 class="ticket-content-author mb-0"></h5>
<div class="peers mt-2">
<div class="peer badge bgc-orange-100 c-orange-700">
<i class="fa fa-users"></i>
</div>
</div>
<div class="ticket-content-badges"></div>
</div>
</div>