564 lines
26 KiB
HTML
564 lines
26 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}{{ schedule.name }} | Schedule Details{% endblock %}
|
|
|
|
{% block css %}
|
|
<!-- DataTables CSS -->
|
|
<link href="{% static 'plugins/datatables.net-bs5/css/dataTables.bootstrap5.min.css' %}" rel="stylesheet" />
|
|
<link href="{% static 'plugins/datatables.net-responsive-bs5/css/responsive.bootstrap5.min.css' %}" rel="stylesheet" />
|
|
<!-- Fullcalendar CSS -->
|
|
<link href="{% static 'plugins/fullcalendar/main.min.css' %}" rel="stylesheet" />
|
|
<style>
|
|
.schedule-header {
|
|
background: linear-gradient(to right, #2d62ed, #3a7bd5);
|
|
color: white;
|
|
padding: 20px;
|
|
border-radius: 5px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.schedule-stats {
|
|
background: #f8f9fa;
|
|
border-radius: 5px;
|
|
padding: 15px;
|
|
}
|
|
.stat-card {
|
|
transition: all 0.3s ease;
|
|
}
|
|
.stat-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
|
}
|
|
.employee-card {
|
|
transition: all 0.3s ease;
|
|
}
|
|
.employee-card:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
|
}
|
|
.tab-content {
|
|
padding: 20px;
|
|
border: 1px solid #dee2e6;
|
|
border-top: none;
|
|
border-radius: 0 0 5px 5px;
|
|
}
|
|
.fc-event {
|
|
cursor: pointer;
|
|
}
|
|
.fc-event-title {
|
|
font-weight: bold;
|
|
}
|
|
.calendar-container {
|
|
height: 600px;
|
|
}
|
|
.assignment-time {
|
|
font-size: 0.85rem;
|
|
color: #6c757d;
|
|
}
|
|
.shift-type {
|
|
display: inline-block;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
margin-right: 5px;
|
|
}
|
|
.shift-morning {
|
|
background-color: #28a745;
|
|
}
|
|
.shift-afternoon {
|
|
background-color: #fd7e14;
|
|
}
|
|
.shift-night {
|
|
background-color: #6f42c1;
|
|
}
|
|
.shift-full {
|
|
background-color: #007bff;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- begin breadcrumb -->
|
|
<ol class="breadcrumb float-xl-end">
|
|
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Home</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'hr:dashboard' %}">HR</a></li>
|
|
<li class="breadcrumb-item"><a href="{% url 'hr:schedule_list' %}">Schedules</a></li>
|
|
<li class="breadcrumb-item active">{{ schedule.name }}</li>
|
|
</ol>
|
|
<!-- end breadcrumb -->
|
|
|
|
<!-- begin page-header -->
|
|
<h1 class="page-header">Schedule Details</h1>
|
|
<!-- end page-header -->
|
|
|
|
<!-- Schedule Header -->
|
|
<div class="schedule-header">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<h2 class="mb-1">{{ schedule.name }}</h2>
|
|
<p class="mb-0">{{ schedule.start_date|date:"M d, Y" }} - {{ schedule.end_date|date:"M d, Y" }}</p>
|
|
</div>
|
|
<div>
|
|
{% if schedule.status == 'DRAFT' %}
|
|
<span class="badge bg-warning fs-6">Draft</span>
|
|
{% elif schedule.status == 'PUBLISHED' %}
|
|
<span class="badge bg-success fs-6">Published</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary fs-6">Archived</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="d-flex justify-content-end mb-4">
|
|
{% if schedule.status == 'DRAFT' %}
|
|
<a href="{% url 'hr:publish_schedule' schedule.id %}" class="btn btn-success me-2">
|
|
<i class="fas fa-check"></i> Publish Schedule
|
|
</a>
|
|
{% endif %}
|
|
<a href="{% url 'hr:schedule_update' schedule.id %}" class="btn btn-primary me-2">
|
|
<i class="fas fa-edit"></i> Edit Schedule
|
|
</a>
|
|
<div class="dropdown">
|
|
<button class="btn btn-secondary dropdown-toggle" type="button" id="exportDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
|
<i class="fas fa-download"></i> Export
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="exportDropdown">
|
|
<li><a class="dropdown-item" href="#" id="export-pdf"><i class="fas fa-file-pdf"></i> PDF</a></li>
|
|
<li><a class="dropdown-item" href="#" id="export-excel"><i class="fas fa-file-excel"></i> Excel</a></li>
|
|
<li><a class="dropdown-item" href="#" id="export-csv"><i class="fas fa-file-csv"></i> CSV</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- begin row -->
|
|
<div class="row">
|
|
<!-- begin col-4 -->
|
|
<div class="col-xl-4">
|
|
<!-- Schedule Info Card -->
|
|
<div class="card mb-4">
|
|
<div class="card-header d-flex align-items-center">
|
|
<h5 class="card-title mb-0">Schedule Information</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-borderless">
|
|
<tbody>
|
|
<tr>
|
|
<th width="35%">Department:</th>
|
|
<td>
|
|
{% if schedule.department %}
|
|
<a href="{% url 'hr:department_detail' schedule.department.id %}">
|
|
{{ schedule.department.name }}
|
|
</a>
|
|
{% else %}
|
|
<span class="text-muted">All Departments</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Period:</th>
|
|
<td>{{ schedule.start_date|date:"M d, Y" }} - {{ schedule.end_date|date:"M d, Y" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Duration:</th>
|
|
<td>{{ schedule.duration }} days</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Status:</th>
|
|
<td>
|
|
{% if schedule.status == 'DRAFT' %}
|
|
<span class="badge bg-warning">Draft</span>
|
|
{% elif schedule.status == 'PUBLISHED' %}
|
|
<span class="badge bg-success">Published</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">Archived</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Created By:</th>
|
|
<td>{{ schedule.created_by.get_full_name }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Created At:</th>
|
|
<td>{{ schedule.created_at|date:"M d, Y H:i" }}</td>
|
|
</tr>
|
|
{% if schedule.published_at %}
|
|
<tr>
|
|
<th>Published At:</th>
|
|
<td>{{ schedule.published_at|date:"M d, Y H:i" }}</td>
|
|
</tr>
|
|
{% endif %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Schedule Statistics Card -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Schedule Statistics</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-6">
|
|
<div class="stat-card bg-primary bg-opacity-10 p-3 rounded text-center">
|
|
<h3 class="mb-1">{{ schedule.assignments.count }}</h3>
|
|
<p class="mb-0">Total Assignments</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="stat-card bg-success bg-opacity-10 p-3 rounded text-center">
|
|
<h3 class="mb-1">{{ employee_count }}</h3>
|
|
<p class="mb-0">Employees</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="stat-card bg-info bg-opacity-10 p-3 rounded text-center">
|
|
<h3 class="mb-1">{{ total_hours }}</h3>
|
|
<p class="mb-0">Total Hours</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="stat-card bg-warning bg-opacity-10 p-3 rounded text-center">
|
|
<h3 class="mb-1">{{ avg_hours_per_employee|floatformat:1 }}</h3>
|
|
<p class="mb-0">Avg Hours/Employee</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Shift Distribution Chart -->
|
|
<div class="mt-4">
|
|
<h6 class="mb-3">Shift Distribution</h6>
|
|
<canvas id="shiftDistributionChart" height="200"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes Card -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Notes</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if schedule.notes %}
|
|
<p>{{ schedule.notes|linebreaks }}</p>
|
|
{% else %}
|
|
<p class="text-muted">No notes available for this schedule.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- end col-4 -->
|
|
|
|
<!-- begin col-8 -->
|
|
<div class="col-xl-8">
|
|
<!-- Schedule Description -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Description</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if schedule.description %}
|
|
<p>{{ schedule.description }}</p>
|
|
{% else %}
|
|
<p class="text-muted">No description available.</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Schedule Tabs -->
|
|
<div class="card mb-4">
|
|
<div class="card-header p-0">
|
|
<ul class="nav nav-tabs" id="scheduleTabs" role="tablist">
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link active" id="calendar-tab" data-bs-toggle="tab" data-bs-target="#calendar" type="button" role="tab" aria-controls="calendar" aria-selected="true">
|
|
<i class="fas fa-calendar-alt me-1"></i> Calendar View
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="assignments-tab" data-bs-toggle="tab" data-bs-target="#assignments" type="button" role="tab" aria-controls="assignments" aria-selected="false">
|
|
<i class="fas fa-tasks me-1"></i> Assignments
|
|
</button>
|
|
</li>
|
|
<li class="nav-item" role="presentation">
|
|
<button class="nav-link" id="employees-tab" data-bs-toggle="tab" data-bs-target="#employees" type="button" role="tab" aria-controls="employees" aria-selected="false">
|
|
<i class="fas fa-users me-1"></i> Employees
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="tab-content" id="scheduleTabsContent">
|
|
<!-- Calendar Tab -->
|
|
<div class="tab-pane fade show active" id="calendar" role="tabpanel" aria-labelledby="calendar-tab">
|
|
<div class="calendar-container">
|
|
<div id="schedule-calendar"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Assignments Tab -->
|
|
<div class="tab-pane fade" id="assignments" role="tabpanel" aria-labelledby="assignments-tab">
|
|
<div class="table-responsive">
|
|
<table id="assignments-table" class="table table-striped table-bordered align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th>Employee</th>
|
|
<th>Date</th>
|
|
<th>Shift</th>
|
|
<th>Hours</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for assignment in schedule.assignments.all %}
|
|
<tr>
|
|
<td>
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar avatar-sm me-2">
|
|
<img src="{% static 'img/user/default-avatar.jpg' %}" alt="{{ assignment.employee.get_full_name }}" class="rounded-circle">
|
|
</div>
|
|
<div>
|
|
<a href="{% url 'hr:employee_detail' assignment.employee.id %}">
|
|
{{ assignment.employee.get_full_name }}
|
|
</a>
|
|
<small class="d-block text-muted">{{ assignment.employee.job_title }}</small>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>{{ assignment.date|date:"M d, Y" }}</td>
|
|
<td>
|
|
<div>
|
|
<span class="shift-type
|
|
{% if assignment.shift_type == 'MORNING' %}shift-morning
|
|
{% elif assignment.shift_type == 'AFTERNOON' %}shift-afternoon
|
|
{% elif assignment.shift_type == 'NIGHT' %}shift-night
|
|
{% else %}shift-full{% endif %}"></span>
|
|
{{ assignment.get_shift_type_display }}
|
|
</div>
|
|
<div class="assignment-time">
|
|
{{ assignment.start_time|time:"H:i" }} - {{ assignment.end_time|time:"H:i" }}
|
|
</div>
|
|
</td>
|
|
<td>{{ assignment.hours }} hrs</td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<a href="{% url 'hr:schedule_assignment_update' assignment.id %}" class="btn btn-sm btn-primary">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal{{ assignment.id }}">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Delete Modal -->
|
|
<div class="modal fade" id="deleteModal{{ assignment.id }}" tabindex="-1" aria-labelledby="deleteModalLabel{{ assignment.id }}" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="deleteModalLabel{{ assignment.id }}">Confirm Deletion</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
Are you sure you want to delete this assignment for {{ assignment.employee.get_full_name }} on {{ assignment.date|date:"M d, Y" }}?
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<a href="{% url 'hr:schedule_assignment_delete' assignment.id %}" class="btn btn-danger">Delete</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="5" class="text-center">No assignments found for this schedule.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<a href="{% url 'hr:schedule_assignment_create' %}?schedule={{ schedule.id }}" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> Add Assignment
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Employees Tab -->
|
|
<div class="tab-pane fade" id="employees" role="tabpanel" aria-labelledby="employees-tab">
|
|
<div class="row g-3">
|
|
{% for employee in employees %}
|
|
<div class="col-md-6">
|
|
<div class="card employee-card h-100">
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="avatar avatar-md me-3">
|
|
<img src="{% static 'img/user/default-avatar.jpg' %}" alt="{{ employee.get_full_name }}" class="rounded-circle">
|
|
</div>
|
|
<div>
|
|
<h5 class="card-title mb-0">{{ employee.get_full_name }}</h5>
|
|
<p class="card-text text-muted">{{ employee.job_title }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<strong>Department:</strong>
|
|
{% if employee.department %}
|
|
<a href="{% url 'hr:department_detail' employee.department.id %}">
|
|
{{ employee.department.name }}
|
|
</a>
|
|
{% else %}
|
|
<span class="text-muted">Not assigned</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<strong>Assignments:</strong> {{ employee.assignments_count }}
|
|
<span class="text-muted">({{ employee.total_hours }} hours)</span>
|
|
</div>
|
|
|
|
<a href="{% url 'hr:employee_detail' employee.id %}" class="btn btn-sm btn-outline-primary">
|
|
<i class="fas fa-user"></i> View Profile
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="alert alert-info">
|
|
No employees assigned to this schedule.
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- end col-8 -->
|
|
</div>
|
|
<!-- end row -->
|
|
{% endblock %}
|
|
|
|
{% block js %}
|
|
<!-- DataTables JS -->
|
|
<script src="{% static 'plugins/datatables.net/js/jquery.dataTables.min.js' %}"></script>
|
|
<script src="{% static 'plugins/datatables.net-bs5/js/dataTables.bootstrap5.min.js' %}"></script>
|
|
<script src="{% static 'plugins/datatables.net-responsive/js/dataTables.responsive.min.js' %}"></script>
|
|
<script src="{% static 'plugins/datatables.net-responsive-bs5/js/responsive.bootstrap5.min.js' %}"></script>
|
|
<!-- Fullcalendar JS -->
|
|
<script src="{% static 'plugins/fullcalendar/main.min.js' %}"></script>
|
|
<!-- Chart.js -->
|
|
<script src="{% static 'plugins/chart.js/dist/Chart.min.js' %}"></script>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize DataTable
|
|
$('#assignments-table').DataTable({
|
|
responsive: true,
|
|
lengthMenu: [10, 25, 50, 100],
|
|
pageLength: 10
|
|
});
|
|
|
|
// Initialize Calendar
|
|
var calendarEl = document.getElementById('schedule-calendar');
|
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
|
initialView: 'timeGridWeek',
|
|
initialDate: '{{ schedule.start_date|date:"Y-m-d" }}',
|
|
headerToolbar: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
|
|
},
|
|
events: [
|
|
{% for assignment in schedule.assignments.all %}
|
|
{
|
|
title: '{{ assignment.employee.get_full_name }}',
|
|
start: '{{ assignment.date|date:"Y-m-d" }}T{{ assignment.start_time|time:"H:i:s" }}',
|
|
end: '{{ assignment.date|date:"Y-m-d" }}T{{ assignment.end_time|time:"H:i:s" }}',
|
|
backgroundColor: '{% if assignment.shift_type == "MORNING" %}#28a745{% elif assignment.shift_type == "AFTERNOON" %}#fd7e14{% elif assignment.shift_type == "NIGHT" %}#6f42c1{% else %}#007bff{% endif %}',
|
|
borderColor: '{% if assignment.shift_type == "MORNING" %}#28a745{% elif assignment.shift_type == "AFTERNOON" %}#fd7e14{% elif assignment.shift_type == "NIGHT" %}#6f42c1{% else %}#007bff{% endif %}',
|
|
extendedProps: {
|
|
employee: '{{ assignment.employee.get_full_name }}',
|
|
shift: '{{ assignment.get_shift_type_display }}',
|
|
hours: '{{ assignment.hours }}'
|
|
}
|
|
}{% if not forloop.last %},{% endif %}
|
|
{% endfor %}
|
|
],
|
|
eventDidMount: function(info) {
|
|
$(info.el).tooltip({
|
|
title: info.event.title + ' - ' + info.event.extendedProps.shift + ' (' + info.event.extendedProps.hours + ' hrs)',
|
|
placement: 'top',
|
|
trigger: 'hover',
|
|
container: 'body'
|
|
});
|
|
}
|
|
});
|
|
calendar.render();
|
|
|
|
// Shift Distribution Chart
|
|
var ctx = document.getElementById('shiftDistributionChart').getContext('2d');
|
|
var shiftDistributionChart = new Chart(ctx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: ['Morning', 'Afternoon', 'Night', 'Full Day'],
|
|
datasets: [{
|
|
data: [
|
|
{{ morning_shifts }},
|
|
{{ afternoon_shifts }},
|
|
{{ night_shifts }},
|
|
{{ full_day_shifts }}
|
|
],
|
|
backgroundColor: [
|
|
'#28a745',
|
|
'#fd7e14',
|
|
'#6f42c1',
|
|
'#007bff'
|
|
],
|
|
borderColor: [
|
|
'#28a745',
|
|
'#fd7e14',
|
|
'#6f42c1',
|
|
'#007bff'
|
|
],
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
legend: {
|
|
position: 'bottom',
|
|
labels: {
|
|
boxWidth: 12
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Export functionality
|
|
$('#export-pdf').click(function(e) {
|
|
e.preventDefault();
|
|
window.location.href = '{% url "hr:export_schedule" schedule.id %}?format=pdf';
|
|
});
|
|
|
|
$('#export-excel').click(function(e) {
|
|
e.preventDefault();
|
|
window.location.href = '{% url "hr:export_schedule" schedule.id %}?format=excel';
|
|
});
|
|
|
|
$('#export-csv').click(function(e) {
|
|
e.preventDefault();
|
|
window.location.href = '{% url "hr:export_schedule" schedule.id %}?format=csv';
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|