[UPDT] HELPDESK: Updated the ticket detailed, list, card views and its functions

This commit is contained in:
Horilla
2024-10-11 12:28:26 +05:30
parent eacd64102b
commit dedfa0c4cc
18 changed files with 2600 additions and 1599 deletions

View File

@@ -3,6 +3,7 @@ from django.contrib import admin
from helpdesk.models import (
FAQ,
Attachment,
ClaimRequest,
Comment,
DepartmentManager,
FAQCategory,
@@ -18,3 +19,4 @@ admin.site.register(FAQ)
admin.site.register(FAQCategory)
admin.site.register(Attachment)
admin.site.register(DepartmentManager)
admin.site.register(ClaimRequest)

View File

@@ -27,7 +27,7 @@ from django import forms
from django.template.loader import render_to_string
from base.forms import ModelForm
from base.methods import is_reportingmanager
from base.methods import filtersubordinatesemployeemodel, is_reportingmanager
from base.models import Department, JobPosition
from employee.forms import MultipleFileField
from employee.models import Employee
@@ -121,14 +121,30 @@ class TicketForm(ModelForm):
self.fields["attachment"] = MultipleFileField(
label="Attachements", required=False
)
self.fields["tags"].choices = list(self.fields["tags"].choices)
self.fields["tags"].choices.append(("create_new_tag", "Create new tag"))
self.fields["ticket_type"].choices = list(self.fields["ticket_type"].choices)
request = getattr(horilla_middlewares._thread_locals, "request", None)
if is_reportingmanager(request):
instance = kwargs.get("instance")
if instance:
employee = instance.employee_id
else:
employee = request.user.employee_get
# initialising employee queryset according to the user
self.fields["employee_id"].queryset = filtersubordinatesemployeemodel(
request, Employee.objects.filter(is_active=True), perm="helpdesk.add_ticket"
) | Employee.objects.filter(employee_user_id=request.user)
self.fields["employee_id"].initial = employee
# appending dynamic create option according to user
if is_reportingmanager(request) or request.user.has_perm(
"helpdesk.add_tickettype"
):
self.fields["ticket_type"].choices = list(
self.fields["ticket_type"].choices
)
self.fields["ticket_type"].choices.append(
("create_new_ticket_type", "Create new ticket type")
)
if is_reportingmanager(request) or request.user.has_perm("base.add_tags"):
self.fields["tags"].choices = list(self.fields["tags"].choices)
self.fields["tags"].choices.append(("create_new_tag", "Create new tag"))
class TicketTagForm(ModelForm):
@@ -152,8 +168,10 @@ class TicketTagForm(ModelForm):
If an instance is provided, sets the initial value for the form's .
"""
super().__init__(*args, **kwargs)
self.fields["tags"].choices = list(self.fields["tags"].choices)
self.fields["tags"].choices.append(("create_new_tag", "Create new tag"))
request = getattr(horilla_middlewares._thread_locals, "request", None)
if is_reportingmanager(request) or request.user.has_perm("base.add_tags"):
self.fields["tags"].choices = list(self.fields["tags"].choices)
self.fields["tags"].choices.append(("create_new_tag", "Create new tag"))
class TicketRaisedOnForm(ModelForm):
@@ -215,3 +233,21 @@ class DepartmentManagerCreateForm(ModelForm):
class Meta:
model = DepartmentManager
fields = ["department", "manager"]
widgets = {
"department": forms.Select(
attrs={
"onchange": "getDepartmentEmployees($(this))",
}
),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if "instance" in kwargs:
department = kwargs["instance"].department
# Get the employees related to this department
employees = department.employeeworkinformation_set.values_list(
"employee_id", flat=True
)
# Set the manager field queryset to be those employees
self.fields["manager"].queryset = Employee.objects.filter(id__in=employees)

21
helpdesk/methods.py Normal file
View File

@@ -0,0 +1,21 @@
from helpdesk.models import DepartmentManager
def is_department_manager(request,ticket):
"""
Method used to find the user is a department manger of given ticket
"""
user_emp = request.user.employee_get
if ticket.assigning_type == "job_position":
job_position = ticket.get_raised_on_object()
department = job_position.department_id
elif ticket.assigning_type == "department":
department = ticket.get_raised_on_object()
else:
return False
return DepartmentManager.objects.filter(
manager = user_emp,
department = department
).exists()

View File

@@ -60,6 +60,14 @@ class DepartmentManager(HorillaModel):
Company, null=True, editable=False, on_delete=models.PROTECT
)
class Meta:
unique_together = ("department", "manager")
def clean(self, *args, **kwargs):
super().clean(*args, **kwargs)
if not self.manager.get_department() == self.department:
raise ValidationError(_(f"This employee is not from {self.department} ."))
class TicketType(HorillaModel):
title = models.CharField(max_length=100, unique=True)
@@ -107,6 +115,9 @@ class Ticket(HorillaModel):
related_company_field="employee_id__employee__work_info__company_id"
)
class Meta:
ordering = ["-created_date"]
def clean(self, *args, **kwargs):
super().clean(*args, **kwargs)
deadline = self.deadline
@@ -144,6 +155,36 @@ class Ticket(HorillaModel):
return get_diff(self)
class ClaimRequest(HorillaModel):
ticket_id = models.ForeignKey(
Ticket,
on_delete=models.CASCADE,
null=True,
blank=True,
)
employee_id = models.ForeignKey(
Employee,
on_delete=models.CASCADE,
null=True,
blank=True,
)
is_approved = models.BooleanField(default=False)
is_rejected = models.BooleanField(default=False)
class Meta:
unique_together = ("ticket_id", "employee_id")
def __str__(self) -> str:
return f"{self.ticket_id}|{self.employee_id}"
def clean(self, *args, **kwargs):
super().clean(*args, **kwargs)
if not self.ticket_id:
raise ValidationError({"ticket_id": _("This field is required.")})
if not self.employee_id:
raise ValidationError({"employee_id": _("This field is required.")})
class Comment(HorillaModel):
comment = models.TextField(null=True, blank=True)
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, related_name="comment")

View File

@@ -64,4 +64,11 @@
</div>
<!-- end of edit modal -->
<!-- end of modals -->
<button id="getDepartmentEmployeesButton" hx-get="{% url "get-department-employee" %}" hx-target="#id_manager" hidden></button>
<script>
function getDepartmentEmployees(element){
var dep_id = element.val()
$('#getDepartmentEmployeesButton').attr('hx-vals', JSON.stringify({ dep_id: dep_id })).click();
}
</script>
{% endblock settings %}

View File

@@ -32,7 +32,7 @@
{% else %}
hx-post="{% url 'department-manager-create' %}"
{% endif %}
hx-target="#deparmentManagersForm"
hx-target="#deparmentManagersModal"
hx-encoding="multipart/form-data"
class="oh-profile-section"
>

View File

@@ -0,0 +1,3 @@
def is_department_manager(request,ticket):

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
{% load static %}{% load i18n %}
<script>
$("#reloadMessagesButton").click()
</script>
<div class="oh-card">
<div class="oh-modal__dialog-header">
<button
type="button"
class="oh-modal__close"
data-dismiss="oh-modal"
aria-label="Close"
{% if refresh %} onclick="event.stopPropagation(); window.location.reload();" {% endif %}
>
<ion-icon name="close-outline"></ion-icon>
</button>
<span class="oh-modal__dialog-title ml-5">
<h5>{% trans "Claim Requests" %}</h5>
</span>
</div>
{% if claim_requests %}
<div class="oh-sticky-table oh-sticky-table--no-overflow mb-5">
<div class="oh-sticky-table">
<div class="oh-sticky-table__table">
<div class="oh-sticky-table__thead">
<div class="oh-sticky-table__tr">
<div
class="oh-sticky-table__th"
>
{% trans "Employee" %}
</div>
<div
class="oh-sticky-table__th"
>
{% trans "Action" %}
</div>
</div>
</div>
<div class="oh-sticky-table__tbody">
{% for req in claim_requests %}
<div
class="oh-sticky-table__tr"
>
<div class="oh-sticky-table__sd">
<div class="oh-profile oh-profile--md">
<div
class="oh-profile__avatar mr-1"
>
<img
src="{{req.employee_id.get_avatar}}"
class="oh-profile__image"
alt=""
/>
</div>
<span
class="oh-profile__name oh-text--dark"
>{{req.employee_id}}</span
>
</div>
</div>
<div class="oh-sticky-table__td">
<div class="oh-btn-group">
{% if not req.is_approved and not req.is_rejected %}
<button
class="oh-btn oh-btn--success w-100"
data-toggle="oh-modal-toggle"
data-target="#objectDetailsModal"
hx-get="{% url 'approve-claim-request' req.id %}?approve=True"
hx-target="#objectDetailsModalTarget"
title="Approve request"
>
<ion-icon name="checkmark-outline"></ion-icon>
</button>
{% else %}
<button
class="oh-btn oh-btn--success w-100"
disabled
title="Approve request"
>
<ion-icon name="checkmark-outline"></ion-icon>
</button>
{% endif %}
{% if not req.is_rejected %}
<button
class="oh-btn oh-btn--danger w-100"
titile = "{% trans 'Reject' %}"
data-toggle="oh-modal-toggle"
data-target="#objectDetailsModal"
hx-get="{% url 'approve-claim-request' req.id %}?approve=False"
hx-target="#objectDetailsModalTarget"
>
<ion-icon name="close-outline"></ion-icon>
</button>
{% else %}
<button
class="oh-btn oh-btn--danger w-100"
titile = "{% trans 'Reject' %}"
disabled
>
<ion-icon name="close-outline"></ion-icon>
</button>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% else %}
<div
style="
height: 70vh;
display: flex;
align-items: center;
justify-content: center;
"
class=""
>
<div style="" class="oh-404">
<img
style="display: block; width: 150px; height: 150px; margin: 10px auto"
src="{% static 'images/ui/ticket.png' %}"
class="mb-4"
alt=""
/>
<h3 style="font-size: 20px" class="oh-404__subtitle">
{% trans "There are no claim requests at the moment." %}
</h3>
</div>
</div>
{% endif %}
</div>

View File

@@ -1,6 +1,6 @@
{% extends 'index.html' %} {% load static %} {% load i18n %}
{% load audit_filters %}
{% load basefilters %}
{% load basefilters %} {% load helpdeskfilters %}
<!DOCTYPE html>
<html lang="en">
<head>
@@ -12,10 +12,85 @@
</head>
<body>
<div class="oh-wrapper-main">
{% comment %} <div id="sidebar"></div> {% endcomment %}
{% block content %}
<style>
.oh-profile__avatar-limit-height {
height: 30px !important;
}
.oh-profile_name_custom {
font-size: 13px;
padding-left: 4px;
}
.oh-profile__image_custm {
width: 26px;
height: 26px;
}
.avatars {
display: flex;
padding: 8px 10px 8px 10px;
}
.avatars__item {
background-color: #596376;
border: 2px solid white;
border-radius: 100%;
color: #ffffff;
display: block;
font-family: sans-serif;
font-size: 12px;
font-weight: 100;
height: 26px;
width: 26px;
line-height: 17px;
text-align: center;
transition: margin 0.1s ease-in-out;
overflow: hidden;
margin-left: -10px;
}
.avatars__item:first-child {
z-index: 5;
}
.avatars__item:nth-child(2) {
z-index: 4;
}
.avatars__item:nth-child(3) {
z-index: 3;
}
.avatars__item:nth-child(4) {
z-index: 2;
}
.avatars__item:nth-child(5) {
z-index: 1;
}
.avatars__item:last-child {
z-index: 0;
}
.avatars__item img {
width: 100%;
}
.avatars:hover .avatars__item {
margin-right: 10px;
}
.horizontal-scroll{
display: flex;
justify-content: flex-start;
overflow-x: auto;
width: 100%;
}
</style>
<div id="main">
<!-- Navigation -->
<div id="mainNav"></div>
@@ -29,19 +104,38 @@
<p class="oh-helpdesk__header-title">{{ticket.title}}</p>
<span class="helpdesk__card-value me-3">
<select name="status_update" title='{% trans "Status" %}' class="oh-select" onchange="updateStatus()"
style="
width:190px;
align-items: center;
border: 1px solid #aaa;
border-radius: 2px;
background-color: #fff;
">
{% for status in ticket_status %}
<option value="{{status.0}}" {% if status.0 == ticket.status %} selected {% endif %}>
<span class="oh-dot oh-dot--small oh-dot--warning">{{status.1}}</span></option>
{% endfor %}
</select>
{% if perms.helpdesk.change_ticket or request.user.employee_get|is_department_manager:ticket or request.user.employee_get in ticket.assigned_to.all or request.user.employee_get == ticket.employee_id %}
<select name="status_update" title='{% trans "Status" %}' class="oh-select" onchange="updateStatus()"
style="
width:190px;
align-items: center;
border: 1px solid #aaa;
border-radius: 2px;
background-color: #fff;
">
{% for status in ticket_status %}
<option value="{{status.0}}" {% if status.0 == ticket.status %} selected {% endif %}>
<span class="oh-dot oh-dot--small oh-dot--warning">{{status.1}}</span></option>
{% endfor %}
</select>
{% else %}
<select name="status_update" title='{% trans "Status" %}' class="oh-select"
style="
width:190px;
align-items: center;
border: 1px solid #aaa;
border-radius: 2px;
background-color: #fff;
">
{% for status in ticket_status %}
{% if status.0 == ticket.status %}
<option value="{{status.0}}" selected >
<span class="oh-dot oh-dot--small oh-dot--warning">{{status.1}}</span></option>
{% endif %}
{% endfor %}
</select>
{% endif %}
</span>
</div>
<!-- Description Goes Here -->
@@ -81,15 +175,13 @@
<div class="oh-profile oh-profile--md">
<div class="oh-profile__avatar mr-1">
<img
src="https://ui-avatars.com/api/?name={{item.comment.employee_id}}&background=random"
src="{{item.comment.employee_id.get_avatar}}"
class="oh-profile__image"
alt="{{item.comment.employee_id}}" />
</div>
</div>
<span class="oh-helpdesk__bubble-username">{{item.comment.employee_id}}</span>
{% comment %} <span class="oh-text--xs oh-badge oh-badge--info ms-2"
>Private</span
> {% endcomment %}
</div>
<span class="oh-helpdesk__bubble-timestamp"
>{{item.comment.date}}
@@ -99,43 +191,40 @@
<span class="oh-helpdesk__comment">
{{item.comment.comment}}
</span>
{% if item.comment.comment_attachment.all %}
{% for attachment in item.comment.comment_attachment.all %}
<div class="oh-helpdesk_attached-items">
<div class="oh-helpdesk_attached--content">
{% with extension=attachment.file.name|lower|slice:"-4:" %}
<li class="oh-helpdesk__document">
<a href="{{ attachment.file.url }}" target="_blank">
<div class="oh-helpdesk__icon">
<span class="oh-file-icon
{% if extension == '.pdf' %} oh-file-icon--pdf
{% elif extension in '.jpg .jpeg .png .gif .svg' %} oh-file-icon--image
{% elif extension in '.mp3 .wav .ogg' %} oh-file-icon--audio
{% elif extension in '.doc .docx' %} oh-file-icon--word
{% elif extension in '.xls .xlsx' %} oh-file-icon--excel
{% elif extension in '.ppt .pptx' %} oh-file-icon--powerpoint
{% elif extension in '.html' %} oh-file-icon--html
{% else %} oh-file-icon--default
{% endif %}">
</span>
</div>
</a>
<a href="{{ attachment.file.url }}" target="_blank">
<span class="oh-helpdesk__filename">{{ attachment }}</span>
</a>
</li>
{% endwith %}
</div>
<a href="{{attachment.file.url}}" target="_blank" >
<span class="oh-helpdesk__filename" >{{attachment}}</span>
</a>
<button class="oh-user-panel__remove">
<ion-icon name="close-outline" role="img" class="md hydrated"
aria-label="close outline"></ion-icon>
</button>
<div class="oh-scrollable-container">
<div class="oh-layout--grid-3 mt-2 mb-2 horizontal-scroll"
>
{% if item.comment.comment_attachment.all %}
{% for attachment in item.comment.comment_attachment.all %}
{% with extension=attachment.file.name|lower|slice:"-4:" %}
<a
class ="me-2"
data-toggle="oh-modal-toggle"
data-target ="#viewFileModal"
hx-target="#viewFile"
hx-get="{% url 'view-ticket-document' attachment.id %}?ticket_id={{ticket.id}}"
>
<div class="oh-helpdesk__icon">
<span class="oh-file-icon
{% if extension == '.pdf' %} oh-file-icon--pdf
{% elif extension in '.jpg .jpeg .png .gif .svg' %} oh-file-icon--image
{% elif extension in '.mp3 .wav .ogg' %} oh-file-icon--audio
{% elif extension in '.doc .docx' %} oh-file-icon--word
{% elif extension in '.xls .xlsx' %} oh-file-icon--excel
{% elif extension in '.ppt .pptx' %} oh-file-icon--powerpoint
{% elif extension in '.html' %} oh-file-icon--html
{% else %} oh-file-icon--default
{% endif %}"
title = "{{attachment}}"
>
</span>
</div>
</a>
{% endwith %}
{% endfor %}
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
@@ -146,7 +235,7 @@
<div class="oh-profile oh-profile--md">
<div class="oh-profile__avatar mr-1">
<img
src="https://ui-avatars.com/api/?name={{item.comment.employee_id}}&background=random"
src="{{item.comment.employee_id.get_avatar}}"
class="oh-profile__image"
alt="{{item.comment.employee_id}}" />
</div>
@@ -180,32 +269,40 @@
</a>
</div>
</div>
{% if item.comment.comment_attachment.all %}
{% for attachment in item.comment.comment_attachment.all %}
{% with extension=attachment.file.name|lower|slice:"-4:" %}
<li class="oh-helpdesk__document">
<a href="{{ attachment.file.url }}" target="_blank">
<div class="oh-helpdesk__icon">
<span class="oh-file-icon
{% if extension == '.pdf' %} oh-file-icon--pdf
{% elif extension in '.jpg .jpeg .png .gif .svg' %} oh-file-icon--image
{% elif extension in '.mp3 .wav .ogg' %} oh-file-icon--audio
{% elif extension in '.doc .docx' %} oh-file-icon--word
{% elif extension in '.xls .xlsx' %} oh-file-icon--excel
{% elif extension in '.ppt .pptx' %} oh-file-icon--powerpoint
{% elif extension in '.html' %} oh-file-icon--html
{% else %} oh-file-icon--default
{% endif %}">
</span>
</div>
</a>
<a href="{{ attachment.file.url }}" target="_blank">
<span class="oh-helpdesk__filename">{{ attachment }}</span>
</a>
</li>
{% endwith %}
{% endfor %}
{% endif %}
<div class="oh-scrollable-container">
<div class="oh-layout--grid-3 mt-2 mb-2 horizontal-scroll"
>
{% if item.comment.comment_attachment.all %}
{% for attachment in item.comment.comment_attachment.all %}
{% with extension=attachment.file.name|lower|slice:"-4:" %}
<a
class ="me-2"
data-toggle="oh-modal-toggle"
data-target ="#viewFileModal"
hx-target="#viewFile"
hx-get="{% url 'view-ticket-document' attachment.id %}"
>
<div class="oh-helpdesk__icon">
<span class="oh-file-icon
{% if extension == '.pdf' %} oh-file-icon--pdf
{% elif extension in '.jpg .jpeg .png .gif .svg' %} oh-file-icon--image
{% elif extension in '.mp3 .wav .ogg' %} oh-file-icon--audio
{% elif extension in '.doc .docx' %} oh-file-icon--word
{% elif extension in '.xls .xlsx' %} oh-file-icon--excel
{% elif extension in '.ppt .pptx' %} oh-file-icon--powerpoint
{% elif extension in '.html' %} oh-file-icon--html
{% else %} oh-file-icon--default
{% endif %}"
title = "{{ attachment }}"
>
</span>
</div>
</a>
{% endwith %}
{% endfor %}
{% endif %}
</div>
</div>
</div>
</div>
{% endif %}
@@ -270,16 +367,6 @@
<div class="oh-helpdesk__card">
<div class="oh-helpdesk__card-header d-flex justify-content-between align-items-center">
<span class="oh-helpdesk__card-title">{% trans "Ticket Info" %}</span>
{% if not ticket.assigned_to.all and request.user.employee_get != ticket.employee_id %}
<a
type="submit"
href = "{% url 'claim-ticket' ticket.id %}"
class="oh-btn oh-btn--info"
title="{% trans 'claim' %}"
>
{% trans "Claim" %}
</a>
{% endif %}
</div>
<div class="oh-helpdesk__card-body">
<ul class="helpdesk__card-items">
@@ -296,8 +383,23 @@
<span class="helpdesk__card-value">{{ticket.created_date}}</span>
</li>
<li class="helpdesk__card-item">
<form hx-swap="none" hx-post='{% url "update-priority" ticket.id %}' method="post">
{% csrf_token %}
{% if perms.helpdesk.change_ticket or request.user.employee_get|is_department_manager:ticket or request.user.employee_get in ticket.assigned_to.all or request.user.employee_get == ticket.employee_id %}
<form hx-swap="none" hx-post='{% url "update-priority" ticket.id %}' method="post">
{% csrf_token %}
<div class="d-flex">
<span class="helpdesk__card-label">{% trans "Priority" %} :</span>
<div class="oh-rate" onclick="event.stopPropagation();$(this).parents().closest('form').find('button').click()">
{% for i in "321" %}
<input type="radio" id="star{{i}}{{ticket.id}}" name="rating" class="rating-radio" value="{{i}}" {% if rating == i %} checked {% endif %}/>
<label for="star{{i}}{{ticket.id}}"
{% if i == '1' %}title="{% trans 'Low' %}"{% elif i == '2' %}title="{% trans 'Medium' %}"{% else %}title="{% trans 'High' %}"{% endif %}></label>
{% endfor %}
</div>
<button type="submit" hidden="true" onclick="event.stopPropagation()"></button>
<span id="rating-radio-error"></span>
</div>
</form>
{% else %}
<div class="d-flex">
<span class="helpdesk__card-label">{% trans "Priority" %} :</span>
<div class="oh-rate" onclick="event.stopPropagation();$(this).parents().closest('form').find('button').click()">
@@ -307,10 +409,7 @@
{% if i == '1' %}title="{% trans 'Low' %}"{% elif i == '2' %}title="{% trans 'Medium' %}"{% else %}title="{% trans 'High' %}"{% endif %}></label>
{% endfor %}
</div>
<button type="submit" hidden="true" onclick="event.stopPropagation()"></button>
<span id="rating-radio-error"></span>
</div>
</form>
{% endif %}
</li>
<li class="helpdesk__card-item">
<span class="helpdesk__card-label">{% trans "Last activity:" %}</span>
@@ -320,15 +419,20 @@
{% endwith %}
</span>
</li>
{% comment %} <li class="helpdesk__card-item">
<span class="helpdesk__card-label">{% trans "Status:" %}</span>
</li> {% endcomment %}
</ul>
<div class="helpdesk__card-footer">
<span class="helpdesk__card-label">{% trans "Tags:" %}</span>
{% if perms.helpdesk.change_ticket or request.user.employee_get == ticket.employee_id or request.user.employee_get|is_department_manager:ticket %}
<div class="oh-helpdesk-selectbox">
{{tag_form.tags}}
</div>
{% else %}
<span class="helpdesk__card-value">
{% for tag in ticket.tags.all %}
{{tag}},
{% endfor %}
</span>
{% endif %}
</div>
</div>
</div>
@@ -343,15 +447,19 @@
<div class="oh-helpdesk__subcard">
<div class="oh-helpdesk__subcard-header">
<span class="oh-helpdesk__subcard-title">{% trans "Forward to" %}</span>
{% if perms.helpdesk.changeticket or request.user.employee_get == ticket.employee_id %}
<a
hx-get="{% url 'ticket-change-raised-on' ticket.id %}"
data-target ="#addTagModal"
hx-target="#addTagTarget"
role="button"
class="oh-link oh-link--secondary oh-text--xs"
>{% trans "Change" %}</a
>
{% if perms.helpdesk.change_ticket or request.user.employee_get == ticket.employee_id %}
<div class="oh-btn-group">
<button
class="oh-btn oh-btn--light-bkg p-3 w-100"
hx-get="{% url 'ticket-change-raised-on' ticket.id %}"
data-target ="#addTagModal"
hx-target="#addTagTarget"
role="button"
title = "{% trans "Edit" %}"
>
<ion-icon name="create-outline"></ion-icon>
</button>
</div>
{% endif %}
</div>
<div class="oh-helpdesk__subcard-body">
@@ -364,39 +472,47 @@
<div class="oh-helpdesk__subcard">
<div class="oh-helpdesk__subcard-header">
<span class="oh-helpdesk__subcard-title">{% trans "Assigned to" %}</span>
{% if perms.helpdesk.changeticket %}
<a
hx-get="{% url 'ticket-change-assignees' ticket.id %}"
data-target ="#addTagModal"
hx-target="#addTagTarget"
role="button"
class="oh-link oh-link--secondary oh-text--xs"
>{% trans "Change" %}</a
>
{% if perms.helpdesk.change_ticket or request.user.employee_get|is_department_manager:ticket %}
<div class="oh-btn-group">
<button
hx-get = "{% url "view-ticket-claim-request" ticket.id %}"
hx-target="#objectDetailsModalTarget"
data-toggle="oh-modal-toggle"
data-target="#objectDetailsModal"
class="oh-btn oh-btn--info p-3 w-100"
title = "{% trans "Claim Requests" %}"
>
<ion-icon name="eye-outline"></ion-icon>
</button>
<button
class="oh-btn oh-btn--light-bkg p-3 w-100"
hx-get="{% url 'ticket-change-assignees' ticket.id %}"
data-target ="#addTagModal"
hx-target="#addTagTarget"
title="{% trans 'Edit' %}"
><ion-icon name="create-outline"></ion-icon>
</button>
</div>
{% endif %}
</div>
{% for assignee in ticket.assigned_to.all %}
<div class="oh-helpdesk__subcard-body">
<ul class="oh-helpdesk__subcard-items">
<li class="oh-helpdesk__subcard-item">
<div class="oh-profile oh-profile--base">
<div class="oh-profile__avatar mr-1">
<img
src="https://ui-avatars.com/api/?name={{assignee}}&background=random"
class="oh-profile__image"
alt="{{assignee}}" />
</div>
<div class="oh-profile__meta">
<span class="oh-profile__name oh-text--dark"
>{{assignee}}</span
>
</div>
</div>
</li>
</ul>
<div class="align-items-center d-flex" style="min-height: 40px;">
<div class="d-flex justify-content-between custom-scroll">
<div class="avatars" id="avatarsContainer">
{% for employee in ticket.assigned_to.all %}
<a
href="{% url 'employee-view-individual' employee.id %}"
class="avatars__item"
title="{{employee}}"
><img class="avatar" src="{{employee.get_avatar}}" alt=""
/></a>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
@@ -406,13 +522,19 @@
<div class="oh-helpdesk__card-header">
<span class="oh-helpdesk__card-title">{% trans "Documents" %}</span>
</div>
<div class="oh-helpdesk__card-body">
<ul class="oh-helpdesk__documents">
<div class="oh-helpdesk__card-body oh-scrollable-container">
<div class="oh-layout--grid-3 mt-2 mb-2 horizontal-scroll"
>
{% if attachments %}
{% for attachment in attachments %}
{% with extension=attachment.file.name|lower|slice:"-4:" %}
<li class="oh-helpdesk__document">
<a href="{{ attachment.file.url }}" target="_blank">
<a
class ="me-2"
data-toggle="oh-modal-toggle"
data-target ="#viewFileModal"
hx-target="#viewFile"
hx-get="{% url 'view-ticket-document' attachment.id %}"
>
<div class="oh-helpdesk__icon">
<span class="oh-file-icon
{% if extension == '.pdf' %} oh-file-icon--pdf
@@ -423,18 +545,17 @@
{% elif extension in '.ppt .pptx' %} oh-file-icon--powerpoint
{% elif extension in '.html' %} oh-file-icon--html
{% else %} oh-file-icon--default
{% endif %}">
{% endif %}"
title = "{{ attachment }}"
>
</span>
</div>
</a>
<a href="{{ attachment.file.url }}" target="_blank">
<span class="oh-helpdesk__filename">{{ attachment }}</span>
</a>
</li>
{% endwith %}
{% endfor %}
{% endif %}
</ul>
</div>
</div>
</div>
<!-- End of Document Card -->
@@ -481,6 +602,31 @@
</div>
<!-- end of create tag modal. -->
<div
class="oh-modal"
id="viewFileModal"
role="dialog"
aria-labelledby="viewFileModal"
aria-hidden="true"
>
<div class="oh-modal__dialog custom-dialog">
<div class="oh-modal__dialog-header">
<span class="oh-modal__dialog-title" id="viewFileModalLabel"
>{% trans "View File" %}</span
>
<button class="oh-modal__close" aria-label="Close">
<ion-icon
name="close-outline"
role="img"
class="md hydrated"
aria-label="close outline"
></ion-icon>
</button>
</div>
<div class="oh-modal__dialog-body" id="viewFile"></div>
</div>
</div>
<script>
// Function to send rating via AJAX
function sendRating(ratingValue) {

View File

@@ -0,0 +1,50 @@
{% load i18n %} {% load horillafilters %} {% load static %}
<div class="d-flex justify-content-end">
<div class="oh-btn-group mb-4">
{% if document.file %}
<a
href="data:{{ content_type }};base64,{{ file_content|base64_encode }}"
class="oh-btn oh-btn--info w-100"
download="{{document.file}}"
title="{% trans 'Download' %}"
><ion-icon class="me-1" name="download-outline"></ion-icon
></a>
{% endif %}
</div>
</div>
{% if document.file %}
<div class="modal-body">
<!-- Display the file content based on the file type -->
{% if file_extension == 'pdf' or file_extension == 'txt' or file_extension == 'docx' or file_extension == 'xlsx' %}
<embed
src="data:{{ content_type }};base64,{{ file_content|base64_encode }}"
type="{{ content_type }}"
width="100%"
height="500px"
/>
{% elif file_extension == 'jpg' or file_extension == 'jpeg' or file_extension == 'png' or file_extension == 'webp' %}
<div class="container">
<img
src="data:{{ content_type }};base64,{{ file_content|base64_encode }}"
alt="File Preview"
class="img-fluid"
style="display: block; width: 100%;height: 500px;"
/>
</div>
{% else %}
<p>{% trans "File format not supported for preview." %}</p>
{% endif %}
</div>
{% else %}
<div class="oh-not-found">
<img
style="width: 150px; height: 150px;"
src="{% static 'images/ui/document.png' %}"
class="oh-404__image mb-4"
alt="Page not found. 404."
/>
<h3 class="oh-404__subtitle">
{% trans "No Document to view." %}
</h3>
</div>
{% endif %}

View File

@@ -1,16 +1,11 @@
{% load i18n %}
<!-- start of filter -->
<form
{% if request.GET.view == 'card' %}
method="get"
action=""
{% else %}
method="post"
hx-get="{% url 'ticket-filter' %}?view={{view}}"
hx-target="#ticket_list"
{% endif %}
id="filterForm"
class="d-flex"
onsubmit="event.preventDefault()"
>
{% csrf_token %}
<div
@@ -186,10 +181,10 @@
</form>
<!-- end of filter -->
<script>
$(document).ready(function () {
{% comment %} $(document).ready(function () { {% endcomment %}
$("#id_field").on("change", function () {
$(".filterButton")[0].click();
});
});
{% comment %} }); {% endcomment %}
</script>

View File

@@ -0,0 +1,139 @@
{% load static %} {% load i18n %}{% load helpdeskfilters %}
<div class="oh-modal__dialog-header">
<span
class="oh-modal__dialog-title"
id="objectDetailsModalLabel"
>
{{ ticket }}
</span>
<button class="oh-modal__close" aria-label="Close">
<button class="oh-modal__close"><ion-icon name="close-outline"></ion-icon></button>
</button>
</div>
<div class="oh-modal__dialog-body">
<a class="oh-timeoff-modal__profile-content pt-4" style="text-decoration:none;"
href ="{% url 'employee-view-individual' ticket.employee_id.id %}">
<div class="oh-profile mb-2">
<div class="oh-profile__avatar">
<img
src="{{ticket.employee_id.get_avatar}}"
class="oh-profile__image me-2"
alt="Mary Magdalene"
/>
</div>
<div class="oh-timeoff-modal__profile-info">
<span class="oh-timeoff-modal__user fw-bold m-0"
>{{ticket.employee_id.get_full_name}}</span
>
<span
class="oh-timeoff-modal__user m-0"
style="font-size: 18px; color: #4d4a4a"
>
{{ticket.employee_id.employee_work_info.department_id}} /
{{ticket.employee_id.employee_work_info.job_position_id}}</span
>
</div>
</div>
</a>
<div class="oh-modal__dialog-header pt-0 pb-0">
<div class="oh-timeoff-modal__stats-container">
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Ticket type" %}
</span>
<span class="oh-timeoff-modal__stat-count"
>{{ticket.ticket_type}}</span
>
</div>
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Forward to" %}</span
>
<span class="oh-timeoff-modal__stat-count"
>{{ticket.get_raised_on}}</span
>
</div>
</div>
<div class="oh-timeoff-modal__stats-container mt-3">
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Dead line" %}
</span>
<span class="oh-timeoff-modal__stat-count dateformat_changer"
>{{ticket.deadline}}</span
>
</div>
<div class="oh-timeoff-modal__stat">
<span class="oh-timeoff-modal__stat-title"
>{% trans "Priority" %}
</span>
<div class="oh-rate">
{% if ticket.priority == 'low' %}
<div class="d-flex">
<div class="oh-rate">
{% for i in "321" %}
<input type="radio" id="star{{i}}{{ticket.id}}" name="rating" class="rating-radio" value="{{i}}" {% if '1' == i %} checked {% endif %}/>
<label for="star{{i}}{{ticket.id}}"
{% if i == '1' %}title="{% trans 'Low' %}"{% elif i == '2' %}title="{% trans 'Medium' %}"{% else %}title="{% trans 'High' %}"{% endif %}></label>
{% endfor %}
</div>
</div>
{% elif ticket.priority == 'medium' %}
<div class="d-flex">
<div class="oh-rate">
{% for i in "321" %}
<input type="radio" id="star{{i}}{{ticket.id}}" name="rating" class="rating-radio" value="{{i}}" {% if '2' == i %} checked {% endif %}/>
<label for="star{{i}}{{ticket.id}}"
{% if i == '1' %}title="{% trans 'Low' %}"{% elif i == '2' %}title="{% trans 'Medium' %}"{% else %}title="{% trans 'High' %}"{% endif %}></label>
{% endfor %}
</div>
</div>
{% else %}
<div class="d-flex">
<div class="oh-rate">
{% for i in "321" %}
<input type="radio" id="star{{i}}{{ticket.id}}" name="rating" class="rating-radio" value="{{i}}" {% if '3' == i %} checked {% endif %}/>
<label for="star{{i}}{{ticket.id}}"
{% if i == '1' %}title="{% trans 'Low' %}"{% elif i == '2' %}title="{% trans 'Medium' %}"{% else %}title="{% trans 'High' %}"{% endif %}></label>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
<div class="oh-timeoff-modal__stats-container mt-3">
<div class="oh-timeoff-modal__stat w-100">
<span class="oh-timeoff-modal__stat-title">{% trans "Description" %}</span>
<span class="oh-timeoff-modal__stat-count"
>{{ticket.description}}</span
>
</div>
</div>
<div class = "oh-modal__button-container text-center mt-3" onclick="event.stopPropagation()">
<div class="oh-btn-group" style="border:none">
{% if ticket|calim_request_exists:request.user.employee_get or request.user.employee_get in ticket.assigned_to.all %}
<a
href="#"
class="oh-btn oh-btn--info w-100 oh-btn--disabled"
title="{% trans 'Claim' %}"
>
<ion-icon name="checkmark-done-outline"></ion-icon>
</a>
{% else %}
<a
type="submit"
href = "{% url 'claim-ticket' ticket.id %}"
class="oh-btn oh-btn--info w-100"
title="{% trans 'Claim' %}"
>
<ion-icon name="checkmark-done-outline"></ion-icon>
</a>
{% endif %}
</div>
</div>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,6 @@
<!-- start of request view -->
<div class="oh-wrapper" id="ticketContainer">
{% if my_tickets or allocated_tickets or all_tickets %}
<!-- start of Quick filters -->
<div class="d-flex flex-row-reverse mb-2">
<span
@@ -148,29 +147,6 @@
</div>
</div>
<!-- end of contents -->
{% else %}
<div
style="
height: 70vh;
display: flex;
align-items: center;
justify-content: center;
"
class=""
>
<div style="" class="oh-404">
<img
style="display: block; width: 150px; height: 150px; margin: 10px auto"
src="{% static 'images/ui/ticket.png' %}"
class="mb-4"
alt=""
/>
<h3 style="font-size: 20px" class="oh-404__subtitle">
{% trans "There are no tickets at the moment." %}
</h3>
</div>
</div>
{% endif %}
</div>
<!-- end of request view -->

View File

View File

@@ -0,0 +1,30 @@
from django import template
from django.template.defaultfilters import register
from helpdesk.models import ClaimRequest,DepartmentManager
register = template.Library()
@register.filter(name="calim_request_exists")
def calim_request_exists(ticket,employee):
return ClaimRequest.objects.filter(
ticket_id = ticket,
employee_id = employee
).exists()
@register.filter(name="is_department_manager")
def is_department_manager(employee,ticket):
"""
Check requested user is a department manger or not
"""
if ticket.assigning_type == "job_position":
job_position = ticket.get_raised_on_object()
department = job_position.department_id
elif ticket.assigning_type == "department":
department = ticket.get_raised_on_object()
else:
return False
return DepartmentManager.objects.filter(
manager = employee,
department = department
).exists()

View File

@@ -57,6 +57,16 @@ urlpatterns = [
name="ticket-detail",
kwargs={"model": Ticket},
),
path(
"ticket-individual-view/<int:ticket_id>",
views.ticket_individual_view,
name="ticket-individual-view",
),
path(
"view-ticket-claim-request/<int:ticket_id>",
views.view_ticket_claim_request,
name="view-ticket-claim-request",
),
path("ticket-change-tag", views.ticket_update_tag, name="ticket-change-tag"),
path(
"ticket-change-raised-on/<int:ticket_id>",
@@ -70,6 +80,11 @@ urlpatterns = [
),
path("ticket-create-tag", views.create_tag, name="ticket-create-tag"),
path("remove-tag", views.remove_tag, name="remove-tag"),
path(
"view-ticket-document/<int:doc_id>",
views.view_ticket_document,
name="view-ticket-document",
),
path("comment-create/<int:ticket_id>", views.comment_create, name="comment-create"),
path("comment-edit/", views.comment_edit, name="comment-edit"),
path(
@@ -77,6 +92,11 @@ urlpatterns = [
),
path("get-raised-on", views.get_raised_on, name="get-raised-on"),
path("claim-ticket/<int:id>", views.claim_ticket, name="claim-ticket"),
path(
"approve-claim-request/<int:req_id>",
views.approve_claim_request,
name="approve-claim-request",
),
path(
"tickets-select-filter",
views.tickets_select_filter,
@@ -123,4 +143,9 @@ urlpatterns = [
views.view_department_managers,
name="department-manager-view",
),
path(
"get-department-employee",
views.get_department_employees,
name="get-department-employee",
),
]