434 lines
17 KiB
HTML
434 lines
17 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Human Resources Dashboard - {{ block.super }}{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
.timeline {
|
|
position: relative;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.timeline-item {
|
|
position: relative;
|
|
padding-bottom: 1.5rem;
|
|
}
|
|
|
|
.timeline-item:not(:last-child)::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -1.5rem;
|
|
top: 1rem;
|
|
width: 2px;
|
|
height: calc(100% - 0.5rem);
|
|
background-color: #dee2e6;
|
|
}
|
|
|
|
.timeline-marker {
|
|
position: absolute;
|
|
left: -1.75rem;
|
|
top: 0.25rem;
|
|
width: 0.5rem;
|
|
height: 0.5rem;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
.timeline-content {
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.timeline-title {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.timeline-text {
|
|
font-size: 0.8rem;
|
|
color: #6c757d;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.list-group-item {
|
|
border: none;
|
|
padding: 0.75rem 0;
|
|
}
|
|
|
|
.list-group-item:first-child {
|
|
border-top: none;
|
|
}
|
|
|
|
.list-group-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.bg-gradient {
|
|
background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.timeline {
|
|
padding-left: 1rem;
|
|
}
|
|
|
|
.timeline-marker {
|
|
left: -1.25rem;
|
|
}
|
|
|
|
.d-flex.justify-content-between {
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.btn-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
{% block content %}
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 mb-1">
|
|
<i class="fas fa-users me-2"></i>Human Resources Dashboard
|
|
</h1>
|
|
<nav aria-label="breadcrumb">
|
|
<ol class="breadcrumb mb-0">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
|
|
<li class="breadcrumb-item active">Human Resources</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="refreshDashboard()">
|
|
<i class="fas fa-sync-alt me-2"></i>Refresh
|
|
</button>
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
|
|
<i class="fas fa-cog me-2"></i>Quick Actions
|
|
</button>
|
|
<ul class="dropdown-menu">
|
|
<li><a class="dropdown-item" href="{% url 'hr:employee_create' %}">
|
|
<i class="fas fa-user-plus me-2"></i>Add Employee
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'hr:department_create' %}">
|
|
<i class="fas fa-building me-2"></i>Add Department
|
|
</a></li>
|
|
<li><a class="dropdown-item" href="{% url 'hr:schedule_create' %}">
|
|
<i class="fas fa-calendar-plus me-2"></i>Create Schedule
|
|
</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="#">
|
|
<i class="fas fa-chart-bar me-2"></i>Generate Report
|
|
</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="row mb-4" id="hr-stats">
|
|
{% include "hr/partials/hr_stats.html" %}
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Recent Activity -->
|
|
<div class="col-lg-8">
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-clock me-2"></i>Recent Activity
|
|
</h5>
|
|
<div class="btn-group btn-group-sm">
|
|
<button type="button" class="btn btn-outline-secondary" onclick="refreshActivity()">
|
|
<i class="fas fa-sync-alt"></i>
|
|
</button>
|
|
<a href="#" class="btn btn-outline-primary">
|
|
<i class="fas fa-list me-1"></i>View All
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="timeline" id="activity-timeline">
|
|
<!-- Recent Hires -->
|
|
{% for employee in recent_hires %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-success"></div>
|
|
<div class="timeline-content">
|
|
<h6 class="timeline-title">New Employee Hired</h6>
|
|
<p class="timeline-text">
|
|
<strong>{{ employee.get_full_name }}</strong> joined
|
|
{% if employee.department %}{{ employee.department.name }}{% else %}the organization{% endif %}
|
|
as {{ employee.job_title|default:"Employee" }}
|
|
</p>
|
|
<small class="text-muted">{{ employee.hire_date|timesince }} ago</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Recent Reviews -->
|
|
{% for review in recent_reviews %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-info"></div>
|
|
<div class="timeline-content">
|
|
<h6 class="timeline-title">Performance Review Completed</h6>
|
|
<p class="timeline-text">
|
|
<strong>{{ review.employee.get_full_name }}</strong> received a performance review
|
|
{% if review.overall_rating %}
|
|
with rating: {{ review.get_overall_rating_display }}
|
|
{% endif %}
|
|
</p>
|
|
<small class="text-muted">{{ review.review_date|timesince }} ago</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Recent Training -->
|
|
{% for training in recent_training %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-marker bg-warning"></div>
|
|
<div class="timeline-content">
|
|
<h6 class="timeline-title">Training Completed</h6>
|
|
<p class="timeline-text">
|
|
<strong>{{ training.employee.get_full_name }}</strong> completed
|
|
"{{ training.training_name }}"
|
|
</p>
|
|
<small class="text-muted">{{ training.completion_date|timesince }} ago</small>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% if not recent_hires and not recent_reviews and not recent_training %}
|
|
<div class="text-center py-4">
|
|
<i class="fas fa-clock fa-3x text-muted mb-3"></i>
|
|
<h6 class="text-muted">No recent activity</h6>
|
|
<p class="text-muted">Recent HR activities will appear here</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Attendance Summary -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-clock me-2"></i>Today's Attendance
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% include "hr/partials/attendance_summary.html" %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-4">
|
|
<!-- Quick Actions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-bolt me-2"></i>Quick Actions
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="{% url 'hr:employee_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-user-plus me-2"></i>Add Employee
|
|
</a>
|
|
<a href="{% url 'hr:employee_list' %}" class="btn btn-outline-primary">
|
|
<i class="fas fa-users me-2"></i>Manage Employees
|
|
</a>
|
|
<a href="{% url 'hr:department_list' %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-building me-2"></i>Departments
|
|
</a>
|
|
<a href="{% url 'hr:schedule_list' %}" class="btn btn-outline-info">
|
|
<i class="fas fa-calendar-alt me-2"></i>Schedules
|
|
</a>
|
|
<a href="{% url 'hr:time_entry_list' %}" class="btn btn-outline-warning">
|
|
<i class="fas fa-clock me-2"></i>Time Tracking
|
|
</a>
|
|
<a href="{% url 'hr:performance_review_list' %}" class="btn btn-outline-success">
|
|
<i class="fas fa-chart-line me-2"></i>Performance Reviews
|
|
</a>
|
|
<a href="{% url 'hr:training_record_list' %}" class="btn btn-outline-dark">
|
|
<i class="fas fa-graduation-cap me-2"></i>Training Records
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Department Overview -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-building me-2"></i>Department Overview
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="list-group list-group-flush">
|
|
{% for dept in departments %}
|
|
<div class="list-group-item d-flex justify-content-between align-items-center px-0">
|
|
<div>
|
|
<h6 class="mb-1">{{ dept.name }}</h6>
|
|
<small class="text-muted">{{ dept.description|default:"No description" }}</small>
|
|
</div>
|
|
<span class="badge bg-primary rounded-pill">{{ dept.employees.count }}</span>
|
|
</div>
|
|
{% empty %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-building fa-2x text-muted mb-2"></i>
|
|
<p class="text-muted mb-0">No departments found</p>
|
|
<a href="{% url 'hr:department_create' %}" class="btn btn-sm btn-outline-primary mt-2">
|
|
<i class="fas fa-plus me-1"></i>Add Department
|
|
</a>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pending Tasks -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="mb-0">
|
|
<i class="fas fa-tasks me-2"></i>Pending Tasks
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="list-group list-group-flush">
|
|
{% if pending_reviews > 0 %}
|
|
<a href="{% url 'hr:performance_review_list' %}?status=PENDING" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center px-0">
|
|
<div>
|
|
<h6 class="mb-1">Pending Reviews</h6>
|
|
<small class="text-muted">Performance reviews awaiting completion</small>
|
|
</div>
|
|
<span class="badge bg-warning rounded-pill">{{ pending_reviews }}</span>
|
|
</a>
|
|
{% endif %}
|
|
|
|
{% if employees_clocked_in > 0 %}
|
|
<a href="{% url 'hr:time_entry_list' %}?status=ACTIVE" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center px-0">
|
|
<div>
|
|
<h6 class="mb-1">Employees Clocked In</h6>
|
|
<small class="text-muted">Currently working</small>
|
|
</div>
|
|
<span class="badge bg-success rounded-pill">{{ employees_clocked_in }}</span>
|
|
</a>
|
|
{% endif %}
|
|
|
|
<div class="list-group-item d-flex justify-content-between align-items-center px-0">
|
|
<div>
|
|
<h6 class="mb-1">Total Hours Today</h6>
|
|
<small class="text-muted">Logged work hours</small>
|
|
</div>
|
|
<span class="badge bg-info rounded-pill">{{ total_hours_today|floatformat:1 }}h</span>
|
|
</div>
|
|
|
|
{% if not pending_reviews and not employees_clocked_in %}
|
|
<div class="text-center py-3">
|
|
<i class="fas fa-check-circle fa-2x text-success mb-2"></i>
|
|
<p class="text-muted mb-0">All tasks completed!</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
{% block js %}
|
|
<script>
|
|
// HR Dashboard functionality
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Auto-refresh dashboard every 5 minutes
|
|
setInterval(refreshDashboard, 300000);
|
|
|
|
// Auto-refresh activity every 2 minutes
|
|
setInterval(refreshActivity, 120000);
|
|
});
|
|
|
|
function refreshDashboard() {
|
|
// Refresh statistics
|
|
fetch('{% url "hr:hr_stats" %}')
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
document.getElementById('hr-stats').innerHTML = html;
|
|
})
|
|
.catch(error => {
|
|
console.error('Error refreshing dashboard:', error);
|
|
});
|
|
}
|
|
|
|
function refreshActivity() {
|
|
// Refresh activity timeline
|
|
const timeline = document.getElementById('activity-timeline');
|
|
if (timeline) {
|
|
// Add loading indicator
|
|
timeline.innerHTML = '<div class="text-center py-3"><i class="fas fa-spinner fa-spin"></i> Loading...</div>';
|
|
|
|
// Simulate refresh (in real implementation, this would fetch from server)
|
|
setTimeout(() => {
|
|
location.reload();
|
|
}, 1000);
|
|
}
|
|
}
|
|
|
|
// Clock in/out functionality
|
|
function clockIn() {
|
|
fetch('{% url "hr:clock_in" %}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Clocked in successfully!');
|
|
refreshDashboard();
|
|
} else {
|
|
alert('Error clocking in: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error clocking in');
|
|
});
|
|
}
|
|
|
|
function clockOut() {
|
|
fetch('{% url "hr:clock_out" %}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
alert('Clocked out successfully!');
|
|
refreshDashboard();
|
|
} else {
|
|
alert('Error clocking out: ' + data.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('Error clocking out');
|
|
});
|
|
}
|
|
</script>
|
|
{% endblock %}
|
|
|