[UPDT] HELPDESK: Replace CRUD Operations into htmx

This commit is contained in:
Horilla
2024-12-13 15:27:36 +05:30
parent 1205749820
commit 73d65a6930
14 changed files with 2404 additions and 477 deletions

View File

@@ -69,7 +69,7 @@ class FAQForm(ModelForm):
"tags": forms.SelectMultiple(
attrs={
"class": "oh-select oh-select-2 select2-hidden-accessible",
"onchange": "updateTag()",
"onchange": "updateTag(this)",
}
),
}

View File

@@ -1,10 +1,19 @@
{% load i18n %}
{% if messages %}
<script>
setTimeout(() => { reloadMessage(this); $('.oh-modal__close').click(); }, 250);
</script>
<span hx-get="{% url 'faq-category-search' %}" hx-trigger="load" {% if faq_category %}
hx-target="#faqCategoryItem{{faq_category.id}}" hx-select="#faqCategoryItem{{faq_category.id}}" hx-swap="outerHTML"
{% else %} hx-target="#faqCategoryList" {% endif %}></span>
{% endif %}
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<div class="oh-modal__dialog-header pb-0">
<h2 class="oh-modal__dialog-title" id="faqCreateModalLabel">
{% if faq_category %}
{% trans "FAQ category Update" %}
{% else %}
{% else %}
{% trans "FAQ category Create" %}
{% endif %}
</h2>
@@ -13,21 +22,13 @@
</button>
</div>
<div class="oh-modal__dialog-body" id="faqCategoryCreateForm">
<form
id="add-form"
<form id="add-form" class="oh-profile-section"
hx-post="{% if faq_category %}{% url 'faq-category-update' faq_category.id %}{% else %}{% url 'faq-category-create' %}{% endif %} "
hx-target="#faqCategoryCreate"
hx-swap="innerHTML"
method="post"
hx-encoding="multipart/form-data"
>
hx-target="#faqCategoryCreate" hx-swap="innerHTML" method="post" hx-encoding="multipart/form-data">
{{form.as_p}}
<div class="d-flex flex-row-reverse">
<input
type="submit"
value='{% trans "Save" %}'
class="oh-btn oh-btn--secondary oh-btn--shadow pl-5 pr-5"
/>
<input type="submit" value="{% trans 'Save' %}"
class="oh-btn oh-btn--secondary oh-btn--shadow pl-5 pr-5" />
</div>
</form>
</div>

View File

@@ -3,69 +3,47 @@
{% include 'filter_tags.html' %}
<div class="oh-wrapper oh-faq-cards">
{% for category in faq_categories %}
<div class="oh-faq-card">
<div class="d-flex justify-content-between align-items-center">
<h3 class="oh-faq-card__title">{{category.title}}</h3>
{% if perms.helpdesk.change_faqcategory or perms.helpdesk.delete_faqcategory %}
<div class="oh-dropdown" x-data="{open: false}">
<button
class="oh-btn oh-stop-prop oh-btn--transparent oh-accordion-meta__btn"
@click="open = !open"
@click.outside="open = false"
title="Actions"
>
<ion-icon
name="ellipsis-vertical"
role="img"
class="md hydrated"
aria-label="ellipsis vertical"
></ion-icon>
</button>
<div class="oh-faq-card" id="faqCategoryItem{{category.id}}">
<div class="d-flex justify-content-between align-items-center">
<h3 class="oh-faq-card__title">{{category.title}}</h3>
{% if perms.helpdesk.change_faqcategory or perms.helpdesk.delete_faqcategory %}
<div class="oh-dropdown" x-data="{open: false}">
<button class="oh-btn oh-stop-prop oh-btn--transparent oh-accordion-meta__btn" @click="open = !open"
@click.outside="open = false" title="Actions">
<ion-icon name="ellipsis-vertical" role="img" class="md hydrated"
aria-label="ellipsis vertical"></ion-icon>
</button>
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open">
<ul class="oh-dropdown__items">
{% if perms.helpdesk.change_faqcategory %}
<li class="oh-dropdown__item">
<a
hx-get="{% url 'faq-category-update' category.id %}"
hx-target="#objectCreateModalTarget"
data-toggle="oh-modal-toggle"
data-target="#objectCreateModal"
role="button"
class="oh-dropdown__link"
>{% trans "Edit" %}</a
>
</li>
{% endif %}
{% if perms.helpdesk.delete_faqcategory %}
<li class="oh-dropdown__item">
<form
action="{% url 'faq-category-delete' category.id %}"
onsubmit="return confirm('Are you sure you want to delete this FAQ Category?');"
method="post"
>
{% csrf_token %}
<button
type="submit"
class="oh-dropdown__link oh-dropdown__link--danger"
>
{% trans "Delete" %}
</button>
</form>
</li>
{% endif %}
</ul>
<div class="oh-dropdown__menu oh-dropdown__menu--right" x-show="open">
<ul class="oh-dropdown__items">
{% if perms.helpdesk.change_faqcategory %}
<li class="oh-dropdown__item">
<a hx-get="{% url 'faq-category-update' category.id %}" hx-target="#objectCreateModalTarget"
data-toggle="oh-modal-toggle" data-target="#objectCreateModal" role="button"
class="oh-dropdown__link">{% trans "Edit" %}</a>
</li>
{% endif %}
{% if perms.helpdesk.delete_faqcategory %}
<li class="oh-dropdown__item">
<form hx-confirm="{% trans 'Are you sure you want to delete this FAQ Category?' %}"
hx-post="{% url 'faq-category-delete' category.id %}" hx-swap="outerHTML"
hx-on-htmx-after-request="setTimeout(() => {reloadMessage(this);},100);"
hx-target="#faqCategoryItem{{category.id}}">
{% csrf_token %}
<button type="submit" class="oh-dropdown__link oh-dropdown__link--danger">
{% trans "Delete" %}
</button>
</form>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
{% endif %}
</div>
{% endif %}
</div>
<p class="oh-faq-card__desc">{{category.description}}</p>
<a
href="{% url 'faq-view' category.id %}"
class="oh-btn oh-btn--secondary oh-btn--block"
>{% trans "View FAQs" %}</a
>
</div>
<p class="oh-faq-card__desc">{{category.description}}</p>
<a href="{% url 'faq-view' category.id %}" class="oh-btn oh-btn--secondary oh-btn--block">{% trans "View FAQs" %}</a>
</div>
{% endfor %}
</div>

View File

@@ -1,10 +1,17 @@
{% load i18n %}
{% if messages %}
<script>
setTimeout(() => { reloadMessage(this); $('.oh-modal__close').click(); }, 160);
</script>
<span hx-get="{% url 'faq-search' %}?cat_id={{cat_id}}" hx-trigger="load" {% if faq %} hx-target="#faqItem{{faq.id}}"
hx-select="#faqItem{{faq.id}}" hx-swap="outerHTML" {% else %} hx-target="#faqList" {% endif %}></span>
{% endif %}
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="faqCreateModalLabel">
{% if faq %}
{% trans "FAQ Update" %}
{% else %}
{% else %}
{% trans "FAQ Create" %}
{% endif %}
</h2>
@@ -13,21 +20,13 @@
</button>
</div>
<div class="oh-modal__dialog-body" id="faqCreateForm">
<form
id="add-form"
<form id="add-form" class="oh-profile-section"
hx-post="{% if faq %}{% url 'faq-update' faq.id %}{% else %}{% url 'faq-create' cat_id %}{% endif %} "
hx-target="#faqCreate"
hx-swap="innerHTML"
method="post"
hx-encoding="multipart/form-data"
>
hx-target="#faqCreate" hx-swap="innerHTML" method="post" hx-encoding="multipart/form-data">
{{form.as_p}}
<div class="d-flex flex-row-reverse">
<input
type="submit"
value='{% trans "Save" %}'
class="oh-btn oh-btn--secondary oh-btn--shadow pl-5 pr-5"
/>
<input type="submit" value='{% trans "Save" %}'
class="oh-btn oh-btn--secondary oh-btn--shadow pl-5 pr-5" />
</div>
</form>
</div>

View File

@@ -1,60 +1,45 @@
{% load i18n %}
{% include 'filter_tags.html' %}
<div class="oh-card">
<div class="oh-card mb-4">
<div class="oh-faq">
<ul class="oh-faq__items">
{% for faq in faqs %}
<li class="oh-faq__item">
<div class="oh-faq__item-header icon-inner" onclick="show_answer(this)">
<div class="oh-faq__item-header__left icon-inner">
<span class="oh-faq__item-title icon-inner"> {{faq.question}} </span>
<ul class="oh-faq__tags">
{% for tag in faq.tags.all %}
<li class="oh-faq__tag text-light" style="background:{{tag.color}};">{{tag|capfirst}}</li>
{% endfor %}
</ul>
</div>
<div class="oh-faq__item-header__right">
{% if perms.helpdesk.change_faq %}
<button
class="oh-btn oh-btn--sq oh-btn--transparent"
title="{% trans 'Edit' %}"
data-toggle="oh-modal-toggle"
data-target="#objectCreateModal"
hx-get="{% url 'faq-update' faq.id %}"
hx-target="#objectCreateModalTarget"
onclick='event.stopPropagation()'
>
<ion-icon name="create-outline"></ion-icon>
</button>
{% endif %}
{% if perms.helpdesk.delete_faq %}
<form
action="{% url 'faq-delete' faq.id %}"
class="w-50"
onsubmit="return confirm('{% trans "Are you sure you want to delete this FAQ?" %}');"
method="post"
onclick="event.stopPropagation()"
>
{% csrf_token %}
<button
type="submit"
class="oh-btn oh-btn--sq oh-btn--danger-text oh-btn--transparent"
title="{% trans 'Delete' %}"
>
<ion-icon
class="me-1 md hydrated"
name="trash-outline"
role="img"
aria-label="trash outline"
></ion-icon>
<li class="oh-faq__item fade-me-out" id="faqItem{{faq.id}}">
<div class="oh-faq__item-header icon-inner" onclick="show_answer(this)">
<div class="oh-faq__item-header__left icon-inner">
<span class="oh-faq__item-title icon-inner"> {{faq.question}} </span>
<ul class="oh-faq__tags">
{% for tag in faq.tags.all %}
<li class="oh-faq__tag text-light" style="background:{{tag.color}};">{{tag|capfirst}}</li>
{% endfor %}
</ul>
</div>
<div class="oh-faq__item-header__right">
{% if perms.helpdesk.change_faq %}
<button class="oh-btn oh-btn--sq oh-btn--transparent" title="{% trans 'Edit' %}"
data-toggle="oh-modal-toggle" data-target="#objectCreateModal"
hx-get="{% url 'faq-update' faq.id %}" hx-target="#objectCreateModalTarget"
onclick='event.stopPropagation()'>
<ion-icon name="create-outline"></ion-icon>
</button>
</form>
{% endif %}
{% endif %}
{% if perms.helpdesk.delete_faq %}
<form hx-confirm="{% trans 'Are you sure you want to delete this FAQ?' %}"
hx-target="#faqItem{{faq.id}}" hx-post="{% url 'faq-delete' faq.id %}" class="w-50"
hx-on:click="event.stopPropagation();"
hx-on-htmx-after-request="setTimeout(() => {reloadMessage(this);},100);"
hx-swap="outerHTML swap:.5s">
{% csrf_token %}
<button type="submit" class="oh-btn oh-btn--sq oh-btn--danger-text oh-btn--transparent">
<ion-icon class="me-1 md hydrated" name="trash-outline" role="img"
aria-label="trash outline"></ion-icon>
</button>
</form>
{% endif %}
</div>
</div>
</div>
<div class="oh-faq__item-body">{{faq.answer}}</div>
</li>
<div class="oh-faq__item-body">{{faq.answer}}</div>
</li>
{% endfor %}
</ul>
</div>

View File

@@ -5,54 +5,56 @@
background: #e9dfec9c;
opacity: 0.7;
}
.oh-faq__tag {
border-radius: 10px;
font-weight: 600;
}
.oh-faq__item-body {
max-height: 0;
transition: max-height 0.3s ease,padding 0.3s ease;
transition: max-height 0.3s ease, padding 0.3s ease;
overflow-y: auto;
}
.oh-faq__item--show .oh-faq__item-body {
max-height: 200px; /* Adjust the max-height as needed */
}
.oh-title_faq__main-header {
width:70%;
}
.oh-select-faq {
width:12%;
}
.oh-select-faq , .oh-select-faq:last-child , .oh-faq__input-search {
margin :1rem 0;
}
.oh-faq_search--icon{
top:17px;
max-height: 200px;
/* Adjust the max-height as needed */
}
.oh-title_faq__main-header {
width: 70%;
}
.oh-select-faq {
width: 12%;
}
.oh-select-faq,
.oh-select-faq:last-child,
.oh-faq__input-search {
margin: 1rem 0;
}
.oh-faq_search--icon {
top: 17px;
}
</style>
<div class="oh-wrapper" >
<div class="oh-wrapper">
{% if faqs %}
<div id="faqList">
{% include "helpdesk/faq/faq_list.html" %}
</div>
<div id="faqList">
{% include "helpdesk/faq/faq_list.html" %}
</div>
{% else %}
<div
style="
<div style="
height: 70vh;
display: flex;
align-items: center;
justify-content: center;
"
>
">
<div class="oh-404">
<img
style="display: block; width: 150px; height: 150px; margin: 10px auto"
src="{% static 'images/ui/attendance.png' %}"
class="mb-4"
alt=""
/>
<img style="display: block; width: 150px; height: 150px; margin: 10px auto"
src="{% static 'images/ui/attendance.png' %}" class="mb-4" alt="" />
<h3 style="font-size: 20px" class="oh-404__subtitle">
{% trans "There are no FAQs at the moment." %}
</h3>
@@ -62,52 +64,33 @@
</div>
<!-- create FAQ modal -->
<div
class="oh-modal"
id="faqCreate"
role="dialog"
aria-labelledby="faqCreate"
aria-hidden="true"
></div>
<div class="oh-modal" id="faqCreate" role="dialog" aria-labelledby="faqCreate" aria-hidden="true"></div>
<!-- modal -->
<div id="addTagTargetModal">
<div id="addTagTarget">
</div>
</div>
</div>
<div
class="oh-modal"
id="addTagModal"
role="dialog"
aria-labelledby="editDialogModal"
aria-hidden="true"
style="z-index: 1100;"
>
<div class="oh-modal" id="addTagModal" role="dialog" aria-labelledby="editDialogModal" aria-hidden="true"
style="z-index: 1100;">
<div class="oh-modal__dialog">
<div class="oh-modal__dialog-header">
<h2 class="oh-modal__dialog-title" id="editTitle">
{% trans "Create Tag" %}
{% trans "Create Tag" %}
</h2>
<button class="oh-modal__close--custom" onclick="$(this).parents().closest('.oh-modal--show').toggleClass('oh-modal--show')" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
<button class="oh-modal__close--custom"
onclick="$(this).parents().closest('.oh-modal--show').toggleClass('oh-modal--show')" aria-label="Close">
<ion-icon name="close-outline"></ion-icon>
</button>
</div>
<div class="oh-modal__dialog-body" id="editTarget">
<form
{% comment %} hx-post="{% url 'ticket-create-tag' %}"
hx-target="#addTagTarget"
method="post"
hx-encoding="multipart/form-data" {% endcomment %}
id ="addTagForm"
>
<form {% comment %} hx-post="{% url 'ticket-create-tag' %}" hx-target="#addTagTarget" method="post"
hx-encoding="multipart/form-data" {% endcomment %} id="addTagForm">
{% csrf_token %}
{{create_tag_f.as_p}}
{% comment %} <button
type="submit"
class="oh-btn oh-btn--secondary mt-2 mr-0 oh-btn--w-100-resp"
>
{% comment %} <button type="submit" class="oh-btn oh-btn--secondary mt-2 mr-0 oh-btn--w-100-resp">
{% trans "Save" %}
</button> {% endcomment %}
</form>
@@ -117,17 +100,14 @@
<script>
function updateTag(event) {
var selectedValues = $("#id_tags option:selected").map(function(){
return $(this).val();
}).get();
if (selectedValues.includes('create_new_tag')) {
const selectedValues = Array.from(event.selectedOptions).map(option => option.value);
if (selectedValues.includes('create_new_tag')) {
$("#addTagModal").addClass("oh-modal--show");
}
}
}
}
function show_answer(element){
if ($(element).parent(".oh-faq__item--show").length !=0){
function show_answer(element) {
if ($(element).parent(".oh-faq__item--show").length != 0) {
$(".oh-faq__item--show").removeClass("oh-faq__item--show");
} else {
$(".oh-faq__item--show").removeClass("oh-faq__item--show");
@@ -135,20 +115,22 @@
}
}
$("#addTagForm").on('submit', function(){
$("#addTagForm").on('submit', function () {
event.preventDefault();
$.ajax({
type:'POST',
url:'/helpdesk/ticket-create-tag',
type: 'POST',
url: '/helpdesk/ticket-create-tag',
data: $(this).serialize(),
success: function (response) {
var newOption = $("<option selected></option>")
.val(response.tag_id)
.text(response.title);
.val(response.tag_id)
.text(response.title);
$("#id_tags option[value='create_new_tag']").before(newOption);
$("#id_tags option[value='create_new_tag']").prop("selected", false);
$("#addTagModal").removeClass("oh-modal--show");
$('#addTagForm').find('input[name="title"]').val('');
},
})

View File

@@ -25,13 +25,13 @@ urlpatterns = [
),
path("faq-category-search/", views.faq_category_search, name="faq-category-search"),
path(
"faq-view/<int:cat_id>/",
"faq-view/<int:obj_id>/",
views.faq_view,
name="faq-view",
kwargs={"model": FAQCategory},
),
path("faq-create/<int:cat_id>/", views.create_faq, name="faq-create"),
path("faq-update/<int:id>", views.faq_update, name="faq-update"),
path("faq-create/<int:obj_id>/", views.create_faq, name="faq-create"),
path("faq-update/<int:obj_id>", views.faq_update, name="faq-update"),
path("faq-search/", views.faq_search, name="faq-search"),
path("faq-filter/<int:id>/", views.faq_filter, name="faq-filter"),
path("faq-suggestion/", views.faq_suggestion, name="faq-suggestion"),

View File

@@ -108,7 +108,8 @@ def faq_category_create(request):
if form.is_valid():
form.save()
messages.success(request, _("The FAQ Category created successfully."))
return HttpResponse("<script>window.location.reload()</script>")
form = FAQCategoryForm()
context = {
"form": form,
}
@@ -138,7 +139,7 @@ def faq_category_update(request, id):
if form.is_valid():
form.save()
messages.success(request, _("The FAQ category updated successfully."))
return HttpResponse("<script>window.location.reload()</script>")
context = {
"form": form,
"faq_category": faq_category,
@@ -153,9 +154,10 @@ def faq_category_delete(request, id):
faq = FAQCategory.objects.get(id=id)
faq.delete()
messages.success(request, _("The FAQ category has been deleted successfully."))
return HttpResponse("")
except ProtectedError:
messages.error(request, _("You cannot delete this FAQ category."))
return redirect(faq_category_view)
return HttpResponse("<script>window.location.reload()</script>")
@login_required
@@ -186,20 +188,20 @@ def faq_category_search(request):
@login_required
def faq_view(request, cat_id, **kwargs):
def faq_view(request, obj_id, **kwargs):
"""
This function is responsible for rendering the FAQ view.
Parameters:
request (HttpRequest): The HTTP request object.
cat_id (int): The id of the the faq category.
obj_id (int): The id of the the faq category.
"""
faqs = FAQ.objects.filter(category=cat_id)
faqs = FAQ.objects.filter(category=obj_id)
context = {
"faqs": faqs,
"f": FAQFilter(request.GET),
"cat_id": cat_id,
"cat_id": obj_id,
"create_tag_f": TagsForm(),
}
@@ -209,7 +211,7 @@ def faq_view(request, cat_id, **kwargs):
@login_required
@hx_request_required
@permission_required("helpdesk.add_faq")
def create_faq(request, cat_id):
def create_faq(request, obj_id):
"""
This function is responsible for creating the FAQ.
@@ -221,16 +223,16 @@ def create_faq(request, cat_id):
POST : return faq view
"""
form = FAQForm(initial={"category": cat_id})
form = FAQForm(initial={"category": obj_id})
if request.method == "POST":
form = FAQForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("The FAQ created successfully."))
return HttpResponse("<script>window.location.reload()</script>")
context = {
"form": form,
"cat_id": cat_id,
"cat_id": obj_id,
}
return render(request, "helpdesk/faq/faq_create.html", context)
@@ -238,7 +240,7 @@ def create_faq(request, cat_id):
@login_required
@hx_request_required
@permission_required("helpdesk.change_faq")
def faq_update(request, id):
def faq_update(request, obj_id):
"""
This function is responsible for updating the FAQ.
@@ -251,17 +253,17 @@ def faq_update(request, id):
POST : return faq view
"""
faq = FAQ.objects.get(id=id)
faq = FAQ.objects.get(id=obj_id)
form = FAQForm(instance=faq)
if request.method == "POST":
form = FAQForm(request.POST, instance=faq)
if form.is_valid():
form.save()
messages.success(request, _("The FAQ updated successfully."))
return HttpResponse("<script>window.location.reload()</script>")
context = {
"form": form,
"faq": faq,
"cat_id": faq.category.id,
}
return render(request, "helpdesk/faq/faq_create.html", context)
@@ -364,9 +366,10 @@ def faq_delete(request, id):
messages.success(
request, _('The FAQ "{}" has been deleted successfully.').format(faq)
)
return HttpResponse("")
except ProtectedError:
messages.error(request, _("You cannot delete this FAQ."))
return redirect(faq_view, cat_id=cat_id)
return HttpResponse("<script>window.location.reload()</script>")
@login_required