584 lines
23 KiB
HTML
584 lines
23 KiB
HTML
{% load i18n %} {% load static %}
|
|
<style>
|
|
.note-modal-backdrop {
|
|
z-index: 998 !important;
|
|
}
|
|
|
|
.tooltip {
|
|
position: relative;
|
|
display: inline-block; /* Ensures that the tooltip container doesn't stretch to the full width */
|
|
}
|
|
|
|
.tooltip .tooltiptext {
|
|
visibility: hidden;
|
|
width: auto; /* Allow the tooltip width to adjust based on content */
|
|
max-width: auto; /* Limit the maximum width of the tooltip to prevent it from overflowing */
|
|
background-color: green;
|
|
color: white;
|
|
text-align: center;
|
|
border-radius: 6px;
|
|
padding: 5px;
|
|
position: absolute;
|
|
z-index: 1;
|
|
bottom: 5%;
|
|
left: 50%;
|
|
transform: translateX(5%);
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
white-space: nowrap; /* Prevents the text from wrapping */
|
|
overflow: hidden; /* Hides any overflowed content */
|
|
font-size: 15px;
|
|
}
|
|
|
|
.tooltip:hover .tooltiptext {
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
</style>
|
|
|
|
{% if recruitment_form.errors %}
|
|
<!-- form errors -->
|
|
<div class="oh-wrapper">
|
|
<div class="oh-alert-container">
|
|
{% for field in recruitment_form %}
|
|
{% if field.errors %}
|
|
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
|
{{ field.label }}: {{ field.errors|join:", " }}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% for error in recruitment_form.non_field_errors %}
|
|
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
|
{{ error }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if stage_form.errors %}
|
|
<!-- form errors -->
|
|
<div class="oh-wrapper">
|
|
<div class="oh-alert-container">
|
|
{% for field in stage_form %}
|
|
{% if field.errors %}
|
|
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
|
{{ field.label }}: {{ field.errors|join:", " }}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% for error in stage_form.non_field_errors %}
|
|
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
|
{{ error }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if candidate_form.errors %}
|
|
<!-- form errors -->
|
|
<div class="oh-wrapper">
|
|
<div class="oh-alert-container">
|
|
{% for field in candidate_form %}
|
|
{% if field.errors %}
|
|
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
|
{{ field.label }}: {{ field.errors|join:", " }}
|
|
</div>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% for error in candidate_form.non_field_errors %}
|
|
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
|
{{ error }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
<section class="oh-wrapper oh-main__topbar" x-data="{searchShow: false}">
|
|
<div class="oh-main__titlebar oh-main__titlebar--left">
|
|
<div
|
|
class="oh-main__titlebar-title fw-bold mb-0 text-dark"
|
|
style="cursor: pointer"
|
|
>
|
|
{% trans "Recruitments" %}
|
|
</div>
|
|
</div>
|
|
<form
|
|
hx-get="{% url 'pipeline-search' %}"
|
|
onsubmit="event.preventDefault()"
|
|
hx-target="#pipelineSearchContainer"
|
|
id="filterForm"
|
|
>
|
|
<div class="oh-main__titlebar oh-main__titlebar--right">
|
|
<div
|
|
class="oh-input-group oh-input__search-group"
|
|
:class="searchShow ? 'oh-input__search-group--show' : ''"
|
|
>
|
|
<ion-icon
|
|
name="search-outline"
|
|
class="oh-input-group__icon oh-input-group__icon--left"
|
|
></ion-icon>
|
|
<input
|
|
type="text"
|
|
id="pipelineSearchInput"
|
|
placeholder="{% trans 'Search' %}"
|
|
name="candidate_name"
|
|
value="{{request.GET.candidate_name}}"
|
|
onkeyup="$(this).closest('form').find('.filterButton').click()"
|
|
class="oh-input oh-input__icon"
|
|
aria-label="Search Input"
|
|
/>
|
|
</div>
|
|
|
|
<ul
|
|
id="viewTypes"
|
|
class="oh-view-types ml-2"
|
|
style="margin-bottom: 0"
|
|
>
|
|
<input type="hidden" name="view" value="list" />
|
|
<li class="oh-view-type candidate-view-type">
|
|
<div
|
|
onclick="$(this).closest('ul').find('[name=view]').val('list');$(this).closest('form').find('.filterButton').click();$('.oh-btn--view-active').removeClass('oh-btn--view-active');$(this).addClass('oh-btn--view-active');"
|
|
class="oh-btn oh-btn--view oh-btn--view-active"
|
|
title="{% trans 'List' %}"
|
|
>
|
|
<ion-icon name="list-outline"></ion-icon>
|
|
</div>
|
|
</li>
|
|
<li class="oh-view-type candidate-view-type">
|
|
<div
|
|
onclick="$(this).closest('ul').find('[name=view]').val('card');$(this).closest('form').find('.filterButton').click();$('.oh-btn--view-active').removeClass('oh-btn--view-active');$(this).addClass('oh-btn--view-active');"
|
|
class="oh-btn oh-btn--view"
|
|
title="{% trans 'Card' %}"
|
|
>
|
|
<ion-icon name="grid-outline"></ion-icon>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="oh-main__titlebar-button-container">
|
|
<div
|
|
id="pipelineFilterDrop"
|
|
class="oh-dropdown"
|
|
x-data="{open: false}"
|
|
>
|
|
<button
|
|
type="button"
|
|
class="oh-btn ml-2"
|
|
@click="open = !open"
|
|
>
|
|
<ion-icon name="filter" class="mr-1"></ion-icon>{% trans "Filter" %}
|
|
<div id="filterCount"></div>
|
|
</button>
|
|
<div
|
|
class="oh-dropdown__menu oh-dropdown__menu--right oh-dropdown__filter p-4"
|
|
x-show="open"
|
|
@click.outside="open = false"
|
|
style="display: none"
|
|
@click.
|
|
>
|
|
{% include "pipeline/filters.html" %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="oh-main__titlebar-button-container">
|
|
{% if perms.recruitment.add_recruitment %}
|
|
<button
|
|
id="createNewRecruitment"
|
|
class="oh-btn oh-btn--secondary"
|
|
hx-get="{% url "recruitment-create" %}"
|
|
hx-target="#objectCreateModalTarget"
|
|
data-toggle="oh-modal-toggle"
|
|
data-target="#objectCreateModal"
|
|
>
|
|
<ion-icon class="me-1" name="add-outline"></ion-icon>
|
|
{% trans 'Recruitment' %}
|
|
</button>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
<div
|
|
class="oh-modal"
|
|
id="dynamicCreateModal"
|
|
role="dialog"
|
|
aria-hidden="true"
|
|
style="z-index: 1022"
|
|
>
|
|
<div
|
|
class="oh-modal__dialog"
|
|
style="max-width: 550px"
|
|
id="dynamicCreateModalBody"
|
|
></div>
|
|
</div>
|
|
<span
|
|
name=""
|
|
id="dynamicSkills"
|
|
data-toggle="oh-modal-toggle"
|
|
data-target="#dynamicCreateModal"
|
|
hx-get="{% url 'create-skills' %}?dynamic=True"
|
|
hx-target="#dynamicCreateModalBody"
|
|
>
|
|
</span>
|
|
|
|
<div
|
|
class="oh-modal"
|
|
id="addRecruitmentModal"
|
|
role="dialog"
|
|
aria-labelledby="addRecruitmentModal"
|
|
aria-hidden="true"
|
|
>
|
|
<div class="oh-modal__dialog">
|
|
<div class="oh-modal__dialog-header">
|
|
<h5 class="oh-modal__dialog-title" id="addRecruitmentModalLabel">
|
|
{% trans "Add Recruitment" %}
|
|
</h5>
|
|
<button class="oh-modal__close" aria-label="Close">
|
|
<ion-icon name="close-outline"></ion-icon>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="oh-modal__dialog-body" id="addRecruitmentModalBody"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="oh-modal"
|
|
id="updateStageModal"
|
|
role="dialog"
|
|
aria-labelledby="updateStageModal"
|
|
aria-hidden="true"
|
|
>
|
|
<div class="oh-modal__dialog" id="updateStageModalBody"></div>
|
|
</div>
|
|
|
|
<div
|
|
class="oh-modal"
|
|
id="recruitmentUpdateModal"
|
|
role="dialog"
|
|
aria-labelledby="recruitmentUpdateModal"
|
|
aria-hidden="true"
|
|
>
|
|
<div class="oh-modal__dialog">
|
|
<div class="oh-modal__dialog-header">
|
|
<h5 class="oh-modal__dialog-title" id="recruitmentUpdateModalLabel">
|
|
{% trans "Edit Recruitment" %}
|
|
</h5>
|
|
<button class="oh-modal__close" aria-label="Close">
|
|
<ion-icon name="close-outline"></ion-icon>
|
|
</button>
|
|
</div>
|
|
<div class="oh-modal__dialog-body" id="recruitmentUpdate"></div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="oh-modal"
|
|
id="bulkResumeUpload"
|
|
role="dialog"
|
|
aria-labelledby="bulkResumeUpload"
|
|
aria-hidden="true"
|
|
>
|
|
<div class="oh-modal__dialog">
|
|
<div class="oh-modal__dialog-header">
|
|
<h2 class="oh-modal__dialog-title" id="bulkResumeUploadLabel">
|
|
{% trans "Upload Resumes" %}
|
|
</h2>
|
|
<button class="oh-modal__close" aria-label="Close">
|
|
<ion-icon name="close-outline"></ion-icon>
|
|
</button>
|
|
<div
|
|
class="oh-modal__dialog-body mb-4 p-0 pt-2"
|
|
id="bulkResumeUploadBody"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
$(document).ready(function(){
|
|
{% include 'select2.js' %}
|
|
$('#candidate-search').keyup(function (e) {
|
|
$('.candidate-view-type').attr('hx-vals',`{"search":"${$(this).val()}"}`)
|
|
});
|
|
$('.candidate-view-type').click(function (e) {
|
|
let view = $(this).attr('data-view');
|
|
$('#candidate-search').attr('hx-vals',`{"view":"${view}"}`);
|
|
});
|
|
$("#job_pos_id").select2();
|
|
$("#portal_stage").select2();
|
|
$("#is_closed").on("change", function() {
|
|
if ($(this).is(":checked")) {
|
|
window.location.href = "{% url 'pipeline' %}?view={{view}}&closed=closed";
|
|
} else {
|
|
window.location.href = "{% url 'pipeline' %}?view={{view}}";
|
|
}
|
|
});
|
|
})
|
|
</script>
|
|
{% include "filter_tags.html" %}
|
|
<div class="d-flex flex-row-reverse oh-wrapper" id="quickFilters">
|
|
<span
|
|
onclick="$('[name=closed]').val('true'); $('[name=closed]').first().change(); $('.filterButton').click()"
|
|
style="cursor: pointer; margin-left: 10px; margin-right: 10px"
|
|
>
|
|
<span
|
|
class="oh-dot oh-dot--small me-1"
|
|
style="background-color: orangered"
|
|
></span>
|
|
{% trans "Closed" %}
|
|
</span>
|
|
<span
|
|
onclick="$('[name=closed]').val('false');$('[name=closed]').first().change(); $('.filterButton').click()"
|
|
style="cursor: pointer; margin-left: 10px; margin-right: 10px"
|
|
>
|
|
<span
|
|
class="oh-dot oh-dot--small me-1"
|
|
style="background-color: yellowgreen"
|
|
></span>
|
|
{% trans "Open" %}
|
|
</span>
|
|
</div>
|
|
<script src="{% static 'build/js/pipelineDriver.js' %}"></script>
|
|
<script>
|
|
initialAvtiveTab = $(".oh-tabs__content--active").length;
|
|
if (!initialAvtiveTab) {
|
|
$(".oh-tabs__tab:first").click();
|
|
}
|
|
</script>
|
|
{% if not request.user.driverviewed_set.first or "pipeline" not in request.user.driverviewed_set.first.user_viewed %}
|
|
<script>
|
|
runDriver();
|
|
</script>
|
|
{% endif %}
|
|
|
|
<script>
|
|
function switchTab(e) {
|
|
let parentContainerEl = e.target.closest(".oh-tabs");
|
|
let tabElement = e.target.closest(".oh-tabs__tab");
|
|
|
|
let targetSelector = e.target.dataset.target;
|
|
let targetEl = parentContainerEl
|
|
? parentContainerEl.querySelector(targetSelector)
|
|
: null;
|
|
|
|
// Highlight active tabs
|
|
if (
|
|
tabElement &&
|
|
!tabElement.classList.contains("oh-tabs__tab--active")
|
|
) {
|
|
parentContainerEl
|
|
.querySelectorAll(".oh-tabs__tab--active")
|
|
.forEach(function (item) {
|
|
item.classList.remove("oh-tabs__tab--active");
|
|
});
|
|
|
|
if (!tabElement.classList.contains("oh-tabs__new-tab")) {
|
|
tabElement.classList.add("oh-tabs__tab--active");
|
|
}
|
|
}
|
|
|
|
// Switch tabs
|
|
if (
|
|
targetEl &&
|
|
!targetEl.classList.contains("oh-tabs__content--active")
|
|
) {
|
|
parentContainerEl
|
|
.querySelectorAll(".oh-tabs__content--active")
|
|
.forEach(function (item) {
|
|
item.classList.remove("oh-tabs__content--active");
|
|
});
|
|
targetEl.classList.add("oh-tabs__content--active");
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<script>
|
|
$(document).ready(function () {
|
|
Toast = Swal.mixin({
|
|
toast: true,
|
|
icon: "success",
|
|
title: "General Title",
|
|
animation: true,
|
|
position: "top-right",
|
|
showConfirmButton: false,
|
|
timer: 3000,
|
|
timerProgressBar: true,
|
|
didOpen: (toast) => {
|
|
toast.addEventListener("mouseenter", Swal.stopTimer);
|
|
toast.addEventListener("mouseleave", Swal.resumeTimer);
|
|
},
|
|
});
|
|
|
|
function isNextStage(currentStageId, targetStageId, parsedStageOrder) {
|
|
var currentStageIndex = parsedStageOrder.findIndex(stage => stage.id == currentStageId);
|
|
var targetStageIndex = parsedStageOrder.findIndex(stage => stage.id == targetStageId);
|
|
return targetStageIndex === currentStageIndex + 1 || currentStageIndex === targetStageIndex ;
|
|
}
|
|
|
|
htmx.onLoad(function (content) {
|
|
var sortables = content.querySelectorAll(".hx-sortable");
|
|
|
|
for (var i = 0; i < sortables.length; i++) {
|
|
var sortable = sortables[i];
|
|
window.candidateCurrentStage = undefined;
|
|
|
|
$(sortable).sortable({
|
|
connectWith: ".hx-sortable",
|
|
animation: 150,
|
|
ghostClass: "blue-background-class",
|
|
items: "> :not(.htmx-indicator)",
|
|
start: function (event, ui) {
|
|
var row = $(ui.item);
|
|
window.candidateCurrentStage = row.closest(".candidate-table").attr("data-stage-id");
|
|
},
|
|
stop: function (event, ui) {
|
|
var row = $(ui.item);
|
|
var candidateId = row.attr("data-candidate-id");
|
|
var currentStageId = parseInt($(this).closest(".candidate-table").attr("data-stage-id"));
|
|
var targetStageId = parseInt(row.find('[name="stage_id"]').val());
|
|
|
|
var stageOrderJson = row.attr("data-stage_order");
|
|
|
|
if (stageOrderJson) {
|
|
var parsedStageOrder = JSON.parse(stageOrderJson);
|
|
var preStageId = row.attr("data-pre_stage_id");
|
|
var preStage = parsedStageOrder.find(stage => stage.id == preStageId);
|
|
var stageOrder = parsedStageOrder.map(stage => stage.id);
|
|
|
|
if (!isNextStage(currentStageId, targetStageId, parsedStageOrder)) {
|
|
if (sessionStorage.getItem('showRecuitmentKanbanConfirmation') !== 'false') {
|
|
Swal.fire({
|
|
title: "Confirm Stage Change",
|
|
text: "The candidate is not being moved to the next stage. Do you want to proceed?",
|
|
html: `
|
|
<p>The candidate is not being moved to the next stage. Do you want to proceed?</p>
|
|
<label><input type="checkbox" id="doNotShowAgain"> Don't show this again in this session</label>
|
|
`,
|
|
icon: "warning",
|
|
showCancelButton: true,
|
|
cancelButtonColor: "#d33",
|
|
confirmButtonColor: "#008000",
|
|
confirmButtonText: "Confirm",
|
|
preConfirm: () => {
|
|
const doNotShowAgain = Swal.getPopup().querySelector('#doNotShowAgain').checked;
|
|
if (doNotShowAgain) {
|
|
sessionStorage.setItem('showRecuitmentKanbanConfirmation', 'false');
|
|
}
|
|
}
|
|
}).then((result) => {
|
|
if (result.isConfirmed) {
|
|
handleValidDrop(targetStageId, candidateId, row, sortable);
|
|
} else {
|
|
$(sortable).sortable('cancel');
|
|
}
|
|
});
|
|
} else {
|
|
handleValidDrop(targetStageId, candidateId, row, sortable);
|
|
}
|
|
|
|
} else {
|
|
handleValidDrop(targetStageId, candidateId, row, sortable);
|
|
}
|
|
} else {
|
|
handleValidDrop(targetStageId, candidateId, row, sortable);
|
|
}
|
|
},
|
|
update: function (event, ui) {
|
|
var array = $(this).find("input[type=text][name=order]:hidden");
|
|
var stageSelect = $(ui.item).find("[name=stage_id]");
|
|
var stageId = $(this).attr("data-stage-id");
|
|
var parent = $(this).parent();
|
|
|
|
if (stageId != stageSelect.val()) {
|
|
stageSelect.val(stageId);
|
|
var stageTitle = $(`[name=stage_id] option[value=${stageId}]:first`).html();
|
|
stageSelect.next().find(".select2-selection__rendered").html(stageTitle);
|
|
}
|
|
|
|
var values = [];
|
|
for (let index = 0; index < array.length; index++) {
|
|
values.push($(array[index]).val());
|
|
}
|
|
|
|
$.ajax({
|
|
type: "get",
|
|
url: "{% url 'update-candidate-sequence' %}",
|
|
traditional: true,
|
|
data: {
|
|
order: values,
|
|
stage_id: stageId,
|
|
},
|
|
success: function (response) {
|
|
$(`.reload-badge`).click();
|
|
$(`.stage-badges`).removeAttr("title");
|
|
},
|
|
});
|
|
},
|
|
});
|
|
}
|
|
});
|
|
|
|
function handleValidDrop(stageId, candidateId, row, sortable) {
|
|
if (stageId != window.candidateCurrentStage) {
|
|
var array = $(sortable).find("input[type=text][name=order]:hidden");
|
|
var values = [];
|
|
setTimeout(function () {
|
|
for (let index = 0; index < array.length; index++) {
|
|
values.push($(array[index]).val());
|
|
}
|
|
$.ajax({
|
|
type: "get",
|
|
url: "{% url 'update-candidate-stage-and-sequence' %}",
|
|
data: {
|
|
stage_id: stageId,
|
|
candidate_id: candidateId,
|
|
order: values,
|
|
},
|
|
success: function (response) {
|
|
row.find('[name="stage_id"]').val(stageId);
|
|
Toast.fire({
|
|
icon: "success",
|
|
title: '{% trans "Sequence updated" %}',
|
|
position: "top-end",
|
|
});
|
|
if (response.message) {
|
|
Swal.fire({
|
|
title: response.message,
|
|
text: `Total vacancy is ${response.vacancy}.`,
|
|
icon: "info",
|
|
confirmButtonText: "Ok",
|
|
});
|
|
}
|
|
},
|
|
error: function (xhr, status, error) {
|
|
Toast.fire({
|
|
icon: "error",
|
|
title: '{% trans "Something went wrong" %}',
|
|
position: "top-end",
|
|
});
|
|
},
|
|
});
|
|
}, 100);
|
|
}
|
|
}
|
|
});
|
|
|
|
$(document).on("htmx:beforeRequest", function (event) {
|
|
var target = event.target;
|
|
|
|
isDrag = $(event.target).attr("data-drag-htmx");
|
|
if (isDrag == "true") {
|
|
$.ajax({
|
|
type: "get",
|
|
url: "{% url 'update-candidate-sequence' %}",
|
|
traditional: true,
|
|
data: {
|
|
order: values,
|
|
},
|
|
success: function (response) {},
|
|
});
|
|
}
|
|
});
|
|
</script>
|