[ADD] PMS: Style updation in feedback detailed view

This commit is contained in:
Horilla
2025-06-24 15:40:23 +05:30
parent d43c911e8e
commit fffb1c1d91
8 changed files with 1041 additions and 52 deletions

View File

@@ -56,14 +56,14 @@
{% if resume %}
<div class="col-12 col-md-6 col-lg-6 sticky">
<iframe style="width: 100%;height:90vh" height="100" src="{{ resume.file.url }}" frameborder="0"></iframe>
</div>
{% else %}
<p class="text-sm mb-2">{{recruitment.description|safe}}</p>
{% endif %}
</div>
</div>
{% comment %} <form action="" method="post" enctype="multipart/form-data"> {% endcomment %}
{% if messages %}
<div class="oh-alert-container">
@@ -73,7 +73,7 @@
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
<form class="col-span-12 lg:col-span-8 bg-white rounded-md shadow-card p-5 myform" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class= "lg:h-[calc(100vh_-_160px)] overflow-hidden overflow-y-auto">
@@ -116,17 +116,17 @@
/>
<label for="recuitment" >{% trans "Choose Job Position" %}</label>
<div class="relative">
<select name="job_position_id" class="oh-select oh-select2 w-100" style="height: 35px">
<select name="job_position_id" class="oh-select oh-select2 w-100" style="height: 35px">
{% for job in recruitment.open_positions.all %}
<option value="{{job.id}}">&nbsp;&nbsp;&nbsp;&nbsp;{{job.job_position}}</option>
{% endfor %}
</select>
</select>
<span class="absolute left-2 top-3 text-sm text-primary-600 pointer-events-none">
<i class="fa-solid fa-briefcase"></i>
</span>
</div>
</div>
<div>
<label for="portfolio">{% trans "Portfolio" %}</label>
<div class="relative">
@@ -141,7 +141,7 @@
<div>
<label class="required-star" for="gender">{% trans "Gender" %}</label>
<div class="relative">
{{form.gender}}
{{form.gender}}
<span class="absolute left-1 top-0 bottom-0 m-auto flex items-center ps-2 text-sm text-primary-600">
<i class="fa-solid fa-circle-dot"></i></span>
</div>
@@ -267,7 +267,7 @@
</div>
</form>
</div>
</div>
</div>
</div>
@@ -295,7 +295,7 @@
}
}
function initSidebarToggle() {
// Reusable Sidebar Toggle
@@ -330,7 +330,7 @@
{% if resume %}
$("#resume").val(resume)
{% endif %}
$('form label').each(function() {
var text = $(this).text();
if (text.startsWith('*')) {
@@ -365,22 +365,22 @@
}
return cookieValue;
}
function setIfEmpty(selector, value) {
if ($(selector).val() === "" ){
$(selector).val(value);
$(selector).change();
$("[name=country]").parent().find("span").remove()
$("[name=country]").select2()
}
}
$("#matching_resume").on("click", function(){
var file = $(this).data("value");
var resume_id = $(this).data("id");
$.ajax({
url: "{% url 'matching-resume-completion' %}",
type: 'GET',
@@ -397,7 +397,7 @@
var dob = response.dob;
var email = response.email_id;
var zip = response.zip;
setIfEmpty("[name='name']", full_name);
setIfEmpty("[name='email']", email);
setIfEmpty("[name='mobile']", phone);
@@ -406,21 +406,21 @@
setIfEmpty("[name='state']", state);
setIfEmpty("[name='zip']", zip);
$("[name='dob']").val(dob);
},
error: function(xhr, status, error) {
console.error('File upload failed: ', status, error);
}
});
})
$("#matching_resume").click()
$("[name='resume']").on("change", function(){
var formData = new FormData();
var file = this.files[0];
formData.append('resume', file);
$.ajax({
url: "{% url 'resume-completion' %}",
type: 'POST',
@@ -431,7 +431,7 @@
'X-CSRFToken': getCookie("csrftoken")
},
success: function(response) {
var full_name = response.full_name;
var address = response.address;
var country = response.country;
@@ -440,7 +440,7 @@
var dob = response.dob;
var email = response.email_id;
var zip = response.zip;
setIfEmpty("[name='name']", full_name);
setIfEmpty("[name='email']", email);
setIfEmpty("[name='mobile']", phone);
@@ -449,7 +449,7 @@
setIfEmpty("[name='state']", state);
setIfEmpty("[name='zip']", zip);
$("[name='dob']").val(dob);
},
error: function(xhr, status, error) {
console.error('File upload failed: ', status, error);
@@ -458,7 +458,7 @@
})
})
</script>
<script>
// Image preview
document.getElementById("profileImageUpload").addEventListener("change", function () {

View File

@@ -0,0 +1,656 @@
{% extends 'index.html' %}
{% block content %}
{% include 'generic/components.html' %}
{% load static i18n %}
{% load i18n %}
{% load basefilters %}
{% load mathfilters %}
<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;
}
</style>
<div id="message"></div>
<main :class="sidebarOpen ? 'oh-main__sidebar-visible' : ''">
<div class="bg-white p-3 rounded-md shadow-card mb-5">
<!-- Top Bar -->
<div class="flex gap-2 md:gap-0 flex-wrap justify-between items-end mb-5 border-b border-dark-50 pb-3">
<h3 class="text-md font-semibold">Feedback: {{ feedback }}</h3>
<div class="flex items-center flex-wrap gap-1">
{% if perms.pms.change_feedback or request.user|is_reportingmanager %}
<div class="relative dropdown-wrapper">
<select id="status" name="feedback_status"
class="px-5 py-2 h-full bg-white rounded-md text-xs border border-primary-500 hover:border-primary-600 transition duration-300"
hx-post="{% url 'feedback-detailed-view-status' id=feedback.id %}"
hx-trigger="change" hx-swap="afterend">
<option value="{{feedback.status}}" selected>{% trans feedback.get_status_display %}</option>
{% for value,label in feedback_status %}
{% if feedback.status != label %}
<option value="{{label}}">{% trans label %}</option>
{% endif %}
{% endfor %}
</select>
</div>
{% else %}
<label class="px-5 py-2 bg-white rounded-md text-xs border border-primary-500">{{ feedback.get_status_display }}</label>
{% endif %}
<!-- Action Buttons -->
{% if perms.pms.change_feedback or perms.pms.delete_feedback or request.user.employee_get == feedback.created_by %}
{% if not feedback_started and perms.pms.change_feedback or request.user.employee_get == feedback.created_by %}
<button data-toggle="oh-modal-toggle"
class="w-8 h-8 flex justify-center items-center bg-white border border-primary-500 hover:border-primary-600 rounded-md"
data-target="#genericModal"
hx-get="{% url 'feedback-update' feedback.id %}"
hx-target="#genericModalBody"
title="{% trans 'Edit' %}">
<img src="/static/horilla_theme/assets/img/icons/edit.svg" width="16" alt="edit">
</button>
{% endif %}
{% if perms.pms.delete_feedback %}
<form action="{% url 'feedback-archive' id=feedback.id %}" method="post"
onsubmit="return confirm('{% trans 'Do you want archive this Feedback ?' %}')">
{% csrf_token %}
<button class="w-8 h-8 flex justify-center items-center bg-white border border-primary-500 hover:border-primary-600 rounded-md"
title="{% trans 'Archive' %}">
<ion-icon name="archive" role="img" class="md hydrated"></ion-icon>
</button>
</form>
{% if not feedback.feedback_answer.all %}
<form action="{% url 'feedback-delete' id=feedback.id %}" method="post"
onsubmit="return confirm('{% trans 'Do you want Delete this Feedback ?' %}')">
{% csrf_token %}
<button class="w-8 h-8 flex justify-center items-center bg-white border border-primary-500 hover:border-primary-600 rounded-md"
title="{% trans 'Delete' %}">
<img src="/static/horilla_theme/assets/img/icons/trash.svg" width="16" alt="delete">
</button>
</form>
{% endif %}
{% endif %}
{% if request.user.employee_get == feedback.employee_id or perms.pms.change_feedback %}
<a hx-get="{% url 'share-feedback' feedback.id %}"
data-toggle="oh-modal-toggle"
data-target="#objectCreateModal"
hx-target="#objectCreateModalTarget"
class="w-8 h-8 justify-center px-1 py-2 bg-primary-600 text-white rounded-md text-xs flex items-center gap-2 hover:bg-primary-800 transition duration-300"
title="{% trans 'Request feedback' %}">
<img src="/static/horilla_theme/assets/img/create.svg" width="18" alt="request" class="filter brightness-0 invert">
</a>
{% endif %}
{% endif %}
</div>
</div>
<!-- Feedback Info Section -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
<div class="mb-2 flex gap-5 items-center">
<span class="font-medium text-sm text-[#565E6C] w-28">{% trans "Owner" %}</span>
<p class="text-sm font-semibold flex items-center gap-5">:
<img src="{{ feedback.employee_id.get_avatar }}" alt="owner" class="w-6 h-6 rounded-full inline-block">
<span>{{ feedback.employee_id }}</span>
</p>
</div>
<div class="mb-2 flex gap-5 items-center">
<span class="font-medium text-sm text-[#565E6C] w-28">{% trans "Start Date" %}</span>
<p class="text-sm font-semibold flex items-center gap-5">: <span>{{ feedback.start_date }}</span></p>
</div>
<div class="mb-2 flex gap-5 items-center">
<span class="font-medium text-sm text-[#565E6C] w-28">{% trans "End Date" %}</span>
<p class="text-sm font-semibold flex items-center gap-5">:
<span> {{ feedback.end_date }}</span>
<span title="Due {% if feedback.end_date == today %} today {% else %}in {{ feedback.end_date|sub:today }}{% endif %}">
<ion-icon
class="text-{% if feedback.end_date < today %}danger{% elif feedback.end_date == today %}warning{% else %}success{% endif %}"
name="time-sharp"></ion-icon>
</span>
</p>
</div>
<div class="mb-2 flex gap-5 items-center ">
<span class="font-medium text-sm text-[#565E6C] w-48">{% trans "Answered Employees" %}</span>
<p class="text-sm font-semibold flex items-center gap-5">:
<ul class="flex items-center gap-1">
{% for employee in employee_statics.yes %}
{% if employee %}
<li><a href="{% url 'employee-view-individual' employee.id %}"><img src="{{ employee.get_avatar }}" class="w-6 h-6 rounded-full" alt=""></a></li>
{% endif %}
{% endfor %}
</ul></p>
</div>
<div class="mb-2 flex gap-5 items-center">
<span class="font-medium text-sm text-[#565E6C] w-48">{% trans "Employees not answered yet" %}</span>
<p class="text-sm font-semibold flex items-center gap-5">:
<ul class="flex items-center gap-1">
{% for employee in employee_statics.no %}
{% if employee %}
<li><a href="{% url 'employee-view-individual' employee.id %}"><img src="{{ employee.get_avatar }}" class="w-6 h-6 rounded-full" alt=""></a></li>
{% endif %}
{% endfor %}
</ul></p>
</div>
{% if perms.pms.view_feedback and feedback.cyclic_feedback %}
<div class="mb-2 flex gap-5 items-center">
<span class="font-medium text-sm text-[#565E6C] w-48">{% trans "Cyclic feedback period" %}</span>
<p class="text-sm font-semibold">: {{ feedback.cyclic_feedback_days_count }} {{ feedback.get_cyclic_feedback_period_display }}</p>
</div>
<div class="mb-2 flex gap-5 items-center">
<span class="font-medium text-sm text-[#565E6C] w-48">{% trans "Next feedback on" %}</span>
<p class="text-sm font-semibold">: {{ feedback.cyclic_next_start_date }} - {{ feedback.cyclic_next_end_date }}</p>
</div>
{% endif %}
</div>
</div>
<div class="bg-white p-3 rounded-md shadow-card ">
<div class="h-[calc(100vh_-_335px)] overflow-hidden overflow-y-auto">
<!-- Tab Buttons -->
<div class="mytab flex sticky top-0 bg-white" role="tablist">
<button
class="tab-button px-4 py-2 text-sm font-medium text-blue-600 w-1/2 active"
onclick="switchTab(event);"
data-target="#feedback-answers"
data-tab="feedback-answers">
{% trans 'Feedback Answers' %}
</button>
<button
class="tab-button px-4 py-2 text-sm font-medium w-1/2"
onclick="switchTab(event); $('#feedback-overview-button').click()"
data-target="#feedback-overview"
data-tab='feedback-overview'>
{% trans 'Feedback Overview' %}
</button>
</div>
<div class="tab-content mt-4">
<div class="tab-pane" id="feedback-answers">
<!-- Employee -->
<div class="overflow-hidden overflow-x-auto">
<table class="w-full">
<thead class="bg-white">
<tr class="border-b border-[#ececec]">
<th class="text-sm font-medium text-left p-3" style="width:40%;">{% trans "Employee" %}</th>
<th class="text-sm font-medium text-left p-3" style="width:28%;">{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr class="border-b border-[#ebebeb]">
<td class="text-sm text-left p-3 text-[#666]">
<div class="flex gap-3 items-center font-normal text-sm">
<img src="{{feedback.employee_id.get_avatar}}" class="rounded-full" alt="" width="30" />
<span>{{feedback.employee_id}}</span>
</div>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<span class="feedback-status" x-data-feedback-id="{{feedback.id}}" x-data-employee-id="{{feedback.employee_id.id}}"></span>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<button class="px-4 py-2 bg-primary-600 text-white rounded-md text-xs flex w-28 justify-center items-center gap-2 hover:bg-primary-800 transition duration-300 oh-activity-sidebar__open"
data-target="#answerViewAccordion"
hx-post="{%url 'feedback-detailed-view-answer' id=feedback.id emp_id=feedback.employee_id.id %}"
hx-target="#answerView">
{% trans "Answer View" %}
</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Manager -->
{% if feedback.manager_id %}
<div class="overflow-hidden overflow-x-auto mb-6">
<table class="w-full">
<thead class="bg-white">
<tr class="border-b border-[#ececec]">
<th class="text-sm font-medium text-left p-3" style="width:40%;">{% trans "Manager" %}</th>
<th class="text-sm font-medium text-left p-3" style="width:28%;">{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr class="border-b border-[#ebebeb]">
<td class="text-sm text-left p-3 text-[#666]">
<div class="flex gap-3 items-center font-normal text-sm">
<img src="{{feedback.manager_id.get_avatar}}" class="rounded-full" alt="" width="30" />
<span>{{feedback.manager_id}}</span>
</div>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<span class="feedback-status" x-data-feedback-id="{{feedback.id}}" x-data-employee-id="{{feedback.manager_id.id}}"></span>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<button class="px-4 py-2 bg-primary-600 text-white rounded-md text-xs flex w-28 justify-center items-center gap-2 hover:bg-primary-800 transition duration-300 oh-activity-sidebar__open"
data-target="#answerViewAccordion"
hx-post="{%url 'feedback-detailed-view-answer' id=feedback.id emp_id=feedback.manager_id.id %}"
hx-target="#answerView">
{% trans "Answer View" %}
</button>
</td>
</tr>
</tbody>
</table>
</div>
{% endif %}
<!-- Subordinates -->
{% if feedback.subordinate_id.all %}
<div class="overflow-hidden overflow-x-auto mb-6">
<table class="w-full">
<thead class="bg-white">
<tr class="border-b border-[#ececec]">
<th class="text-sm font-medium text-left p-3" style="width:40%;">{% trans "Subordinates" %}</th>
<th class="text-sm font-medium text-left p-3" style="width:28%;">{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for employee in feedback.subordinate_id.all %}
<tr class="border-b border-[#ebebeb]">
<td class="text-sm text-left p-3 text-[#666]">
<div class="flex gap-3 items-center font-normal text-sm">
<img src="{{employee.get_avatar}}" class="rounded-full" alt="" width="30" />
<span>{{employee}}</span>
</div>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<span class="feedback-status" x-data-feedback-id="{{feedback.id}}" x-data-employee-id="{{employee.id}}"></span>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<button class="px-4 py-2 bg-primary-600 text-white rounded-md text-xs flex w-28 justify-center items-center gap-2 hover:bg-primary-800 transition duration-300 oh-activity-sidebar__open"
data-target="#answerViewAccordion"
hx-post="{%url 'feedback-detailed-view-answer' id=feedback.id emp_id=employee.id %}"
hx-target="#answerView">
{% trans "Answer View" %}
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Colleague -->
{% if feedback.colleague_id.all %}
<div class="overflow-hidden overflow-x-auto mb-6">
<table class="w-full">
<thead class="bg-white">
<tr class="border-b border-[#ececec]">
<th class="text-sm font-medium text-left p-3" style="width:40%;">{% trans "Colleague" %}</th>
<th class="text-sm font-medium text-left p-3" style="width:28%;">{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for employee in feedback.colleague_id.all %}
<tr class="border-b border-[#ebebeb]">
<td class="text-sm text-left p-3 text-[#666]">
<div class="flex gap-3 items-center font-normal text-sm">
<img src="{{employee.get_avatar}}" class="rounded-full" alt="" width="30" />
<span>{{employee}}</span>
</div>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<span class="feedback-status" x-data-feedback-id="{{feedback.id}}" x-data-employee-id="{{employee.id}}"></span>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<button class="px-4 py-2 bg-primary-600 text-white rounded-md text-xs flex w-28 justify-center items-center gap-2 hover:bg-primary-800 transition duration-300 oh-activity-sidebar__open"
data-target="#answerViewAccordion"
hx-post="{%url 'feedback-detailed-view-answer' id=feedback.id emp_id=employee.id %}"
hx-target="#answerView">
{% trans "Answer View" %}
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<!-- Others -->
{% if feedback.others_id.all %}
<div class="overflow-hidden overflow-x-auto mb-6">
<table class="w-full">
<thead class="bg-white">
<tr class="border-b border-[#ececec]">
<th class="text-sm font-medium text-left p-3" style="width:40%;">{% trans "Other Employees" %}</th>
<th class="text-sm font-medium text-left p-3" style="width:28%;">{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for employee in feedback.others_id.all %}
<tr class="border-b border-[#ebebeb]">
<td class="text-sm text-left p-3 text-[#666]">
<div class="flex gap-3 items-center font-normal text-sm">
<img src="{{employee.get_avatar}}" class="rounded-full" alt="" width="30" />
<span>{{employee}}</span>
</div>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<span class="feedback-status" x-data-feedback-id="{{feedback.id}}" x-data-employee-id="{{employee.id}}"></span>
</td>
<td class="text-sm text-left p-3 text-[#666]">
<button class="px-4 py-2 bg-primary-600 text-white rounded-md text-xs flex w-28 justify-center items-center gap-2 hover:bg-primary-800 transition duration-300 oh-activity-sidebar__open"
data-target="#answerViewAccordion"
hx-post="{%url 'feedback-detailed-view-answer' id=feedback.id emp_id=employee.id %}"
hx-target="#answerView">
{% trans "Answer View" %}
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane hidden" id="feedback-overview">
<div id="feedback_overview_div"></div>
</div>
</div>
</div>
</div>
<button
hx-get="{% url 'get-feedback-overview' feedback.id %}"
hx-target="#feedback-overview" id="feedback-overview-button" hidden>
</button>
</main>
<!-- modals -->
<div
class="oh-modal"
id="objectCreateModal"
role="dialog"
aria-labelledby="objectCreateModal"
aria-hidden="true"
>
<div class="oh-modal__dialog" id="objectCreateModalTarget"></div>
</div>
<!-- answer off canvas -->
<div class="oh-activity-sidebar" id="answerViewAccordion" style="z-index:1000">
<div class="oh-activity-sidebar__header">
<span id="closanswer" style="cursor: pointer;" title="{% trans 'Close' %}">
<ion-icon
name="chevron-back-outline"
class="oh-activity-sidebar__header-icon me-2 oh-activity-sidebar__close"
id="close"
data-target="#activitySidebar"
style="font-size: 24px;"
></ion-icon>
<span class="oh-activity-sidebar__title"> {% trans "Answers" %}</span>
</div>
<div class="oh-activity-sidebar__body">
<ol class="oh-activity-sidebar__qa-list" role="list" id="answerView">
</ol>
</div>
</div>
<script src="{% static 'src/feedback/feedback_answer.js' %}"></script>
<script src="{% static 'src/feedback/feedback_detailed_view.js' %}"></script>
<script>
$(document).ready(function () {
$("#close").attr(
"class",
"oh-activity-sidebar__header-icon me-2 oh-activity-sidebar__close md hydrated"
);
});
$("#closanswer").click(function (e) {
$("#answerViewAccordion").removeClass("oh-activity-sidebar--show");
});
$(` .oh-activity-sidebar__open`).on("click", function () {
let sideBarId = $(this).attr("data-target");
$(`${sideBarId}`).addClass("oh-activity-sidebar--show");
});
$(` .oh-activity-sidebar__open`).on("click", function () {
$(".oh-modal--show").removeClass("oh-activity-sidebar__close");
});
function get_collegues(element) {
if (typeof element === 'object')
{
var employee_id = $(element).val();
}
else {
var employee_id = element
}
// Check if the employee_id is valid
if (employee_id) {
// Dynamically set the hx-vals attribute for the manager button
$('#managerButton').attr('hx-vals', JSON.stringify({ employee_id: employee_id, data: 'manager' })).click();
// Dynamically set the hx-vals attribute for the colleagues button
$('#colleguesButton').attr('hx-vals', JSON.stringify({ employee_id: employee_id, data: 'colleagues' })).click();
// Dynamically set the hx-vals attribute for the subordinates button
$('#subordinatesButton').attr('hx-vals', JSON.stringify({ employee_id: employee_id, data: 'subordinates' })).click();
// Dynamically set the hx-vals attribute for the keyresult button
$('#keyresultButtton').attr('hx-vals', JSON.stringify({ employee_id: employee_id, data: 'keyresults' })).click();
} else {
console.error('Invalid employee_id');
}
}
function changeCyclicFeedback(element) {
if (element.checked) {
$("#cyclic_feedback_period").show();
$("#id_cyclic_feedback_days_count").attr("required", true);
$("#id_cyclic_feedback_period").attr("required", true);
} else {
$("#cyclic_feedback_period").hide();
$("#id_cyclic_feedback_days_count").attr("required", false);
$("#id_cyclic_feedback_period").attr("required", false);
}
}
function copyLink(link){
var $temp = $("<input>");
var $url = $(location).attr('href');
$("body").append($temp);
$temp.val(link).select();
document.execCommand("copy");
$temp.remove();
Swal.fire({
text: `Link copied ${link}`,
icon: "success",
showConfirmButton: false,
timer: 3000, // Set the timer to 3000 milliseconds (3 seconds)
timerProgressBar: true, // Show a progress bar as the timer counts down
});
}
</script>
<!-- Script to load components and all notifications -->
<script>
async function loadComponent(elementId, path, callback) {
try {
const response = await fetch(path);
const html = await response.text();
document.getElementById(elementId).innerHTML = html;
if (typeof callback === "function") {
callback(); // Run script after component loads
}
} catch (error) {
console.error("Error loading component:", error);
}
}
function initSidebarToggle() {
// Reusable Sidebar Toggle
document.querySelectorAll(".toggleSidemenu").forEach((button) => {
button.addEventListener("click", () => {
const sidebarId = button.getAttribute("data-sidebar");
const sidebar = document.getElementById(sidebarId);
if (sidebar) {
sidebar.classList.toggle("active");
document.body.classList.toggle("overflow-hidden");
}
});
});
document.querySelectorAll(".closeSidemenu").forEach((button) => {
button.addEventListener("click", () => {
const sidebarId = button.getAttribute("data-sidebar");
const sidebar = document.getElementById(sidebarId);
if (sidebar) {
sidebar.classList.remove("active");
document.body.classList.remove("overflow-hidden");
}
});
});
}
</script>
<script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
<!-- TAB DESIGN -->
<script>
const tabButtons = document.querySelectorAll(".tab-button");
const tabPanes = document.querySelectorAll(".tab-pane");
tabButtons.forEach((btn) => {
btn.addEventListener("click", () => {
// Remove active class and hide all panes
tabButtons.forEach((b) => b.classList.remove("active", "text-blue-600", "border-blue-500"));
tabPanes.forEach((pane) => pane.classList.add("hidden"));
// Add active class to clicked button
btn.classList.add("active", "text-blue-600", "border-blue-500");
// Show the selected tab
const target = btn.getAttribute("data-tab");
document.getElementById(target).classList.remove("hidden");
});
});
// Trigger default active
tabButtons[0].click();
</script>
<!-- ACCORDION -->
<script>
document.querySelectorAll('.accordion-btn').forEach((btn) => {
btn.addEventListener('click', () => {
const panel = btn.nextElementSibling;
const icon = btn.querySelector('.icon');
const isOpen = panel.style.maxHeight && panel.style.maxHeight !== "0px";
// Collapse all others (optional — comment this block if you want multiple open)
document.querySelectorAll('.accordion-panel').forEach(p => {
p.style.maxHeight = null;
p.previousElementSibling.querySelector('.icon').textContent = '+';
p.previousElementSibling.classList.remove("bg-[#e54f38]", "text-white");
p.previousElementSibling.classList.add("bg-[#fff5f1]", "text-[#e54f38]");
});
// Toggle current
if (!isOpen) {
panel.style.maxHeight = panel.scrollHeight + 'px';
icon.textContent = '';
btn.classList.remove("bg-[#fff5f1]", "text-[#e54f38]");
btn.classList.add("bg-[#fff5f1]", "text-[#e54f38]");
} else {
panel.style.maxHeight = null;
icon.textContent = '+';
btn.classList.remove("bg-[#e54f38]", "text-white");
btn.classList.add("bg-[#fff5f1]", "text-[#e54f38]");
}
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,132 @@
{% load i18n %}
{% for answer in answers %}
<li class="oh-activity-sidebar__qa-item">
<span class="oh-activity-sidebar__q"> {{forloop.counter}}. {{answer.question_id.question}}?</span>
{% if answer.question_id.question_type == '1' %}
<span class="oh-activity-sidebar__a">{{ answer.answer.answer}}.</span>
{% endif %}
{% if answer.question_id.question_type == '2' %}
<ul class="flex gap-2 text-lg ps-4 mb-4">
{% for i in "12345" %}
<li>
<i class="fa-solid fa-star {% if i <= answer.answer.answer %}text-[#FFA800]{% else %}text-[#dfdfdf]{% endif %}"></i>
</li>
{% endfor %}
</ul>
{% endif %}
{% if answer.question_id.question_type == '3' %}
<div class="oh-input__group">
<div class="oh-input-picker-group">
<div class="oh-input-picker oh-input-picker--selected boolean-colour ">
{{answer.answer.answer}}
<input type="radio" selected />
</div>
</div>
</div>
{% endif %}
{% if answer.question_id.question_type == '4' %}
<div class="d-block">
<ul class="oh-questions mt-2">
<li class="oh-questions__answer">
<div class="oh-radio">
<input type="radio" class="oh-radio " id="answer1" name="answer{{q.id}}" value="{{option.option_a}}"
checked />
<span class="oh-radio__checkmark"></span>
<label class="oh-label" for="answer1"> {{answer.answer.answer}}</label>
</div>
</li>
</ul>
</div>
{% endif %}
{% if answer.question_id.question_type == '5' %}
<div class="d-block">
<div class="oh-input-picker-group oh-input-picker-group--resp mt-2">
<div class="oh-input-picker oh-input-picker--likert likert-colour oh-input-picker--selected ">
{{answer.answer.answer}}
</div>
</div>
</div>
{% endif %}
</li>
{% endfor %}
{% if kr_feedbacks %}
<hr/>
<span class="oh-activity-sidebar__q" style="font-size: medium;">{% trans "Key results" %}</span>
{% for kr_feedback in kr_feedbacks %}
<li class="oh-activity-sidebar__qa-item">
<span class="oh-activity-sidebar__q"> {{forloop.counter}}. {{kr_feedback.key_result_id.key_result_id}}</span>
<div class="d-block">
<div class="oh-input-picker-group oh-input-picker-group--resp mt-2">
<div class="oh-input-picker oh-input-picker--likert oh-input-picker--selected
{% if kr_feedback.answer.answer == "Bad" %}
oh-input-picker--1
{% elif kr_feedback.answer.answer == "Average" %}
oh-input-picker--2
{% elif kr_feedback.answer.answer == "Good" %}
oh-input-picker--3
{% elif kr_feedback.answer.answer == "Perfect" %}
oh-input-picker--4
{% endif %}
"
>
{{ kr_feedback.answer.answer }}
</div>
</div>
</div>
</li>
{% endfor %}
{% endif %}
<script>
$(document).ready(function () {
// answer color for likert
var booleanText = $('.boolean-colour').text().trim()
var booleanEl = $('.boolean-colour')
var red = "oh-input-picker--1"
var orange = "oh-input-picker--2"
var yellow = "oh-input-picker--3"
var light_green = "oh-input-picker--4"
var green = "oh-input-picker--5"
$('.likert-colour').each(function() {
var likertText = $(this).text().trim()
if (likertText === 'Strongly Agree'){
$(this).addClass(green)
}
else if (likertText === 'Agree'){
$(this).addClass(light_green)
}
else if (likertText === 'Neutral'){
$(this).addClass(yellow)
}
else if (likertText === 'Disagree'){
$(this).addClass(orange)
}
else if (likertText === 'Strongly Disagree'){
$(this).addClass(red)
}
});
// boolean text colour adding
$('.boolean-colour').each(function() {
var booleanText = $(this).text().trim()
if (booleanText === 'yes'){
$(this).addClass(green)
}
else if (booleanText === 'no'){
$(this).addClass(red)
}
})
});
</script>

View File

@@ -0,0 +1,133 @@
{% load i18n %}
<div class="accordion-wrapper space-y-2">
{% for question, answers in feedback_overview.items %}
<div class="accordion-item border border-primary-300 rounded-md overflow-hidden">
<!-- Accordion Button -->
<button
class="accordion-btn w-full flex justify-between items-center px-4 py-2 bg-[#fff5f1] text-[#e54f38] text-sm transition-all">
<div class="flex items-center gap-2">
{{ question }}
</div>
<div class="flex items-center gap-5">
<span class="icon text-xl font-bold">+</span>
</div>
</button>
<!-- Accordion Panel -->
<div class="accordion-panel max-h-0 overflow-hidden transition-all duration-300 bg-white px-4 text-sm text-gray-700">
<div class="py-3">
{% if answers %}
<table class="w-full">
<thead class="bg-white">
<tr class="border-b border-[#ececec]">
<th class="text-sm font-medium text-left p-3">{% trans "Employee" %}</th>
<th class="text-sm font-medium text-left p-3">{% trans "Answer" %}</th>
</tr>
</thead>
<tbody>
{% for answer in answers %}
{% for employee, value in answer.items %}
<tr class="border-b border-[#ebebeb]">
<td class="text-sm text-left p-3 text-[#666]">
<div class="flex gap-3 items-center font-normal text-sm">
<img src="{{ employee.get_avatar }}" class="rounded-full" alt="" width="30" />
<span>{{ employee }}</span>
</div>
</td>
<td class="text-sm text-left p-3 text-[#666]">
{% if value.1.type == '1' %}
<span class="oh-activity-sidebar__a">{{ value.0.answer }}</span>
{% elif value.1.type == '2' %}
<ul class="flex gap-2 text-lg ps-4 mb-4">
{% for i in "12345" %}
<li>
<i class="fa-solid fa-star {% if i <= value.0.answer %}text-[#FFA800]{% else %}text-[#dfdfdf]{% endif %}"></i>
</li>
{% endfor %}
</ul>
{% elif value.1.type == '3' %}
<div class="oh-input__group">
<div class="oh-input-picker-group">
<div class="oh-input-picker oh-input-picker--selected boolean-colour">
{{ value.0.answer }}
<input type="radio" selected />
</div>
</div>
</div>
{% elif value.1.type == '4' %}
<div class="d-block">
<label class="oh-label" for="answer1">{{ value.0.answer }}</label>
</div>
{% elif value.1.type == '5' %}
<div class="d-block mt-2">
<div class="oh-input-picker-group oh-input-picker-group--resp">
<div class="oh-input-picker oh-input-picker--likert likert-colour oh-input-picker--selected">
{{ value.0.answer }}
</div>
</div>
</div>
{% elif value.1.type == '6' %}
<div class="mt-2">
<div class="oh-input-picker-group oh-input-picker-group--resp">
<div class="oh-input-picker oh-input-picker--likert oh-input-picker--selected
{% if value.0.answer == 'Bad' %} oh-input-picker--1
{% elif value.0.answer == 'Average' %} oh-input-picker--2
{% elif value.0.answer == 'Good' %} oh-input-picker--3
{% elif value.0.answer == 'Perfect' %} oh-input-picker--4
{% endif %}
">
{{ value.0.answer }}
</div>
</div>
</div>
{% endif %}
</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
{% else %}
<p class="py-2 text-gray-500 text-sm">{% trans "No answers yet." %}</p>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
<!-- ACCORDION -->
<script>
document.querySelectorAll('.accordion-btn').forEach((btn) => {
btn.addEventListener('click', () => {
const panel = btn.nextElementSibling;
const icon = btn.querySelector('.icon');
const isOpen = panel.style.maxHeight && panel.style.maxHeight !== "0px";
// Collapse all others (optional — comment this block if you want multiple open)
document.querySelectorAll('.accordion-panel').forEach(p => {
p.style.maxHeight = null;
p.previousElementSibling.querySelector('.icon').textContent = '+';
p.previousElementSibling.classList.remove("bg-[#e54f38]", "text-white");
p.previousElementSibling.classList.add("bg-[#fff5f1]", "text-[#e54f38]");
});
// Toggle current
if (!isOpen) {
panel.style.maxHeight = panel.scrollHeight + 'px';
icon.textContent = '';
btn.classList.remove("bg-[#fff5f1]", "text-[#e54f38]");
btn.classList.add("bg-[#fff5f1]", "text-[#e54f38]");
} else {
panel.style.maxHeight = null;
icon.textContent = '+';
btn.classList.remove("bg-[#e54f38]", "text-white");
btn.classList.add("bg-[#fff5f1]", "text-[#e54f38]");
}
});
});
</script>

View File

@@ -0,0 +1,84 @@
{% load static i18n generic_template_filters %}
<style>
.userbox {
border: 1px solid #eaeaea;
}
.userbox.active {
background-color: rgb(255 245 241 / var(--tw-bg-opacity, 1)) !important;
}
.userbox.active button:not(.text-xs button) {
background-color: rgb(249 83 58) !important;
}
</style>
<div class="userbox p-4 rounded-md mb-2 active">
<div class="flex flex-col gap-3 relative">
{% if actions %}
<div class="absolute inline-block dropdown-wrapper right-0 -top-1">
<a class="cursor-pointer dropdown-toggle"><i class="fa-solid fa-ellipsis-vertical text-black"></i></a>
<!-- Dropdown Content -->
<div class="dropdown-menu absolute right-0 min-w-[100px] bg-white rounded-lg shadow-card py-2 hidden z-50 transition-all duration-200">
<ul class="text-xs" style="width: ;">
{% for action in actions %}
{% if action.accessibility|accessibility:instance %}
<li>
<a href="#" class="w-full text-left px-3 py-1 hover:text-primary-600 transition duration-300 oh-profile-dropdown-link" {{ action.attrs|format:instance|safe }}>
{% comment %} <img src="{{ action.src }}" style="width: 20px; height: auto" title="{{ action.title }}" /> {% endcomment %}
<button style="border: none; background: transparent">{{ action.title }}</button>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<div class="flex items-center gap-3">
<img style="border-radius :30px ;overflow :hidden;" src="{{ object.get_avatar }}" alt="" width="35" />
<h5 class="mb-1 font-medium text-sm">{{ instance.get_full_name }}</h5>
</div>
<div class="flex items-center justify-between gap-2">
<div>
<p class="text-[13px] text-[#565E6C] mb-1">{{ instance.get_subtitle }}</p>
</div>
<div>
<button hx-get="{{ instance.get_profile_url }}" hx-target="#{{ view_id|safe }}" hx-swap="outerHTML" class="active w-8 h-8 rounded-md bg-[#f7f7f7] bg-primary-600 flex items-center justify-center text-white text-xs transition duration-300 hover:bg-primary-700"><i class="fa-solid fa-angle-right"></i></button>
</div>
</div>
</div>
</div>
{% for object in instances|slice:'0:9' %}
{% if not object.pk == instance.pk %}
<div class="userbox p-4 rounded-md mb-2">
<div class="flex flex-col gap-3 relative">
<div class="absolute inline-block dropdown-wrapper right-0 -top-1">
<a class="cursor-pointer dropdown-toggle"><i class="fa-solid fa-ellipsis-vertical text-black"></i></a>
<!-- Dropdown Content -->
<div class="dropdown-menu absolute right-0 min-w-[100px] bg-white rounded-lg shadow-card py-2 hidden z-50 transition-all duration-200">
<ul class="text-xs">
<li>
<button class="w-full text-left px-3 py-1 hover:text-primary-600 transition duration-300">Edit</button>
</li>
<li>
<button class="w-full text-left px-3 py-1 hover:text-primary-600 transition duration-300">Delete</button>
</li>
</ul>
</div>
</div>
<div class="flex items-center gap-3">
<img style="border-radius :30px ;overflow :hidden;" src="{{ object.get_avatar }}" alt="" width="35" />
<h5 class="mb-1 font-medium text-sm">{{ object.get_full_name }}</h5>
</div>
<div class="flex items-center justify-between gap-2">
<div>
<p class="text-[13px] text-[#565E6C] mb-1">{{ object.get_subtitle }}</p>
</div>
<div>
<button hx-get="{{ object.get_profile_url }}" hx-target="#{{ view_id|safe }}" hx-swap="outerHTML" class="active w-8 h-8 rounded-md bg-[#f7f7f7] bg-primary-600 flex items-center justify-center text-white text-xs transition duration-300 hover:bg-primary-700"><i class="fa-solid fa-angle-right"></i></button>
</div>
</div>
</div>
</div>
{% endif %}
{% endfor %}

View File

@@ -89,7 +89,7 @@
{% if request.user.employee_get in emp_objective.objective_id.managers.all or emp_objective.employee_id == request.user.employee_get or perms.pms.view_employeekeyresult %}
<a class="cursor-pointer px-2 py-1 text-sm bg-primary-300 text-primary-600 rounded-sm w-7 h-7 flex items-center justify-center hover:bg-primary-600 hover:text-white transition"
<a class="oh-activity-sidebar__open cursor-pointer px-2 py-1 text-sm bg-primary-300 text-primary-600 rounded-sm w-7 h-7 flex items-center justify-center hover:bg-primary-600 hover:text-white transition"
hx-get='{% url "objective-detailed-view-activity" emp_objective.id %}'
hx-target="#sidebarModalBody4"
data-target="#sidebarModal4"
@@ -176,29 +176,13 @@
</div>
<!-- Modals -->
<!-- Activity Sidebar -->
<div class="sidebarModal overflow-hidden overflow-y-auto" id="sidebarModal4">
<!-- <button class="closebtn closeSidemenu absolute top-3 right-3" data-sidebar="sidebarModal3"><i
class="fa-solid fa-xmark"></i></button> -->
<div class="flex justify-between items-center mb-4">
<h3 class="font-semibold flex items-center gap-2">
<button
class="closebtn closeSidemenu cursor-pointer text-xs group text-primary-600 w-6 h-6 bg-primary-100 hover:bg-primary-600 hover:text-white rounded-full transition duration-300 flex items-center justify-center"
data-sidebar="sidebarModal4"
>
<i class="fa-solid fa-angle-right"></i></button
>{% trans "All Notifications" %}
</h3>
<button
class="text-primary-600 text-xs flex justify-center font-semibold hover:text-[#ac3b2a] transition duration-300"
>
{% trans "Clear All" %}
</button>
<div class="oh-activity-sidebar" id="sidebarModal4" style="z-index:1000">
<div class="oh-activity-sidebar__body">
<ol class="oh-activity-sidebar__qa-list" role="list" id="sidebarModalBody4">
</ol>
</div>
<div class="" id="sidebarModalBody4"></div>
<!--sidecont-->
</div>

View File

@@ -42,7 +42,7 @@
</head>
<body class="bg-secondary-50 font-[Inter,_sans-serif]">
{% if messages %}
<div class="oh-alert-container">
{% for message in messages %}
@@ -52,7 +52,7 @@
{% endfor %}
</div>
{% endif %}
<div class="p-5 flex justify-center items-center h-screen flex-col">
<div class="w-full max-w-5xl pl-5 flex justify-start mb-2">
{% if request.user.is_authenticated %}
@@ -187,11 +187,11 @@
document.body.classList.toggle("overflow-hidden");
}
});
});
});
});
</script>
</body>
</html>
</html>

View File

@@ -259,7 +259,7 @@ urlpatterns = [
cbvs.BulkFeedbackFormView.as_view(),
name="bulk-feedback-create",
),
path("feedback-update/<int:id>", views.feedback_update, name="feedback-update"),
# path("feedback-update/<int:id>", views.feedback_update, name="feedback-update"),
path("feedback-delete/<int:id>", views.feedback_delete, name="feedback-delete"),
path("feedback-archive/<int:id>", views.feedback_archive, name="feedback-archive"),
path("get-collegues", views.get_collegues, name="get-collegues"),