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:
parent
4344dfb271
commit
90abffac05
@ -152,5 +152,5 @@ class TicketSerializer(DynamicModelSerializer):
|
|||||||
fields = (
|
fields = (
|
||||||
"uuid", "title", "description", "author", "create_timestamp",
|
"uuid", "title", "description", "author", "create_timestamp",
|
||||||
"edit_timestamp", "is_edited", "was_yesterday", "is_older_than_day",
|
"edit_timestamp", "is_edited", "was_yesterday", "is_older_than_day",
|
||||||
"timestamp", "priority", "tags", "short_description"
|
"timestamp", "priority", "tags", "short_description", "string_datetime"
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
import logging
|
||||||
import bleach
|
import bleach
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
@ -9,6 +10,8 @@ from django.conf import settings
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TicketPriority(models.Model):
|
class TicketPriority(models.Model):
|
||||||
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||||
@ -152,7 +155,7 @@ class Ticket(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def was_yesterday(self) -> bool:
|
def was_yesterday(self) -> bool:
|
||||||
"""_summary_
|
"""Returns a boolean dependent on if `self.timestamp` is from before midnight yesterday.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -165,6 +168,45 @@ class Ticket(models.Model):
|
|||||||
|
|
||||||
return self.timestamp < midnight_today
|
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
|
@property
|
||||||
def timestamp(self) -> datetime:
|
def timestamp(self) -> datetime:
|
||||||
|
@ -113,10 +113,21 @@ body {
|
|||||||
.ticket-item:hover {
|
.ticket-item:hover {
|
||||||
background-color: var(--bs-tertiary-bg);
|
background-color: var(--bs-tertiary-bg);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
.ticket-item .ticket-item-indicator {
|
.ticket-item .ticket-item-indicator {
|
||||||
width: 5px;
|
writing-mode: vertical-rl;
|
||||||
height: 100%;
|
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 {
|
.ticket-item .ticket-item-icon {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
var filters = {"order": "-edit_timestamp"};
|
var filters = {"ordering": "-edit_timestamp"};
|
||||||
global_loadingTickets = false;
|
global_loadingTickets = false;
|
||||||
searchTimeout = null;
|
searchTimeout = null;
|
||||||
pagination = {};
|
pagination = {};
|
||||||
|
complexItems = false;
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
initSearchBar();
|
initSearchBar();
|
||||||
@ -220,7 +221,7 @@ function loadTicketItems(page=1) {
|
|||||||
filters["page"] = page;
|
filters["page"] = page;
|
||||||
|
|
||||||
var fetchFilters = { ...filters };
|
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) => {
|
fetchTicketsPromise(fetchFilters).then((response) => {
|
||||||
// Update the counts to show how many tickets were found
|
// Update the counts to show how many tickets were found
|
||||||
@ -256,17 +257,10 @@ function loadTicketItems(page=1) {
|
|||||||
// Iterate over and handle each ticket
|
// Iterate over and handle each ticket
|
||||||
response.results.forEach(function(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
|
// Create a copy of the template using the ticket data
|
||||||
var template = $($("#ticketItemTemplate").html());
|
var template = $($("#ticketItemTemplate").html());
|
||||||
template.find(".ticket-item-author").text(`${ticket.author.forename} ${ticket.author.surname}`);
|
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-title").text(ticket.title);
|
||||||
template.find(".ticket-item-desc").html(ticket.short_description);
|
template.find(".ticket-item-desc").html(ticket.short_description);
|
||||||
template.find(".ticket-item-icon").attr("src", ticket.author.icon);
|
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);
|
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.css("background-color", ticket.priority.backgroundcolour);
|
||||||
priorityElem.attr("data-title", ticket.priority.title + " Priority");
|
|
||||||
|
|
||||||
// Add the priority using the badge template
|
var departmentElem = template.find(".ticket-item-department");
|
||||||
var priorityTemplate = $($("#ticketContentBadgeTemplate").html());
|
departmentElem.addClass("bgc-orange-100 c-orange-700")
|
||||||
priorityTemplate.find(".ticket-content-badge-text").text(ticket.priority.title + " Priority");
|
|
||||||
priorityTemplate.css({ "color": ticket.priority.colour, "background-color": ticket.priority.backgroundcolour });
|
// // Add the priority using the badge template
|
||||||
priorityTemplate.removeClass("rounded-pill").addClass("rounded-1");
|
// var priorityTemplate = $($("#ticketContentBadgeTemplate").html());
|
||||||
template.find(".ticket-item-priority").append(priorityTemplate);
|
// 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
|
// Add the content to the interface
|
||||||
$("#ticketsContainer .content").append(template);
|
$("#ticketsContainer .content").append(template);
|
||||||
@ -315,17 +313,10 @@ function loadTicketContent(uuid) {
|
|||||||
fetchTicketsPromise({uuid: uuid}).then((response) => {
|
fetchTicketsPromise({uuid: uuid}).then((response) => {
|
||||||
ticket = response.results[0];
|
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
|
// Create a copy of the template using the ticket data
|
||||||
var template = $($("#ticketContentTemplate").html());
|
var template = $($("#ticketContentTemplate").html());
|
||||||
template.find(".ticket-content-author").text(`${ticket.author.forename} ${ticket.author.surname}`);
|
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-title").text(ticket.title);
|
||||||
template.find(".ticket-content-desc").html(ticket.description);
|
template.find(".ticket-content-desc").html(ticket.description);
|
||||||
template.find(".ticket-content-icon").attr("src", ticket.author.icon);
|
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.
|
// Prevent certain dropdowns from closing when the user clicks.
|
||||||
$(".dropdown-menu.prevent-click-close").on("click", function(e) {
|
$(".dropdown-menu.prevent-click-close").on("click", function(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function toggleComplexItems() {
|
||||||
|
complexItems = !complexItems;
|
||||||
|
|
||||||
|
if (complexItems) {
|
||||||
|
$("#ticketsContainer").addClass("complex-items");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$("#ticketsContainer").removeClass("complex-items");
|
||||||
|
}
|
||||||
|
}
|
@ -77,25 +77,6 @@
|
|||||||
<input type="checkbox" name="strictTags" id="strictTags" class="form-check-input" checked="checked">
|
<input type="checkbox" name="strictTags" id="strictTags" class="form-check-input" checked="checked">
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</h6>
|
</h6>
|
||||||
@ -167,7 +148,7 @@
|
|||||||
<div class="email-list h-100 layers">
|
<div class="email-list h-100 layers">
|
||||||
<div class="layer w-100">
|
<div class="layer w-100">
|
||||||
<div class="bg-body-tertiary peers ai-c p-20 fxw-nw">
|
<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">
|
<div class="btn-group" role="group">
|
||||||
<button type="button" class="email-side-toggle d-n@md+ btn bg-body bdrs-2 mR-3">
|
<button type="button" class="email-side-toggle d-n@md+ btn bg-body bdrs-2 mR-3">
|
||||||
<i class="ti-menu"></i>
|
<i class="ti-menu"></i>
|
||||||
@ -178,9 +159,17 @@
|
|||||||
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:loadTicketItems();">
|
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:loadTicketItems();">
|
||||||
<i class="ti-reload"></i>
|
<i class="ti-reload"></i>
|
||||||
</button>
|
</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>
|
</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">
|
<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>
|
<button type="button" id="ticketItemsPrevPage" class="btn bg-body bdrs-2" onclick="javascript: changeItemsPage(false);" disabled>
|
||||||
<i class="ti-angle-left"></i>
|
<i class="ti-angle-left"></i>
|
||||||
@ -417,30 +406,30 @@
|
|||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
<!-- Ticket Item Template -->
|
<!-- Ticket Item Template -->
|
||||||
<script id="ticketItemTemplate" type="text/template">
|
<script id="ticketItemTemplate" type="text/template">
|
||||||
<div class="ticket-item peers fxw-nw bdB" data-uuid="-1">
|
<div class="ticket-item fxw-nw bdB peers fxw-nw p-20 w-100" data-uuid="-1">
|
||||||
<div class="peer align-self-stretch">
|
<div class="ticket-item-complex peer mR-20 d-flex flex-column align-self-stretch">
|
||||||
<div class="ticket-item-indicator"></div>
|
<img src="" alt="" class="ticket-item-icon">
|
||||||
</div>
|
<div class="ticket-item-department badge rounded mt-auto mx-auto">
|
||||||
<div class="peer peer-greed peers fxw-nw p-20 w-100">
|
<i class="fa fa-users"></i>
|
||||||
<div class="peer mR-10">
|
|
||||||
<img src="" alt="" class="ticket-item-icon me-2">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="peer peer-greed ov-h">
|
<div class="ticket-item-priority badge rounded mt-2 mx-auto">
|
||||||
<div class="peers ai-c mb-2">
|
<i class="fa fa-folder"></i>
|
||||||
<div class="peer peer-greed">
|
</div>
|
||||||
<h6 class="ticket-item-author"></h6>
|
</div>
|
||||||
</div>
|
<div class="peer peer-greed ov-h">
|
||||||
<div class="peer">
|
<div class="peers ai-c mb-2">
|
||||||
<small class="ticket-item-datetime"></small>
|
<div class="peer peer-greed">
|
||||||
</div>
|
<h6 class="ticket-item-author"></h6>
|
||||||
</div>
|
</div>
|
||||||
<h5 class="ticket-item-title mb-0"></h5>
|
<div class="peer">
|
||||||
<div class="ticket-item-desc mt-2"></div>
|
<small class="ticket-item-datetime"></small>
|
||||||
<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>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
@ -455,8 +444,13 @@
|
|||||||
<img class="ticket-content-icon" src="" alt="">
|
<img class="ticket-content-icon" src="" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="peer">
|
<div class="peer">
|
||||||
<small class="ticket-content-datetime"></small>
|
<!-- <small class="ticket-content-datetime"></small> -->
|
||||||
<h5 class="ticket-content-author mb-0"></h5>
|
<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 class="ticket-content-badges"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user