2025-08-12 13:33:25 +03:00

554 lines
25 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}Employee Management - {{ block.super }}{% 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>Employee Management
</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="{% url 'hr:dashboard' %}">HR</a></li>
<li class="breadcrumb-item active">Employees</li>
</ol>
</nav>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown">
<i class="fas fa-download me-2"></i>Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" onclick="exportEmployees('csv')">
<i class="fas fa-file-csv me-2"></i>Export as CSV
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportEmployees('excel')">
<i class="fas fa-file-excel me-2"></i>Export as Excel
</a></li>
<li><a class="dropdown-item" href="#" onclick="exportEmployees('pdf')">
<i class="fas fa-file-pdf me-2"></i>Export as PDF
</a></li>
</ul>
<a href="{% url 'hr:employee_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Add Employee
</a>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Total Employees</h6>
<h3 class="mb-0 text-primary">{{ total_employees }}</h3>
<small class="text-muted">All staff members</small>
</div>
<div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-users fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Active Employees</h6>
<h3 class="mb-0 text-success">{{ active_employees }}</h3>
<small class="text-muted">Currently employed</small>
</div>
<div class="bg-success bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-user-check fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">New Hires</h6>
<h3 class="mb-0 text-info">{{ new_hires_count }}</h3>
<small class="text-muted">This month</small>
</div>
<div class="bg-info bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-user-plus fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="card-title text-muted mb-1">Departments</h6>
<h3 class="mb-0 text-warning">{{ departments_count }}</h3>
<small class="text-muted">Active departments</small>
</div>
<div class="bg-warning bg-gradient rounded-circle d-flex align-items-center justify-content-center" style="width: 50px; height: 50px;">
<i class="fas fa-building fa-lg text-white"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Filters Card -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-filter me-2"></i>Filters
</h5>
</div>
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-2">
<label for="department" class="form-label">Department</label>
<select name="department" id="department" class="form-select">
<option value="">All Departments</option>
{% for dept in departments %}
<option value="{{ dept.id }}" {% if request.GET.department == dept.id|stringformat:"s" %}selected{% endif %}>
{{ dept.name }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label for="employment_status" class="form-label">Status</label>
<select name="employment_status" id="employment_status" class="form-select">
<option value="">All Status</option>
<option value="ACTIVE" {% if request.GET.employment_status == "ACTIVE" %}selected{% endif %}>Active</option>
<option value="INACTIVE" {% if request.GET.employment_status == "INACTIVE" %}selected{% endif %}>Inactive</option>
<option value="TERMINATED" {% if request.GET.employment_status == "TERMINATED" %}selected{% endif %}>Terminated</option>
<option value="ON_LEAVE" {% if request.GET.employment_status == "ON_LEAVE" %}selected{% endif %}>On Leave</option>
</select>
</div>
<div class="col-md-2">
<label for="job_title" class="form-label">Job Title</label>
<input type="text" name="job_title" id="job_title" class="form-control"
placeholder="Job title..." value="{{ request.GET.job_title }}">
</div>
<div class="col-md-2">
<label for="hire_date_from" class="form-label">Hired From</label>
<input type="date" name="hire_date_from" id="hire_date_from" class="form-control"
value="{{ request.GET.hire_date_from }}">
</div>
<div class="col-md-3">
<label for="search" class="form-label">Search</label>
<input type="text" name="search" id="search" class="form-control"
placeholder="Search employees..." value="{{ request.GET.search }}">
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="submit" class="btn btn-primary me-2">
<i class="fas fa-search me-1"></i>Filter
</button>
<a href="{% url 'hr:employee_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-times"></i>
</a>
</div>
</form>
</div>
</div>
<!-- Employees List -->
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-list me-2"></i>Employees
{% if employees %}
<span class="badge bg-secondary ms-2">{{ employees|length }}</span>
{% endif %}
</h5>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-secondary" onclick="refreshEmployees()">
<i class="fas fa-sync-alt me-1"></i>Refresh
</button>
<button type="button" class="btn btn-outline-secondary" onclick="toggleBulkActions()">
<i class="fas fa-check-square me-1"></i>Bulk Actions
</button>
</div>
</div>
</div>
<div class="card-body p-0">
<!-- Bulk Actions Bar (Hidden by default) -->
<div id="bulk-actions-bar" class="bg-light border-bottom p-3" style="display: none;">
<div class="d-flex justify-content-between align-items-center">
<div>
<span id="selected-count">0</span> employee(s) selected
</div>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" onclick="bulkUpdateStatus()">
<i class="fas fa-edit me-1"></i>Update Status
</button>
<button type="button" class="btn btn-outline-success" onclick="bulkExport()">
<i class="fas fa-download me-1"></i>Export Selected
</button>
<button type="button" class="btn btn-outline-secondary" onclick="clearSelection()">
<i class="fas fa-times me-1"></i>Clear
</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th width="40">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="select-all">
</div>
</th>
<th>Employee</th>
<th>Employee ID</th>
<th>Department</th>
<th>Job Title</th>
<th>Hire Date</th>
<th>Status</th>
<th width="120">Actions</th>
</tr>
</thead>
<tbody>
{% for employee in employees %}
<tr>
<td>
<div class="form-check">
<input class="form-check-input employee-checkbox" type="checkbox"
value="{{ employee.id }}">
</div>
</td>
<td>
<div class="d-flex align-items-center">
<div class="avatar-sm bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center me-2">
<span class="text-white small fw-bold">
{{ employee.first_name.0 }}{{ employee.last_name.0 }}
</span>
</div>
<div>
<div class="fw-semibold">{{ employee.get_full_name }}</div>
<small class="text-muted">{{ employee.email|default:"No email" }}</small>
</div>
</div>
</td>
<td>
<span class="font-monospace">{{ employee.employee_number }}</span>
</td>
<td>
{% if employee.department %}
<span class="badge bg-info">{{ employee.department.name }}</span>
{% else %}
<span class="text-muted">Not assigned</span>
{% endif %}
</td>
<td>{{ employee.job_title|default:"Not specified" }}</td>
<td>
<div>{{ employee.hire_date|date:"M d, Y" }}</div>
<small class="text-muted">{{ employee.hire_date|timesince }} ago</small>
</td>
<td>
<span class="badge bg-{% if employee.employment_status == 'ACTIVE' %}success{% elif employee.employment_status == 'INACTIVE' %}secondary{% elif employee.employment_status == 'TERMINATED' %}danger{% elif employee.employment_status == 'ON_LEAVE' %}warning{% else %}secondary{% endif %}">
{{ employee.get_employment_status_display }}
</span>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="{% url 'hr:employee_detail' employee.pk %}"
class="btn btn-outline-primary btn-sm" title="View Details">
<i class="fas fa-eye"></i>
</a>
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown">
<span class="visually-hidden">Toggle Dropdown</span>
<i class="fas fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'hr:employee_update' employee.pk %}">
<i class="fas fa-edit me-2"></i>Edit Employee
</a></li>
<li><a class="dropdown-item" href="#">
<i class="fas fa-clock me-2"></i>View Schedule
</a></li>
<li><a class="dropdown-item" href="#">
<i class="fas fa-chart-line me-2"></i>Performance Review
</a></li>
<li><a class="dropdown-item" href="#">
<i class="fas fa-graduation-cap me-2"></i>Training Records
</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">
<i class="fas fa-print me-2"></i>Print Profile
</a></li>
<li><a class="dropdown-item text-danger" href="{% url 'hr:employee_delete' employee.pk %}">
<i class="fas fa-trash me-2"></i>Delete
</a></li>
</ul>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="8" class="text-center py-5">
<i class="fas fa-users fa-3x text-muted mb-3"></i>
<h5 class="text-muted">No employees found</h5>
<p class="text-muted">No employees match your current filters.</p>
<a href="{% url 'hr:employee_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Add First Employee
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Pagination -->
{% if is_paginated %}
<div class="card-footer">
<div class="d-flex justify-content-between align-items-center">
<div class="text-muted small">
Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} employees
</div>
<nav aria-label="Employees pagination">
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
{% endif %}
</div>
</div>
<script>
// Employee list functionality
document.addEventListener('DOMContentLoaded', function() {
// Initialize bulk selection
const selectAllCheckbox = document.getElementById('select-all');
const employeeCheckboxes = document.querySelectorAll('.employee-checkbox');
if (selectAllCheckbox) {
selectAllCheckbox.addEventListener('change', function() {
employeeCheckboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateSelectedCount();
});
}
employeeCheckboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
updateSelectedCount();
// Update select all checkbox state
const checkedCount = document.querySelectorAll('.employee-checkbox:checked').length;
if (selectAllCheckbox) {
selectAllCheckbox.checked = checkedCount === employeeCheckboxes.length;
selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < employeeCheckboxes.length;
}
});
});
});
function updateSelectedCount() {
const checkedCount = document.querySelectorAll('.employee-checkbox:checked').length;
const selectedCountElement = document.getElementById('selected-count');
if (selectedCountElement) {
selectedCountElement.textContent = checkedCount;
}
// Show/hide bulk actions bar
const bulkActionsBar = document.getElementById('bulk-actions-bar');
if (bulkActionsBar) {
bulkActionsBar.style.display = checkedCount > 0 ? 'block' : 'none';
}
}
function toggleBulkActions() {
const bulkActionsBar = document.getElementById('bulk-actions-bar');
const isVisible = bulkActionsBar.style.display !== 'none';
if (isVisible) {
clearSelection();
} else {
bulkActionsBar.style.display = 'block';
}
}
function clearSelection() {
document.querySelectorAll('.employee-checkbox').forEach(checkbox => {
checkbox.checked = false;
});
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
}
updateSelectedCount();
}
function refreshEmployees() {
location.reload();
}
function exportEmployees(format) {
const form = document.querySelector('form');
const formData = new FormData(form);
const params = new URLSearchParams(formData);
params.append('format', format);
window.open(`{% url 'hr:employee_list' %}export/?${params}`, '_blank');
}
function bulkUpdateStatus() {
const selectedIds = Array.from(document.querySelectorAll('.employee-checkbox:checked'))
.map(checkbox => checkbox.value);
if (selectedIds.length === 0) {
alert('Please select employees to update');
return;
}
const newStatus = prompt('Enter new status (ACTIVE, INACTIVE, TERMINATED, ON_LEAVE):');
if (newStatus) {
// Implementation for bulk status update
console.log('Updating employees:', selectedIds, 'to status:', newStatus);
alert('Bulk status update functionality would be implemented here');
}
}
function bulkExport() {
const selectedIds = Array.from(document.querySelectorAll('.employee-checkbox:checked'))
.map(checkbox => checkbox.value);
if (selectedIds.length === 0) {
alert('Please select employees to export');
return;
}
const params = new URLSearchParams();
selectedIds.forEach(id => params.append('ids', id));
window.open(`{% url 'hr:employee_list' %}export/?${params}`, '_blank');
}
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') {
return;
}
switch (e.key) {
case 'n':
e.preventDefault();
window.location.href = "{% url 'hr:employee_create' %}";
break;
case 'r':
e.preventDefault();
refreshEmployees();
break;
case '/':
e.preventDefault();
document.getElementById('search').focus();
break;
case 'a':
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
const selectAllCheckbox = document.getElementById('select-all');
if (selectAllCheckbox) {
selectAllCheckbox.click();
}
}
break;
}
});
</script>
<style>
.avatar-sm {
width: 32px;
height: 32px;
font-size: 0.75rem;
}
.bg-gradient {
background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
}
.table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.font-monospace {
font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
.form-check-input:indeterminate {
background-color: #0d6efd;
border-color: #0d6efd;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
}
#bulk-actions-bar {
border-bottom: 1px solid #dee2e6;
}
@media (max-width: 768px) {
.btn-group-sm .btn {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
.table-responsive {
font-size: 0.875rem;
}
.d-flex.justify-content-between {
flex-direction: column;
gap: 1rem;
}
}
</style>
{% endblock %}