[UPDT] OFFBOARDING: Note section to new design and workflow

This commit is contained in:
Horilla
2024-02-20 14:17:21 +05:30
parent 3a63d9609a
commit 485d5eac7d
7 changed files with 243 additions and 94 deletions

View File

@@ -295,7 +295,6 @@ class OffboardingNote(models.Model):
attachments = models.ManyToManyField(
OffboardingStageMultipleFile, blank=True, editable=False
)
title = models.CharField(max_length=20, null=True)
description = models.TextField(null=True, blank=True,max_length=255)
note_by = models.ForeignKey(
Employee, on_delete=models.SET_NULL, null=True, editable=False

View File

@@ -1,43 +1,174 @@
{% load i18n static %}
<style>
#enlargeImageContainer {
position: absolute;
left: -300px;
top: 100px;
height: 200px;
width: 200px;
}
#enlargeImageContainer {
position: absolute;
left: -300px;
top: 100px;
height: 200px;
width: 400px;
}
</style>
<a hx-get="{% url 'add-offboarding-note' %}?employee_id={{ employee.id }}" style="width: 125px;position: sticky;top: 0;" hx-target="#offboardingModalBody" data-toggle="oh-modal-toggle" data-target="#offboardingModal" class="mb-3 oh-btn oh-btn--secondary">
<ion-icon name="add-outline" role="img" class="md hydrated" aria-label="add outline"></ion-icon>
{% trans 'Add note' %}
</a>
<ol class="oh-activity-sidebar__qa-list" role="list">
{% for note in employee.offboardingnote_set.all %}
<li class="oh-activity-sidebar__qa-item">
<span class="oh-activity-sidebar__q">{{ note.title }}</span>
<span class="oh-activity-sidebar__a">{{ note.description }}</span>
<div class="d-flex mt-2 mb-2">
{% for attachment in note.attachments.all %}
<a href="{{ attachment.attachment.url }}" rel="noopener noreferrer" target="_blank"><span class="oh-file-icon oh-file-icon--pdf" onmouseover="enlargeImage('{{ attachment.attachment.url }}',$(this))" style="width:40px;height:40px"><img src="{% static 'images/ui/minus-icon.png' %}" style="display:block;width:50%;height:50%" hx-get="{% url 'delete-note-attachment' %}?ids={{ attachment.id }}&employee_id={{ employee.id }}" hx-target="#activitySidebar" onclick="event.stopPropagation();event.preventDefault()" /></span></a>
{% endfor %}
{% if messages %}
<div class="oh-alert-container">
{% for message in messages %}
<div class="oh-alert oh-alert--animated {{message.tags}}">{{ message }}</div>
{% endfor %}
</div>
<script>
setTimeout(() => {
$(".oh-modal__close").click();
}, 1000);
</script>
{% endif %}
<form hx-post="{% url 'view-offboarding-note' %}?note_id={{ note.id }}&employee_id={{ employee.id }}" hx-target="#noteContainer" class="add-files-form" hx-encoding="multipart/form-data">
{% csrf_token %}
<label for="addFile_20" title="Add Files"><ion-icon name="add-outline" style="font-size: 24px" role="img" class="md hydrated" aria-label="add outline"></ion-icon></label>
<input type="file" name="files" class="d-none" multiple="true" id="addFile_20" onchange="submitForm(this)" />
<input type="submit" class="d-none add_more_submit" value="save" />
</form>
</div>
<span class="oh-activity-sidebar__a">
{% trans 'by' %}
<img src="{{ note.note_by.get_avatar }}" style="width: 1.5em; border-radius: 100%" />
{{ note.note_by.get_full_name }} @{{ note.stage_id }}
</span>
<div style="width: 50%;">
<div id="enlargeImageContainer" class="enlargeImageContainer"></div>
</div>
</li>
{% endfor %}
</ol>
<div class="oh-activity-sidebar__header">
<a
style="cursor: pointer;"
onclick="$('.oh-activity-sidebar--show').removeClass('oh-activity-sidebar--show');">
<ion-icon
name="chevron-forward-outline"
class="oh-activity-sidebar__header-icon me-2 oh-activity-sidebar__close"
data-target="#activitySidebar"
></ion-icon>
</a>
<span class="oh-activity-sidebar__title">{{employee}}'s {% trans "Notes" %}</span>
</div>
<form method="post"
hx-target="#noteContainer"
hx-post="{% url 'add-offboarding-note' %}?employee_id={{ employee.id }}" id="commentForm">
{% csrf_token %}
<div>
<input type="text" name="description" id="commentInput" class="oh-input w-100" placeholder="Add notes">
<button type="submit" id="commentButton" class="oh-btn oh-btn--secondary mt-2 mr-0 oh-btn--w-100-resp" style="display: none;">
{% trans "Add" %}
</button>
</div>
<div class="oh-inner-sidebar-content__footer"></div>
</form>
{% if employee.offboardingnote_set.all %}
<ol class="oh-activity-sidebar__qa-list" role="list">
{% for note in employee.offboardingnote_set.all %}
<li class="oh-activity-sidebar__qa-item">
<span class="oh-activity-sidebar__q">{{ note.description }}
<span class="float-end" title="Delete" hx-get="{% url 'offboarding-note-delete' note.id %}" hx-swap="innerHTML" hx-target="#noteContainer">
<ion-icon name="close-outline" style="font-size: 24px" role="img" class="md hydrated" aria-label="close outline"></ion-icon></span>
</span>
<div class="d-flex mt-2 mb-2">
{% for file in note.attachments.all %}
<a
href="{{ file.files.url }}"
rel="noopener noreferrer"
target="_blank"
><span
class="oh-file-icon oh-file-icon--pdf"
onmouseover="enlargeImage('{{ file.attachment.url }}',$(this))"
style="width: 40px; height: 40px"
><img
src="{% static 'images/ui/minus-icon.png' %}"
style="display: block; width: 50%; height: 50%"
hx-get="{% url 'delete-note-attachment' %}?ids={{ file.id }}&employee_id={{ employee.id }}"
hx-target="#noteContainer"
onclick="event.stopPropagation();event.preventDefault()" /></span
></a>
{% endfor %}
<form
hx-post="{% url 'view-offboarding-note' employee.id %}?note_id={{ note.id }}"
class="add-files-form"
hx-encoding="multipart/form-data"
hx-swap="innerHTML" hx-target="#noteContainer"
>
{% csrf_token %}
<label for="addFile_{{note.id}}" title="Add Files"
><ion-icon
name="add-outline"
style="font-size: 24px"
role="img"
class="md hydrated"
aria-label="add outline"
></ion-icon
></label>
<input
type="file"
name="files"
class="d-none"
multiple="true"
id="addFile_{{note.id}}"
onchange="submitForm(this)"
/>
<input
type="submit"
class="d-none add_more_submit"
value="save"
/>
</form>
</div>
<span class="oh-activity-sidebar__a">
{% trans 'by' %}
<img
src="{{ note.note_by.get_avatar }}"
style="width: 1.5em; border-radius: 100%"
/>
{{ note.updated_by }} @ {{note.stage_id }}
{% trans "stage" %}
</span>
<div style="width: 50%">
<div id="enlargeImageContainer" class="enlargeImageContainer"></div>
</div>
</li>
{% endfor %}
</ol>
{% else %}
<div class="oh-timeoff-modal__profile-content">
<div class="oh-404">
<div class="">
<span class="oh-timeoff-title fw-bold" style="display: block"
>{% trans "No notes have been added for this employee" %}</span
>
<img
style="display: block; width: 100px; margin: 20px auto"
src="/static/images/ui/no_notes.png"
class=""
/>
</div>
</div>
</div>
{% endif %}
<!-- start of comment modal -->
<div
class="oh-modal"
id="shiftcommentModal"
role="dialog"
aria-labelledby="emptagModal"
aria-hidden="true"
>
<div class="oh-modal__dialog" id="shiftRequestCommentForm">
</div>
</div>
<!-- end of comment modal -->
<script>
// Get references to the input field and comment button
const commentInput = document.getElementById('commentInput');
const commentButton = document.getElementById('commentButton');
// Add event listener to the input field
commentInput.addEventListener('input', function() {
// Show the comment button if the input field is not empty, hide it otherwise
if (commentInput.value.trim() !== '') {
commentButton.style.display = 'inline';
} else {
commentButton.style.display = 'none';
}
});
</script>

View File

@@ -33,3 +33,8 @@
<div class="oh-modal__dialog-body" id="offboardingModalBody"></div>
</div>
</div>
<div class="oh-activity-sidebar" id="activitySidebar" style="z-index:1000">
<div class="oh-activity-sidebar__body" id="noteContainer">
</div>
</div>

View File

@@ -8,22 +8,6 @@
<div class="oh-card" id="offboardingBody{{offboarding.offboarding.id}}">
{% include "offboarding/stage/offboarding_body.html" %}
</div>
<div class="oh-activity-sidebar" id="activitySidebar">
<div class="oh-activity-sidebar__header">
<a
style="cursor: pointer;"
onclick="$('.oh-activity-sidebar--show').removeClass('oh-activity-sidebar--show');">
<ion-icon
name="chevron-back-outline"
class="oh-activity-sidebar__header-icon me-2 oh-activity-sidebar__close"
data-target="#activitySidebar"
></ion-icon>
</a>
<span class="oh-activity-sidebar__title"> {% trans "Notes" %} </span>
</div>
<div class="oh-activity-sidebar__body" id="noteContainer">
</div>
</div>
</div>
<script>
// This lines is used to set default selected stage for exits lines

View File

@@ -31,8 +31,8 @@
<div class="oh-sticky-table__td">
{% trans 'In' %} {{ employee.notice_period }} {{ employee.get_unit_display }}
</div>
<div class="oh-sticky-table__td">{{ employee.notice_period_starts }}</div>
<div class="oh-sticky-table__td">{{ employee.notice_period_ends }}</div>
<div class="oh-sticky-table__td dateformat_changer">{{ employee.notice_period_starts }}</div>
<div class="oh-sticky-table__td dateformat_changer">{{ employee.notice_period_ends }}</div>
{% if request.user.employee_get|is_any_stage_manager or perms.offboarding.change_offboarding or perms.offboarding.change_offboardingemployee %}
<div class="oh-sticky-table__td" onclick="event.stopPropagation()">
<form hx-get="{% url "offboarding-change-stage" %}?employee_ids={{employee.id}}"
@@ -62,7 +62,7 @@
title="{% trans 'Notes' %}"
class="oh-btn oh-btn--light oh-activity-sidebar__open"
data-target="#activitySidebar"
hx-get="{% url 'view-offboarding-note' %}?employee_id={{ employee.id }}"
hx-get="{% url 'view-offboarding-note' employee.id %}"
hx-target="#noteContainer"
style="flex: 1 0 auto; width: 20px; height: 40.68px; padding: 0"
onclick="event.stopPropagation()"
@@ -129,7 +129,7 @@
{% endfor %} {% else %}
<button
hx-get="{% url "offboarding-assign-task" %}?employee_ids={{employee.id}}&task_id={{task.id}}"
hx-target="#offboardingBody{{offboarding.id}}"
hx-target="#offboardingBody{{offboarding.offboarding.id}}"
class="oh-checkpoint-badge text-info"
data-toggle="oh-modal-toggle"
onclick="event.stopPropagation()"
@@ -143,9 +143,6 @@
</div>
{% endif %} {% endfor %}
<script>
$("[data-archive-stage=true]")
.find(".oh-sticky-table__tr[data-employee-id]")
.hide();
function showArchived($element) {
let checked = $element.is(":checked");
if (checked) {

View File

@@ -21,7 +21,7 @@ urlpatterns = [
path(
"offboarding-change-stage", views.change_stage, name="offboarding-change-stage"
),
path("view-offboarding-note", views.view_notes, name="view-offboarding-note"),
path("view-offboarding-note/<int:employee_id>/", views.view_notes, name="view-offboarding-note"),
path("add-offboarding-note", views.add_note, name="add-offboarding-note"),
path(
"delete-note-attachment", views.delete_attachment, name="delete-note-attachment"
@@ -40,6 +40,11 @@ urlpatterns = [
views.offboarding_individual_view,
name="offboarding-individual-view",
),
path(
"offboarding-note-delete/<int:note_id>/",
views.offboarding_note_delete,
name="offboarding-note-delete",
),
path(
"resignation-requests-view/",
views.request_view,

View File

@@ -6,7 +6,7 @@ from django.contrib import messages
from django.contrib.auth.models import User
from base.context_processors import intial_notice_period
from employee.models import Employee
from horilla.decorators import login_required, permission_required
from horilla.decorators import login_required, manager_can_enter, permission_required
from base.views import paginator_qry
from offboarding.decorators import (
any_manager_can_enter,
@@ -53,6 +53,7 @@ from recruitment.pipeline_grouper import group_by_queryset
def pipeline_grouper(filters={}, offboardings=[]):
groups = []
for offboarding in offboardings:
stages = PipelineStageFilter(
filters, queryset=offboarding.offboardingstage_set.all()
@@ -63,28 +64,35 @@ def pipeline_grouper(filters={}, offboardings=[]):
all_stages_grouper.append({"grouper": stage, "list": []})
stage_employees = PipelineEmployeeFilter(
filters,
OffboardingEmployee.objects.filter(stage_id=stage).order_by("-id"),
).qs
OffboardingEmployee.objects.filter(stage_id=stage),
).qs.order_by("stage_id__id")
page_name = "page" + stage.title + str(offboarding.id)
grouper = group_by_queryset(
employee_grouper = group_by_queryset(
stage_employees,
"stage_id",
filters.get(page_name),
page_name,
).object_list
data["stages"] = data["stages"] + grouper
data["stages"] = data["stages"] + employee_grouper
ordered_data = []
existing_grouper_ids = list({item["grouper"].id for item in data["stages"]})
for item in all_stages_grouper:
if item["grouper"].id not in existing_grouper_ids:
data["stages"].append(
{
"grouper": item["grouper"],
"list": [],
"dynamic_name": f'dynamic_page_page{item["grouper"].id}',
}
)
if set(existing_grouper_ids) != set(stages.values_list("id", flat=True)):
for sg in stages:
for grouper in data["stages"]:
if sg != grouper["grouper"]:
ordered_data.append(
{"grouper": sg, "list": [], "dynamic_name": ""}
)
else:
ordered_data.append(grouper)
else:
ordered_data = data["stages"]
data = {
"offboarding": offboarding,
"stages": ordered_data,
}
groups.append(data)
return groups
@@ -337,7 +345,7 @@ def change_stage(request):
@any_manager_can_enter(
"offboarding.view_offboardingnote", offboarding_employee_can_enter=True
)
def view_notes(request):
def view_notes(request, employee_id=None):
"""
This method is used to render all the notes of the employee
"""
@@ -352,8 +360,9 @@ def view_notes(request):
attachment.save()
attachments.append(attachment)
note.attachments.add(*attachments)
offboarding_employee_id = request.GET["employee_id"]
offboarding_employee_id = employee_id
employee = OffboardingEmployee.objects.get(id=offboarding_employee_id)
return render(
request,
"offboarding/note/view_notes.html",
@@ -377,23 +386,35 @@ def add_note(request):
form.instance.employee_id = employee
if form.is_valid():
form.save()
return HttpResponse(
f"""
<div id="asfdoiioe09092u09un320" hx-get="/offboarding/view-offboarding-note?employee_id={employee.pk}"
hx-target="#noteContainer"
>
</div>
<script>
$("#asfdoiioe09092u09un320").click()
$(".oh-modal--show").removeClass("oh-modal--show")
</script>
"""
)
messages.success(request, _("Note added successfully"))
return redirect("view-offboarding-note", employee_id=employee.id)
return render(
request, "offboarding/note/form.html", {"form": form, "employee": employee}
request,
"offboarding/note/view_notes.html",
{
"form": form,
"employee": employee,
},
)
@login_required
@manager_can_enter(perm="offboarding.delete_offboardingNote")
def offboarding_note_delete(request, note_id):
"""
This method is used to delete the offboarding note
"""
try:
note = OffboardingNote.objects.get(id=note_id)
employee = note.employee_id.id
note.delete()
messages.success(request, _("Note deleted"))
except OffboardingNote.DoesNotExist:
messages.error(request, _("Note not found."))
return redirect("view-offboarding-note", employee_id=employee)
@login_required
@permission_required("offboarding.delete_offboardingnote")
def delete_attachment(request):
@@ -521,10 +542,17 @@ def task_assign(request):
offboarding = employees.first().stage_id.offboarding_id
stage_forms = {}
stage_forms[str(offboarding.id)] = StageSelectForm(offboarding=offboarding)
groups = pipeline_grouper({}, [task.stage_id.offboarding_id])
for item in groups:
setattr(item["offboarding"], "stages", item["stages"])
return render(
request,
"offboarding/stage/offboarding_body.html",
{"offboarding": offboarding, "stage_forms": stage_forms},
{
"offboarding": groups[0],
"stage_forms": stage_forms,
"response_message": _("Task Assigned"),
},
)