Compare commits
32 Commits
dev-user-m
...
new-james-
Author | SHA1 | Date | |
---|---|---|---|
123e75266e | |||
7d408193f9 | |||
2e4eff5f3a | |||
fc992c75a6 | |||
fc8cea1d0a | |||
92c6cda583 | |||
75531f872b | |||
c9e47e3e9e | |||
4890d7a4c3 | |||
c35428792e | |||
c94775d93c | |||
2691380f81 | |||
950676a9fe | |||
7c00552714 | |||
6effc6799e | |||
3446c3b760 | |||
1c5282e6c9 | |||
dfef67642f | |||
6f6a0f8632 | |||
e342680351 | |||
4d7f2ba3ea | |||
bf6a004d64 | |||
a482be8883 | |||
2364277c1a | |||
2fb6ea6c64 | |||
f6568230b1 | |||
97141dec4d | |||
62bd7de0be | |||
c32eb91d03 | |||
3f04f3561d | |||
863f5cf6ec | |||
4761db6541 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -31,7 +31,7 @@ staticfiles/*
|
||||
!staticfiles/.gitkeep
|
||||
.vscode/symbols.json
|
||||
|
||||
apps/static/assets/node_modules
|
||||
apps/static/assets/yarn.lock
|
||||
apps/static/assets/.temp
|
||||
apps/static/node_modules
|
||||
apps/static/yarn.lock
|
||||
apps/static/.temp
|
||||
|
||||
|
21
Dockerfile
21
Dockerfile
@ -1,18 +1,27 @@
|
||||
FROM python:3.9
|
||||
FROM python:3.12
|
||||
|
||||
# set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV DJANGO_SETTINGS_MODULE core.settings
|
||||
|
||||
WORKDIR /website
|
||||
|
||||
COPY requirements.txt .
|
||||
# install python dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
COPY . /website/
|
||||
|
||||
# running migrations
|
||||
# collect static files
|
||||
RUN python manage.py collectstatic --noinput
|
||||
|
||||
# run migrations
|
||||
RUN python manage.py migrate
|
||||
|
||||
# Port that the site runs on
|
||||
EXPOSE 4411
|
||||
|
||||
# gunicorn
|
||||
CMD ["gunicorn", "--config", "gunicorn-cfg.py", "core.wsgi"]
|
||||
CMD ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:4411"]
|
@ -111,7 +111,7 @@ class DepartmentSerializer(DynamicModelSerializer):
|
||||
class Meta:
|
||||
model = Department
|
||||
fields = [
|
||||
"uuid", "title", "icon"
|
||||
"uuid", "title", "colour", "backgroundcolour", "order"
|
||||
]
|
||||
|
||||
|
||||
@ -130,7 +130,7 @@ class TicketPrioritySerializer(DynamicModelSerializer):
|
||||
class Meta:
|
||||
model = TicketPriority
|
||||
fields = [
|
||||
"uuid", "title", "colour", "backgroundcolour"
|
||||
"uuid", "title", "colour", "backgroundcolour", "order"
|
||||
]
|
||||
|
||||
|
||||
@ -138,7 +138,7 @@ class TicketTagSerializer(DynamicModelSerializer):
|
||||
class Meta:
|
||||
model = TicketTag
|
||||
fields = [
|
||||
"uuid", "title", "colour", "backgroundcolour"
|
||||
"uuid", "title", "colour", "backgroundcolour", "order"
|
||||
]
|
||||
|
||||
|
||||
@ -151,6 +151,6 @@ class TicketSerializer(DynamicModelSerializer):
|
||||
model = Ticket
|
||||
fields = (
|
||||
"uuid", "title", "description", "author", "create_timestamp",
|
||||
"edit_timestamp", "is_edited", "was_yesterday", "is_older_than_day",
|
||||
"timestamp", "priority", "tags", "short_description", "string_datetime"
|
||||
"edit_timestamp", "is_edited", "timestamp", "priority", "tags",
|
||||
"short_description", "display_datetime"
|
||||
)
|
||||
|
@ -47,7 +47,7 @@ class TicketListApiView(generics.ListAPIView):
|
||||
pagination_class = TicketPaginiation
|
||||
serializer_class = TicketSerializer
|
||||
|
||||
queryset = Ticket.objects.all()
|
||||
# queryset = Ticket.objects.all()
|
||||
|
||||
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
|
||||
filterset_fields = ["uuid", "priority", "tags", "author", "author__department"]
|
||||
@ -55,8 +55,16 @@ class TicketListApiView(generics.ListAPIView):
|
||||
ordering_fields = ["create_timestamp", "edit_timestamp"]
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.user.is_superuser:
|
||||
queryset=Ticket.objects.all()
|
||||
else:
|
||||
queryset = Ticket.objects.filter(author=self.request.user)
|
||||
|
||||
strict_tags = self.request.query_params.get("strict-tags")
|
||||
if not strict_tags:
|
||||
return queryset
|
||||
|
||||
tag_uuids = self.request.query_params.getlist("tags", [])
|
||||
queryset = self.queryset
|
||||
|
||||
log.debug("tag uuids %s", tag_uuids)
|
||||
|
||||
@ -73,7 +81,11 @@ class FilterCountListApiView(generics.ListAPIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
if self.request.user.is_superuser:
|
||||
self._tickets = Ticket.objects.all()
|
||||
else:
|
||||
self._tickets = Ticket.objects.filter(author=self.request.user)
|
||||
|
||||
data = {"tickets": self._tickets.count()}
|
||||
|
||||
self._fill_data(TicketPriority, data, "priority")
|
||||
|
@ -18,7 +18,7 @@ class UserAdmin(admin.ModelAdmin):
|
||||
|
||||
class DepartmentAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ["uuid", "title", "icon", "get_user_count"]
|
||||
list_display = ["uuid", "title", "colour", "backgroundcolour", "order", "get_user_count"]
|
||||
|
||||
@admin.display(description="Users")
|
||||
def get_user_count(self, obj):
|
||||
|
@ -4,7 +4,9 @@
|
||||
"pk": "4e245769-6b67-4a6e-b804-54a3ceb3b8c0",
|
||||
"fields": {
|
||||
"title": "Development",
|
||||
"icon": null
|
||||
"colour": "#1976d2",
|
||||
"backgroundcolour": "#bbdefb",
|
||||
"order": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -12,7 +14,9 @@
|
||||
"pk": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
|
||||
"fields": {
|
||||
"title": "Marketing",
|
||||
"icon": null
|
||||
"colour": "#7b1fa2",
|
||||
"backgroundcolour": "#e1bee7",
|
||||
"order": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -20,7 +24,9 @@
|
||||
"pk": "a6517555-0bcc-4baa-8e2f-798916562b1c",
|
||||
"fields": {
|
||||
"title": "Management",
|
||||
"icon": null
|
||||
"colour": "#689f38",
|
||||
"backgroundcolour": "#dcedc8",
|
||||
"order": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -28,7 +34,9 @@
|
||||
"pk": "bae35c7a-a929-4465-b70f-03254b0774e0",
|
||||
"fields": {
|
||||
"title": "Sales",
|
||||
"icon": null
|
||||
"colour": "#e64a19",
|
||||
"backgroundcolour": "#ffccbc",
|
||||
"order": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -36,7 +44,9 @@
|
||||
"pk": "c2bbabd7-05ac-4bc8-97a3-15bafdb478d9",
|
||||
"fields": {
|
||||
"title": "Business Strategy",
|
||||
"icon": null
|
||||
"colour": "#c2185b",
|
||||
"backgroundcolour": "#f8bbd0",
|
||||
"order": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 3.2.16 on 2024-01-20 19:15
|
||||
# Generated by Django 3.2.16 on 2024-01-22 14:39
|
||||
|
||||
import apps.authentication.models
|
||||
from django.db import migrations, models
|
||||
@ -21,7 +21,9 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('title', models.CharField(max_length=150)),
|
||||
('icon', models.CharField(blank=True, max_length=32, null=True)),
|
||||
('colour', models.CharField(max_length=7)),
|
||||
('backgroundcolour', models.CharField(max_length=7)),
|
||||
('order', models.PositiveIntegerField(default=0)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
@ -31,7 +31,9 @@ class Department(models.Model):
|
||||
uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False)
|
||||
|
||||
title = models.CharField(max_length=150)
|
||||
icon = models.CharField(max_length=32, null=True, blank=True)
|
||||
colour = models.CharField(max_length=7)
|
||||
backgroundcolour = models.CharField(max_length=7)
|
||||
order = models.PositiveIntegerField(default=0, blank=False, null=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
@ -39,9 +39,9 @@ def register_user(request):
|
||||
user = form.save(commit=False)
|
||||
|
||||
# Develepment, give all new users admin
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
# user.is_staff = True
|
||||
# user.is_superuser = True
|
||||
# user.save()
|
||||
|
||||
email = form.cleaned_data.get("email")
|
||||
raw_password = form.cleaned_data.get("password1")
|
||||
|
@ -26,7 +26,7 @@ class TicketAdmin(admin.ModelAdmin):
|
||||
|
||||
class TicketPriorityAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ["uuid", "title", "colour", "backgroundcolour", "get_used_count"]
|
||||
list_display = ["uuid", "title", "colour", "backgroundcolour", "order", "get_used_count"]
|
||||
|
||||
@admin.display(description="Used by")
|
||||
def get_used_count(self, obj):
|
||||
@ -35,7 +35,7 @@ class TicketPriorityAdmin(admin.ModelAdmin):
|
||||
|
||||
class TicketTagAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ["uuid", "title", "colour", "backgroundcolour", "get_used_count"]
|
||||
list_display = ["uuid", "title", "colour", "backgroundcolour", "order", "get_used_count"]
|
||||
|
||||
@admin.display(description="Used by")
|
||||
def get_used_count(self, obj):
|
||||
|
@ -1,204 +0,0 @@
|
||||
[
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "0725deef-d48c-4e38-814a-ae35fcbad152",
|
||||
"fields": {
|
||||
"title": "Software Compatibility Challenge",
|
||||
"description": "Attempting to integrate a new software tool into our existing workflow, but facing compatibility challenges with other applications. Providing a detailed overview of the software stack and the specific issues encountered during integration attempts. Seeking expert guidance to resolve compatibility issues and ensure smooth workflow integration.",
|
||||
"author": "4965745e-b82a-4496-80e1-055217a780b0",
|
||||
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
|
||||
"create_timestamp": "2024-01-09T00:11:40Z",
|
||||
"edit_timestamp": "2024-01-09T00:11:40Z",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
"cc473838-acaf-43f9-a601-dc4ab1f9026c"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "1e648e67-ddf6-4007-bb68-4a863961a827",
|
||||
"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": "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",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
"cc473838-acaf-43f9-a601-dc4ab1f9026c"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "433200a2-3a62-4e81-86cc-cd5e31fecd8b",
|
||||
"fields": {
|
||||
"title": "Network Connectivity Issue",
|
||||
"description": "Experiencing intermittent connection drops in the office. Several users affected. Need urgent assistance to resolve the issue.",
|
||||
"author": "4965745e-b82a-4496-80e1-055217a780b0",
|
||||
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
|
||||
"create_timestamp": "2024-01-09T00:05:54Z",
|
||||
"edit_timestamp": "2024-01-09T00:05:54Z",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"28b160b5-5c8b-43a5-84d1-4179bde87e6f",
|
||||
"72fb255c-132f-4124-802d-f4c051620540"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "4b893b70-8fce-49a1-b546-d1960ef97f9c",
|
||||
"fields": {
|
||||
"title": "New Software Request",
|
||||
"description": "Requesting the installation of a new software tool for project management. Providing specific details on the software requirements and its relevance to project workflows. Seeking guidance on the installation process and any potential compatibility issues. Your assistance in this matter is highly appreciated.",
|
||||
"author": "4965745e-b82a-4496-80e1-055217a780b0",
|
||||
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
|
||||
"create_timestamp": "2024-01-09T00:09:59Z",
|
||||
"edit_timestamp": "2024-01-09T17:38:15Z",
|
||||
"tags": [
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
"cc473838-acaf-43f9-a601-dc4ab1f9026c",
|
||||
"dc4dc2d5-2784-4726-8a4e-99df35a143d2"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "587b35a3-1b47-4716-99bf-ebe22da283a1",
|
||||
"fields": {
|
||||
"title": "Database Query Performance",
|
||||
"description": "Users are reporting slow response times when querying the database. This issue is impacting productivity across multiple teams. Detailed logs and steps taken to troubleshoot are provided within the description. Urgently seeking assistance to optimize database performance and resolve the slowdown.",
|
||||
"author": "4965745e-b82a-4496-80e1-055217a780b0",
|
||||
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
|
||||
"create_timestamp": "2024-01-09T00:08:32Z",
|
||||
"edit_timestamp": "2024-01-09T00:08:32Z",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"28b160b5-5c8b-43a5-84d1-4179bde87e6f",
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
"cc473838-acaf-43f9-a601-dc4ab1f9026c",
|
||||
"e8c7e801-57c3-4699-a4f4-4308fb489f60"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "5f39644f-0356-4818-aa07-ee6f07f36553",
|
||||
"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": "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",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
"e8c7e801-57c3-4699-a4f4-4308fb489f60"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "68fa33a7-9ac0-4612-8d92-71201c1dd5d5",
|
||||
"fields": {
|
||||
"title": "Printer Configuration Error",
|
||||
"description": "Encountering issues with configuring a new printer for the design team. Detailed steps taken to set up the printer and the specific error messages received are provided. Urgently seeking assistance to ensure the printer is operational and meets the team's printing requirements.",
|
||||
"author": "4965745e-b82a-4496-80e1-055217a780b0",
|
||||
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
|
||||
"create_timestamp": "2024-01-09T00:10:25Z",
|
||||
"edit_timestamp": "2024-01-09T00:10:25Z",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
"e8c7e801-57c3-4699-a4f4-4308fb489f60"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "9f72bd78-2d1f-4fd6-89fc-d9ecf8c69b3c",
|
||||
"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": "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",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"28b160b5-5c8b-43a5-84d1-4179bde87e6f",
|
||||
"72fb255c-132f-4124-802d-f4c051620540"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "b0142315-b5c4-46a9-b02e-bdfdd6dffc37",
|
||||
"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": "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",
|
||||
"tags": [
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
"cc473838-acaf-43f9-a601-dc4ab1f9026c"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "c470a8b3-ca54-4324-abdf-1cfa4cdf70d6",
|
||||
"fields": {
|
||||
"title": "Email Configuration Assistance",
|
||||
"description": "Need assistance in configuring email settings for a new team member. Providing the email client details and steps taken so far. Urgently seeking guidance to ensure the seamless setup of email accounts and communication channels for the new team member.",
|
||||
"author": "4965745e-b82a-4496-80e1-055217a780b0",
|
||||
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
|
||||
"create_timestamp": "2024-01-09T00:11:57Z",
|
||||
"edit_timestamp": "2024-01-09T00:12:57Z",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"72fb255c-132f-4124-802d-f4c051620540"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "dd5764ad-e765-4f9b-9a9b-a3a27e60c6dd",
|
||||
"fields": {
|
||||
"title": "General Inquiry",
|
||||
"description": "Have a question regarding the new IT policies. Need clarification on specific guidelines. Please provide assistance at your earliest convenience.",
|
||||
"author": "4965745e-b82a-4496-80e1-055217a780b0",
|
||||
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
|
||||
"create_timestamp": "2024-01-09T00:07:26Z",
|
||||
"edit_timestamp": "2024-01-09T00:07:26Z",
|
||||
"tags": [
|
||||
"dc4dc2d5-2784-4726-8a4e-99df35a143d2"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "home.ticket",
|
||||
"pk": "fd8a4ad9-04fb-4a0c-beac-e60e0739e881",
|
||||
"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": "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",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
"cc473838-acaf-43f9-a601-dc4ab1f9026c"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -8,7 +8,7 @@
|
||||
"author": "21b457a1-b64a-4499-8c53-0e2f3b42fe3c",
|
||||
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
|
||||
"create_timestamp": "2024-01-09T00:11:40Z",
|
||||
"edit_timestamp": "2024-01-16T15:53:48.787Z",
|
||||
"edit_timestamp": "2024-01-03T15:53:48.787Z",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
@ -25,7 +25,7 @@
|
||||
"author": "248bc1ef-df52-445e-847c-e370dccf436a",
|
||||
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
|
||||
"create_timestamp": "2024-01-09T00:09:20Z",
|
||||
"edit_timestamp": "2024-01-16T15:53:31.535Z",
|
||||
"edit_timestamp": "2023-06-29T15:53:31.535Z",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"72fb255c-132f-4124-802d-f4c051620540",
|
||||
@ -42,7 +42,7 @@
|
||||
"author": "9f469a37-4d8d-4bd0-ba4e-16b07549f42a",
|
||||
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
|
||||
"create_timestamp": "2024-01-09T00:05:54Z",
|
||||
"edit_timestamp": "2024-01-16T15:53:18.994Z",
|
||||
"edit_timestamp": "2024-01-13T15:53:18.994Z",
|
||||
"tags": [
|
||||
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
|
||||
"28b160b5-5c8b-43a5-84d1-4179bde87e6f",
|
23
apps/home/migrations/0003_auto_20240122_1323.py
Normal file
23
apps/home/migrations/0003_auto_20240122_1323.py
Normal file
@ -0,0 +1,23 @@
|
||||
# Generated by Django 3.2.16 on 2024-01-22 13:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0002_auto_20240112_1604'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ticketpriority',
|
||||
name='order',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tickettag',
|
||||
name='order',
|
||||
field=models.PositiveIntegerField(default=0),
|
||||
),
|
||||
]
|
@ -19,6 +19,7 @@ class TicketPriority(models.Model):
|
||||
title = models.CharField(max_length=32)
|
||||
colour = models.CharField(max_length=7)
|
||||
backgroundcolour = models.CharField(max_length=7)
|
||||
order = models.PositiveIntegerField(default=0, blank=False, null=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
@ -30,6 +31,7 @@ class TicketTag(models.Model):
|
||||
title = models.CharField(max_length=32)
|
||||
colour = models.CharField(max_length=7)
|
||||
backgroundcolour = models.CharField(max_length=7)
|
||||
order = models.PositiveIntegerField(default=0, blank=False, null=False)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
@ -53,8 +55,6 @@ class Ticket(models.Model):
|
||||
max_length=650,
|
||||
)
|
||||
|
||||
# Dirty Foreigers
|
||||
|
||||
author = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
verbose_name=_("author"),
|
||||
@ -141,35 +141,7 @@ class Ticket(models.Model):
|
||||
return self.create_timestamp != self.edit_timestamp
|
||||
|
||||
@property
|
||||
def is_older_than_day(self) -> bool:
|
||||
"""Returns boolean dependent on if `self.timestamp` is older than 24 hours.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if `self.timestamp` is older than 24 hours, False otherwise.
|
||||
"""
|
||||
|
||||
dayago = timezone.now() - timedelta(hours=24)
|
||||
return self.timestamp <= dayago
|
||||
|
||||
@property
|
||||
def was_yesterday(self) -> bool:
|
||||
"""Returns a boolean dependent on if `self.timestamp` is from before midnight yesterday.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
_description_
|
||||
"""
|
||||
|
||||
now = timezone.now()
|
||||
midnight_today = now - timedelta(hours=now.hour, minutes=now.minute, seconds=now.second, microseconds=now.microsecond)
|
||||
|
||||
return self.timestamp < midnight_today
|
||||
|
||||
@property
|
||||
def string_datetime(self) -> str:
|
||||
def display_datetime(self) -> str:
|
||||
"""Provides a human readable string representation of `self.timestamp` that should be displayed
|
||||
to represent the ticket's age.
|
||||
|
||||
|
@ -4,14 +4,14 @@ from django.urls import path, include
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from apps.home import views
|
||||
from .views import TicketView
|
||||
from .views import DashboardView, TicketView
|
||||
|
||||
def reverse_to_index(reqeust):
|
||||
return redirect("dashboard")
|
||||
|
||||
urlpatterns = [
|
||||
path("", reverse_to_index, name="index"),
|
||||
path('dashboard/', views.dashboard, name="dashboard"),
|
||||
path('dashboard/', DashboardView.as_view(), name="dashboard"),
|
||||
path('tickets/', include([
|
||||
path('', TicketView.as_view(), name="tickets"),
|
||||
path('new/', views.new_ticket, name="ticket-new"),
|
||||
|
@ -19,9 +19,12 @@ from ..authentication.models import Department
|
||||
from .models import Ticket, TicketPriority, TicketTag
|
||||
|
||||
|
||||
@login_required()
|
||||
def dashboard(request):
|
||||
return render(request, "home/dashboard.html")
|
||||
class DashboardView(TemplateView):
|
||||
template_name = "home/dashboard.html"
|
||||
|
||||
@method_decorator(login_required)
|
||||
def get(self, request, *args, **kwargs):
|
||||
return render(request, self.template_name)
|
||||
|
||||
|
||||
class TicketView(TemplateView):
|
||||
@ -29,15 +32,14 @@ class TicketView(TemplateView):
|
||||
|
||||
@method_decorator(login_required)
|
||||
def get(self, request):
|
||||
priorities = TicketPriority.objects.all()
|
||||
tags = TicketTag.objects.all()
|
||||
departments = Department.objects.all()
|
||||
priorities = TicketPriority.objects.all().order_by("order")
|
||||
tags = TicketTag.objects.all().order_by("order")
|
||||
departments = Department.objects.all().order_by("order")
|
||||
|
||||
context = {
|
||||
"priorities": priorities,
|
||||
"tags": tags,
|
||||
"departments": departments,
|
||||
"dayago": datetime.now() - timedelta(hours=24)
|
||||
"departments": departments
|
||||
}
|
||||
|
||||
return render(request, "home/tickets.html", context)
|
||||
|
7
apps/static/bootstrap.min.js
vendored
7
apps/static/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
@ -130,6 +130,10 @@ body {
|
||||
width: 0 !important;
|
||||
}
|
||||
|
||||
.ticket-item .ticket-item-complex .badge {
|
||||
min-width: 1.8rem;
|
||||
}
|
||||
|
||||
.ticket-item .ticket-item-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
|
@ -1,7 +1,7 @@
|
||||
*
|
||||
/*
|
||||
* Container style
|
||||
*/
|
||||
.ps {
|
||||
.ps {
|
||||
overflow: hidden !important;
|
||||
overflow-anchor: none;
|
||||
-ms-overflow-style: none;
|
||||
@ -15,8 +15,8 @@
|
||||
.ps__rail-x {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||
-webkit-transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||
transition: background-color .2s linear, opacity .2s linear;
|
||||
-webkit-transition: background-color .2s linear, opacity .2s linear;
|
||||
height: 15px;
|
||||
/* there must be 'bottom' or 'top' for ps__rail-x */
|
||||
bottom: 0px;
|
||||
@ -27,8 +27,8 @@
|
||||
.ps__rail-y {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||
-webkit-transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||
transition: background-color .2s linear, opacity .2s linear;
|
||||
-webkit-transition: background-color .2s linear, opacity .2s linear;
|
||||
width: 15px;
|
||||
/* there must be 'right' or 'left' for ps__rail-y */
|
||||
right: 0;
|
||||
@ -57,8 +57,7 @@
|
||||
.ps .ps__rail-y:focus,
|
||||
.ps .ps__rail-x.ps--clicking,
|
||||
.ps .ps__rail-y.ps--clicking {
|
||||
/* background-color: #eee; */
|
||||
background-color: var(--bs-tertiary-bg); /* Custom Change */
|
||||
background-color: #eee;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@ -68,8 +67,8 @@
|
||||
.ps__thumb-x {
|
||||
background-color: #aaa;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s linear, height 0.2s ease-in-out;
|
||||
-webkit-transition: background-color 0.2s linear, height 0.2s ease-in-out;
|
||||
transition: background-color .2s linear, height .2s ease-in-out;
|
||||
-webkit-transition: background-color .2s linear, height .2s ease-in-out;
|
||||
height: 6px;
|
||||
/* there must be 'bottom' for ps__thumb-x */
|
||||
bottom: 2px;
|
||||
@ -80,8 +79,8 @@
|
||||
.ps__thumb-y {
|
||||
background-color: #aaa;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s linear, width 0.2s ease-in-out;
|
||||
-webkit-transition: background-color 0.2s linear, width 0.2s ease-in-out;
|
||||
transition: background-color .2s linear, width .2s ease-in-out;
|
||||
-webkit-transition: background-color .2s linear, width .2s ease-in-out;
|
||||
width: 6px;
|
||||
/* there must be 'right' for ps__thumb-y */
|
||||
right: 2px;
|
||||
@ -100,7 +99,6 @@
|
||||
.ps__rail-y:focus > .ps__thumb-y,
|
||||
.ps__rail-y.ps--clicking .ps__thumb-y {
|
||||
background-color: #999;
|
||||
/* background-color: var(--bs-body); */
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
@ -110,6 +108,7 @@
|
||||
overflow: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
||||
.ps {
|
||||
overflow: auto !important;
|
23
apps/static/js/base.js
Normal file
23
apps/static/js/base.js
Normal file
@ -0,0 +1,23 @@
|
||||
$(document).ready(function() {
|
||||
|
||||
// Activate all tooltips
|
||||
$('[data-bs-toggle="tooltip"]').tooltip();
|
||||
|
||||
// Apply the user preferred theme
|
||||
const theme = localStorage.getItem("theme");
|
||||
if (theme == "light" || theme == "dark") {
|
||||
$("body").attr("data-bs-theme", theme);
|
||||
}
|
||||
else {
|
||||
$("body").attr("data-bs-theme", "light");
|
||||
}
|
||||
});
|
||||
|
||||
$("#themeToggle").on("click", function() {
|
||||
var theme = $("body").attr("data-bs-theme");
|
||||
|
||||
theme = theme == "light" ? "dark" : "light";
|
||||
|
||||
localStorage.setItem("theme", theme)
|
||||
$("body").attr("data-bs-theme", theme);
|
||||
});
|
7
apps/static/js/bootstrap.bundle.min.js
vendored
Normal file
7
apps/static/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
39
apps/static/js/perfectscrollbar.js
Normal file
39
apps/static/js/perfectscrollbar.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,4 +1,4 @@
|
||||
var filters = {"ordering": "-edit_timestamp"};
|
||||
var filters = {"ordering": "-edit_timestamp", "strict-tags": true};
|
||||
global_loadingTickets = false;
|
||||
searchTimeout = null;
|
||||
pagination = {};
|
||||
@ -6,6 +6,7 @@ var filters = {"ordering": "-edit_timestamp"};
|
||||
|
||||
$(document).ready(function() {
|
||||
initSearchBar();
|
||||
toggleComplexItems(localStorage.getItem("hideComplexTickets") === "true");
|
||||
|
||||
setupFilter("#filterSidebar .filter-department", "author__department");
|
||||
setupFilter("#filterSidebar .filter-tags", "tags");
|
||||
@ -17,20 +18,26 @@ $(document).ready(function() {
|
||||
|
||||
function updateItemsState(state) {
|
||||
console.debug(`updating items state to '${state}'`);
|
||||
$("#ticketsContainer").scrollTop(0);
|
||||
$("#ticketsContainer").trigger("updateScrollbar");
|
||||
|
||||
switch (state) {
|
||||
case "content":
|
||||
$("#ticketsContainer .none-found").hide();
|
||||
$("#ticketsContainer .loading").hide();
|
||||
$("#filterSidebar input").prop("disabled", false);
|
||||
break;
|
||||
case "loading":
|
||||
$("#ticketsContainer .content").empty();
|
||||
$("#ticketsContainer .none-found").hide();
|
||||
$("#ticketsContainer .loading").show();
|
||||
$("#filterSidebar input").prop("disabled", true);
|
||||
break;
|
||||
case "no-content":
|
||||
$("#ticketsContainer .content").empty();
|
||||
$("#ticketsContainer .none-found").show();
|
||||
$("#ticketsContainer .loading").hide();
|
||||
$("#filterSidebar input").prop("disabled", false);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid Items State '${state}'`);
|
||||
@ -216,12 +223,34 @@ function changeItemsPage(next) {
|
||||
loadTicketItems(page);
|
||||
}
|
||||
|
||||
$("#strictTags").on("change", function() {
|
||||
const strictTags = $(this).prop("checked");
|
||||
if (strictTags) filters["strict-tags"] = strictTags;
|
||||
else delete filters["strict-tags"];
|
||||
|
||||
loadTicketItems();
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Loads the list of ticket items using the current filters.
|
||||
*
|
||||
* @function loadTicketItems
|
||||
* @param {Number} page For pagination, an invalid page will result in an error.
|
||||
*/
|
||||
function loadTicketItems(page=1) {
|
||||
|
||||
if (global_loadingTickets) {
|
||||
alert("Spam prevention\nStopped loadTicketItems because already loading.");
|
||||
return;
|
||||
}
|
||||
global_loadingTickets = true;
|
||||
|
||||
updateItemsState("loading");
|
||||
filters["page"] = page;
|
||||
|
||||
var fetchFilters = { ...filters };
|
||||
fetchFilters["only_fields"] = "uuid,title,short_description,author,priority,tags,timestamp,is_edited,author__forename,author__surname,author__department,author__icon,string_datetime";
|
||||
fetchFilters["only_fields"] = "uuid,title,short_description,author,priority,tags,timestamp,is_edited,author__forename,author__surname,author__department,author__icon,display_datetime";
|
||||
|
||||
fetchTicketsPromise(fetchFilters).then((response) => {
|
||||
// Update the counts to show how many tickets were found
|
||||
@ -256,11 +285,30 @@ function loadTicketItems(page=1) {
|
||||
|
||||
// Iterate over and handle each ticket
|
||||
response.results.forEach(function(ticket) {
|
||||
$("#ticketsContainer .content").append(createTicketItem(ticket));
|
||||
});
|
||||
|
||||
// Make tickets clickable
|
||||
applyTicketClickFunction();
|
||||
|
||||
updateItemsState("content");
|
||||
global_loadingTickets = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a jquery object representing an element for a ticket item, constructed using the passed
|
||||
* ticket and a predefined template.
|
||||
*
|
||||
* @function createTicketItem
|
||||
* @param {Object} ticket An object representing a ticket.
|
||||
* @return {jQuery} ticketElement to be appeneded as content.
|
||||
*/
|
||||
function createTicketItem(ticket) {
|
||||
// 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(ticket.string_datetime);
|
||||
template.find(".ticket-item-datetime").text(ticket.display_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);
|
||||
@ -274,32 +322,36 @@ function loadTicketItems(page=1) {
|
||||
template.find(".ticket-item-tags").append(tagTemplate);
|
||||
});
|
||||
|
||||
const priority = ticket.priority;
|
||||
|
||||
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("color", priority.colour);
|
||||
priorityElem.css("background-color", priority.backgroundcolour);
|
||||
priorityElem.attr("data-bs-title", priority.title + " Priority");
|
||||
priorityElem.tooltip();
|
||||
|
||||
const department = ticket.author.department;
|
||||
|
||||
var departmentElem = template.find(".ticket-item-department");
|
||||
departmentElem.addClass("bgc-orange-100 c-orange-700")
|
||||
if (department === null) {
|
||||
departmentElem.hide();
|
||||
}
|
||||
else {
|
||||
departmentElem.css("color", ticket.author.department.colour);
|
||||
departmentElem.css("background-color", ticket.author.department.backgroundcolour);
|
||||
departmentElem.attr("data-bs-title", ticket.author.department.title + " Department");
|
||||
departmentElem.tooltip();
|
||||
}
|
||||
|
||||
// // 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);
|
||||
});
|
||||
|
||||
// Make tickets clickable
|
||||
applyTicketClickFunction();
|
||||
|
||||
updateItemsState("content");
|
||||
});
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the content of a selected ticket.
|
||||
*
|
||||
* @function loadTicketContent
|
||||
* @param {String} uuid A string representation of the ticket's UUID.
|
||||
*/
|
||||
function loadTicketContent(uuid) {
|
||||
updateContentState("loading");
|
||||
|
||||
@ -307,16 +359,31 @@ function loadTicketContent(uuid) {
|
||||
console.debug("Spam prevention\nStopped loadTicketContent because already loading.");
|
||||
return;
|
||||
}
|
||||
global_loadingTickets = true;
|
||||
|
||||
$("#ticketContent .content").empty();
|
||||
|
||||
fetchTicketsPromise({uuid: uuid}).then((response) => {
|
||||
ticket = response.results[0];
|
||||
$("#ticketContent .content").append(createTicketContent(ticket));
|
||||
updateContentState("content");
|
||||
global_loadingTickets = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a jquery object representing an element for a ticket content, constructed using the
|
||||
* passed ticket and a predefined template.
|
||||
*
|
||||
* @function createTicketItem
|
||||
* @param {Object} ticket An object representing a ticket.
|
||||
* @return {jQuery} ticketElement to be shown as content.
|
||||
*/
|
||||
function createTicketContent(ticket) {
|
||||
// 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(ticket.string_datetime);
|
||||
template.find(".ticket-content-datetime").text(ticket.display_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);
|
||||
@ -329,14 +396,21 @@ function loadTicketContent(uuid) {
|
||||
template.find(".ticket-content-badges").append(tagTemplate);
|
||||
});
|
||||
|
||||
$("#ticketContent .content").append(template);
|
||||
var departmentElem = template.find(".ticket-content-department");
|
||||
|
||||
updateContentState("content");
|
||||
});
|
||||
if (ticket.author.department === null) {
|
||||
template.find(".ticket-content-department").hide();
|
||||
}
|
||||
else {
|
||||
|
||||
// $("#ticketContent").empty();
|
||||
}
|
||||
|
||||
// updateInterfaceState("showing-content");
|
||||
var priorityElem = template.find(".ticket-content-priority");
|
||||
priorityElem.css({"color": ticket.priority.colour, "background-color": ticket.priority.backgroundcolour});
|
||||
priorityElem.attr("data-bs-title", ticket.priority.title);
|
||||
priorityElem.tooltip();
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
function fetchTicketsPromise(queryFilters) {
|
||||
@ -370,13 +444,13 @@ $(".dropdown-menu.prevent-click-close").on("click", function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
function toggleComplexItems() {
|
||||
complexItems = !complexItems;
|
||||
function toggleComplexItems(hideComplex=null) {
|
||||
if (hideComplex === null) {
|
||||
hideComplex = !(localStorage.getItem("hideComplexTickets") === "true");
|
||||
}
|
||||
|
||||
if (complexItems) {
|
||||
$("#ticketsContainer").addClass("complex-items");
|
||||
}
|
||||
else {
|
||||
$("#ticketsContainer").removeClass("complex-items");
|
||||
}
|
||||
if (hideComplex) $("#ticketsContainer").removeClass("complex-items");
|
||||
else $("#ticketsContainer").addClass("complex-items");
|
||||
|
||||
localStorage.setItem("hideComplexTickets", hideComplex);
|
||||
}
|
@ -5,341 +5,321 @@
|
||||
|
||||
<!-- Specific CSS goes HERE -->
|
||||
{% block stylesheets %}
|
||||
<link rel="stylesheet" href="{% static '/css/select2-bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{{ ASSETS_ROOT }}/css/select2-bootstrap.min.css">
|
||||
{% endblock stylesheets %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<!-- ### $App Screen Content ### -->
|
||||
<main class='main-content'>
|
||||
<main class='main-content bgc-grey-100'>
|
||||
<div id='mainContent'>
|
||||
<div class="full-container" style="overflow: hidden;">
|
||||
<div class="full-container">
|
||||
<div class="email-app">
|
||||
<div class="email-side-nav remain-height ov-h">
|
||||
<div class="h-100 layers">
|
||||
<div class="p-20 bg-body-tertiary layer w-100">
|
||||
<div class="p-20 bgc-grey-100 layer w-100">
|
||||
<button type="button" class="btn btn-danger c-white w-100" data-bs-toggle="modal" data-bs-target="#ticketModal">New Ticket</button>
|
||||
</div>
|
||||
<div class="scrollable pos-r ov-h bdT layer w-100 fxg-1 bg-body">
|
||||
<ul id="filterSidebar" class="p-20 nav flex-column">
|
||||
<div class="scrollable pos-r bdT layer w-100 fxg-1">
|
||||
<ul class="p-20 nav flex-column">
|
||||
<li class="nav-item">
|
||||
<h6>Filters</h6>
|
||||
</li>
|
||||
|
||||
{% if priorities %}
|
||||
|
||||
<li class="nav-item mT-10 filter-priority">
|
||||
<h6 class="peers ai-c jc-sb mb-2 px-3">
|
||||
<span class="peer-greed">Priorities</span>
|
||||
<label for="filterPriority-all" class="nav-link text-reset actived p-0 cur-p" style="display: none">
|
||||
<input type="radio" id="filterPriority-all" name="filterPriorities" class="btn-check deselect-radio-filters" checked="checked" value="all">
|
||||
<span class="text-body badge small bg-none border-none py-0">
|
||||
<i class="ti-close"></i>
|
||||
</span>
|
||||
</label>
|
||||
</h6>
|
||||
</li>
|
||||
|
||||
<li class="nav-item px-3 mt-3">
|
||||
<h6 class="small">Priority</h6>
|
||||
{% for priority in priorities %}
|
||||
|
||||
<li class="nav-item filter-priority">
|
||||
<label for="filterPriority-{{ priority.uuid }}" class="nav-link text-reset actived">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<input type="radio" id="filterPriority-{{ priority.uuid }}" name="filterPriorities" class="form-check-input me-2" value="{{ priority.uuid }}">
|
||||
<span>{{ priority.title }}</span>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill" style="color: {{ priority.colour }}; background-color: {{ priority.backgroundcolour }};">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<li class="nav-item">
|
||||
<hr class="mY-30 border-secondary">
|
||||
</li>
|
||||
|
||||
{% if tags %}
|
||||
|
||||
<li class="nav-item">
|
||||
<h6 class="peers ai-c jc-sb mb-2 px-3">
|
||||
<span class="peer-greed">Tags</span>
|
||||
<div class="peer dropdown">
|
||||
<span class="text-body badge small bg-none border-none py-0 cur-p" data-bs-toggle="dropdown">
|
||||
<i class="ti-more-alt"></i>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" id="priority-{{ priority.id }}" class="form-check-input me-3">
|
||||
<label for="priority-{{ priority.id }}" class="form-check-label">
|
||||
<span class="badge rounded-pill" style="color: {{ priority.colour }}; background-color: {{ priority.backgroundcolour }};">
|
||||
{{ priority.title }}
|
||||
<i class="ti-control-record ms-auto"></i>
|
||||
</span>
|
||||
<div class="dropdown-menu prevent-click-close mT-5">
|
||||
<li class="px-3 py-2">
|
||||
<label for="strictTags" class="form-check-label small mb-2 fw-normal">Only show tickets matching all selected tags?</label>
|
||||
<div class="form-switch flex-wrap">
|
||||
<input type="checkbox" name="strictTags" id="strictTags" class="form-check-input" checked="checked">
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
</li>
|
||||
|
||||
{% for tag in tags %}
|
||||
|
||||
<li class="nav-item filter-tags">
|
||||
<label for="filterTag-{{ tag.uuid }}" class="nav-link text-reset actived">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<input type="checkbox" id="filterTag-{{ tag.uuid }}" class="form-check-input me-2" value="{{ tag.uuid }}">
|
||||
<span>{{ tag.title }}</span>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill" style="color: {{ tag.colour }}; background-color: {{ tag.backgroundcolour }};">0</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<li class="nav-item">
|
||||
<hr class="mY-30 border-secondary">
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if departments %}
|
||||
|
||||
<li id="filterDepartmentAll" class="nav-item filter-department">
|
||||
<h6 class="peers ai-c jc-sb mb-2 px-3">
|
||||
<span class="peer-greed">Departments</span>
|
||||
<label for="filterDepartment-all" class="nav-link text-reset actived p-0 cur-p" style="display: none">
|
||||
<input type="radio" id="filterDepartment-all" name="filterDepartment" class="btn-check deselect-radio-filters" checked="checked" value="all">
|
||||
<span class="text-body badge small bg-none border-none py-0">
|
||||
<i class="ti-close"></i>
|
||||
</span>
|
||||
</label>
|
||||
</h6>
|
||||
</li>
|
||||
|
||||
<li class="nav-item px-3 mt-3">
|
||||
<h6 class="small">Department</h6>
|
||||
{% for department in departments %}
|
||||
|
||||
<li class="nav-item filter-department">
|
||||
<label for="filterDepartment-{{ department.uuid }}" class="nav-link text-reset actived">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<input type="radio" id="filterDepartment-{{ department.uuid }}" name="filterDepartment" class="form-check-input me-2" value="{{ department.uuid }}">
|
||||
<span>{{ department.title }}</span>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill bg-secondary-subtle text-secondary-emphasis">0</span> <!-- bgc-green-50 c-green-700 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" id="department-{{ department.id }}" class="form-check-input me-2">
|
||||
<label for="department-{{ department.id }}" class="form-check-label d-flex jc-sb">
|
||||
{{ department.title }}
|
||||
<i class="{{ department.icon }} ms-auto"></i>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-wrapper row remain-height bg-body">
|
||||
<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-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>
|
||||
</button>
|
||||
<!-- <button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-tag"></i>
|
||||
</button> -->
|
||||
<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 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>
|
||||
</button>
|
||||
<div id="paginationCounts" class="bg-body pX-3 small d-flex ai-c">
|
||||
<span class="current">-</span>/<span class="total">-</span>
|
||||
</div>
|
||||
<button type="button" id="ticketItemsNextPage" class="btn bg-body bdrs-2" onclick="javascript: changeItemsPage(true);" disabled>
|
||||
<i class="ti-angle-right align-center"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer w-100">
|
||||
<div class="bdT bdB peers d-flex flex-nowrap">
|
||||
<label for="searchTickets" class="peer my-auto mX-15">
|
||||
<i class="ti-search"></i>
|
||||
{% if tags %}
|
||||
<li class="nav-item px-3 mt-3">
|
||||
<h6 class="small">Tags</h6>
|
||||
{% for tag in tags %}
|
||||
<div class="form-check mb-2">
|
||||
<input type="checkbox" id="tag-{{ tag.id }}" class="form-check-input me-3">
|
||||
<label for="tag-{{ tag.id }}" class="form-check-label">
|
||||
<span class="badge rounded-pill" style="color: {{ tag.colour }}; background-color: {{ tag.backgroundcolour }};">
|
||||
{{ tag.title }}
|
||||
<i class="ti-tag ms-auto"></i>
|
||||
</span>
|
||||
</label>
|
||||
<input type="text" id="searchTickets" class="form-control m-0 bdw-0 pY-15 pR-20 pL-0 bdrs-0 peer-greed shadow-none" placeholder="Search...">
|
||||
<label for="searchTickets" id="ticketCounts" class="peer my-auto mX-15 small">
|
||||
<span class="current"></span>/<span class="total"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ticketsContainer" class="layer w-100 fxg-1 scrollable pos-r ov-h">
|
||||
<div class="content"></div>
|
||||
<div class="none-found pos-a top-50 start-50 translate-middle" style="display: none;">
|
||||
<h6 class="fw-bold">No Tickets Found</h6>
|
||||
<ul class="text-body-tertiary small">
|
||||
<li>Try clearing your search</li>
|
||||
<li>Try removing your filters</li>
|
||||
<li>Try using the refresh button</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="loading bg-body-secondary h-100" style="display: none;">
|
||||
{% for i in "x"|rjust:"3" %}
|
||||
<div class="email-list-item peers fxw-nw p-20 bdB placeholder-glow" >
|
||||
<div class="peer mR-10">
|
||||
<span class="placeholder w-2r h-2r bdrs-50p me-2"></span>
|
||||
</div>
|
||||
<div class="peer peer-greed ov-h">
|
||||
<div class="peers ai-c">
|
||||
<div class="peer peer-greed">
|
||||
<span class="placeholder col-5 rounded"></span>
|
||||
</div>
|
||||
<div class="peer peer-greed d-flex jc-fe">
|
||||
<span class="placeholder col-4 rounded"></span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="placeholder rounded col-7 mT-10"></span>
|
||||
<div class="row">
|
||||
<span class="col-1"></span>
|
||||
<span class="placeholder rounded col-7 mT-10"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-list-item peers fxw-nw p-20 bdB placeholder-glow" >
|
||||
<div class="peer mR-10">
|
||||
<span class="placeholder w-2r h-2r bdrs-50p me-2"></span>
|
||||
</div>
|
||||
<div class="peer peer-greed ov-h">
|
||||
<div class="peers ai-c">
|
||||
<div class="peer peer-greed">
|
||||
<span class="placeholder col-6 rounded"></span>
|
||||
</div>
|
||||
<div class="peer peer-greed d-flex jc-fe">
|
||||
<span class="placeholder col-4 rounded"></span>
|
||||
</div>
|
||||
</div>
|
||||
<span class="placeholder rounded col-8 mT-10"></span>
|
||||
<span class="placeholder rounded col-7 mT-10"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-list-item peers fxw-nw p-20 bdB placeholder-glow" >
|
||||
<div class="peer mR-10">
|
||||
<span class="placeholder w-2r h-2r bdrs-50p me-2"></span>
|
||||
</div>
|
||||
<div class="peer peer-greed ov-h">
|
||||
<div class="peers ai-c">
|
||||
<div class="peer peer-greed">
|
||||
<span class="placeholder col-5 rounded"></span>
|
||||
</div>
|
||||
<div class="peer peer-greed d-flex jc-fe">
|
||||
<span class="placeholder col-4 rounded"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span class="col-1"></span>
|
||||
<span class="placeholder rounded col-7 mT-10"></span>
|
||||
</div>
|
||||
<span class="placeholder rounded col-7 mT-10"></span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</li>
|
||||
{% endif %}
|
||||
<!--
|
||||
<li class="nav-item mt-5">
|
||||
<a href="javascript:void(0)" class="nav-link c-grey-800 cH-blue-500 actived">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<i class="mR-10 ti-email"></i>
|
||||
<span>Inbox</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="email-content h-100 bg-body">
|
||||
<div class="h-100 scrollable pos-r">
|
||||
<div class="bg-body-tertiary peers ai-c jc-sb p-20 fxw-nw d-n@md+">
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill bgc-deep-purple-50 c-deep-purple-700">+99</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li> -->
|
||||
<!--<li class="nav-item">
|
||||
<a href="" class="nav-link c-grey-800 cH-blue-500">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<i class="mR-10 ti-share"></i>
|
||||
<span>Sent</span>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill bgc-green-50 c-green-700">12</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="" class="nav-link c-grey-800 cH-blue-500">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<i class="mR-10 ti-star"></i>
|
||||
<span>Important</span>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill bgc-blue-50 c-blue-700">3</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="" class="nav-link c-grey-800 cH-blue-500">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<i class="mR-10 ti-file"></i>
|
||||
<span>Drafts</span>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill bgc-amber-50 c-amber-700">5</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="" class="nav-link c-grey-800 cH-blue-500">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<i class="mR-10 ti-alert"></i>
|
||||
<span>Spam</span>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill bgc-red-50 c-red-700">1</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="" class="nav-link c-grey-800 cH-blue-500">
|
||||
<div class="peers ai-c jc-sb">
|
||||
<div class="peer peer-greed">
|
||||
<i class="mR-10 ti-trash"></i>
|
||||
<span>Trash</span>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<span class="badge rounded-pill bgc-red-50 c-red-700">+99</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-wrapper row remain-height bgc-white ov-h">
|
||||
<div class="email-list h-100 layers">
|
||||
<div class="layer w-100">
|
||||
<div class="bgc-grey-100 peers ai-c p-20 fxw-nw">
|
||||
<div class="peer me-auto">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="back-to-mailbox btn bg-body bdrs-2 mR-3">
|
||||
<i class="ti-angle-left"></i>
|
||||
<button type="button" class="email-side-toggle d-n@md+ btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-menu"></i>
|
||||
</button>
|
||||
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:reloadCurrentTicket();">
|
||||
<i class="ti-reload"></i>
|
||||
<button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-folder"></i>
|
||||
</button>
|
||||
<button type="button" class="btn bg-body bdrs-2 mR-3">
|
||||
<button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-tag"></i>
|
||||
</button>
|
||||
<div class="btn-group" role="group">
|
||||
<button id="btnGroupDrop1" type="button" class="btn cur-p bgc-white no-after dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="ti-more-alt"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu fsz-sm" aria-labelledby="btnGroupDrop1">
|
||||
<li>
|
||||
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
|
||||
<i class="ti-trash mR-10"></i>
|
||||
<span>Delete</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
|
||||
<i class="ti-alert mR-10"></i>
|
||||
<span>Mark as Spam</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
|
||||
<i class="ti-star mR-10"></i>
|
||||
<span>Star</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:changeTicket(false);">
|
||||
<button type="button" class="fsz-xs btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-angle-left"></i>
|
||||
</button>
|
||||
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:changeTicket(true);">
|
||||
<button type="button" class="fsz-xs btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-angle-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ticketContent" class="email-content-wrapper">
|
||||
<div class="content"></div>
|
||||
<div class="loading" style="display: none;">
|
||||
</div>
|
||||
<div class="layer w-100">
|
||||
<div class="bdT bdB">
|
||||
<input type="text" class="form-control m-0 bdw-0 pY-15 pX-20 bdrs-0" placeholder="Search...">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ticketsContainer" class="layer w-100 fxg-1 scrollable pos-r"></div>
|
||||
|
||||
</div>
|
||||
<div class="email-content h-100">
|
||||
<div class="h-100 scrollable pos-r">
|
||||
<div class="bgc-grey-100 peers ai-c jc-sb p-20 fxw-nw d-n@md+">
|
||||
<div class="peer">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="back-to-mailbox btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-angle-left"></i>
|
||||
</button>
|
||||
<button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-folder"></i>
|
||||
</button>
|
||||
<button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-tag"></i>
|
||||
</button>
|
||||
<div class="btn-group" role="group">
|
||||
<button id="btnGroupDrop1" type="button" class="btn cur-p bgc-white no-after dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="ti-more-alt"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu fsz-sm" aria-labelledby="btnGroupDrop1">
|
||||
<li>
|
||||
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
|
||||
<i class="ti-trash mR-10"></i>
|
||||
<span>Delete</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
|
||||
<i class="ti-alert mR-10"></i>
|
||||
<span>Mark as Spam</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
|
||||
<i class="ti-star mR-10"></i>
|
||||
<span>Star</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="fsz-xs btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-angle-left"></i>
|
||||
</button>
|
||||
<button type="button" class="fsz-xs btn bgc-white bdrs-2 mR-3 cur-p">
|
||||
<i class="ti-angle-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="email-content-wrapper">
|
||||
<!-- Header -->
|
||||
<div class="ticket-content placeholder-glow">
|
||||
<div class="peers ai-c jc-sb pX-40 pY-30">
|
||||
<div class="peers peer-greed">
|
||||
<div class="peer mR-20">
|
||||
<span class="ticket-content-icon placeholder me-2"></span>
|
||||
<img id="ticketAuthorImg" class="bdrs-50p w-3r h-3r" alt="" src="" style="display: none; object-fit: cover;">
|
||||
</div>
|
||||
<div class="peer-greed">
|
||||
<div >
|
||||
<span class="placeholder rounded col-4"></span>
|
||||
<div class="peer">
|
||||
<small id="ticketTimestamp"></small>
|
||||
<h5 id="ticketAuthor" class="c-grey-900 mB-5"></h5>
|
||||
<div id="ticketBadges" style="display: none;"></div>
|
||||
</div>
|
||||
<div class="mY-5">
|
||||
<span class="placeholder rounded col-5"></span>
|
||||
</div>
|
||||
<div class="row g-0 ">
|
||||
{% for i in "x"|rjust:"12" %}
|
||||
<div class="col pe-3">
|
||||
<div class="placeholder rounded w-100"></div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="peer">
|
||||
<div class="btn-group" role="group">
|
||||
<button id="btnGroupDrop2" class="btn btn-danger c-white bdrs-50p p-15 lh-0" style="display: none;" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="ti-menu"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu fsz-sm" aria-labelledby="btnGroupDrop2">
|
||||
test
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="bdT pX-40 pY-30">
|
||||
<span class="placeholder rounded col-6 mB-30"></span>
|
||||
<div class="row g-3">
|
||||
<div class="placeholder rounded col-12 px-3"></div>
|
||||
<div class="placeholder rounded col-7"></div>
|
||||
<div class="col-1"></div>
|
||||
<div class="placeholder rounded col-4"></div>
|
||||
<div class="placeholder rounded col-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 id="ticketTitle"></h4>
|
||||
<div id="ticketDesc"></div>
|
||||
<!-- <h4>Title of this email goes here</h4>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
|
||||
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
|
||||
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
|
||||
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
</p>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam
|
||||
</p> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -352,7 +332,7 @@
|
||||
|
||||
<div id="ticketModal" class="modal fade" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="post">
|
||||
<form method="post" action="/tickets/new/">
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
@ -377,16 +357,16 @@
|
||||
<label for="newPriority" class="form-label">Priority</label>
|
||||
<select name="newPriority" id="newPriority" class="select-2">
|
||||
{% for priority in priorities %}
|
||||
<option value="{{ priority.uuid }}">{{ priority.title }}</option>
|
||||
<option value="{{ priority.id }}">{{ priority.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small class="text-muted">How important is this ticket?</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="newTags" class="form-label">Tags</label>
|
||||
<label for="newTagss" class="form-label">Tags</label>
|
||||
<select name="newTags" id="newTags" class="select-2" multiple="multiple">
|
||||
{% for tag in tags %}
|
||||
<option value="{{ tag.uuid }}">{{ tag.title }}</option>
|
||||
<option value="{{ tag.id }}">{{ tag.title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small class="text-muted">Use tags to categorize this ticket.</small>
|
||||
@ -403,82 +383,208 @@
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
<!-- Specific Page JS goes HERE -->
|
||||
{% block javascripts %}
|
||||
<!-- Ticket Item Template -->
|
||||
<script id="ticketItemTemplate" type="text/template">
|
||||
<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="ticket-item-priority badge rounded mt-2 mx-auto">
|
||||
<i class="fa fa-folder"></i>
|
||||
</div>
|
||||
<script>
|
||||
var displayedTicketID = -1;
|
||||
|
||||
$(document).ready(function() {
|
||||
// $(".email-list-item").on("click", function() {
|
||||
// displayTicket(this);
|
||||
// });
|
||||
|
||||
ClassicEditor
|
||||
.create( document.getElementById("newDesc"), {})
|
||||
.catch( error => {
|
||||
console.error(error)
|
||||
});
|
||||
|
||||
loadAllTickets();
|
||||
});
|
||||
|
||||
$("#ticketModal form").on("submit", function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
$.ajax({
|
||||
url: "{% url 'ticket-new' %}",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {
|
||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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 loadAllTickets() {
|
||||
$("#ticketsContainer").empty();
|
||||
|
||||
$.ajax({
|
||||
url: "{% url 'ticket-getmany' %}",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: {
|
||||
csrfmiddlewaretoken: "{{ csrf_token }}",
|
||||
filters: {}
|
||||
},
|
||||
success: function(data) {
|
||||
console.log(JSON.stringify(data, null, 4))
|
||||
|
||||
data.tickets.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 = $(`
|
||||
<div class="email-list-item peers fxw-nw p-20 bdB bgcH-grey-100 cur-p" data-ticket-id="${ticket.id}" data-author-icon="${ticket.author.icon}">
|
||||
<div class="peer mR-10">
|
||||
<img src="${ticket.author.icon}" alt="" class="w-2r h-2r bdrs-50p me-2" style="object-fit: cover;">
|
||||
</div>
|
||||
<div class="peer peer-greed ov-h">
|
||||
<div class="peers ai-c mb-2">
|
||||
<div class="peers ai-c">
|
||||
<div class="peer peer-greed">
|
||||
<h6 class="ticket-item-author"></h6>
|
||||
<h6 class="ticket-author">${ticket.author.forename} ${ticket.author.surname}</h6>
|
||||
</div>
|
||||
<div class="peer">
|
||||
<small class="ticket-item-datetime"></small>
|
||||
<small class="ticket-timestamp">${formattedTime}</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>
|
||||
<h5 class="fsz-def tt-c c-grey-900 ticket-title">${ticket.title}</h5>
|
||||
<span class="whs-nw w-100 ov-h tov-e d-b ticket-desc">${ticket.description}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
`);
|
||||
|
||||
<!-- Ticket Content Template -->
|
||||
<script id="ticketContentTemplate" type="text/template">
|
||||
<!-- Header -->
|
||||
<div class="ticket-content">
|
||||
<div class="peers ai-c jc-sb pX-40 pY-30">
|
||||
<div class="peers peer-greed">
|
||||
<div class="peer mR-20">
|
||||
<img class="ticket-content-icon" src="" alt="">
|
||||
</div>
|
||||
<div class="peer">
|
||||
<!-- <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>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="bdT pX-40 pY-30">
|
||||
<h4 class="ticket-content-title"></h4>
|
||||
<div class="ticket-content-desc w-100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
$("#ticketsContainer").append(item);
|
||||
});
|
||||
|
||||
<!-- Ticket Content Badge Template -->
|
||||
<script id="ticketContentBadgeTemplate" type=text/template>
|
||||
<div class="ticket-content-badge badge rounded mt-2 me-2">
|
||||
<span class="ticket-content-badge-text"></span>
|
||||
<i class="ticket-content-badge-icon"></i>
|
||||
</div>
|
||||
</script>
|
||||
$(".email-list-item").on("click", function() {
|
||||
displayTicket(this);
|
||||
});
|
||||
},
|
||||
error: function(data) {
|
||||
alert(JSON.stringify(data, null, 4))
|
||||
}
|
||||
});
|
||||
|
||||
<!-- Define Variables -->
|
||||
<script>
|
||||
const URL_Tickets = "{% url 'api:tickets' %}";
|
||||
const URL_NewTicket = "{% url 'ticket-new' %}";
|
||||
const URL_FilterCounts = "{% url 'api:filter-counts' %}";
|
||||
const CSRFMiddlewareToken = "{{ csrf_token }}";
|
||||
const CurrentUserID = "{{ request.user.uuid }}";
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
$("#ticketTitle").text("")
|
||||
$("#ticketDesc").empty();
|
||||
$("#ticketAuthor").text("");
|
||||
$("#ticketAuthorImg").hide();
|
||||
$("#ticketAuthorImg").prop("src", "");
|
||||
$("#ticketTimestamp").text("");
|
||||
$("#btnGroupDrop2").hide();
|
||||
$("#ticketBadges").empty().hide();
|
||||
|
||||
if (displayedTicketID === ticketID) {
|
||||
displayedTicketID = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
displayedTicketID = ticketID;
|
||||
|
||||
$.ajax({
|
||||
url: `{% url 'ticket-getone' %}`,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: {
|
||||
csrfmiddlewaretoken: '{{ csrf_token }}',
|
||||
ticket_id: ticketID
|
||||
},
|
||||
success: function (data) {
|
||||
console.log(JSON.stringify(data, null, 4));
|
||||
|
||||
var ticket = data.ticket;
|
||||
var author = ticket.author;
|
||||
var department = author.department;
|
||||
var priority = ticket.priority;
|
||||
|
||||
$("#ticketTitle").text(ticket.title);
|
||||
$("#ticketDesc").append($(`<div class="w-100">${ticket.description}</div>`));
|
||||
$("#ticketAuthor").text(`${author.forename} ${author.surname}`);
|
||||
$("#ticketAuthorImg").show();
|
||||
$("#ticketAuthorImg").prop("src", author.icon);
|
||||
$("#btnGroupDrop2").show();
|
||||
$("#ticketBadges").show();
|
||||
|
||||
$("#ticketBadges").append($(`<div class="badge me-1" style="color: ${priority.colour}; background-color: ${priority.backgroundcolour};">${priority.title} Priority <i class="ti-control-record "></i></div>`));
|
||||
|
||||
if (department != null) {
|
||||
$("#ticketBadges").append($(`<div class="badge bgc-deep-purple-500 me-1">${department.title}</div>`));
|
||||
}
|
||||
|
||||
ticket.tags.forEach(function(tag) {
|
||||
$("#ticketBadges").append($(`<div class="badge me-1" style="color: ${tag.colour}; background-color: ${tag.backgroundcolour};">${tag.title} <i class="ti-tag"></i></div>`));
|
||||
});
|
||||
|
||||
// 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));
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<script src="{% static '/js/tickets.js' %}"></script>
|
||||
{% endblock javascripts %}
|
||||
|
@ -17,6 +17,11 @@
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav-right">
|
||||
<li>
|
||||
<a href="" id="themeToggle" data-bs-toggle="dropdown">
|
||||
<i class="ti-shine"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="notifications dropdown">
|
||||
<!-- <span class="counter bgc-red">3</span> -->
|
||||
<a href="" class="dropdown-toggle no-after" data-bs-toggle="dropdown">
|
||||
|
@ -8,4 +8,10 @@
|
||||
|
||||
<script src="{% static '/js/select2.min.js' %}"></script>
|
||||
|
||||
<script src="{% static '/js/perfectscrollbar.js' %}"></script>
|
||||
|
||||
<script src="{% static '/js/bootstrap.bundle.min.js' %}"></script>
|
||||
|
||||
<script src="{% static '/js/index.js' %}"></script>
|
||||
|
||||
<script src="{% static '/js/base.js' %}"></script>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<link type="text/css" rel="stylesheet" href="{% static '/css/datepicker.css' %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static '/css/fontawesome.css' %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static '/css/themify-icons.css' %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static '/css/scrollbar.css' %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static '/css/perfectscrollbar.css' %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static '/css/adminator.css' %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static '/css/jquery.dataTables.min.css' %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static '/css/select2.min.css' %}" />
|
||||
|
@ -128,12 +128,12 @@ LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'file': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.FileHandler',
|
||||
'filename': LOGGING_DIR / f'{timezone.now()}.log',
|
||||
"formatter": "verbose",
|
||||
},
|
||||
# 'file': {
|
||||
# 'level': 'DEBUG',
|
||||
# 'class': 'logging.FileHandler',
|
||||
# 'filename': LOGGING_DIR / f'{timezone.now()}.log',
|
||||
# "formatter": "verbose",
|
||||
# },
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
@ -163,7 +163,7 @@ LOGGING = {
|
||||
},
|
||||
"django.request": {
|
||||
"handlers": ["timed_file", "console"],
|
||||
"level": "ERROR",
|
||||
"level": "DEBUG",
|
||||
"propagate": True
|
||||
}
|
||||
},
|
||||
|
BIN
examples/dark.webp
Normal file
BIN
examples/dark.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
BIN
examples/light.png
Normal file
BIN
examples/light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 165 KiB |
21
scripts/fixtures.bat
Normal file
21
scripts/fixtures.bat
Normal file
@ -0,0 +1,21 @@
|
||||
@echo off
|
||||
|
||||
echo migrating
|
||||
python manage.py migrate
|
||||
|
||||
echo installing authentication fixtures
|
||||
python manage.py loaddata apps\authentication\fixtures\department.json
|
||||
|
||||
echo installing default users
|
||||
python manage.py loaddata apps\authentication\fixtures\user.json
|
||||
|
||||
echo installing ticket priorities
|
||||
python manage.py loaddata apps\home\fixtures\ticketpriority.json
|
||||
|
||||
echo installing ticket tags
|
||||
python manage.py loaddata apps\home\fixtures\tickettag.json
|
||||
|
||||
echo installing default tickets
|
||||
python manage.py loaddata apps\home\fixtures\ticket.json
|
||||
|
||||
echo all done!
|
19
scripts/fixtures.ps1
Normal file
19
scripts/fixtures.ps1
Normal file
@ -0,0 +1,19 @@
|
||||
Write-Host "migrating"
|
||||
python manage.py migrate
|
||||
|
||||
Write-Host "installing authentication fixtures"
|
||||
python manage.py loaddata apps\authentication\fixtures\department.json
|
||||
|
||||
Write-Host "installing default users"
|
||||
python manage.py loaddata apps\authentication\fixtures\user.json
|
||||
|
||||
Write-Host "installing ticket priorities"
|
||||
python manage.py loaddata apps\home\fixtures\ticketpriority.json
|
||||
|
||||
Write-Host "installing ticket tags"
|
||||
python manage.py loaddata apps\home\fixtures\tickettag.json
|
||||
|
||||
Write-Host "installing default tickets"
|
||||
python manage.py loaddata apps\home\fixtures\ticket.json
|
||||
|
||||
Write-Host "all done!"
|
@ -5,7 +5,7 @@ echo installing authentication fixtures
|
||||
python manage.py loaddata apps/authentication/fixtures/department.json
|
||||
|
||||
echo installing default users
|
||||
python manage.py loaddata apps/authentication/fixtures/defaultuser.json
|
||||
python manage.py loaddata apps/authentication/fixtures/user.json
|
||||
|
||||
echo installing ticket priorities
|
||||
python manage.py loaddata apps/home/fixtures/ticketpriority.json
|
||||
@ -14,6 +14,6 @@ echo installing ticket tags
|
||||
python manage.py loaddata apps/home/fixtures/tickettag.json
|
||||
|
||||
echo installing default tickets
|
||||
python manage.py loaddata apps/home/fixtures/newtickets.json
|
||||
python manage.py loaddata apps/home/fixtures/ticket.json
|
||||
|
||||
echo all done!
|
7
scripts/runserver.bat
Normal file
7
scripts/runserver.bat
Normal file
@ -0,0 +1,7 @@
|
||||
@echo off
|
||||
|
||||
echo Migrating database...
|
||||
python manage.py migrate
|
||||
|
||||
echo Running without reload...
|
||||
python manage.py runserver 0.0.0.0:8000 --noreload
|
5
scripts/runserver.ps1
Normal file
5
scripts/runserver.ps1
Normal file
@ -0,0 +1,5 @@
|
||||
Write-Host "Migrating database..."
|
||||
python manage.py migrate
|
||||
|
||||
Write-Host "Running without reload..."
|
||||
python manage.py runserver 0.0.0.0:8000 --noreload
|
Reference in New Issue
Block a user