ATS/templates/recruitment/dashboard.html
2026-01-29 14:19:03 +03:00

325 lines
12 KiB
HTML

{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Recruitment Dashboard" %} - {{ block.super }}{% endblock %}
{% block content %}
<div class="space-y-6">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<h1 class="text-2xl font-semibold text-gray-900">{% trans "Recruitment Analytics" %}</h1>
<p class="text-sm text-gray-400 mt-1">{% trans "Overview of your recruitment pipeline" %}</p>
</div>
{# Job Filter Dropdown #}
<form method="get" action="" class="flex items-center gap-3">
<label for="job-select" class="text-sm font-medium text-gray-700">{% trans "Filter Job:" %}</label>
<select name="selected_job_pk" id="job-select" onchange="this.form.submit()" class="px-4 py-2 bg-white border border-gray-200 rounded-lg text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
<option value="">{% trans "All Jobs (Default View)" %}</option>
{% for job in jobs%}
<option value="{{ job.pk}}" {% if current_job_id|slugify == job.pk|slugify %}selected{% endif %}>
{{ job.title }}
</option>
{% endfor %}
</select>
</form>
</div>
{# -------------------------------------------------------------------------- #}
{# STATS CARDS SECTION (12 KPIs) #}
{# -------------------------------------------------------------------------- #}
{% include 'recruitment/partials/stats_cards.html' %}
{# -------------------------------------------------------------------------- #}
{# CHARTS SECTION #}
{# -------------------------------------------------------------------------- #}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
{# AREA CHART - Daily Candidate Applications Trend (Global Chart) #}
<div class="lg:col-span-2 bg-white rounded-2xl p-6 shadow-sm border border-gray-100">
<div class="flex justify-between items-center mb-6">
<div>
<h4 class="font-bold text-gray-800">{% trans "Daily Applications Trend" %}</h4>
<p class="text-xs text-gray-400">{% trans "Daily application volume over time" %}</p>
</div>
</div>
<div class="h-80">
<canvas id="dailyApplicationsChart"></canvas>
</div>
</div>
{# BAR CHART - Application Volume (Global Chart) #}
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100">
<div class="flex justify-between items-center mb-6">
<div>
<h4 class="font-bold text-gray-800">{% trans "Top 5 Application Volume" %}</h4>
<p class="text-xs text-gray-400">{% trans "Jobs with most applications" %}</p>
</div>
</div>
<div class="h-64">
<canvas id="applicationsChart"></canvas>
</div>
</div>
{# FUNNEL CHART - Candidate Pipeline Status (Scoped Chart) #}
<div class="bg-white rounded-2xl p-6 shadow-sm border border-gray-100">
<div class="flex justify-between items-center mb-6">
<div>
<h4 class="font-bold text-gray-800">
{% if current_job %}
{% trans "Pipeline Funnel: " %}{{ current_job.title }}
{% else %}
{% trans "Total Pipeline Funnel (All Jobs)" %}
{% endif %}
</h4>
<p class="text-xs text-gray-400">{% trans "Candidate pipeline stages" %}</p>
</div>
</div>
<div class="h-64">
<canvas id="application_funnel_chart"></canvas>
</div>
</div>
{# GAUGE CHART - Average Time-to-Hire (Avg. Days) #}
<div class="lg:col-span-2 bg-white rounded-2xl p-6 shadow-sm border border-gray-100">
<div class="flex justify-between items-center mb-6">
<div>
<h4 class="font-bold text-gray-800">{% trans "Time-to-Hire Target Check" %}</h4>
<p class="text-xs text-gray-400">{% trans "Average days to hire candidates" %}</p>
</div>
</div>
<div class="h-64 flex justify-center items-center">
<div id="timeToHireGauge" class="text-center w-full">
{% include "recruitment/partials/_guage_chart.html" %}
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/luxon@3.4.4"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.3.1"></script>
<script>
// Pass context data safely to JavaScript
const totalCandidatesScoped = parseInt('{{ total_applications|default:0 }}');
const jobTitles = JSON.parse('{{ job_titles|escapejs }}').slice(0, 5);
const jobAppCounts = JSON.parse('{{ job_app_counts|escapejs }}').slice(0, 5);
const stages = JSON.parse('{{ application_stage|escapejs }}');
const counts = JSON.parse('{{ application_count|safe }}');
// --- 1. BAR CHART configuration (Top 5 Applications) ---
const ctxBar = document.getElementById('applicationsChart').getContext('2d');
new Chart(ctxBar, {
type: 'bar',
data: {
labels: jobTitles,
datasets: [{
label: '{% trans "Applications" %}',
data: jobAppCounts,
backgroundColor: '#9d2235',
borderColor: '#7a1a29',
borderWidth: 1,
barThickness: 50,
borderRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
title: {
display: true,
text: '{% trans "Top 5 Most Applied Jobs" %}',
font: { size: 14, weight: '600' },
color: '#343a40'
}
},
scales: {
y: {
beginAtZero: true,
title: { display: true, text: '{% trans "Total Applications" %}', font: { size: 11 } },
ticks: { color: '#666', precision: 0 },
grid: { color: '#e5e7eb' }
},
x: {
ticks: { color: '#666', font: { size: 11 } },
grid: { display: false }
}
}
}
});
// --- 2. CANDIDATE PIPELINE CENTERED FUNNEL CHART ---
// 1. Find the maximum count (for the widest bar)
const maxCount = Math.max(...counts);
// 2. Calculate the transparent "spacer" data needed to center each bar
const spacerData = counts.map(count => (maxCount - count) / 2);
// Define the dark-to-light temple red shades (5 stages, reverse order for funnel look)
const templeRedShades = [
'#c04050', // APPLIED - Lighter Red
'#a83242', // EXAM
'#9d2235', // INTERVIEW
'#8a1c2d', // OFFER
'#7a1a29', // HIRED - Darkest Red
];
// Slice and use the first N shades based on the number of stages
const stageColors = templeRedShades.slice(templeRedShades.length - stages.length);
const ctxFunnel = document.getElementById('application_funnel_chart').getContext('2d');
new Chart(ctxFunnel, {
type: 'bar',
data: {
labels: stages,
datasets: [
// 1. TRANSPARENT SPACER DATASET (Pushes the bar to the center)
{
label: 'Spacer',
data: spacerData,
backgroundColor: 'transparent',
hoverBackgroundColor: 'transparent',
barThickness: 50,
datalabels: { display: false },
tooltip: { enabled: false }
},
// 2. VISIBLE CANDIDATE COUNT DATASET
{
label: '{% trans "Application Count" %}',
data: counts,
backgroundColor: stageColors,
barThickness: 50,
borderRadius: 4
}
]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
beginAtZero: true,
stacked: true,
display: false,
max: maxCount
},
y: {
stacked: true,
grid: { display: false },
ticks: {
color: '#343a40',
font: { size: 12, weight: '500' }
}
}
},
plugins: {
legend: { display: false },
title: { display: false },
tooltip: {
filter: (tooltipItem) => {
return tooltipItem.datasetIndex === 1;
}
}
}
}
});
// Pass context data safely to JavaScript
const globalDates = JSON.parse('{{ global_dates|escapejs }}');
const globalCounts = JSON.parse('{{ global_counts|escapejs }}');
const scopedDates = JSON.parse('{{ scoped_dates|escapejs }}');
const scopedCounts = JSON.parse('{{ scoped_counts|escapejs }}');
const isJobScoped = '{{ is_job_scoped }}' === 'True';
// --- 4. DAILY APPLICATIONS LINE CHART ---
const ctxLine = document.getElementById('dailyApplicationsChart').getContext('2d');
// Create datasets
const datasets = [{
label: '{% trans "All Jobs" %}',
data: globalCounts,
borderColor: '#7a1a29',
backgroundColor: 'rgba(122, 26, 41, 0.05)',
fill: true,
tension: 0.4,
pointRadius: 4,
pointBackgroundColor: '#fff',
pointBorderWidth: 2
}];
// Add scoped data if a job is selected
if (isJobScoped) {
datasets.push({
label: '{% trans "Current Job" %}',
data: scopedCounts,
borderColor: '#9d2235',
backgroundColor: 'rgba(157, 34, 53, 0.05)',
fill: true,
tension: 0.4,
pointRadius: 4,
pointBackgroundColor: '#fff',
pointBorderWidth: 2
});
}
new Chart(ctxLine, {
type: 'line',
data: {
// Use global dates as the base labels
labels: globalDates,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
},
title: {
display: true,
text: '{% trans "Daily Applications (Last 30 Days)" %}',
font: { size: 16, weight: '600' },
color: '#343a40'
}
},
scales: {
x: {
type: 'time',
time: {
unit: 'day',
tooltipFormat: 'MMM D',
displayFormats: {
day: 'MMM D'
}
},
title: { display: true, text: '{% trans "Date" %}', font: { size: 11 } },
grid: { display: false }
},
y: {
beginAtZero: true,
title: { display: true, text: '{% trans "New Applications" %}', font: { size: 11 } },
ticks: { precision: 0 },
grid: { color: '#e5e7eb' }
}
}
}
});
</script>
{% endblock %}