469 lines
21 KiB
HTML
469 lines
21 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
|
|
{% block title %}Time Entry Details | HR Management{% endblock %}
|
|
|
|
{% block css %}
|
|
<style>
|
|
.time-entry-header {
|
|
background-color: #f8f9fa;
|
|
border-radius: 5px;
|
|
padding: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.status-badge {
|
|
font-size: 14px;
|
|
padding: 8px 15px;
|
|
border-radius: 30px;
|
|
}
|
|
.status-pending {
|
|
background-color: #fff3cd;
|
|
color: #856404;
|
|
border: 1px solid #ffeeba;
|
|
}
|
|
.status-approved {
|
|
background-color: #d4edda;
|
|
color: #155724;
|
|
border: 1px solid #c3e6cb;
|
|
}
|
|
.status-rejected {
|
|
background-color: #f8d7da;
|
|
color: #721c24;
|
|
border: 1px solid #f5c6cb;
|
|
}
|
|
.timeline {
|
|
position: relative;
|
|
padding-left: 30px;
|
|
}
|
|
.timeline:before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
height: 100%;
|
|
width: 2px;
|
|
background-color: #e9ecef;
|
|
}
|
|
.timeline-item {
|
|
position: relative;
|
|
padding-bottom: 20px;
|
|
}
|
|
.timeline-item:last-child {
|
|
padding-bottom: 0;
|
|
}
|
|
.timeline-item:before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -34px;
|
|
top: 0;
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 50%;
|
|
background-color: #2d62ed;
|
|
border: 2px solid #fff;
|
|
}
|
|
.timeline-item.approved:before {
|
|
background-color: #28a745;
|
|
}
|
|
.timeline-item.rejected:before {
|
|
background-color: #dc3545;
|
|
}
|
|
.timeline-date {
|
|
color: #6c757d;
|
|
font-size: 12px;
|
|
margin-bottom: 5px;
|
|
}
|
|
.action-card {
|
|
background-color: #f8f9fa;
|
|
border-radius: 5px;
|
|
padding: 15px;
|
|
}
|
|
</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">Time Entry Details</li>
|
|
</ol>
|
|
<!-- end breadcrumb -->
|
|
|
|
<!-- begin page-header -->
|
|
<h1 class="page-header">Time Entry Details</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">Time Entry Information</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">
|
|
<!-- Time Entry Header -->
|
|
<div class="time-entry-header">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="d-flex align-items-center">
|
|
<div class="avatar avatar-lg me-3">
|
|
<img src="{% static 'img/user/default-avatar.jpg' %}" alt="{{ time_entry.employee.get_full_name }}" class="rounded-circle">
|
|
</div>
|
|
<div>
|
|
<h4 class="mb-0">{{ time_entry.employee.get_full_name }}</h4>
|
|
<p class="text-muted mb-0">{{ time_entry.employee.job_title }}</p>
|
|
<p class="text-muted mb-0">
|
|
{% if time_entry.employee.department %}
|
|
{{ time_entry.employee.department.name }}
|
|
{% else %}
|
|
No Department
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 text-md-end mt-3 mt-md-0">
|
|
{% if time_entry.status == 'PENDING' %}
|
|
<span class="status-badge status-pending">
|
|
<i class="fas fa-hourglass-half"></i> Pending Approval
|
|
</span>
|
|
{% elif time_entry.status == 'APPROVED' %}
|
|
<span class="status-badge status-approved">
|
|
<i class="fas fa-check-circle"></i> Approved
|
|
</span>
|
|
{% elif time_entry.status == 'REJECTED' %}
|
|
<span class="status-badge status-rejected">
|
|
<i class="fas fa-times-circle"></i> Rejected
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Time Entry Details -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Time Details</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tbody>
|
|
<tr>
|
|
<th width="40%">Date:</th>
|
|
<td>{{ time_entry.date|date:"F d, Y" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Start Time:</th>
|
|
<td>{{ time_entry.start_time|time:"H:i" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>End Time:</th>
|
|
<td>{{ time_entry.end_time|time:"H:i" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Total Hours:</th>
|
|
<td>
|
|
<strong>{{ time_entry.hours }}</strong>
|
|
{% if time_entry.is_overtime %}
|
|
<span class="badge bg-danger ms-2">Overtime</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<table class="table table-borderless">
|
|
<tbody>
|
|
<tr>
|
|
<th width="40%">Type:</th>
|
|
<td>
|
|
{% if time_entry.is_overtime %}
|
|
Overtime
|
|
{% else %}
|
|
Regular
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Location:</th>
|
|
<td>{{ time_entry.location|default:"Not specified" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Created:</th>
|
|
<td>{{ time_entry.created_at|date:"F d, Y H:i" }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Last Updated:</th>
|
|
<td>{{ time_entry.updated_at|date:"F d, Y H:i" }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
{% if time_entry.description %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Description</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<p class="mb-0">{{ time_entry.description|linebreaks }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Related Schedule Assignment -->
|
|
{% if time_entry.schedule_assignment %}
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Related Schedule Assignment</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<p class="mb-1"><strong>Schedule:</strong> {{ time_entry.schedule_assignment.schedule.name }}</p>
|
|
<p class="mb-1"><strong>Shift Type:</strong> {{ time_entry.schedule_assignment.shift_type }}</p>
|
|
<p class="mb-0"><strong>Status:</strong> {{ time_entry.schedule_assignment.get_status_display }}</p>
|
|
</div>
|
|
<div>
|
|
<a href="{% url 'hr:schedule_assignment_detail' time_entry.schedule_assignment.id %}" class="btn btn-sm btn-primary">
|
|
<i class="fas fa-external-link-alt"></i> View Assignment
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Approval History -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Approval History</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="timeline">
|
|
<div class="timeline-item">
|
|
<div class="timeline-date">{{ time_entry.created_at|date:"F d, Y H:i" }}</div>
|
|
<div class="timeline-content">
|
|
<h6 class="mb-1">Time Entry Created</h6>
|
|
<p class="mb-0">Created by {{ time_entry.created_by.get_full_name }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{% if time_entry.status == 'APPROVED' %}
|
|
<div class="timeline-item approved">
|
|
<div class="timeline-date">{{ time_entry.approved_at|date:"F d, Y H:i" }}</div>
|
|
<div class="timeline-content">
|
|
<h6 class="mb-1">Time Entry Approved</h6>
|
|
<p class="mb-0">Approved by {{ time_entry.approved_by.get_full_name }}</p>
|
|
{% if time_entry.approval_notes %}
|
|
<p class="mb-0 text-muted">{{ time_entry.approval_notes }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% elif time_entry.status == 'REJECTED' %}
|
|
<div class="timeline-item rejected">
|
|
<div class="timeline-date">{{ time_entry.rejected_at|date:"F d, Y H:i" }}</div>
|
|
<div class="timeline-content">
|
|
<h6 class="mb-1">Time Entry Rejected</h6>
|
|
<p class="mb-0">Rejected by {{ time_entry.rejected_by.get_full_name }}</p>
|
|
{% if time_entry.rejection_reason %}
|
|
<p class="mb-0 text-danger">Reason: {{ time_entry.rejection_reason }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if time_entry.updated_at != time_entry.created_at %}
|
|
<div class="timeline-item">
|
|
<div class="timeline-date">{{ time_entry.updated_at|date:"F d, Y H:i" }}</div>
|
|
<div class="timeline-content">
|
|
<h6 class="mb-1">Time Entry Updated</h6>
|
|
<p class="mb-0">Updated by {{ time_entry.updated_by.get_full_name }}</p>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- end panel-body -->
|
|
</div>
|
|
<!-- end panel -->
|
|
</div>
|
|
<!-- end col-8 -->
|
|
|
|
<!-- begin col-4 -->
|
|
<div class="col-xl-4">
|
|
<!-- Actions -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Actions</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-grid gap-2">
|
|
<a href="{% url 'hr:time_entry_update' time_entry.id %}" class="btn btn-primary">
|
|
<i class="fas fa-edit"></i> Edit Time Entry
|
|
</a>
|
|
|
|
{% if time_entry.status == 'PENDING' and perms.hr.approve_time_entry %}
|
|
<a href="{% url 'hr:time_entry_approve' time_entry.id %}" class="btn btn-success">
|
|
<i class="fas fa-check"></i> Approve Time Entry
|
|
</a>
|
|
|
|
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#rejectModal">
|
|
<i class="fas fa-times"></i> Reject Time Entry
|
|
</button>
|
|
{% endif %}
|
|
|
|
<a href="{% url 'hr:time_entry_list' %}" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i> Back to List
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Employee Information -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Employee Information</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="d-flex align-items-center mb-3">
|
|
<div class="avatar avatar-lg me-3">
|
|
<img src="{% static 'img/user/default-avatar.jpg' %}" alt="{{ time_entry.employee.get_full_name }}" class="rounded-circle">
|
|
</div>
|
|
<div>
|
|
<h5 class="mb-0">{{ time_entry.employee.get_full_name }}</h5>
|
|
<p class="text-muted mb-0">{{ time_entry.employee.job_title }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<table class="table table-borderless">
|
|
<tbody>
|
|
<tr>
|
|
<th width="40%">Employee ID:</th>
|
|
<td>{{ time_entry.employee.employee_id }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Department:</th>
|
|
<td>
|
|
{% if time_entry.employee.department %}
|
|
{{ time_entry.employee.department.name }}
|
|
{% else %}
|
|
Not assigned
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Email:</th>
|
|
<td>{{ time_entry.employee.email }}</td>
|
|
</tr>
|
|
<tr>
|
|
<th>Phone:</th>
|
|
<td>{{ time_entry.employee.phone|default:"Not provided" }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="d-grid">
|
|
<a href="{% url 'hr:employee_detail' time_entry.employee.id %}" class="btn btn-sm btn-outline-primary">
|
|
<i class="fas fa-user"></i> View Employee Profile
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Time Summary -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">Time Summary</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<h6>Current Week</h6>
|
|
<div class="d-flex justify-content-between">
|
|
<span>Regular Hours:</span>
|
|
<strong>{{ weekly_regular_hours|default:"0" }}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<span>Overtime Hours:</span>
|
|
<strong>{{ weekly_overtime_hours|default:"0" }}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<span>Total Hours:</span>
|
|
<strong>{{ weekly_total_hours|default:"0" }}</strong>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h6>Current Month</h6>
|
|
<div class="d-flex justify-content-between">
|
|
<span>Regular Hours:</span>
|
|
<strong>{{ monthly_regular_hours|default:"0" }}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<span>Overtime Hours:</span>
|
|
<strong>{{ monthly_overtime_hours|default:"0" }}</strong>
|
|
</div>
|
|
<div class="d-flex justify-content-between">
|
|
<span>Total Hours:</span>
|
|
<strong>{{ monthly_total_hours|default:"0" }}</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- end col-4 -->
|
|
</div>
|
|
<!-- end row -->
|
|
|
|
<!-- Reject Modal -->
|
|
{% if time_entry.status == 'PENDING' and perms.hr.approve_time_entry %}
|
|
<div class="modal fade" id="rejectModal" tabindex="-1" aria-labelledby="rejectModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<form method="post" action="{% url 'hr:time_entry_reject' time_entry.id %}">
|
|
{% csrf_token %}
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="rejectModalLabel">Reject Time Entry</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="rejection_reason" class="form-label">Rejection Reason <span class="text-danger">*</span></label>
|
|
<textarea class="form-control" id="rejection_reason" name="rejection_reason" rows="3" required></textarea>
|
|
<div class="form-text">Please provide a reason for rejecting this time entry.</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-danger">Reject Time Entry</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|
|
|