Compare commits

...

8 Commits

Author SHA1 Message Date
jamesparkin675
123e75266e changed viewing perms
users can now only see their own tickets and filter fixed.

default user signup now user, not admin
2024-04-28 23:48:45 +01:00
7d408193f9 Merge branch 'jones-dev' of https://gitea.corbz.dev/corbz/ticket-website into origin/jones-dev 2024-03-06 16:06:30 +00:00
2e4eff5f3a Delete useless files 2024-03-06 16:06:25 +00:00
fc992c75a6 Comment out annoying file logger 2024-03-06 16:05:49 +00:00
fc8cea1d0a Display department and priority 2024-03-06 16:05:29 +00:00
92c6cda583 Update Dockerfile 2024-03-06 16:02:27 +00:00
75531f872b corrected static paths 2024-03-06 16:02:15 +00:00
c9e47e3e9e Dashboard view to template 2024-03-06 16:01:46 +00:00
12 changed files with 535 additions and 12065 deletions

6
.gitignore vendored
View File

@ -31,7 +31,7 @@ staticfiles/*
!staticfiles/.gitkeep !staticfiles/.gitkeep
.vscode/symbols.json .vscode/symbols.json
apps/static/assets/node_modules apps/static/node_modules
apps/static/assets/yarn.lock apps/static/yarn.lock
apps/static/assets/.temp apps/static/.temp

View File

@ -1,18 +1,27 @@
FROM python:3.9 FROM python:3.12
# set environment variables # set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
ENV DJANGO_SETTINGS_MODULE core.settings
WORKDIR /website
COPY requirements.txt .
# install python dependencies # install python dependencies
COPY requirements.txt .
RUN pip install --upgrade pip RUN pip install --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt 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 RUN python manage.py migrate
# Port that the site runs on
EXPOSE 4411
# gunicorn # gunicorn
CMD ["gunicorn", "--config", "gunicorn-cfg.py", "core.wsgi"] CMD ["gunicorn", "core.wsgi:application", "--bind", "0.0.0.0:4411"]

View File

@ -47,7 +47,7 @@ class TicketListApiView(generics.ListAPIView):
pagination_class = TicketPaginiation pagination_class = TicketPaginiation
serializer_class = TicketSerializer serializer_class = TicketSerializer
queryset = Ticket.objects.all() # queryset = Ticket.objects.all()
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter] filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["uuid", "priority", "tags", "author", "author__department"] filterset_fields = ["uuid", "priority", "tags", "author", "author__department"]
@ -55,12 +55,16 @@ class TicketListApiView(generics.ListAPIView):
ordering_fields = ["create_timestamp", "edit_timestamp"] ordering_fields = ["create_timestamp", "edit_timestamp"]
def get_queryset(self): 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") strict_tags = self.request.query_params.get("strict-tags")
if not strict_tags: if not strict_tags:
return self.queryset return queryset
tag_uuids = self.request.query_params.getlist("tags", []) tag_uuids = self.request.query_params.getlist("tags", [])
queryset = self.queryset
log.debug("tag uuids %s", tag_uuids) log.debug("tag uuids %s", tag_uuids)
@ -77,7 +81,11 @@ class FilterCountListApiView(generics.ListAPIView):
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
def get(self, request): def get(self, request):
if self.request.user.is_superuser:
self._tickets = Ticket.objects.all() self._tickets = Ticket.objects.all()
else:
self._tickets = Ticket.objects.filter(author=self.request.user)
data = {"tickets": self._tickets.count()} data = {"tickets": self._tickets.count()}
self._fill_data(TicketPriority, data, "priority") self._fill_data(TicketPriority, data, "priority")

View File

@ -39,9 +39,9 @@ def register_user(request):
user = form.save(commit=False) user = form.save(commit=False)
# Develepment, give all new users admin # Develepment, give all new users admin
user.is_staff = True # user.is_staff = True
user.is_superuser = True # user.is_superuser = True
user.save() # user.save()
email = form.cleaned_data.get("email") email = form.cleaned_data.get("email")
raw_password = form.cleaned_data.get("password1") raw_password = form.cleaned_data.get("password1")

View File

@ -4,14 +4,14 @@ from django.urls import path, include
from django.shortcuts import redirect from django.shortcuts import redirect
from apps.home import views from apps.home import views
from .views import TicketView from .views import DashboardView, TicketView
def reverse_to_index(reqeust): def reverse_to_index(reqeust):
return redirect("dashboard") return redirect("dashboard")
urlpatterns = [ urlpatterns = [
path("", reverse_to_index, name="index"), path("", reverse_to_index, name="index"),
path('dashboard/', views.dashboard, name="dashboard"), path('dashboard/', DashboardView.as_view(), name="dashboard"),
path('tickets/', include([ path('tickets/', include([
path('', TicketView.as_view(), name="tickets"), path('', TicketView.as_view(), name="tickets"),
path('new/', views.new_ticket, name="ticket-new"), path('new/', views.new_ticket, name="ticket-new"),

View File

@ -19,9 +19,12 @@ from ..authentication.models import Department
from .models import Ticket, TicketPriority, TicketTag from .models import Ticket, TicketPriority, TicketTag
@login_required() class DashboardView(TemplateView):
def dashboard(request): template_name = "home/dashboard.html"
return render(request, "home/dashboard.html")
@method_decorator(login_required)
def get(self, request, *args, **kwargs):
return render(request, self.template_name)
class TicketView(TemplateView): class TicketView(TemplateView):

View File

@ -1,4 +1,5 @@
$(document).ready(function() { $(document).ready(function() {
// Activate all tooltips // Activate all tooltips
$('[data-bs-toggle="tooltip"]').tooltip(); $('[data-bs-toggle="tooltip"]').tooltip();

View File

@ -396,6 +396,20 @@ function createTicketContent(ticket) {
template.find(".ticket-content-badges").append(tagTemplate); template.find(".ticket-content-badges").append(tagTemplate);
}); });
var departmentElem = template.find(".ticket-content-department");
if (ticket.author.department === null) {
template.find(".ticket-content-department").hide();
}
else {
}
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; return template;
} }

View File

@ -5,339 +5,321 @@
<!-- Specific CSS goes HERE --> <!-- Specific CSS goes HERE -->
{% block stylesheets %} {% 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 %} {% endblock stylesheets %}
{% block content %} {% block content %}
<!-- ### $App Screen Content ### --> <!-- ### $App Screen Content ### -->
<main class='main-content'> <main class='main-content bgc-grey-100'>
<div id='mainContent'> <div id='mainContent'>
<div class="full-container" style="overflow: hidden;"> <div class="full-container">
<div class="email-app"> <div class="email-app">
<div class="email-side-nav remain-height ov-h"> <div class="email-side-nav remain-height ov-h">
<div class="h-100 layers"> <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> <button type="button" class="btn btn-danger c-white w-100" data-bs-toggle="modal" data-bs-target="#ticketModal">New Ticket</button>
</div> </div>
<div class="scrollable pos-r ov-h bdT layer w-100 fxg-1 bg-body"> <div class="scrollable pos-r bdT layer w-100 fxg-1">
<ul id="filterSidebar" class="p-20 nav flex-column"> <ul class="p-20 nav flex-column">
<li class="nav-item">
<h6>Filters</h6>
</li>
{% if priorities %} {% if priorities %}
<li class="nav-item px-3 mt-3">
<li class="nav-item mT-10 filter-priority"> <h6 class="small">Priority</h6>
<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>
{% for priority in priorities %} {% for priority in priorities %}
<div class="form-check mb-2">
<li class="nav-item filter-priority"> <input type="checkbox" id="priority-{{ priority.id }}" class="form-check-input me-3">
<label for="filterPriority-{{ priority.uuid }}" class="nav-link text-reset actived"> <label for="priority-{{ priority.id }}" class="form-check-label">
<div class="peers ai-c jc-sb"> <span class="badge rounded-pill" style="color: {{ priority.colour }}; background-color: {{ priority.backgroundcolour }};">
<div class="peer peer-greed"> {{ priority.title }}
<input type="radio" id="filterPriority-{{ priority.uuid }}" name="filterPriorities" class="form-check-input me-2" value="{{ priority.uuid }}"> <i class="ti-control-record ms-auto"></i>
<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>
</span> </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> </label>
</li> </div>
{% endfor %} {% endfor %}
{% endif %}
<li class="nav-item">
<hr class="mY-30 border-secondary">
</li> </li>
{% endif %}
{% if departments %} {% if departments %}
<li class="nav-item px-3 mt-3">
<li id="filterDepartmentAll" class="nav-item filter-department"> <h6 class="small">Department</h6>
<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>
{% for department in departments %} {% for department in departments %}
<div class="form-check mb-2">
<li class="nav-item filter-department"> <input type="checkbox" id="department-{{ department.id }}" class="form-check-input me-2">
<label for="filterDepartment-{{ department.uuid }}" class="nav-link text-reset actived"> <label for="department-{{ department.id }}" class="form-check-label d-flex jc-sb">
<div class="peers ai-c jc-sb"> {{ department.title }}
<div class="peer peer-greed"> <i class="{{ department.icon }} ms-auto"></i>
<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" style="color: {{ department.colour }}; background-color: {{ department.backgroundcolour }};">0</span>
</div>
</div>
</label> </label>
</li> </div>
{% endfor %} {% endfor %}
</li>
{% endif %} {% endif %}
{% 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>
</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 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> </ul>
</div> </div>
</div> </div>
</div> </div>
<div class="email-wrapper row remain-height bg-body"> <div class="email-wrapper row remain-height bgc-white ov-h">
<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="bgc-grey-100 peers ai-c p-20 fxw-nw">
<div class="peer me-2"> <div class="peer me-auto">
<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 bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-menu"></i> <i class="ti-menu"></i>
</button> </button>
<!-- <button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p"> <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> <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>
<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 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">
</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>
</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 p-20" style="display: none;">
<div class="pos-a top-50 start-50 translate-middle text-center">
<div class="fs-1 fw-bolder">404</div>
<div class="fs-4 fw-bold text-body-tertiary">No Tickets Found</div>
</div>
</div>
<div class="loading bg-body-tertiary 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 %}
</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">
<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>
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:reloadCurrentTicket();">
<i class="ti-reload"></i>
</button>
<button type="button" class="btn bg-body bdrs-2 mR-3">
<i class="ti-more-alt"></i> <i class="ti-more-alt"></i>
</button> </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> </div>
<div class="peer"> <div class="peer">
<div class="btn-group" role="group"> <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> <i class="ti-angle-left"></i>
</button> </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> <i class="ti-angle-right"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div id="ticketContent" class="email-content-wrapper"> </div>
<div class="content"></div> <div class="layer w-100">
<div class="loading" style="display: none;"> <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 --> <!-- Header -->
<div class="ticket-content placeholder-glow">
<div class="peers ai-c jc-sb pX-40 pY-30"> <div class="peers ai-c jc-sb pX-40 pY-30">
<div class="peers peer-greed"> <div class="peers peer-greed">
<div class="peer mR-20"> <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>
<div class="peer-greed"> <div class="peer">
<div > <small id="ticketTimestamp"></small>
<span class="placeholder rounded col-4"></span> <h5 id="ticketAuthor" class="c-grey-900 mB-5"></h5>
<div id="ticketBadges" style="display: none;"></div>
</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>
<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> </div>
</div> </div>
<!-- Content --> <!-- Content -->
<div class="bdT pX-40 pY-30"> <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> </div>
</div> </div>
@ -350,7 +332,7 @@
<div id="ticketModal" class="modal fade" aria-hidden="true"> <div id="ticketModal" class="modal fade" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<form method="post"> <form method="post" action="/tickets/new/">
{% csrf_token %} {% csrf_token %}
@ -375,16 +357,16 @@
<label for="newPriority" class="form-label">Priority</label> <label for="newPriority" class="form-label">Priority</label>
<select name="newPriority" id="newPriority" class="select-2"> <select name="newPriority" id="newPriority" class="select-2">
{% for priority in priorities %} {% for priority in priorities %}
<option value="{{ priority.uuid }}">{{ priority.title }}</option> <option value="{{ priority.id }}">{{ priority.title }}</option>
{% endfor %} {% endfor %}
</select> </select>
<small class="text-muted">How important is this ticket?</small> <small class="text-muted">How important is this ticket?</small>
</div> </div>
<div class="mb-3"> <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"> <select name="newTags" id="newTags" class="select-2" multiple="multiple">
{% for tag in tags %} {% for tag in tags %}
<option value="{{ tag.uuid }}">{{ tag.title }}</option> <option value="{{ tag.id }}">{{ tag.title }}</option>
{% endfor %} {% endfor %}
</select> </select>
<small class="text-muted">Use tags to categorize this ticket.</small> <small class="text-muted">Use tags to categorize this ticket.</small>
@ -401,84 +383,208 @@
{% endblock content %} {% endblock content %}
<!-- Specific Page JS goes HERE -->
{% block javascripts %} {% block javascripts %}
<!-- Ticket Item Template --> <script>
<script id="ticketItemTemplate" type="text/template"> var displayedTicketID = -1;
<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-items-center align-self-stretch pos-r"> $(document).ready(function() {
<img src="" alt="" class="ticket-item-icon"> // $(".email-list-item").on("click", function() {
<div class="mt-auto"> // displayTicket(this);
<div class="ticket-item-department badge rounded mb-2" data-bs-toggle="tooltip"> // });
<i class="fa fa-users"></i>
</div> ClassicEditor
<div class="ticket-item-priority badge rounded" data-bs-toggle="tooltip"> .create( document.getElementById("newDesc"), {})
<i class="fa fa-folder"></i> .catch( error => {
</div> console.error(error)
</div> });
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>
<div class="peer peer-greed ov-h"> <div class="peer peer-greed ov-h">
<div class="peers ai-c mb-2"> <div class="peers ai-c">
<div class="peer peer-greed"> <div class="peer peer-greed">
<h6 class="ticket-item-author"></h6> <h6 class="ticket-author">${ticket.author.forename} ${ticket.author.surname}</h6>
</div> </div>
<div class="peer"> <div class="peer">
<small class="ticket-item-datetime"></small> <small class="ticket-timestamp">${formattedTime}</small>
</div> </div>
</div> </div>
<h5 class="ticket-item-title mb-0"></h5> <h5 class="fsz-def tt-c c-grey-900 ticket-title">${ticket.title}</h5>
<div class="ticket-item-desc mt-2"></div> <span class="whs-nw w-100 ov-h tov-e d-b ticket-desc">${ticket.description}</span>
<div class="peers">
<div class="ticket-item-tags peer d-flex flex-wrap mw-100"></div>
</div> </div>
</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 --> $("#ticketsContainer").append(item);
<div class="bdT pX-40 pY-30"> });
<h4 class="ticket-content-title"></h4>
<div class="ticket-content-desc w-100"></div>
</div>
</div>
</script>
<!-- Ticket Content Badge Template --> $(".email-list-item").on("click", function() {
<script id="ticketContentBadgeTemplate" type=text/template> displayTicket(this);
<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> error: function(data) {
</div> alert(JSON.stringify(data, null, 4))
</script> }
});
<!-- Define Variables --> }
<script>
const URL_Tickets = "{% url 'api:tickets' %}"; function displayTicket(ticketElement) {
const URL_NewTicket = "{% url 'ticket-new' %}"; ticket = $(ticketElement);
const URL_FilterCounts = "{% url 'api:filter-counts' %}"; ticketID = ticket.data("ticket-id");
const CSRFMiddlewareToken = "{{ csrf_token }}";
const CurrentUserID = "{{ request.user.uuid }}"; $(".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>
<script src="{% static '/js/tickets.js' %}"></script>
{% endblock javascripts %} {% endblock javascripts %}

View File

@ -128,12 +128,12 @@ LOGGING = {
'version': 1, 'version': 1,
'disable_existing_loggers': False, 'disable_existing_loggers': False,
'handlers': { 'handlers': {
'file': { # 'file': {
'level': 'DEBUG', # 'level': 'DEBUG',
'class': 'logging.FileHandler', # 'class': 'logging.FileHandler',
'filename': LOGGING_DIR / f'{timezone.now()}.log', # 'filename': LOGGING_DIR / f'{timezone.now()}.log',
"formatter": "verbose", # "formatter": "verbose",
}, # },
'console': { 'console': {
'level': 'DEBUG', 'level': 'DEBUG',
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
@ -163,7 +163,7 @@ LOGGING = {
}, },
"django.request": { "django.request": {
"handlers": ["timed_file", "console"], "handlers": ["timed_file", "console"],
"level": "ERROR", "level": "DEBUG",
"propagate": True "propagate": True
} }
}, },

File diff suppressed because it is too large Load Diff

View File