[FIX] DASHBOARD: Missing chart styles
This commit is contained in:
@@ -1,71 +1,324 @@
|
||||
$(document).ready(function () {
|
||||
// function employeeChart(dataSet, labels) {
|
||||
// const data = {
|
||||
// labels: labels,
|
||||
// datasets: dataSet,
|
||||
// };
|
||||
// // Create chart using the Chart.js library
|
||||
// window["myChart"] = {};
|
||||
// if (document.getElementById("totalEmployees")) {
|
||||
// const ctx = document.getElementById("totalEmployees").getContext("2d");
|
||||
// employeeChart = new Chart(ctx, {
|
||||
// type: "doughnut",
|
||||
// data: data,
|
||||
// options: {
|
||||
// responsive: true,
|
||||
// maintainAspectRatio: false,
|
||||
// onClick: (e, activeEls) => {
|
||||
// let datasetIndex = activeEls[0].datasetIndex;
|
||||
// let dataIndex = activeEls[0].index;
|
||||
// let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
// let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
// let label = e.chart.data.labels[dataIndex];
|
||||
// var active = "False";
|
||||
// if (label.toLowerCase() == "active") {
|
||||
// active = "True";
|
||||
// }
|
||||
// localStorage.removeItem("savedFilters");
|
||||
// window.location.href = "/employee/employee-view?is_active=" + active;
|
||||
// },
|
||||
// },
|
||||
// plugins: [
|
||||
// {
|
||||
// afterRender: (chart) => emptyChart(chart),
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// function genderChart(dataSet, labels) {
|
||||
// const data = {
|
||||
// labels: labels,
|
||||
// datasets: dataSet,
|
||||
// };
|
||||
// console.log(data)
|
||||
// // Create chart using the Chart.js library
|
||||
// window["genderChart"] = {};
|
||||
// if (document.getElementById("genderChart")) {
|
||||
// const ctx = document.getElementById("genderChart").getContext("2d");
|
||||
// genderChart = new Chart(ctx, {
|
||||
// type: "doughnut",
|
||||
// data: data,
|
||||
// options: {
|
||||
// responsive: true,
|
||||
// maintainAspectRatio: false,
|
||||
// onClick: (e, activeEls) => {
|
||||
// let datasetIndex = activeEls[0].datasetIndex;
|
||||
// let dataIndex = activeEls[0].index;
|
||||
// let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
// let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
// let label = e.chart.data.labels[dataIndex];
|
||||
// localStorage.removeItem("savedFilters");
|
||||
// window.location.href =
|
||||
// "/employee/employee-view?gender=" + label.toLowerCase();
|
||||
// },
|
||||
// },
|
||||
// plugins: [
|
||||
// {
|
||||
// afterRender: (chart) => emptyChart(chart),
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// function departmentChart(dataSet, labels) {
|
||||
// console.log(dataSet);
|
||||
// console.log(labels);
|
||||
// const data = {
|
||||
// labels: labels,
|
||||
// datasets: dataSet,
|
||||
// };
|
||||
// // Create chart using the Chart.js library
|
||||
// window["departmentChart"] = {};
|
||||
// if (document.getElementById("departmentChart")) {
|
||||
// const ctx = document.getElementById("departmentChart").getContext("2d");
|
||||
// departmentChart = new Chart(ctx, {
|
||||
// type: "doughnut",
|
||||
// data: data,
|
||||
// options: {
|
||||
// responsive: true,
|
||||
// maintainAspectRatio: false,
|
||||
// onClick: (e, activeEls) => {
|
||||
// let datasetIndex = activeEls[0].datasetIndex;
|
||||
// let dataIndex = activeEls[0].index;
|
||||
// let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
// let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
// let label = e.chart.data.labels[dataIndex];
|
||||
// localStorage.removeItem("savedFilters");
|
||||
// window.location.href =
|
||||
// "/employee/employee-view?department=" + label;
|
||||
|
||||
// },
|
||||
// },
|
||||
// plugins: [
|
||||
// {
|
||||
// afterRender: (chart) => emptyChart(chart),
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
function employeeChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window["myChart"] = {};
|
||||
if (document.getElementById("totalEmployees")) {
|
||||
const ctx = document.getElementById("totalEmployees").getContext("2d");
|
||||
employeeChart = new Chart(ctx, {
|
||||
$(document).ready(function () {
|
||||
const ctx = document.getElementById("totalEmployees")?.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const values = dataSet[0].data;
|
||||
const colors = [
|
||||
"#34d399", // Active - green
|
||||
"#f87171", // Inactive - red
|
||||
];
|
||||
const visibility = Array(labels.length).fill(true);
|
||||
|
||||
// Create chart instance
|
||||
const employeeChartInstance = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: data,
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
...dataSet[0],
|
||||
backgroundColor: colors.slice(0, labels.length),
|
||||
borderWidth: 0,
|
||||
borderRadius: 10,
|
||||
hoverOffset: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
cutout: "70%",
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
onClick: (e, activeEls) => {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
var active = "False";
|
||||
if (label.toLowerCase() == "active") {
|
||||
active = "True";
|
||||
}
|
||||
onClick: function (e, activeEls) {
|
||||
if (!activeEls.length) return;
|
||||
|
||||
const dataIndex = activeEls[0].index;
|
||||
const label = labels[dataIndex];
|
||||
let active = label.toLowerCase() === "active" ? "True" : "False";
|
||||
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href = "/employee/employee-view?is_active=" + active;
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: "#111827",
|
||||
bodyColor: "#f3f4f6",
|
||||
borderColor: "#e5e7eb",
|
||||
borderWidth: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
afterRender: (chart) => emptyChart(chart),
|
||||
id: "centerText",
|
||||
afterDraw(chart) {
|
||||
const { width, height, ctx } = chart;
|
||||
ctx.save();
|
||||
|
||||
const total = chart.data.datasets[0].data.reduce(
|
||||
(sum, val) => sum + val,
|
||||
0
|
||||
);
|
||||
|
||||
ctx.font = "bold 22px sans-serif";
|
||||
ctx.fillStyle = "#374151";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(total, width / 2, height / 2 - 5);
|
||||
|
||||
ctx.font = "15px sans-serif";
|
||||
ctx.fillStyle = "#9ca3af";
|
||||
ctx.fillText("Total", width / 2, height / 2 + 20);
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
},
|
||||
{
|
||||
afterRender: (chart) => {
|
||||
if (typeof emptyChart === "function") {
|
||||
emptyChart(chart);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// 🧩 Custom Legend Generation
|
||||
const $legendContainer = $("#employeeChartLegend"); // Make sure to have this element in DOM
|
||||
$legendContainer.empty();
|
||||
|
||||
labels.forEach((label, index) => {
|
||||
const color = colors[index % colors.length];
|
||||
|
||||
const $item = $(`
|
||||
<div style="display: flex; align-items: center; margin-bottom: 6px; cursor: pointer;">
|
||||
<span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${color}; margin-right: 8px;"></span>
|
||||
<span class="legend-label">${label}</span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$legendContainer.append($item);
|
||||
|
||||
$item.on("click", function () {
|
||||
visibility[index] = !visibility[index];
|
||||
|
||||
employeeChartInstance.data.datasets[0].data = values.map((val, i) =>
|
||||
visibility[i] ? val : 0
|
||||
);
|
||||
|
||||
const $dot = $(this).find("span").first();
|
||||
const $label = $(this).find(".legend-label");
|
||||
|
||||
if (visibility[index]) {
|
||||
$dot.css("opacity", "1");
|
||||
$label.css("text-decoration", "none");
|
||||
} else {
|
||||
$dot.css("opacity", "0.4");
|
||||
$label.css("text-decoration", "line-through");
|
||||
}
|
||||
|
||||
employeeChartInstance.update();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function genderChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window["genderChart"] = {};
|
||||
const centerImage = new Image();
|
||||
centerImage.src = "/static/horilla_theme/assets/img/icons/gender.svg";
|
||||
|
||||
if (document.getElementById("genderChart")) {
|
||||
const ctx = document.getElementById("genderChart").getContext("2d");
|
||||
genderChart = new Chart(ctx, {
|
||||
|
||||
// Override dataset background colors with new design colors
|
||||
const updatedDataSet = dataSet.map((ds) => ({
|
||||
...ds,
|
||||
backgroundColor: ["#cfe9ff", "#ffc9de", "#e6ccff"],
|
||||
borderWidth: 0,
|
||||
}));
|
||||
|
||||
window["genderChart"] = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: data,
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: updatedDataSet,
|
||||
},
|
||||
options: {
|
||||
cutout: "70%",
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
onClick: (e, activeEls) => {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href =
|
||||
"/employee/employee-view?gender=" + label.toLowerCase();
|
||||
if (activeEls.length > 0) {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href = "/employee/employee-view?gender=" + label.toLowerCase();
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "bottom",
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
pointStyle: "circle",
|
||||
padding: 20,
|
||||
font: {
|
||||
size: 12,
|
||||
},
|
||||
color: "#000",
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
padding: 10,
|
||||
cornerRadius: 4,
|
||||
backgroundColor: "#333",
|
||||
titleColor: "#fff",
|
||||
bodyColor: "#fff",
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
return context.parsed; // This shows only "13", not "Employees: 13"
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
afterRender: (chart) => emptyChart(chart),
|
||||
id: "centerIcon",
|
||||
afterDatasetsDraw(chart) {
|
||||
if (!centerImage.complete) return;
|
||||
const ctx = chart.ctx;
|
||||
const size = 70;
|
||||
ctx.drawImage(
|
||||
centerImage,
|
||||
chart.width / 2 - size / 2,
|
||||
chart.height / 2 - size / 2 - 20,
|
||||
size,
|
||||
size
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
afterRender: (chart) => {
|
||||
if (typeof emptyChart === "function") {
|
||||
emptyChart(chart);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -73,39 +326,124 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
function departmentChart(dataSet, labels) {
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
};
|
||||
// Create chart using the Chart.js library
|
||||
window["departmentChart"] = {};
|
||||
if (document.getElementById("departmentChart")) {
|
||||
const ctx = document.getElementById("departmentChart").getContext("2d");
|
||||
departmentChart = new Chart(ctx, {
|
||||
$(document).ready(function () {
|
||||
const ctx = $("#departmentChart")[0]?.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const values = dataSet[0].data;
|
||||
const colors = [
|
||||
"#facc15",
|
||||
"#f87171",
|
||||
"#ddd6fe",
|
||||
"#a5b4fc",
|
||||
"#93c5fd",
|
||||
"#d1d5db",
|
||||
];
|
||||
const visibility = Array(labels.length).fill(true);
|
||||
|
||||
// Create the chart instance
|
||||
const departmentChartInstance = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: data,
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
...dataSet[0],
|
||||
backgroundColor: colors.slice(0, labels.length),
|
||||
borderWidth: 0,
|
||||
borderRadius: 10,
|
||||
hoverOffset: 8,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
cutout: "70%",
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
onClick: (e, activeEls) => {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href =
|
||||
"/employee/employee-view?department=" + label;
|
||||
onClick: function (e, activeEls) {
|
||||
if (!activeEls.length) return;
|
||||
const dataIndex = activeEls[0].index;
|
||||
const label = labels[dataIndex];
|
||||
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href = "/employee/employee-view?department=" + label;
|
||||
},
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: "#111827",
|
||||
bodyColor: "#f3f4f6",
|
||||
borderColor: "#e5e7eb",
|
||||
borderWidth: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
afterRender: (chart) => emptyChart(chart),
|
||||
id: "centerText",
|
||||
afterDraw(chart) {
|
||||
const { width, height, ctx } = chart;
|
||||
ctx.save();
|
||||
|
||||
const total = chart.data.datasets[0].data.reduce(
|
||||
(sum, val) => sum + val,
|
||||
0
|
||||
);
|
||||
|
||||
ctx.font = "bold 22px sans-serif";
|
||||
ctx.fillStyle = "#374151";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(total, width / 2, height / 2 - 5);
|
||||
|
||||
ctx.font = "15px sans-serif";
|
||||
ctx.fillStyle = "#9ca3af";
|
||||
ctx.fillText("Total", width / 2, height / 2 + 20);
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// 🧩 Generate Custom Legend
|
||||
const $legendContainer = $("#chartLegend");
|
||||
$legendContainer.empty();
|
||||
|
||||
labels.forEach((label, index) => {
|
||||
const color = colors[index % colors.length];
|
||||
|
||||
const $item = $(`
|
||||
<div style="display: flex; align-items: center; margin-bottom: 6px; cursor: pointer;">
|
||||
<span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${color}; margin-right: 8px;"></span>
|
||||
<span class="legend-label">${label}</span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$legendContainer.append($item);
|
||||
|
||||
$item.on("click", function () {
|
||||
visibility[index] = !visibility[index];
|
||||
|
||||
departmentChartInstance.data.datasets[0].data = values.map((val, i) =>
|
||||
visibility[i] ? val : 0
|
||||
);
|
||||
|
||||
const $dot = $(this).find("span").first();
|
||||
const $label = $(this).find(".legend-label");
|
||||
|
||||
if (visibility[index]) {
|
||||
$dot.css("opacity", "1");
|
||||
$label.css("text-decoration", "none");
|
||||
} else {
|
||||
$dot.css("opacity", "0.4");
|
||||
$label.css("text-decoration", "line-through");
|
||||
}
|
||||
|
||||
departmentChartInstance.update();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
|
||||
@@ -91,4 +91,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,67 +1,168 @@
|
||||
$(document).ready(function () {
|
||||
//Todays leave count department wise chart
|
||||
if (document.getElementById("overAllLeave")){
|
||||
var myChart1 = document.getElementById("overAllLeave").getContext("2d");
|
||||
var overAllLeave = new Chart(myChart1, {
|
||||
let overAllLeaveChart = null;
|
||||
|
||||
function renderOverAllLeaveChart(data, labels) {
|
||||
const ctx = document.getElementById("overAllLeave")?.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const dataset = [{
|
||||
label: "Leave count",
|
||||
data: data,
|
||||
backgroundColor: ["#cfe9ff", "#ffc9de", "#e6ccff"], // Customize as needed
|
||||
borderWidth: 0,
|
||||
}];
|
||||
|
||||
if (overAllLeaveChart) {
|
||||
overAllLeaveChart.data.labels = labels;
|
||||
overAllLeaveChart.data.datasets = dataset;
|
||||
overAllLeaveChart.update();
|
||||
return;
|
||||
}
|
||||
|
||||
overAllLeaveChart = new Chart(ctx, {
|
||||
type: "doughnut",
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [
|
||||
{
|
||||
label: "Leave count",
|
||||
data: [],
|
||||
backgroundColor: null,
|
||||
},
|
||||
],
|
||||
labels: labels,
|
||||
datasets: dataset,
|
||||
},
|
||||
options: {
|
||||
cutout: "70%",
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
onClick: (e, activeEls) => {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
params =`?department_name=${label}&overall_leave=${$("#overAllLeaveSelect").val()}`;
|
||||
window.location.href = "/leave/request-view" + params;
|
||||
if (activeEls.length > 0) {
|
||||
const datasetIndex = activeEls[0].datasetIndex;
|
||||
const dataIndex = activeEls[0].index;
|
||||
const label = e.chart.data.labels[dataIndex];
|
||||
const selected = $("#overAllLeaveSelect").val();
|
||||
const params = `?department_name=${label}&overall_leave=${selected}`;
|
||||
window.location.href = "/leave/request-view" + params;
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: "bottom",
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
pointStyle: "circle",
|
||||
padding: 20,
|
||||
font: {
|
||||
size: 12,
|
||||
},
|
||||
color: "#000",
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
padding: 10,
|
||||
cornerRadius: 4,
|
||||
backgroundColor: "#333",
|
||||
titleColor: "#fff",
|
||||
bodyColor: "#fff",
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
return context.parsed;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
afterRender: (chart) => emptyChart(chart),
|
||||
afterRender: (chart) => {
|
||||
if (typeof emptyChart === "function") {
|
||||
emptyChart(chart);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/leave/overall-leave?overall_leave=today",
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
if (overAllLeave){
|
||||
overAllLeave.data.labels = response.labels;
|
||||
overAllLeave.data.datasets[0].data = response.data;
|
||||
overAllLeave.data.datasets[0].backgroundColor = null;
|
||||
overAllLeave.update();
|
||||
}
|
||||
},
|
||||
});
|
||||
$(document).on("change", "#overAllLeaveSelect", function () {
|
||||
var selected = $(this).val();
|
||||
function fetchOverAllLeaveData(overallLeaveType = "today") {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: `/leave/overall-leave?overall_leave=${selected}`,
|
||||
url: `/leave/overall-leave?overall_leave=${overallLeaveType}`,
|
||||
dataType: "json",
|
||||
success: function (response) {
|
||||
overAllLeave.data.labels = response.labels;
|
||||
overAllLeave.data.datasets[0].data = response.data;
|
||||
overAllLeave.data.datasets[0].backgroundColor = null;
|
||||
overAllLeave.update();
|
||||
renderOverAllLeaveChart(response.data, response.labels);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//Today leave employees chart
|
||||
// Initial chart load
|
||||
fetchOverAllLeaveData();
|
||||
|
||||
// Dropdown change event
|
||||
$(document).on("change", "#overAllLeaveSelect", function () {
|
||||
fetchOverAllLeaveData($(this).val());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// $(document).ready(function () {
|
||||
// //Todays leave count department wise chart
|
||||
// if (document.getElementById("overAllLeave")){
|
||||
// var myChart1 = document.getElementById("overAllLeave").getContext("2d");
|
||||
// var overAllLeave = new Chart(myChart1, {
|
||||
// type: "doughnut",
|
||||
// data: {
|
||||
// labels: [],
|
||||
// datasets: [
|
||||
// {
|
||||
// label: "Leave count",
|
||||
// data: [],
|
||||
// backgroundColor: null,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// options: {
|
||||
// responsive: true,
|
||||
// maintainAspectRatio: false,
|
||||
// onClick: (e, activeEls) => {
|
||||
// let datasetIndex = activeEls[0].datasetIndex;
|
||||
// let dataIndex = activeEls[0].index;
|
||||
// let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
// let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
// let label = e.chart.data.labels[dataIndex];
|
||||
// params =`?department_name=${label}&overall_leave=${$("#overAllLeaveSelect").val()}`;
|
||||
// window.location.href = "/leave/request-view" + params;
|
||||
// },
|
||||
// },
|
||||
// plugins: [
|
||||
// {
|
||||
// afterRender: (chart) => emptyChart(chart),
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
|
||||
// $.ajax({
|
||||
// type: "GET",
|
||||
// url: "/leave/overall-leave?overall_leave=today",
|
||||
// dataType: "json",
|
||||
// success: function (response) {
|
||||
// if (overAllLeave){
|
||||
// overAllLeave.data.labels = response.labels;
|
||||
// overAllLeave.data.datasets[0].data = response.data;
|
||||
// overAllLeave.data.datasets[0].backgroundColor = null;
|
||||
// overAllLeave.update();
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// $(document).on("change", "#overAllLeaveSelect", function () {
|
||||
// var selected = $(this).val();
|
||||
// $.ajax({
|
||||
// type: "GET",
|
||||
// url: `/leave/overall-leave?overall_leave=${selected}`,
|
||||
// dataType: "json",
|
||||
// success: function (response) {
|
||||
// overAllLeave.data.labels = response.labels;
|
||||
// overAllLeave.data.datasets[0].data = response.data;
|
||||
// overAllLeave.data.datasets[0].backgroundColor = null;
|
||||
// overAllLeave.update();
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
|
||||
// //Today leave employees chart
|
||||
// });
|
||||
|
||||
@@ -1,59 +1,181 @@
|
||||
$(document).ready(function () {
|
||||
//Hired candididates recruitment wise chart
|
||||
|
||||
|
||||
//onboarding started candidate chart
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/onboarding/onboard-candidate-chart",
|
||||
success: function (response) {
|
||||
const ctx = document.getElementById("onboardCandidate");
|
||||
if (ctx) {
|
||||
new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: response.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "#onboarding candidates",
|
||||
data: response.data,
|
||||
backgroundColor: response.background_color,
|
||||
borderColor: response.border_color,
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
// message:response.message,
|
||||
// emptyImageSrc:'/static/images/ui/sunbed.png'
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
const ctx = document.getElementById("onboardCandidate")?.getContext("2d");
|
||||
if (!ctx || !response?.data || !response?.labels) return;
|
||||
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
onClick: (e, activeEls) => {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href =
|
||||
"/recruitment/candidate-view" +
|
||||
"?recruitment=" +
|
||||
label +
|
||||
"&start_onboard=true";
|
||||
},
|
||||
const labels = response.labels;
|
||||
const values = response.data;
|
||||
const colors = [
|
||||
"#a5b4fc", "#fca5a5", "#fdba74", "#8de5b3", "#fcd34d", "#c2c7cc"
|
||||
];
|
||||
const visibility = Array(labels.length).fill(true);
|
||||
|
||||
const onboardChartInstance = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: "Onboarding Candidates",
|
||||
data: values,
|
||||
backgroundColor: colors,
|
||||
borderRadius: 20,
|
||||
barPercentage: 0.8,
|
||||
categoryPercentage: 0.8,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
onClick: (e, activeEls) => {
|
||||
if (!activeEls.length) return;
|
||||
const dataIndex = activeEls[0].index;
|
||||
const label = labels[dataIndex];
|
||||
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href = `/recruitment/candidate-view?recruitment=${encodeURIComponent(label)}&start_onboard=true`;
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
afterRender: (chart) => emptyChart(chart),
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
callbacks: {
|
||||
title: tooltipItems => labels[tooltipItems[0].dataIndex],
|
||||
label: tooltipItem => `Onboarding Candidates: ${tooltipItem.raw}`
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { stepSize: 1, color: "#6b7280" },
|
||||
grid: { drawBorder: false, color: "#e5e7eb" }
|
||||
},
|
||||
],
|
||||
x: {
|
||||
ticks: { display: false },
|
||||
grid: { display: false },
|
||||
border: { display: true, color: "#d1d5db" }
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [{
|
||||
afterRender: (chart) => {
|
||||
if (typeof emptyChart === "function") emptyChart(chart);
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// 🧩 Generate Custom Legend (same style as departmentChart)
|
||||
const $legendContainer = $("#onboardCandidateLegend");
|
||||
$legendContainer.empty();
|
||||
|
||||
labels.forEach((label, index) => {
|
||||
const color = colors[index % colors.length];
|
||||
|
||||
const $item = $(`
|
||||
<div style="display: flex; align-items: center; margin-bottom: 6px; cursor: pointer;">
|
||||
<span style="
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: ${color};
|
||||
margin-right: 8px;
|
||||
transition: opacity 0.3s;
|
||||
"></span>
|
||||
<span class="legend-label" style="color: #111827; font-size: 14px;">${label}</span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$legendContainer.append($item);
|
||||
|
||||
$item.on("click", function () {
|
||||
visibility[index] = !visibility[index];
|
||||
|
||||
onboardChartInstance.data.datasets[0].data = values.map((val, i) =>
|
||||
visibility[i] ? val : 0
|
||||
);
|
||||
|
||||
const $dot = $(this).find("span").first();
|
||||
const $label = $(this).find(".legend-label");
|
||||
|
||||
if (visibility[index]) {
|
||||
$dot.css("opacity", "1");
|
||||
$label.css("text-decoration", "none");
|
||||
} else {
|
||||
$dot.css("opacity", "0.4");
|
||||
$label.css("text-decoration", "line-through");
|
||||
}
|
||||
|
||||
onboardChartInstance.update();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// $(document).ready(function () {
|
||||
// //Hired candididates recruitment wise chart
|
||||
|
||||
|
||||
// //onboarding started candidate chart
|
||||
// $.ajax({
|
||||
// type: "GET",
|
||||
// url: "/onboarding/onboard-candidate-chart",
|
||||
// success: function (response) {
|
||||
// const ctx = document.getElementById("onboardCandidate");
|
||||
// if (ctx) {
|
||||
// new Chart(ctx, {
|
||||
// type: "bar",
|
||||
// data: {
|
||||
// labels: response.labels,
|
||||
// datasets: [
|
||||
// {
|
||||
// label: "#onboarding candidates",
|
||||
// data: response.data,
|
||||
// backgroundColor: response.background_color,
|
||||
// borderColor: response.border_color,
|
||||
// borderWidth: 1,
|
||||
// },
|
||||
// ],
|
||||
// // message:response.message,
|
||||
// // emptyImageSrc:'/static/images/ui/sunbed.png'
|
||||
// },
|
||||
// options: {
|
||||
// responsive: true,
|
||||
|
||||
// scales: {
|
||||
// y: {
|
||||
// beginAtZero: true,
|
||||
// },
|
||||
// },
|
||||
// onClick: (e, activeEls) => {
|
||||
// let datasetIndex = activeEls[0].datasetIndex;
|
||||
// let dataIndex = activeEls[0].index;
|
||||
// let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
// let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
// let label = e.chart.data.labels[dataIndex];
|
||||
// localStorage.removeItem("savedFilters");
|
||||
// window.location.href =
|
||||
// "/recruitment/candidate-view" +
|
||||
// "?recruitment=" +
|
||||
// label +
|
||||
// "&start_onboard=true";
|
||||
// },
|
||||
// },
|
||||
// plugins: [
|
||||
// {
|
||||
// afterRender: (chart) => emptyChart(chart),
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
|
||||
@@ -12,6 +12,7 @@ from django.utils.http import urlencode
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from horilla_views.cbv_methods import login_required
|
||||
from horilla_views.generic.cbv.pipeline import KanbanView
|
||||
from horilla_views.generic.cbv.views import (
|
||||
HorillaFormView,
|
||||
HorillaListView,
|
||||
@@ -227,6 +228,7 @@ class GetStages(TemplateView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["stages"] = self.stages
|
||||
context["view_id"] = get_short_uuid(6, "hsv")
|
||||
context["rec_id"] = kwargs["rec_id"]
|
||||
return context
|
||||
|
||||
|
||||
@@ -416,8 +418,185 @@ class CandidateList(HorillaListView):
|
||||
return self.queryset
|
||||
|
||||
|
||||
class CandidateCard(CandidateList):
|
||||
template_name = "pipeline/kanban_components/candidate_kanban_components.html"
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
@method_decorator(
|
||||
manager_can_enter(perm="recruitment.view_recruitment"), name="dispatch"
|
||||
)
|
||||
class CandidateCard(KanbanView):
|
||||
model = models.Candidate
|
||||
filter_class = filters.CandidateFilter
|
||||
group_key = "stage_id"
|
||||
records_per_page = 10
|
||||
filter_keys_to_remove = ["rec_id"]
|
||||
|
||||
kanban_attrs = """
|
||||
onclick="window.location.href = `{get_individual_url}`"
|
||||
data-group-order='{ordered_group_json}'
|
||||
"""
|
||||
|
||||
details = {
|
||||
"image_src": "{get_avatar}",
|
||||
"title": "{get_full_name}",
|
||||
"email": "{email}",
|
||||
"position": "{job_position_id}",
|
||||
}
|
||||
|
||||
group_actions = [
|
||||
{
|
||||
"action": _("Add Candidate"),
|
||||
"accessibility": "recruitment.accessibility.add_candidate_accessibility",
|
||||
"attrs": """
|
||||
hx-target="#genericModalBody"
|
||||
hx-get="{get_add_candidate_url}"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Edit"),
|
||||
"accessibility": "recruitment.accessibility.edit_stage_accessibility",
|
||||
"attrs": """
|
||||
hx-target="#genericModalBody"
|
||||
hx-get="{get_stage_update_url}"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Bulk Mail"),
|
||||
"accessibility": "recruitment.accessibility.edit_stage_accessibility",
|
||||
"attrs": """
|
||||
hx-target="#objectCreateModalTarget"
|
||||
hx-get="{get_send_email_url}"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#objectCreateModal"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Delete"),
|
||||
"accessibility": "recruitment.accessibility.delete_stage_accessibility",
|
||||
"attrs": """
|
||||
hx-target="#deleteConfirmationBody"
|
||||
hx-get="{get_delete_url}"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#deleteConfirmation"
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
actions = [
|
||||
{
|
||||
"action": _("Schedule Interview"),
|
||||
"attrs": """
|
||||
hx-get = "{get_schedule_interview}"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-target="#genericModalBody"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Send Mail"),
|
||||
"attrs": """
|
||||
hx-get = "{get_send_mail}"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#objectDetailsModal"
|
||||
hx-target="#objectDetailsModalTarget"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "Add to Skill Zone",
|
||||
"accessibility": "recruitment.cbv.accessibility.add_skill_zone",
|
||||
"attrs": """
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-get="{get_add_to_skill}"
|
||||
hx-target="#genericModalBody"
|
||||
class="oh-dropdown__link"
|
||||
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "View candidate self tracking",
|
||||
"accessibility": "recruitment.cbv.accessibility.check_candidate_self_tracking",
|
||||
"attrs": """
|
||||
href="{get_self_tracking_url}"
|
||||
class="oh-dropdown__link"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "Request Document",
|
||||
"accessibility": "recruitment.cbv.accessibility.request_document",
|
||||
"attrs": """
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-get="{get_document_request_doc}"
|
||||
hx-target="#genericModalBody"
|
||||
class="oh-dropdown__link"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "Add to Rejected",
|
||||
"accessibility": "recruitment.cbv.accessibility.add_reject",
|
||||
"attrs": """
|
||||
hx-target="#genericModalBody"
|
||||
hx-swap="innerHTML"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-get="{get_add_to_reject}"
|
||||
class="oh-dropdown__link"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "Edit Rejected Candidate",
|
||||
"accessibility": "recruitment.cbv.accessibility.edit_reject",
|
||||
"attrs": """
|
||||
hx-target="#genericModalBody"
|
||||
hx-swap="innerHTML"
|
||||
data-toggle="oh-modal-toggle"
|
||||
data-target="#genericModal"
|
||||
hx-get="{get_add_to_reject}"
|
||||
class="oh-dropdown__link"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("View Note"),
|
||||
"attrs": """
|
||||
hx-get="{get_view_note_url}"
|
||||
data-target="#activitySidebar"
|
||||
hx-target="#activitySidebar"
|
||||
onclick="$('#activitySidebar').addClass('oh-activity-sidebar--show')"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": _("Resume"),
|
||||
"attrs": """
|
||||
href="{get_resume_url}" target="_blank"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "archive_status",
|
||||
"attrs": """
|
||||
class="oh-dropdown__link"
|
||||
onclick="archiveCandidate({get_archive_url});"
|
||||
""",
|
||||
},
|
||||
{
|
||||
"action": "Delete",
|
||||
"attrs": """
|
||||
class="oh-dropdown__link oh-dropdown__link--danger"
|
||||
onclick="event.stopPropagation();
|
||||
deleteCandidate('{get_delete_url}'); "
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
def get_related_groups(self, *args, **kwargs):
|
||||
related_groups = super().get_related_groups(*args, **kwargs)
|
||||
rec_id = self.request.GET.get("rec_id")
|
||||
if rec_id:
|
||||
related_groups = related_groups.filter(recruitment_id=rec_id)
|
||||
|
||||
return related_groups
|
||||
|
||||
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
|
||||
@@ -467,9 +467,6 @@ class Stage(HorillaModel):
|
||||
sequence = models.IntegerField(null=True, default=0)
|
||||
objects = HorillaCompanyManager(related_company_field="recruitment_id__company_id")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.stage}"
|
||||
|
||||
class Meta:
|
||||
"""
|
||||
Meta class to add the additional info
|
||||
@@ -571,6 +568,30 @@ class Stage(HorillaModel):
|
||||
|
||||
return dict(stage_types).get(self.stage_type)
|
||||
|
||||
def get_stage_update_url(self):
|
||||
"""
|
||||
This method to get update url
|
||||
"""
|
||||
return reverse("stage-update-pipeline", kwargs={"pk": self.id})
|
||||
|
||||
def get_add_candidate_url(self):
|
||||
"""
|
||||
This method to get add candidate url
|
||||
"""
|
||||
return f'{reverse_lazy("add-candidate-to-stage")}?stage_id={self.id}'
|
||||
|
||||
def get_send_email_url(self):
|
||||
"""
|
||||
This method to get send email url
|
||||
"""
|
||||
return f'{reverse_lazy("send-mail")}?stage_id={self.id}'
|
||||
|
||||
def get_delete_url(self):
|
||||
"""
|
||||
This method to get delete url
|
||||
"""
|
||||
return f"{reverse_lazy('generic-delete')}?model=recruitment.Stage&pk={self.pk}"
|
||||
|
||||
|
||||
def candidate_upload_path(instance, filename):
|
||||
"""
|
||||
@@ -1126,6 +1147,23 @@ class Candidate(HorillaModel):
|
||||
context={"instance": self, "interviews": interviews},
|
||||
)
|
||||
|
||||
def ordered_group_json(self):
|
||||
"""
|
||||
This method is used to get the ordered stages in json format for the candidate
|
||||
"""
|
||||
|
||||
ordered_stages = self.recruitment_id.ordered_stages()
|
||||
ordered_group_json = json.dumps(
|
||||
[
|
||||
{
|
||||
"id": s.id,
|
||||
"stage": s.stage,
|
||||
}
|
||||
for s in ordered_stages
|
||||
]
|
||||
)
|
||||
return ordered_group_json
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.stage_id is not None:
|
||||
self.hired = self.stage_id.stage_type == "hired"
|
||||
|
||||
@@ -1,39 +1,99 @@
|
||||
|
||||
$(document).ready(function () {
|
||||
function recruitmentChart(dataSet, labels) {
|
||||
const styledDataSets = dataSet.map((dataset, index) => {
|
||||
const colors = ['#a5b4fc', '#fca5a5', '#fdba74', '#34d399', '#fbbf24', '#fb7185', '#60a5fa'];
|
||||
return {
|
||||
...dataset,
|
||||
backgroundColor: colors[index % colors.length],
|
||||
borderRadius: 10,
|
||||
barPercentage: 0.6,
|
||||
categoryPercentage: 0.8
|
||||
};
|
||||
});
|
||||
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: dataSet,
|
||||
datasets: styledDataSets,
|
||||
};
|
||||
|
||||
// Create chart using the Chart.js library
|
||||
window["myChart"] = {};
|
||||
if (document.getElementById("recruitmentChart1")){
|
||||
if (document.getElementById("recruitmentChart1")) {
|
||||
const ctx = document.getElementById("recruitmentChart1").getContext("2d");
|
||||
myChart = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
stepSize: 20,
|
||||
color: '#6b7280'
|
||||
},
|
||||
grid: {
|
||||
color: '#e5e7eb'
|
||||
}
|
||||
},
|
||||
x: {
|
||||
ticks: {
|
||||
color: '#6b7280'
|
||||
},
|
||||
grid: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
pointStyle: 'circle',
|
||||
font: {
|
||||
size: 12
|
||||
},
|
||||
color: '#374151',
|
||||
padding: 15
|
||||
},
|
||||
onClick: (e, legendItem, legend) => {
|
||||
const index = legendItem.datasetIndex;
|
||||
const chart = legend.chart;
|
||||
if (chart.isDatasetVisible(index)) {
|
||||
chart.hide(index);
|
||||
legendItem.hidden = true;
|
||||
} else {
|
||||
chart.show(index);
|
||||
legendItem.hidden = false;
|
||||
}
|
||||
chart.update();
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
onClick: (e, activeEls) => {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href =
|
||||
"/recruitment/candidate-view" +
|
||||
"?recruitment=" +
|
||||
datasetLabel +
|
||||
"&stage_id__stage_type=" +
|
||||
label.toLowerCase();
|
||||
if (activeEls.length > 0) {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href =
|
||||
"/recruitment/candidate-view" +
|
||||
"?recruitment=" +
|
||||
datasetLabel +
|
||||
"&stage_id__stage_type=" +
|
||||
label.toLowerCase();
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
afterRender: (chart) => emptyChart(chart),
|
||||
},
|
||||
],
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -58,55 +118,178 @@ $(document).ready(function () {
|
||||
myChart.config.type = chartType;
|
||||
myChart.update();
|
||||
});
|
||||
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/recruitment/hired-candidate-chart",
|
||||
success: function (response) {
|
||||
const ctx = document.getElementById("hiredCandidate");
|
||||
if (ctx) {
|
||||
new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: response.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "#Hired candidates",
|
||||
data: response.data,
|
||||
backgroundColor: response.background_color,
|
||||
borderColor: response.border_color,
|
||||
borderWidth: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
const ctx = document.getElementById("hiredCandidate")?.getContext("2d");
|
||||
if (!ctx || !response?.labels || !response?.data) return;
|
||||
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
},
|
||||
onClick: (e, activeEls) => {
|
||||
let datasetIndex = activeEls[0].datasetIndex;
|
||||
let dataIndex = activeEls[0].index;
|
||||
let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
let label = e.chart.data.labels[dataIndex];
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href =
|
||||
"/recruitment/candidate-view" +
|
||||
"?recruitment=" +
|
||||
label +
|
||||
"&hired=true";
|
||||
},
|
||||
const labels = response.labels;
|
||||
const values = response.data;
|
||||
const colors = [
|
||||
"#facc15",
|
||||
"#f87171",
|
||||
"#ddd6fe",
|
||||
"#a5b4fc",
|
||||
"#93c5fd",
|
||||
"#d1d5db",
|
||||
];
|
||||
const visibility = Array(labels.length).fill(true);
|
||||
|
||||
const hiredCandidateInstance = new Chart(ctx, {
|
||||
type: "bar",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: "Hired Candidates",
|
||||
data: values,
|
||||
backgroundColor: colors,
|
||||
borderRadius: 20,
|
||||
barPercentage: 0.8,
|
||||
categoryPercentage: 0.8,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
onClick: (e, activeEls) => {
|
||||
if (!activeEls.length) return;
|
||||
const dataIndex = activeEls[0].index;
|
||||
const label = labels[dataIndex];
|
||||
|
||||
localStorage.removeItem("savedFilters");
|
||||
window.location.href =
|
||||
`/recruitment/candidate-view?recruitment=${label}&hired=true`;
|
||||
},
|
||||
plugins: [
|
||||
{
|
||||
afterRender: (chart) => emptyChart(chart),
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
callbacks: {
|
||||
title: tooltipItems => labels[tooltipItems[0].dataIndex],
|
||||
label: tooltipItem => `Hired Candidates: ${tooltipItem.raw}`
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { stepSize: 1, color: "#6b7280" },
|
||||
grid: { drawBorder: false, color: "#e5e7eb" }
|
||||
},
|
||||
],
|
||||
x: {
|
||||
ticks: { display: false },
|
||||
grid: { display: false },
|
||||
border: { display: false }
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [{
|
||||
afterRender: (chart) => {
|
||||
if (typeof emptyChart === "function") emptyChart(chart);
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// 🧩 Generate Custom Legend
|
||||
const $legendContainer = $("#hiredLegend");
|
||||
$legendContainer.empty();
|
||||
|
||||
labels.forEach((label, index) => {
|
||||
const color = colors[index % colors.length];
|
||||
|
||||
const $item = $(`
|
||||
<div style="display: flex; align-items: center; margin-bottom: 6px; cursor: pointer;">
|
||||
<span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background-color: ${color}; margin-right: 8px;"></span>
|
||||
<span class="legend-label">${label}</span>
|
||||
</div>
|
||||
`);
|
||||
|
||||
$legendContainer.append($item);
|
||||
|
||||
$item.on("click", function () {
|
||||
visibility[index] = !visibility[index];
|
||||
|
||||
// Toggle bar value visibility
|
||||
hiredCandidateInstance.data.datasets[0].data = values.map((val, i) =>
|
||||
visibility[i] ? val : 0
|
||||
);
|
||||
|
||||
const $dot = $(this).find("span").first();
|
||||
const $label = $(this).find(".legend-label");
|
||||
|
||||
if (visibility[index]) {
|
||||
$dot.css("opacity", "1");
|
||||
$label.css("text-decoration", "none");
|
||||
} else {
|
||||
$dot.css("opacity", "0.4");
|
||||
$label.css("text-decoration", "line-through");
|
||||
}
|
||||
|
||||
hiredCandidateInstance.update();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error("Chart data fetch failed:", error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//og
|
||||
// $.ajax({
|
||||
// type: "GET",
|
||||
// url: "/recruitment/hired-candidate-chart",
|
||||
// success: function (response) {
|
||||
// const ctx = document.getElementById("hiredCandidate");
|
||||
// if (ctx) {
|
||||
// new Chart(ctx, {
|
||||
// type: "bar",
|
||||
// data: {
|
||||
// labels: response.labels,
|
||||
// datasets: [
|
||||
// {
|
||||
// label: "#Hired candidates",
|
||||
// data: response.data,
|
||||
// backgroundColor: response.background_color,
|
||||
// borderColor: response.border_color,
|
||||
// borderWidth: 1,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// options: {
|
||||
// responsive: true,
|
||||
|
||||
// scales: {
|
||||
// y: {
|
||||
// beginAtZero: true,
|
||||
// },
|
||||
// },
|
||||
// onClick: (e, activeEls) => {
|
||||
// let datasetIndex = activeEls[0].datasetIndex;
|
||||
// let dataIndex = activeEls[0].index;
|
||||
// let datasetLabel = e.chart.data.datasets[datasetIndex].label;
|
||||
// let value = e.chart.data.datasets[datasetIndex].data[dataIndex];
|
||||
// let label = e.chart.data.labels[dataIndex];
|
||||
// localStorage.removeItem("savedFilters");
|
||||
// window.location.href =
|
||||
// "/recruitment/candidate-view" +
|
||||
// "?recruitment=" +
|
||||
// label +
|
||||
// "&hired=true";
|
||||
// },
|
||||
// },
|
||||
// plugins: [
|
||||
// {
|
||||
// afterRender: (chart) => emptyChart(chart),
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -92,4 +92,52 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function archiveCandidate(url,msg) {
|
||||
Swal.fire({
|
||||
text: msg,
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "green",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.href = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getCSRFToken() {
|
||||
return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||
}
|
||||
|
||||
function deleteCandidate(url) {
|
||||
Swal.fire({
|
||||
text: "Do you want to delete this candidate?",
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "green",
|
||||
cancelButtonColor: "#d33",
|
||||
confirmButtonText: "Confirm"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
const form = document.createElement('form');
|
||||
form.setAttribute('action', url);
|
||||
form.setAttribute('method', 'post');
|
||||
|
||||
const csrfTokenInput = document.createElement('input');
|
||||
csrfTokenInput.setAttribute('type', 'hidden');
|
||||
csrfTokenInput.setAttribute('name', 'csrfmiddlewaretoken');
|
||||
csrfTokenInput.value = getCSRFToken();
|
||||
form.appendChild(csrfTokenInput);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -966,7 +966,7 @@ urlpatterns = [
|
||||
name="candidate-lists-cbv",
|
||||
),
|
||||
path(
|
||||
"candidate-card-cbv/<int:stage_id>",
|
||||
"candidate-card-cbv/",
|
||||
pipeline.CandidateCard.as_view(),
|
||||
name="candidate-card-cbv",
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user