263 lines
9.9 KiB
HTML
263 lines
9.9 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Department Benchmarks" %} - PX360{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Header -->
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb">
|
|
<li class="breadcrumb-item"><a href="{% url 'dashboard:admin_evaluation' %}">{% trans "Admin Evaluation" %}</a></li>
|
|
<li class="breadcrumb-item active">{% trans "Department Benchmarks" %}</li>
|
|
</ol>
|
|
</nav>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h2 class="h3 mb-1">{% trans "Department Benchmarks" %}</h2>
|
|
<p class="text-muted mb-0">{{ benchmarks.department }} - {% trans "Staff Performance Comparison" %}</p>
|
|
</div>
|
|
<div>
|
|
<a href="{% url 'dashboard:admin_evaluation' %}" class="btn btn-outline-secondary">
|
|
<i class="bi bi-arrow-left me-2"></i>{% trans "Back to Evaluation" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% if benchmarks.error %}
|
|
<div class="alert alert-warning">
|
|
{{ benchmarks.error }}
|
|
</div>
|
|
{% else %}
|
|
|
|
<!-- Summary Cards -->
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-3">
|
|
<div class="card bg-primary text-white">
|
|
<div class="card-body">
|
|
<h6 class="card-title">{% trans "Total Staff" %}</h6>
|
|
<h3 class="mb-0">{{ benchmarks.staff_count }}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-info text-white">
|
|
<div class="card-body">
|
|
<h6 class="card-title">{% trans "Average Score" %}</h6>
|
|
<h3 class="mb-0">{{ benchmarks.average_score }}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-success text-white">
|
|
<div class="card-body">
|
|
<h6 class="card-title">{% trans "Top Performer" %}</h6>
|
|
<h5 class="mb-0 text-truncate">{{ benchmarks.top_performer.name|default:"-" }}</h5>
|
|
<small>{{ benchmarks.top_performer.score|default:"0" }} pts</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="card bg-warning text-dark">
|
|
<div class="card-body">
|
|
<h6 class="card-title">{% trans "Avg Items/Staff" %}</h6>
|
|
<h3 class="mb-0">{{ benchmarks.average_items_per_staff }}</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Charts Row -->
|
|
<div class="row g-4 mb-4">
|
|
<div class="col-lg-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">{% trans "Performance Rankings" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="rankingsChart" height="300"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">{% trans "Rating Distribution" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="ratingDistributionChart" height="300"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Needs Improvement Alert -->
|
|
{% if benchmarks.needs_improvement %}
|
|
<div class="alert alert-warning mb-4">
|
|
<h5><i class="bi bi-exclamation-triangle me-2"></i>{% trans "Staff Needing Improvement" %}</h5>
|
|
<p class="mb-2">{% trans "The following staff members have performance scores below 60:" %}</p>
|
|
<ul class="mb-0">
|
|
{% for staff in benchmarks.needs_improvement %}
|
|
<li>
|
|
<a href="{% url 'dashboard:staff_performance_detail' staff.id %}" class="fw-bold">
|
|
{{ staff.name }}
|
|
</a>
|
|
- {{ staff.score }} pts ({{ staff.total_items }} items)
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Rankings Table -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">{% trans "Detailed Rankings" %}</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th class="text-center">{% trans "Rank" %}</th>
|
|
<th>{% trans "Staff Name" %}</th>
|
|
<th class="text-center">{% trans "Performance Score" %}</th>
|
|
<th class="text-center">{% trans "Rating" %}</th>
|
|
<th class="text-center">{% trans "Complaints" %}</th>
|
|
<th class="text-center">{% trans "Inquiries" %}</th>
|
|
<th class="text-center">{% trans "Total Items" %}</th>
|
|
<th>{% trans "Actions" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for staff in benchmarks.rankings %}
|
|
<tr class="{% if forloop.first %}table-success{% endif %}">
|
|
<td class="text-center">
|
|
{% if forloop.first %}
|
|
<i class="bi bi-trophy-fill text-warning fs-5"></i>
|
|
{% elif forloop.counter == 2 %}
|
|
<i class="bi bi-trophy-fill text-secondary fs-5"></i>
|
|
{% elif forloop.counter == 3 %}
|
|
<i class="bi bi-trophy-fill text-danger fs-5"></i>
|
|
{% else %}
|
|
{{ forloop.counter }}
|
|
{% endif %}
|
|
</td>
|
|
<td class="fw-bold">{{ staff.name }}</td>
|
|
<td class="text-center">
|
|
<span class="badge bg-{% if staff.score >= 90 %}success{% elif staff.score >= 75 %}info{% elif staff.score >= 60 %}warning{% else %}danger{% endif %} fs-6">
|
|
{{ staff.score }}
|
|
</span>
|
|
</td>
|
|
<td class="text-center">{{ staff.rating }}</td>
|
|
<td class="text-center">{{ staff.complaints }}</td>
|
|
<td class="text-center">{{ staff.inquiries }}</td>
|
|
<td class="text-center fw-bold">{{ staff.total_items }}</td>
|
|
<td>
|
|
<a href="{% url 'dashboard:staff_performance_detail' staff.id %}" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-eye me-1"></i>{% trans "View" %}
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="8" class="text-center text-muted">
|
|
{% trans "No staff data available" %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script>
|
|
const rankingsData = {{ benchmarks.rankings|safe }};
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (rankingsData.length === 0) return;
|
|
|
|
// Rankings Bar Chart
|
|
const rankingsCtx = document.getElementById('rankingsChart').getContext('2d');
|
|
new Chart(rankingsCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: rankingsData.map(r => r.name),
|
|
datasets: [{
|
|
label: '{% trans "Performance Score" %}',
|
|
data: rankingsData.map(r => r.score),
|
|
backgroundColor: rankingsData.map(r => {
|
|
if (r.score >= 90) return '#10b981';
|
|
if (r.score >= 75) return '#3b82f6';
|
|
if (r.score >= 60) return '#f59e0b';
|
|
return '#ef4444';
|
|
}),
|
|
borderWidth: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
indexAxis: 'y',
|
|
scales: {
|
|
x: {
|
|
beginAtZero: true,
|
|
max: 100
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: false
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Rating Distribution Pie Chart
|
|
const ratingCounts = {
|
|
'Excellent': 0,
|
|
'Good': 0,
|
|
'Average': 0,
|
|
'Below Average': 0,
|
|
'Needs Improvement': 0
|
|
};
|
|
|
|
rankingsData.forEach(r => {
|
|
if (ratingCounts.hasOwnProperty(r.rating)) {
|
|
ratingCounts[r.rating]++;
|
|
}
|
|
});
|
|
|
|
const ratingCtx = document.getElementById('ratingDistributionChart').getContext('2d');
|
|
new Chart(ratingCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: Object.keys(ratingCounts),
|
|
datasets: [{
|
|
data: Object.values(ratingCounts),
|
|
backgroundColor: ['#10b981', '#3b82f6', '#f59e0b', '#ef4444', '#6b7280']
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|