515 lines
26 KiB
HTML
515 lines
26 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Schedules | HR Management{% 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-card {
|
|
transition: all 0.3s ease;
|
|
}
|
|
.schedule-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
|
}
|
|
.view-toggle .btn {
|
|
min-width: 100px;
|
|
}
|
|
.fc-event {
|
|
cursor: pointer;
|
|
}
|
|
.schedule-status {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
margin-right: 5px;
|
|
}
|
|
.status-draft {
|
|
background-color: #ffc107;
|
|
}
|
|
.status-published {
|
|
background-color: #28a745;
|
|
}
|
|
.status-archived {
|
|
background-color: #6c757d;
|
|
}
|
|
.calendar-container {
|
|
height: 700px;
|
|
}
|
|
.calendar-legend {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
font-size: 0.85rem;
|
|
}
|
|
.legend-color {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 2px;
|
|
margin-right: 5px;
|
|
}
|
|
</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 active">Schedules</li>
|
|
</ol>
|
|
<!-- end breadcrumb -->
|
|
|
|
<!-- begin page-header -->
|
|
<h1 class="page-header">Schedule Management <small>view and manage work schedules</small></h1>
|
|
<!-- end page-header -->
|
|
|
|
<!-- begin row -->
|
|
<div class="row">
|
|
<!-- begin col-12 -->
|
|
<div class="col-xl-12">
|
|
<!-- begin panel -->
|
|
<div class="panel panel-inverse">
|
|
<!-- begin panel-heading -->
|
|
<div class="panel-heading">
|
|
<h4 class="panel-title">Schedules</h4>
|
|
<div class="panel-heading-btn">
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-default" data-toggle="panel-expand"><i class="fa fa-expand"></i></a>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-success" data-toggle="panel-reload"><i class="fa fa-redo"></i></a>
|
|
<a href="javascript:;" class="btn btn-xs btn-icon btn-warning" data-toggle="panel-collapse"><i class="fa fa-minus"></i></a>
|
|
</div>
|
|
</div>
|
|
<!-- end panel-heading -->
|
|
|
|
<!-- begin panel-body -->
|
|
<div class="panel-body">
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<div>
|
|
<a href="{% url 'hr:schedule_create' %}" class="btn btn-primary">
|
|
<i class="fas fa-plus"></i> Create Schedule
|
|
</a>
|
|
<div class="btn-group ms-2 view-toggle" role="group">
|
|
<button type="button" class="btn btn-outline-secondary active" id="table-view-btn">
|
|
<i class="fas fa-table"></i> Table
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" id="card-view-btn">
|
|
<i class="fas fa-th-large"></i> Cards
|
|
</button>
|
|
<button type="button" class="btn btn-outline-secondary" id="calendar-view-btn">
|
|
<i class="fas fa-calendar-alt"></i> Calendar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="d-flex">
|
|
<form method="get" class="me-2">
|
|
<div class="input-group">
|
|
<input type="text" class="form-control" placeholder="Search schedules..." name="search" value="{{ search }}">
|
|
<button class="btn btn-outline-secondary" type="submit">
|
|
<i class="fas fa-search"></i>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<div class="dropdown">
|
|
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="filterDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
|
<i class="fas fa-filter"></i> Filter
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="filterDropdown">
|
|
<li><h6 class="dropdown-header">Status</h6></li>
|
|
<li><a class="dropdown-item" href="?status=DRAFT">Draft</a></li>
|
|
<li><a class="dropdown-item" href="?status=PUBLISHED">Published</a></li>
|
|
<li><a class="dropdown-item" href="?status=ARCHIVED">Archived</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><h6 class="dropdown-header">Department</h6></li>
|
|
{% for dept in departments %}
|
|
<li><a class="dropdown-item" href="?department={{ dept.id }}">{{ dept.name }}</a></li>
|
|
{% endfor %}
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><a class="dropdown-item" href="{% url 'hr:schedule_list' %}">Clear Filters</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table View -->
|
|
<div id="table-view">
|
|
<table id="schedules-table" class="table table-striped table-bordered align-middle">
|
|
<thead>
|
|
<tr>
|
|
<th>Schedule Name</th>
|
|
<th>Department</th>
|
|
<th>Period</th>
|
|
<th>Status</th>
|
|
<th>Assignments</th>
|
|
<th>Created By</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for schedule in schedules %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ schedule.name }}</strong>
|
|
</td>
|
|
<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>
|
|
<td>
|
|
{{ schedule.start_date|date:"M d, Y" }} - {{ schedule.end_date|date:"M d, Y" }}
|
|
</td>
|
|
<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>
|
|
<td>
|
|
<span class="badge bg-primary">{{ schedule.assignments.count }}</span>
|
|
</td>
|
|
<td>
|
|
{{ schedule.created_by.get_full_name }}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group">
|
|
<a href="{% url 'hr:schedule_detail' schedule.id %}" class="btn btn-sm btn-info">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
<a href="{% url 'hr:schedule_update' schedule.id %}" class="btn btn-sm btn-primary">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
{% if schedule.status == 'DRAFT' %}
|
|
<a href="{% url 'hr:publish_schedule' schedule.id %}" class="btn btn-sm btn-success">
|
|
<i class="fas fa-check"></i>
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="7" class="text-center">No schedules found.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Pagination -->
|
|
{% if is_paginated %}
|
|
<nav aria-label="Page navigation">
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}" aria-label="First">
|
|
<span aria-hidden="true">««</span>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}" aria-label="Previous">
|
|
<span aria-hidden="true">«</span>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active"><a class="page-link" href="#">{{ num }}</a></li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item"><a class="page-link" href="?page={{ num }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}">{{ num }}</a></li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}" aria-label="Next">
|
|
<span aria-hidden="true">»</span>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}" aria-label="Last">
|
|
<span aria-hidden="true">»»</span>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Card View -->
|
|
<div id="card-view" class="row g-3" style="display: none;">
|
|
{% for schedule in schedules %}
|
|
<div class="col-md-6 col-lg-4">
|
|
<div class="card schedule-card h-100">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0">{{ schedule.name }}</h5>
|
|
{% 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 %}
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<strong>Department:</strong>
|
|
{% 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 %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<strong>Period:</strong><br>
|
|
{{ schedule.start_date|date:"M d, Y" }} - {{ schedule.end_date|date:"M d, Y" }}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<strong>Description:</strong><br>
|
|
{% if schedule.description %}
|
|
{{ schedule.description|truncatechars:100 }}
|
|
{% else %}
|
|
<span class="text-muted">No description provided</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<i class="fas fa-users"></i> {{ schedule.assignments.count }} Assignments
|
|
</div>
|
|
<div>
|
|
<i class="fas fa-user"></i> {{ schedule.created_by.get_full_name|truncatechars:15 }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<div class="btn-group w-100">
|
|
<a href="{% url 'hr:schedule_detail' schedule.id %}" class="btn btn-sm btn-info">
|
|
<i class="fas fa-eye"></i> View
|
|
</a>
|
|
<a href="{% url 'hr:schedule_update' schedule.id %}" class="btn btn-sm btn-primary">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</a>
|
|
{% if schedule.status == 'DRAFT' %}
|
|
<a href="{% url 'hr:publish_schedule' schedule.id %}" class="btn btn-sm btn-success">
|
|
<i class="fas fa-check"></i> Publish
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% empty %}
|
|
<div class="col-12">
|
|
<div class="alert alert-info">
|
|
No schedules found.
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
<!-- Pagination for Card View -->
|
|
{% if is_paginated %}
|
|
<div class="col-12 mt-3">
|
|
<nav aria-label="Page navigation">
|
|
<ul class="pagination justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page=1{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}" aria-label="First">
|
|
<span aria-hidden="true">««</span>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}" aria-label="Previous">
|
|
<span aria-hidden="true">«</span>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<li class="page-item active"><a class="page-link" href="#">{{ num }}</a></li>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<li class="page-item"><a class="page-link" href="?page={{ num }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}">{{ num }}</a></li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}" aria-label="Next">
|
|
<span aria-hidden="true">»</span>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search %}&search={{ search }}{% endif %}{% if status %}&status={{ status }}{% endif %}{% if department %}&department={{ department }}{% endif %}" aria-label="Last">
|
|
<span aria-hidden="true">»»</span>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Calendar View -->
|
|
<div id="calendar-view" style="display: none;">
|
|
<div class="calendar-legend">
|
|
<div class="legend-item">
|
|
<div class="legend-color" style="background-color: #3788d8;"></div>
|
|
<span>Published</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-color" style="background-color: #ffc107;"></div>
|
|
<span>Draft</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-color" style="background-color: #6c757d;"></div>
|
|
<span>Archived</span>
|
|
</div>
|
|
</div>
|
|
<div class="calendar-container">
|
|
<div id="schedule-calendar"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- end panel-body -->
|
|
</div>
|
|
<!-- end panel -->
|
|
</div>
|
|
<!-- end col-12 -->
|
|
</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>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize DataTable
|
|
var table = $('#schedules-table').DataTable({
|
|
dom: "<'row'<'col-md-6'l><'col-md-6'f>><'row'<'col-md-12't>><'row'<'col-md-5'i><'col-md-7'p>>",
|
|
lengthMenu: [10, 25, 50, 100],
|
|
responsive: true,
|
|
paging: false, // We're using Django's pagination
|
|
searching: false, // We're using our own search
|
|
info: false // We're using Django's pagination info
|
|
});
|
|
|
|
// View toggle functionality
|
|
$('#table-view-btn').click(function() {
|
|
$(this).addClass('active');
|
|
$('#card-view-btn').removeClass('active');
|
|
$('#calendar-view-btn').removeClass('active');
|
|
$('#table-view').show();
|
|
$('#card-view').hide();
|
|
$('#calendar-view').hide();
|
|
localStorage.setItem('schedule-view', 'table');
|
|
});
|
|
|
|
$('#card-view-btn').click(function() {
|
|
$(this).addClass('active');
|
|
$('#table-view-btn').removeClass('active');
|
|
$('#calendar-view-btn').removeClass('active');
|
|
$('#table-view').hide();
|
|
$('#card-view').show();
|
|
$('#calendar-view').hide();
|
|
localStorage.setItem('schedule-view', 'card');
|
|
});
|
|
|
|
$('#calendar-view-btn').click(function() {
|
|
$(this).addClass('active');
|
|
$('#table-view-btn').removeClass('active');
|
|
$('#card-view-btn').removeClass('active');
|
|
$('#table-view').hide();
|
|
$('#card-view').hide();
|
|
$('#calendar-view').show();
|
|
localStorage.setItem('schedule-view', 'calendar');
|
|
|
|
// Initialize calendar if not already done
|
|
if (!calendarInitialized) {
|
|
initializeCalendar();
|
|
calendarInitialized = true;
|
|
}
|
|
});
|
|
|
|
// Load saved view preference
|
|
var savedView = localStorage.getItem('schedule-view');
|
|
var calendarInitialized = false;
|
|
|
|
if (savedView === 'card') {
|
|
$('#card-view-btn').click();
|
|
} else if (savedView === 'calendar') {
|
|
$('#calendar-view-btn').click();
|
|
}
|
|
|
|
// Initialize Calendar
|
|
function initializeCalendar() {
|
|
var calendarEl = document.getElementById('schedule-calendar');
|
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
|
initialView: 'dayGridMonth',
|
|
headerToolbar: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'dayGridMonth,timeGridWeek,listMonth'
|
|
},
|
|
events: [
|
|
{% for schedule in all_schedules %}
|
|
{
|
|
title: '{{ schedule.name }}',
|
|
start: '{{ schedule.start_date|date:"Y-m-d" }}',
|
|
end: '{{ schedule.end_date|date:"Y-m-d" }}',
|
|
url: '{% url "hr:schedule_detail" schedule.id %}',
|
|
backgroundColor: '{% if schedule.status == "PUBLISHED" %}#3788d8{% elif schedule.status == "DRAFT" %}#ffc107{% else %}#6c757d{% endif %}',
|
|
borderColor: '{% if schedule.status == "PUBLISHED" %}#3788d8{% elif schedule.status == "DRAFT" %}#ffc107{% else %}#6c757d{% endif %}',
|
|
textColor: '{% if schedule.status == "DRAFT" %}#212529{% else %}#ffffff{% endif %}',
|
|
extendedProps: {
|
|
department: '{{ schedule.department.name|default:"All Departments" }}',
|
|
status: '{{ schedule.status }}'
|
|
}
|
|
}{% if not forloop.last %},{% endif %}
|
|
{% endfor %}
|
|
],
|
|
eventDidMount: function(info) {
|
|
$(info.el).tooltip({
|
|
title: info.event.title + ' (' + info.event.extendedProps.department + ')',
|
|
placement: 'top',
|
|
trigger: 'hover',
|
|
container: 'body'
|
|
});
|
|
}
|
|
});
|
|
calendar.render();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|