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

532 lines
24 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}
{% if object %}Edit Time Entry{% else %}Create Time Entry{% endif %}
{% endblock %}
{% block css %}
<!-- DateTimePicker CSS -->
<link href="{% static 'plugins/bootstrap-datepicker/css/bootstrap-datepicker.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/bootstrap-timepicker/css/bootstrap-timepicker.min.css' %}" rel="stylesheet" />
<!-- Select2 CSS -->
<link href="{% static 'plugins/select2/dist/css/select2.min.css' %}" rel="stylesheet" />
<link href="{% static 'plugins/select2-bootstrap-5-theme/dist/select2-bootstrap-5-theme.min.css' %}" rel="stylesheet" />
<style>
.help-sidebar {
background-color: #f8f9fa;
border-radius: 5px;
padding: 15px;
}
.form-floating > .form-control:focus ~ label,
.form-floating > .form-control:not(:placeholder-shown) ~ label {
opacity: .65;
transform: scale(.85) translateY(-.5rem) translateX(.15rem);
}
.time-type-selector {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 15px;
}
.time-type-option {
flex: 1;
min-width: 120px;
text-align: center;
padding: 15px 10px;
border: 2px solid #dee2e6;
border-radius: 5px;
cursor: pointer;
}
.time-type-option.selected {
border-color: #2d62ed;
background-color: rgba(45, 98, 237, 0.1);
}
.conflict-warning {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px;
margin-bottom: 20px;
display: none;
}
</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:time_entry_list' %}">Time Entries</a></li>
<li class="breadcrumb-item active">
{% if object %}Edit Time Entry{% else %}Create Time Entry{% endif %}
</li>
</ol>
<!-- end breadcrumb -->
<!-- begin page-header -->
<h1 class="page-header">
{% if object %}Edit Time Entry{% else %}Create Time Entry{% endif %}
</h1>
<!-- end page-header -->
<!-- begin row -->
<div class="row">
<!-- begin col-8 -->
<div class="col-xl-8">
<!-- begin panel -->
<div class="panel panel-inverse">
<!-- begin panel-heading -->
<div class="panel-heading">
<h4 class="panel-title">
{% if object %}Edit Time Entry for {{ object.employee.get_full_name }}{% else %}Create New Time Entry{% endif %}
</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">
<!-- Conflict Warning -->
<div class="conflict-warning" id="conflict-warning">
<div class="d-flex">
<div class="me-3">
<i class="fas fa-exclamation-triangle fa-2x text-warning"></i>
</div>
<div>
<h5 class="mb-1">Potential Time Conflict</h5>
<p class="mb-0" id="conflict-message">This employee already has a time entry during this period.</p>
</div>
</div>
</div>
<form method="post" id="time-entry-form">
{% csrf_token %}
<!-- Form Errors -->
{% if form.errors %}
<div class="alert alert-danger">
<h5><i class="fas fa-exclamation-circle"></i> Please correct the errors below:</h5>
<ul class="mb-0">
{% for field in form %}
{% for error in field.errors %}
<li>{{ field.label }}: {{ error }}</li>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
<!-- Basic Information -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Basic Information</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="{{ form.employee.id_for_label }}" class="form-label">Employee <span class="text-danger">*</span></label>
{{ form.employee }}
{% if form.employee.errors %}
<div class="invalid-feedback d-block">
{{ form.employee.errors }}
</div>
{% endif %}
</div>
<div class="col-md-6">
<label for="{{ form.date.id_for_label }}" class="form-label">Date <span class="text-danger">*</span></label>
<div class="input-group">
{{ form.date }}
<span class="input-group-text"><i class="fas fa-calendar"></i></span>
</div>
{% if form.date.errors %}
<div class="invalid-feedback d-block">
{{ form.date.errors }}
</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="{{ form.schedule_assignment.id_for_label }}" class="form-label">Schedule Assignment</label>
{{ form.schedule_assignment }}
{% if form.schedule_assignment.errors %}
<div class="invalid-feedback d-block">
{{ form.schedule_assignment.errors }}
</div>
{% endif %}
<div class="form-text">Optional: Link this time entry to a schedule assignment</div>
</div>
<div class="col-md-6">
<label for="{{ form.location.id_for_label }}" class="form-label">Location</label>
{{ form.location }}
{% if form.location.errors %}
<div class="invalid-feedback d-block">
{{ form.location.errors }}
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Time Information -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Time Information</h5>
</div>
<div class="card-body">
<!-- Time Type -->
<div class="mb-3">
<label class="form-label">Time Type</label>
<div class="time-type-selector">
<div class="time-type-option selected" data-type="regular">
<i class="fas fa-clock fa-2x mb-2"></i>
<div>Regular Time</div>
</div>
<div class="time-type-option" data-type="overtime">
<i class="fas fa-business-time fa-2x mb-2"></i>
<div>Overtime</div>
</div>
</div>
<div class="form-check form-switch d-none">
{{ form.is_overtime }}
<label class="form-check-label" for="{{ form.is_overtime.id_for_label }}">
Overtime
</label>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="{{ form.start_time.id_for_label }}" class="form-label">Start Time <span class="text-danger">*</span></label>
<div class="input-group">
{{ form.start_time }}
<span class="input-group-text"><i class="fas fa-clock"></i></span>
</div>
{% if form.start_time.errors %}
<div class="invalid-feedback d-block">
{{ form.start_time.errors }}
</div>
{% endif %}
</div>
<div class="col-md-6">
<label for="{{ form.end_time.id_for_label }}" class="form-label">End Time <span class="text-danger">*</span></label>
<div class="input-group">
{{ form.end_time }}
<span class="input-group-text"><i class="fas fa-clock"></i></span>
</div>
{% if form.end_time.errors %}
<div class="invalid-feedback d-block">
{{ form.end_time.errors }}
</div>
{% endif %}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="{{ form.hours.id_for_label }}" class="form-label">Hours <span class="text-danger">*</span></label>
{{ form.hours }}
{% if form.hours.errors %}
<div class="invalid-feedback d-block">
{{ form.hours.errors }}
</div>
{% endif %}
<div class="form-text">Automatically calculated from start and end times</div>
</div>
<div class="col-md-6">
<label for="{{ form.break_duration.id_for_label }}" class="form-label">Break Duration (minutes)</label>
{{ form.break_duration }}
{% if form.break_duration.errors %}
<div class="invalid-feedback d-block">
{{ form.break_duration.errors }}
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Additional Information -->
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Additional Information</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="{{ form.description.id_for_label }}" class="form-label">Description</label>
{{ form.description }}
{% if form.description.errors %}
<div class="invalid-feedback d-block">
{{ form.description.errors }}
</div>
{% endif %}
<div class="form-text">Provide details about the work performed during this time period</div>
</div>
{% if perms.hr.approve_time_entry and not object %}
<div class="form-check mb-3">
{{ form.auto_approve }}
<label class="form-check-label" for="{{ form.auto_approve.id_for_label }}">
Auto-approve this time entry
</label>
</div>
{% endif %}
</div>
</div>
<!-- Form Actions -->
<div class="d-flex justify-content-between mt-4">
<a href="{% url 'hr:time_entry_list' %}" class="btn btn-secondary">
<i class="fas fa-times"></i> Cancel
</a>
<div>
<button type="submit" name="save_and_add" class="btn btn-info me-2">
<i class="fas fa-save"></i> Save & Add Another
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Save Time Entry
</button>
</div>
</div>
</form>
</div>
<!-- end panel-body -->
</div>
<!-- end panel -->
</div>
<!-- end col-8 -->
<!-- begin col-4 -->
<div class="col-xl-4">
<!-- Help Sidebar -->
<div class="help-sidebar">
<h5><i class="fas fa-info-circle"></i> Help & Guidelines</h5>
<div class="card mb-3">
<div class="card-header">
<h6 class="card-title mb-0">Time Entry Tips</h6>
</div>
<div class="card-body">
<ul class="mb-0">
<li>Enter the actual start and end times</li>
<li>Hours will be calculated automatically</li>
<li>Include break time if applicable</li>
<li>Link to a schedule assignment if available</li>
<li>Provide a detailed description of work performed</li>
</ul>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h6 class="card-title mb-0">Overtime Guidelines</h6>
</div>
<div class="card-body">
<ul class="mb-0">
<li>Select "Overtime" for hours worked beyond regular schedule</li>
<li>Overtime requires manager approval</li>
<li>Ensure overtime was pre-approved when possible</li>
<li>Provide detailed justification for overtime hours</li>
</ul>
</div>
</div>
<div class="card">
<div class="card-header">
<h6 class="card-title mb-0">Approval Process</h6>
</div>
<div class="card-body">
<ol class="mb-0">
<li>Submit time entry</li>
<li>Manager reviews submission</li>
<li>Time entry is approved or rejected</li>
<li>If rejected, you'll need to update and resubmit</li>
</ol>
</div>
</div>
</div>
</div>
<!-- end col-4 -->
</div>
<!-- end row -->
{% endblock %}
{% block js %}
<!-- DateTimePicker JS -->
<script src="{% static 'plugins/bootstrap-datepicker/js/bootstrap-datepicker.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-timepicker/js/bootstrap-timepicker.min.js' %}"></script>
<!-- Select2 JS -->
<script src="{% static 'plugins/select2/dist/js/select2.min.js' %}"></script>
<script>
$(document).ready(function() {
// Initialize Select2
$('#{{ form.employee.id_for_label }}, #{{ form.schedule_assignment.id_for_label }}').select2({
theme: 'bootstrap-5',
placeholder: "Select an option"
});
// Initialize DatePicker
$('#{{ form.date.id_for_label }}').datepicker({
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true
});
// Initialize TimePicker
$('#{{ form.start_time.id_for_label }}, #{{ form.end_time.id_for_label }}').timepicker({
showMeridian: false,
defaultTime: false
});
// Calculate hours based on start and end time
function calculateHours() {
var startTime = $('#{{ form.start_time.id_for_label }}').val();
var endTime = $('#{{ form.end_time.id_for_label }}').val();
var breakDuration = parseInt($('#{{ form.break_duration.id_for_label }}').val()) || 0;
if (startTime && endTime) {
// Parse times
var start = parseTime(startTime);
var end = parseTime(endTime);
// Calculate hours
var hours;
if (end < start) {
// Overnight shift
hours = (24 - start/60) + (end/60);
} else {
hours = (end - start) / 60;
}
// Subtract break duration
hours -= breakDuration / 60;
// Ensure hours is not negative
hours = Math.max(0, hours);
$('#{{ form.hours.id_for_label }}').val(hours.toFixed(2));
}
}
// Parse time string to minutes
function parseTime(timeStr) {
var parts = timeStr.split(':');
return parseInt(parts[0]) * 60 + parseInt(parts[1]);
}
// Update hours when times or break duration change
$('#{{ form.start_time.id_for_label }}, #{{ form.end_time.id_for_label }}, #{{ form.break_duration.id_for_label }}').change(function() {
calculateHours();
});
// Time type selector
$('.time-type-option').click(function() {
$('.time-type-option').removeClass('selected');
$(this).addClass('selected');
var type = $(this).data('type');
if (type === 'overtime') {
$('#{{ form.is_overtime.id_for_label }}').prop('checked', true);
} else {
$('#{{ form.is_overtime.id_for_label }}').prop('checked', false);
}
});
// Initialize time type selector based on form value
{% if form.is_overtime.value %}
$('.time-type-option[data-type="overtime"]').click();
{% endif %}
// Check for conflicts when employee, date, start time, or end time changes
$('#{{ form.employee.id_for_label }}, #{{ form.date.id_for_label }}, #{{ form.start_time.id_for_label }}, #{{ form.end_time.id_for_label }}').change(function() {
checkForConflicts();
});
function checkForConflicts() {
var employeeId = $('#{{ form.employee.id_for_label }}').val();
var date = $('#{{ form.date.id_for_label }}').val();
var startTime = $('#{{ form.start_time.id_for_label }}').val();
var endTime = $('#{{ form.end_time.id_for_label }}').val();
if (employeeId && date && startTime && endTime) {
$.ajax({
url: '{% url "hr:check_time_entry_conflicts" %}',
data: {
employee_id: employeeId,
date: date,
start_time: startTime,
end_time: endTime,
{% if object %}
exclude_id: '{{ object.id }}',
{% endif %}
},
success: function(data) {
if (data.conflicts) {
$('#conflict-message').text(data.message);
$('#conflict-warning').show();
} else {
$('#conflict-warning').hide();
}
}
});
}
}
// Load schedule assignments when employee changes
$('#{{ form.employee.id_for_label }}').change(function() {
var employeeId = $(this).val();
var date = $('#{{ form.date.id_for_label }}').val();
if (employeeId && date) {
$.ajax({
url: '{% url "hr:get_employee_schedule_assignments" %}',
data: {
employee_id: employeeId,
date: date
},
success: function(data) {
var select = $('#{{ form.schedule_assignment.id_for_label }}');
select.empty();
select.append('<option value="">Select an assignment</option>');
$.each(data.assignments, function(index, assignment) {
select.append('<option value="' + assignment.id + '">' + assignment.shift_type + ' (' + assignment.start_time + ' - ' + assignment.end_time + ')</option>');
});
}
});
}
});
// Populate times when schedule assignment is selected
$('#{{ form.schedule_assignment.id_for_label }}').change(function() {
var assignmentId = $(this).val();
if (assignmentId) {
$.ajax({
url: '{% url "hr:get_schedule_assignment_details" %}',
data: {
assignment_id: assignmentId
},
success: function(data) {
$('#{{ form.start_time.id_for_label }}').val(data.start_time);
$('#{{ form.end_time.id_for_label }}').val(data.end_time);
calculateHours();
}
});
}
});
});
</script>
{% endblock %}