hospital-management/templates/hr/schedule_management.html
2025-08-12 13:33:25 +03:00

1089 lines
46 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Schedule Management{% endblock %}
{% block content %}
<div id="content" class="app-content">
<div class="container">
<ul class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'core:dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'hr:dashboard' %}">HR</a></li>
<li class="breadcrumb-item active">Schedule Management</li>
</ul>
<div class="row align-items-center mb-3">
<div class="col">
<h1 class="page-header">Schedule Management</h1>
<p class="text-muted">Employee scheduling and shift management system</p>
</div>
<div class="col-auto">
<div class="btn-group">
<button class="btn btn-primary" onclick="createSchedule()">
<i class="fa fa-plus me-2"></i>New Schedule
</button>
<button class="btn btn-outline-secondary" onclick="importSchedule()">
<i class="fa fa-upload me-2"></i>Import
</button>
<button class="btn btn-outline-info" onclick="exportSchedule()">
<i class="fa fa-download me-2"></i>Export
</button>
<button class="btn btn-outline-success" onclick="publishSchedule()">
<i class="fa fa-share me-2"></i>Publish
</button>
</div>
</div>
</div>
<!-- Schedule Overview -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h4 class="mb-0">{{ total_employees }}</h4>
<p class="mb-0">Total Employees</p>
</div>
<div class="ms-3">
<i class="fa fa-users fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-success text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h4 class="mb-0">{{ scheduled_today }}</h4>
<p class="mb-0">Scheduled Today</p>
</div>
<div class="ms-3">
<i class="fa fa-calendar-check fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-warning text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h4 class="mb-0">{{ open_shifts }}</h4>
<p class="mb-0">Open Shifts</p>
</div>
<div class="ms-3">
<i class="fa fa-exclamation-triangle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card bg-info text-white">
<div class="card-body">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<h4 class="mb-0">{{ coverage_percentage }}%</h4>
<p class="mb-0">Coverage Rate</p>
</div>
<div class="ms-3">
<i class="fa fa-chart-pie fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Schedule Controls -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">Schedule Controls</h4>
<div class="card-tools">
<div class="btn-group">
<button class="btn btn-outline-primary btn-sm" onclick="autoSchedule()">
<i class="fa fa-magic me-1"></i>Auto Schedule
</button>
<button class="btn btn-outline-warning btn-sm" onclick="checkConflicts()">
<i class="fa fa-exclamation-triangle me-1"></i>Check Conflicts
</button>
<button class="btn btn-outline-info btn-sm" onclick="optimizeSchedule()">
<i class="fa fa-cogs me-1"></i>Optimize
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-2">
<label class="form-label">View Type</label>
<select class="form-select" id="viewType" onchange="changeView()">
<option value="week" {% if view_type == 'week' %}selected{% endif %}>Week View</option>
<option value="month" {% if view_type == 'month' %}selected{% endif %}>Month View</option>
<option value="day" {% if view_type == 'day' %}selected{% endif %}>Day View</option>
<option value="employee" {% if view_type == 'employee' %}selected{% endif %}>Employee View</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Department</label>
<select class="form-select" id="departmentFilter" onchange="filterSchedule()">
<option value="">All Departments</option>
{% for dept in departments %}
<option value="{{ dept.id }}" {% if selected_department == dept.id|stringformat:"s" %}selected{% endif %}>{{ dept.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label class="form-label">Shift Type</label>
<select class="form-select" id="shiftFilter" onchange="filterSchedule()">
<option value="">All Shifts</option>
<option value="day" {% if selected_shift == 'day' %}selected{% endif %}>Day Shift</option>
<option value="evening" {% if selected_shift == 'evening' %}selected{% endif %}>Evening Shift</option>
<option value="night" {% if selected_shift == 'night' %}selected{% endif %}>Night Shift</option>
<option value="weekend" {% if selected_shift == 'weekend' %}selected{% endif %}>Weekend</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Status</label>
<select class="form-select" id="statusFilter" onchange="filterSchedule()">
<option value="">All Status</option>
<option value="draft" {% if selected_status == 'draft' %}selected{% endif %}>Draft</option>
<option value="published" {% if selected_status == 'published' %}selected{% endif %}>Published</option>
<option value="confirmed" {% if selected_status == 'confirmed' %}selected{% endif %}>Confirmed</option>
<option value="completed" {% if selected_status == 'completed' %}selected{% endif %}>Completed</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Week Starting</label>
<input type="date" class="form-control" id="weekStart" value="{{ current_week_start }}" onchange="changeWeek()">
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<div class="btn-group w-100">
<button class="btn btn-outline-secondary" onclick="previousPeriod()">
<i class="fa fa-chevron-left"></i>
</button>
<button class="btn btn-outline-primary" onclick="goToToday()">Today</button>
<button class="btn btn-outline-secondary" onclick="nextPeriod()">
<i class="fa fa-chevron-right"></i>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Schedule Calendar -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h4 class="card-title">
Schedule Calendar
<span class="badge bg-secondary ms-2">{{ current_period_display }}</span>
</h4>
<div class="card-tools">
<div class="btn-group">
<button class="btn btn-outline-primary btn-sm" onclick="addShift()">
<i class="fa fa-plus me-1"></i>Add Shift
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="copyWeek()">
<i class="fa fa-copy me-1"></i>Copy Week
</button>
<button class="btn btn-outline-info btn-sm" onclick="printSchedule()">
<i class="fa fa-print me-1"></i>Print
</button>
</div>
</div>
</div>
<div class="card-body">
<!-- Week View -->
<div id="weekView" class="schedule-view">
<div class="table-responsive">
<table class="table table-bordered schedule-table">
<thead>
<tr>
<th width="150">Employee</th>
{% for day in week_days %}
<th class="text-center">
<div>{{ day.name }}</div>
<small class="text-muted">{{ day.date|date:"M d" }}</small>
</th>
{% endfor %}
<th width="80">Total Hours</th>
</tr>
</thead>
<tbody>
{% for employee in employees %}
<tr>
<td>
<div class="d-flex align-items-center">
<div class="avatar avatar-sm me-2">
{% if employee.photo %}
<img src="{{ employee.photo.url }}" alt="{{ employee.get_full_name }}" class="rounded-circle">
{% else %}
<div class="avatar-initial rounded-circle bg-primary">
{{ employee.first_name|first }}{{ employee.last_name|first }}
</div>
{% endif %}
</div>
<div>
<h6 class="mb-0">{{ employee.get_full_name }}</h6>
<small class="text-muted">{{ employee.job_title }}</small>
</div>
</div>
</td>
{% for day in week_days %}
<td class="schedule-cell" data-employee="{{ employee.id }}" data-date="{{ day.date|date:'Y-m-d' }}">
{% for shift in employee.shifts_for_day %}
{% if shift.date == day.date %}
<div class="shift-block shift-{{ shift.type }}"
onclick="editShift('{{ shift.id }}')"
title="{{ shift.start_time }} - {{ shift.end_time }}">
<div class="shift-time">{{ shift.start_time|time:"H:i" }} - {{ shift.end_time|time:"H:i" }}</div>
<div class="shift-location">{{ shift.location|default:"" }}</div>
{% if shift.status == 'open' %}
<span class="badge badge-warning">Open</span>
{% elif shift.status == 'conflict' %}
<span class="badge badge-danger">Conflict</span>
{% endif %}
</div>
{% endif %}
{% endfor %}
<button class="btn btn-outline-primary btn-sm add-shift-btn"
onclick="addShiftForEmployee('{{ employee.id }}', '{{ day.date|date:'Y-m-d' }}')"
title="Add shift">
<i class="fa fa-plus"></i>
</button>
</td>
{% endfor %}
<td class="text-center">
<strong>{{ employee.total_hours|default:"0" }}h</strong>
{% if employee.overtime_hours %}
<br><small class="text-warning">+{{ employee.overtime_hours }}h OT</small>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr class="table-secondary">
<th>Daily Totals</th>
{% for day in week_days %}
<th class="text-center">
<div>{{ day.total_employees }} staff</div>
<small class="text-muted">{{ day.total_hours }}h</small>
</th>
{% endfor %}
<th class="text-center">{{ week_total_hours }}h</th>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- Shift Legend -->
<div class="mt-3">
<div class="d-flex flex-wrap gap-3">
<div class="d-flex align-items-center">
<div class="shift-legend shift-day me-2"></div>
<span>Day Shift (7AM-7PM)</span>
</div>
<div class="d-flex align-items-center">
<div class="shift-legend shift-evening me-2"></div>
<span>Evening Shift (3PM-11PM)</span>
</div>
<div class="d-flex align-items-center">
<div class="shift-legend shift-night me-2"></div>
<span>Night Shift (11PM-7AM)</span>
</div>
<div class="d-flex align-items-center">
<div class="shift-legend shift-weekend me-2"></div>
<span>Weekend Shift</span>
</div>
<div class="d-flex align-items-center">
<div class="shift-legend shift-oncall me-2"></div>
<span>On-Call</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Schedule Conflicts -->
{% if conflicts %}
<div class="row mt-4">
<div class="col-12">
<div class="card border-warning">
<div class="card-header bg-warning text-dark">
<h4 class="card-title mb-0">
<i class="fa fa-exclamation-triangle me-2"></i>Schedule Conflicts
</h4>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Employee</th>
<th>Date</th>
<th>Conflict Type</th>
<th>Details</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for conflict in conflicts %}
<tr>
<td>{{ conflict.employee.get_full_name }}</td>
<td>{{ conflict.date|date:"M d, Y" }}</td>
<td>
<span class="badge bg-warning">{{ conflict.get_type_display }}</span>
</td>
<td>{{ conflict.description }}</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" onclick="resolveConflict('{{ conflict.id }}')">
<i class="fa fa-check"></i> Resolve
</button>
<button class="btn btn-outline-secondary" onclick="viewConflictDetails('{{ conflict.id }}')">
<i class="fa fa-eye"></i> Details
</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Add/Edit Shift Modal -->
<div class="modal fade" id="shiftModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="shiftModalTitle">Add Shift</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="shiftForm">
{% csrf_token %}
<input type="hidden" name="shift_id" id="shiftId">
<div class="modal-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Employee <span class="text-danger">*</span></label>
<select class="form-select" name="employee" id="shiftEmployee" required>
<option value="">Select employee...</option>
{% for employee in all_employees %}
<option value="{{ employee.id }}">{{ employee.get_full_name }} ({{ employee.job_title }})</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Date <span class="text-danger">*</span></label>
<input type="date" class="form-control" name="date" id="shiftDate" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Start Time <span class="text-danger">*</span></label>
<input type="time" class="form-control" name="start_time" id="shiftStartTime" required>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">End Time <span class="text-danger">*</span></label>
<input type="time" class="form-control" name="end_time" id="shiftEndTime" required>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">Shift Type</label>
<select class="form-select" name="shift_type" id="shiftType">
<option value="day">Day Shift</option>
<option value="evening">Evening Shift</option>
<option value="night">Night Shift</option>
<option value="weekend">Weekend Shift</option>
<option value="oncall">On-Call</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Location</label>
<select class="form-select" name="location" id="shiftLocation">
<option value="">Select location...</option>
<option value="emergency">Emergency Department</option>
<option value="icu">ICU</option>
<option value="surgery">Surgery</option>
<option value="medical">Medical Ward</option>
<option value="pediatrics">Pediatrics</option>
<option value="maternity">Maternity</option>
<option value="outpatient">Outpatient</option>
<option value="pharmacy">Pharmacy</option>
<option value="lab">Laboratory</option>
<option value="radiology">Radiology</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Role</label>
<input type="text" class="form-control" name="role" id="shiftRole" placeholder="e.g., Charge Nurse, Resident, etc.">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Break Duration (minutes)</label>
<input type="number" class="form-control" name="break_duration" id="shiftBreakDuration" value="30" min="0" max="120">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Status</label>
<select class="form-select" name="status" id="shiftStatus">
<option value="draft">Draft</option>
<option value="published">Published</option>
<option value="confirmed">Confirmed</option>
<option value="completed">Completed</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Notes</label>
<textarea class="form-control" name="notes" id="shiftNotes" rows="3" placeholder="Additional notes or special instructions..."></textarea>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="is_overtime" id="shiftIsOvertime">
<label class="form-check-label" for="shiftIsOvertime">
Overtime shift
</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="requires_certification" id="shiftRequiresCert">
<label class="form-check-label" for="shiftRequiresCert">
Requires special certification
</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary" id="shiftSubmitBtn">
<i class="fa fa-save me-2"></i>Save Shift
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Copy Week Modal -->
<div class="modal fade" id="copyWeekModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Copy Week Schedule</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="copyWeekForm">
{% csrf_token %}
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Source Week</label>
<input type="date" class="form-control" name="source_week" value="{{ current_week_start }}">
</div>
<div class="mb-3">
<label class="form-label">Target Week(s)</label>
<input type="date" class="form-control" name="target_week" required>
<div class="form-text">Select the Monday of the target week</div>
</div>
<div class="mb-3">
<label class="form-label">Copy Options</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="copy_employees" id="copyEmployees" checked>
<label class="form-check-label" for="copyEmployees">
Copy employee assignments
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="copy_times" id="copyTimes" checked>
<label class="form-check-label" for="copyTimes">
Copy shift times
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="copy_locations" id="copyLocations" checked>
<label class="form-check-label" for="copyLocations">
Copy locations and roles
</label>
</div>
</div>
<div class="mb-3">
<label class="form-label">Department Filter</label>
<select class="form-select" name="department_filter">
<option value="">All departments</option>
{% for dept in departments %}
<option value="{{ dept.id }}">{{ dept.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">
<i class="fa fa-copy me-2"></i>Copy Schedule
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
$(document).ready(function() {
setupEventHandlers();
setupDragAndDrop();
loadScheduleData();
});
function setupEventHandlers() {
// Shift form
$('#shiftForm').on('submit', function(e) {
e.preventDefault();
saveShift();
});
// Copy week form
$('#copyWeekForm').on('submit', function(e) {
e.preventDefault();
copyWeekSchedule();
});
// Auto-calculate shift type based on time
$('#shiftStartTime, #shiftEndTime').on('change', function() {
autoDetectShiftType();
});
}
function setupDragAndDrop() {
// Enable drag and drop for shift blocks
$('.shift-block').draggable({
helper: 'clone',
revert: 'invalid',
start: function(event, ui) {
$(this).addClass('dragging');
},
stop: function(event, ui) {
$(this).removeClass('dragging');
}
});
$('.schedule-cell').droppable({
accept: '.shift-block',
hoverClass: 'drop-hover',
drop: function(event, ui) {
var shiftId = ui.draggable.data('shift-id');
var employeeId = $(this).data('employee');
var date = $(this).data('date');
moveShift(shiftId, employeeId, date);
}
});
}
function loadScheduleData() {
// Load additional schedule data via AJAX
var params = {
view_type: $('#viewType').val(),
department: $('#departmentFilter').val(),
shift_type: $('#shiftFilter').val(),
status: $('#statusFilter').val(),
week_start: $('#weekStart').val()
};
$.get('{% url "hr:schedule_data" %}', params, function(data) {
if (data.success) {
updateScheduleDisplay(data);
}
});
}
function createSchedule() {
$('#shiftModalTitle').text('Add New Shift');
$('#shiftId').val('');
$('#shiftForm')[0].reset();
$('#shiftModal').modal('show');
}
function addShift() {
createSchedule();
}
function addShiftForEmployee(employeeId, date) {
$('#shiftModalTitle').text('Add Shift');
$('#shiftId').val('');
$('#shiftForm')[0].reset();
$('#shiftEmployee').val(employeeId);
$('#shiftDate').val(date);
$('#shiftModal').modal('show');
}
function editShift(shiftId) {
$('#shiftModalTitle').text('Edit Shift');
$('#shiftId').val(shiftId);
// Load shift data
$.get('{% url "hr:get_shift_details" %}', {shift_id: shiftId}, function(data) {
if (data.success) {
populateShiftForm(data.shift);
$('#shiftModal').modal('show');
} else {
toastr.error('Failed to load shift details');
}
});
}
function populateShiftForm(shift) {
$('#shiftEmployee').val(shift.employee_id);
$('#shiftDate').val(shift.date);
$('#shiftStartTime').val(shift.start_time);
$('#shiftEndTime').val(shift.end_time);
$('#shiftType').val(shift.shift_type);
$('#shiftLocation').val(shift.location);
$('#shiftRole').val(shift.role);
$('#shiftBreakDuration').val(shift.break_duration);
$('#shiftStatus').val(shift.status);
$('#shiftNotes').val(shift.notes);
$('#shiftIsOvertime').prop('checked', shift.is_overtime);
$('#shiftRequiresCert').prop('checked', shift.requires_certification);
}
function saveShift() {
var formData = $('#shiftForm').serialize();
var url = $('#shiftId').val() ? '{% url "hr:update_shift" %}' : '{% url "hr:create_shift" %}';
$.post(url, formData, function(data) {
if (data.success) {
toastr.success('Shift saved successfully');
$('#shiftModal').modal('hide');
location.reload();
} else {
toastr.error('Failed to save shift: ' + data.error);
}
});
}
function moveShift(shiftId, employeeId, date) {
$.post('{% url "hr:move_shift" %}', {
shift_id: shiftId,
employee_id: employeeId,
date: date,
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function(data) {
if (data.success) {
toastr.success('Shift moved successfully');
location.reload();
} else {
toastr.error('Failed to move shift: ' + data.error);
}
});
}
function autoDetectShiftType() {
var startTime = $('#shiftStartTime').val();
var endTime = $('#shiftEndTime').val();
if (startTime && endTime) {
var start = new Date('2000-01-01 ' + startTime);
var end = new Date('2000-01-01 ' + endTime);
if (start.getHours() >= 7 && start.getHours() < 15) {
$('#shiftType').val('day');
} else if (start.getHours() >= 15 && start.getHours() < 23) {
$('#shiftType').val('evening');
} else {
$('#shiftType').val('night');
}
}
}
function changeView() {
var viewType = $('#viewType').val();
var params = new URLSearchParams(window.location.search);
params.set('view_type', viewType);
window.location.href = '{% url "hr:schedule_management" %}?' + params.toString();
}
function filterSchedule() {
var params = new URLSearchParams();
params.set('department', $('#departmentFilter').val());
params.set('shift_type', $('#shiftFilter').val());
params.set('status', $('#statusFilter').val());
params.set('week_start', $('#weekStart').val());
window.location.href = '{% url "hr:schedule_management" %}?' + params.toString();
}
function changeWeek() {
var weekStart = $('#weekStart').val();
var params = new URLSearchParams(window.location.search);
params.set('week_start', weekStart);
window.location.href = '{% url "hr:schedule_management" %}?' + params.toString();
}
function previousPeriod() {
var currentDate = new Date($('#weekStart').val());
currentDate.setDate(currentDate.getDate() - 7);
$('#weekStart').val(currentDate.toISOString().split('T')[0]);
changeWeek();
}
function nextPeriod() {
var currentDate = new Date($('#weekStart').val());
currentDate.setDate(currentDate.getDate() + 7);
$('#weekStart').val(currentDate.toISOString().split('T')[0]);
changeWeek();
}
function goToToday() {
var today = new Date();
var monday = new Date(today.setDate(today.getDate() - today.getDay() + 1));
$('#weekStart').val(monday.toISOString().split('T')[0]);
changeWeek();
}
function autoSchedule() {
$.post('{% url "hr:auto_schedule" %}', {
week_start: $('#weekStart').val(),
department: $('#departmentFilter').val(),
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function(data) {
if (data.success) {
toastr.success('Auto-scheduling completed');
location.reload();
} else {
toastr.error('Auto-scheduling failed: ' + data.error);
}
});
}
function checkConflicts() {
$.post('{% url "hr:check_schedule_conflicts" %}', {
week_start: $('#weekStart').val(),
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function(data) {
if (data.success) {
if (data.conflicts.length > 0) {
toastr.warning(data.conflicts.length + ' conflicts found');
displayConflicts(data.conflicts);
} else {
toastr.success('No conflicts found');
}
} else {
toastr.error('Failed to check conflicts');
}
});
}
function optimizeSchedule() {
$.post('{% url "hr:optimize_schedule" %}', {
week_start: $('#weekStart').val(),
department: $('#departmentFilter').val(),
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function(data) {
if (data.success) {
toastr.success('Schedule optimized');
location.reload();
} else {
toastr.error('Optimization failed: ' + data.error);
}
});
}
function publishSchedule() {
if (confirm('Are you sure you want to publish this schedule? Employees will be notified.')) {
$.post('{% url "hr:publish_schedule" %}', {
week_start: $('#weekStart').val(),
department: $('#departmentFilter').val(),
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function(data) {
if (data.success) {
toastr.success('Schedule published successfully');
location.reload();
} else {
toastr.error('Failed to publish schedule: ' + data.error);
}
});
}
}
function copyWeek() {
$('#copyWeekModal').modal('show');
}
function copyWeekSchedule() {
var formData = $('#copyWeekForm').serialize();
$.post('{% url "hr:copy_week_schedule" %}', formData, function(data) {
if (data.success) {
toastr.success('Week schedule copied successfully');
$('#copyWeekModal').modal('hide');
location.reload();
} else {
toastr.error('Failed to copy schedule: ' + data.error);
}
});
}
function printSchedule() {
var params = new URLSearchParams(window.location.search);
params.set('print', 'true');
window.open('{% url "hr:schedule_management" %}?' + params.toString(), '_blank');
}
function importSchedule() {
// Implement schedule import functionality
toastr.info('Schedule import feature coming soon');
}
function exportSchedule() {
var params = new URLSearchParams(window.location.search);
params.set('export', 'true');
window.location.href = '{% url "hr:schedule_management" %}?' + params.toString();
}
function resolveConflict(conflictId) {
$.post('{% url "hr:resolve_schedule_conflict" %}', {
conflict_id: conflictId,
csrfmiddlewaretoken: '{{ csrf_token }}'
}, function(data) {
if (data.success) {
toastr.success('Conflict resolved');
location.reload();
} else {
toastr.error('Failed to resolve conflict: ' + data.error);
}
});
}
function viewConflictDetails(conflictId) {
// Implement conflict details view
toastr.info('Conflict details feature coming soon');
}
function updateScheduleDisplay(data) {
// Update the schedule display with new data
// This would be implemented based on the specific data structure
}
function displayConflicts(conflicts) {
// Display conflicts in a modal or alert
var conflictList = conflicts.map(function(conflict) {
return conflict.employee + ': ' + conflict.description;
}).join('\n');
alert('Schedule Conflicts:\n\n' + conflictList);
}
</script>
<style>
.schedule-table {
font-size: 0.875rem;
}
.schedule-cell {
min-height: 80px;
vertical-align: top;
position: relative;
padding: 4px;
}
.shift-block {
background: #e3f2fd;
border: 1px solid #2196f3;
border-radius: 4px;
padding: 4px 6px;
margin-bottom: 2px;
cursor: pointer;
font-size: 0.75rem;
position: relative;
}
.shift-block:hover {
background: #bbdefb;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.shift-day { background: #e8f5e8; border-color: #4caf50; }
.shift-evening { background: #fff3e0; border-color: #ff9800; }
.shift-night { background: #e1f5fe; border-color: #03a9f4; }
.shift-weekend { background: #f3e5f5; border-color: #9c27b0; }
.shift-oncall { background: #fce4ec; border-color: #e91e63; }
.shift-time {
font-weight: 600;
color: #333;
}
.shift-location {
font-size: 0.7rem;
color: #666;
}
.shift-legend {
width: 20px;
height: 12px;
border-radius: 2px;
display: inline-block;
}
.add-shift-btn {
position: absolute;
bottom: 2px;
right: 2px;
width: 24px;
height: 24px;
padding: 0;
font-size: 0.7rem;
opacity: 0;
transition: opacity 0.2s;
}
.schedule-cell:hover .add-shift-btn {
opacity: 1;
}
.drop-hover {
background-color: #f0f8ff;
border: 2px dashed #2196f3;
}
.dragging {
opacity: 0.5;
transform: rotate(5deg);
}
.avatar {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-sm {
width: 24px;
height: 24px;
font-size: 0.75rem;
}
.avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-initial {
background-color: #6c757d;
color: white;
font-weight: 600;
font-size: 0.875rem;
}
.card-tools {
margin-left: auto;
}
.badge {
font-size: 0.7rem;
padding: 2px 6px;
}
.badge-warning {
background-color: #ffc107;
color: #000;
}
.badge-danger {
background-color: #dc3545;
color: #fff;
}
@media (max-width: 768px) {
.schedule-table {
font-size: 0.75rem;
}
.shift-block {
font-size: 0.7rem;
padding: 2px 4px;
}
.schedule-cell {
min-height: 60px;
}
.btn-group {
flex-direction: column;
}
.btn-group .btn {
margin-bottom: 0.25rem;
}
}
.table-responsive {
overflow-x: auto;
}
.schedule-view {
min-height: 400px;
}
.conflict-highlight {
background-color: #ffebee !important;
border: 2px solid #f44336 !important;
}
.overtime-highlight {
background-color: #fff8e1 !important;
border: 2px solid #ff9800 !important;
}
</style>
{% endblock %}