Files
ihrm/recruitment/templates/candidate/candidate_self_tracking.html

706 lines
29 KiB
HTML

{% load static i18n recruitmentfilters %}
<!DOCTYPE html>
<html lang="en">
<!-- Previous head section remains the same -->
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% trans "Application Tracking" %}</title>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
rel="stylesheet"
/>
<link rel="stylesheet" href="{% static 'build/css/style.min.css' %}" />
<link
rel="stylesheet"
type="text/css"
href="{% static 'css/style.css' %}"
/>
<script src="{% static '/jquery/jquery.min.js' %}"></script>
<style>
body {
background-color: #f8f9fa;
margin: 0;
padding: 0;
min-height: 100vh;
}
.tracking-container {
padding: 1rem;
width: 80%;
margin: 0 auto;
}
@media (min-width: 768px) {
.tracking-container {
padding: 2rem;
}
}
.tracking-card {
position:relative;
padding:1rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
height: 100%;
margin-bottom: 1rem;
}
.profile-image {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 50%;
}
.status-badge {
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.875rem;
display: inline-block;
white-space: nowrap;
margin: 0.5rem 0;
}
.status-badge.pending {
background-color: #fff3cd;
color: #856404;
}
.status-badge.success {
background-color: #d4edda;
color: #155724;
}
.status-badge.danger {
background-color: #f8d7da;
color: #721c24;
}
.status-badge i {
margin-right: 0.5rem;
}
.interview-item {
border-bottom: 1px solid #e9ecef;
padding: 1rem 0;
}
.interview-item:last-child {
border-bottom: none;
}
.interview-date,
.interview-time,
.interview-interviewer {
font-size: 0.875rem;
color: #6c757d;
margin-bottom: 0.5rem;
}
.interview-description {
font-size: 0.875rem;
color: #6c757d;
margin-top: 0.5rem;
padding-left: 0.75rem;
border-left: 3px solid #e9ecef;
}
.interviewer-chip {
display: inline-block;
padding: 0.25rem 0.5rem;
background-color: #e9ecef;
border-radius: 12px;
margin-right: 0.25rem;
margin-bottom: 0.25rem;
font-size: 0.8125rem;
white-space: nowrap;
}
.document-card {
transition: all 0.3s ease;
margin-bottom: 1rem;
}
.document-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.note-card {
border-left: 4px solid #007bff;
margin-bottom: 1rem;
position: relative;
}
.note-close-btn {
position: absolute;
top: 0.5rem;
right: 0.5rem;
width: 28px;
height: 28px;
border-radius: 50%;
background-color: #fff;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
padding: 0;
font-size: 14px;
color: #dc3545;
}
.note-close-btn:hover {
background: #e9ecef;
color: #dc3545;
}
.note-card .card-body {
padding-right: 2.5rem;
}
@media (max-width: 768px) {
.note-close-btn {
width: 32px;
height: 32px;
font-size: 16px;
}
}
.document-list, .notes-list,.interview-list {
height: 400px;
overflow-y : auto;
}
.notes-list {
height: 340px;
}
.status-completed {
color: #28a745;
}
.status-expired {
color: #dc3545;
}
.status-upcoming {
color: #ffc107;
}
.status-today {
color: #007bff;
}
@media (max-width: 576px) {
h2.h4 {
font-size: 1.25rem;
}
h3.h5 {
font-size: 1.125rem;
}
.interview-description {
font-size: 0.8125rem;
}
.status-badge {
font-size: 0.8125rem;
}
}
.row {
--bs-gutter-x: 1rem;
--bs-gutter-y: 1rem;
}
@media (min-width: 768px) {
.row {
--bs-gutter-x: 1.5rem;
--bs-gutter-y: 1.5rem;
}
}
.oh-rate:not(:checked) > label:hover,
.oh-rate:not(:checked) > label:hover ~ label {
color: #ccc;
}
.tracking-card {
padding: 1rem;
}
@media (min-width: 768px) {
.tracking-card {
padding: 1.5rem;
}
}
.btn,
.form-control {
padding: 0.375rem 0.75rem;
}
.bg-orange {
background-color: #e54f38;
}
@media (max-width: 576px) {
.btn,
.form-control {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
}
.d-flex {
gap: 0.5rem;
}
@media (max-width: 576px) {
.btn,
.form-control {
min-height: 44px;
}
.document-card .card-body {
padding: 0.75rem;
}
}
@media (max-width: 767.98px) {
.mobile-section-gap {
margin-bottom: 1.5rem;
}
}
.tracking-card {
margin-bottom: 1.5rem;
}
@media (min-width: 768px) {
.tracking-card {
margin-bottom: 0;
}
}
.custom-dialog {
max-width: 1000px;
max-height: 800px;
}
.oh-not-found {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 50vh;
opacity: 0.5;
}
.file-validation {
color: #4f5bd9;
background-color: #d8e7f0;
border-color: #d6e9c6;
padding: 15px;
border: 1px solid transparent;
border-radius: 4px;
}
.banner-container {
position: absolute;
top: 25px;
right: -43px;
transform: rotate(34deg);
background-color: #40ac40;
color: white;
padding: 8px 76px;
font-size: 12px;
z-index: 2;
clip-path: polygon(26% 0, 81% 0, 91% 94%, 0 115%);
text-align: center;
}
@media (max-width: 768px) {
.banner-container {
top: 20px;
right: -33px;
font-size: 10px;
padding: 6px 56px;
}
}
@media (max-width: 480px) {
.banner-container {
top: 17px;
right: -29px;
font-size: 8px;
padding: 5px 50px;
}
}
</style>
</head>
<body>
{% now "Y-m-d" as now %}
<div id="reloadMessages">{% include "generic/messages.html" %}</div>
<button
id="reloadMessagesButton"
hx-get="{% url 'reload-messages' %}"
hx-swap="afterend"
hidden
hx-target="#reloadMessages"
></button>
<div class="tracking-container">
<div class="row justify-content-center mb-4">
<div class="col-12 col-md-10 col-lg-6">
<div class="tracking-card">
{% if candidate.converted %}
<div class="banner-container fw-bold">
<span>{% trans "Converted" %}</span>
</div>
{% endif %}
<div class="d-flex align-items-center mb-3 flex-wrap">
<img
src="{{ candidate.get_avatar }}"
alt="Profile"
width="50px"
class="profile-image me-3 mb-2"
/>
<div>
<h2 class="h4 mb-1">
{{candidate.get_full_name|title}}
</h2>
{% if check_candidate_self_tracking_rating %}
<div class="d-block mb-0">
<div
class="oh-rate"
>
{% for i in "54321" %}
<input
type="radio"
id="star{{i}}{{candidate.id}}"
name="rating" class="rating-radio"
value="{{i}}" disabled
{% if candidate.candidate_rating.all|avg_rating:candidate == i %}
checked
{% endif %}
/>
<label for="star{{i}}{{candidate.id}}"
>5</label
>
{% endfor %}
</div>
<span id="rating-radio-error"></span>
</div>
{% endif %}
</div>
</div>
<div class="mb-3 d-flex justify-content-between">
<h5 class="text-secondary">
{{ candidate.recruitment_id.title }} /
{{ candidate.stage_id.stage }}
</h5>
<div class="oh-recuritment_tag">
{{ candidate.get_job_position }}
</div>
</div>
{% if candidate.hired or candidate.converted %}
<span class="status-badge success">
<i class="fas fa-check-circle me-2"></i>
{% trans "Congratulation on your selection" %}
</span>
{% elif candidate.canceled %}
<span class="status-badge danger">
<i class="fas fa-times-circle me-2"></i>
{% trans "You not selected" %}
</span>
{% else %}
<span class="status-badge pending">
<i class="fas fa-clock me-2"></i>
{% trans "Application under review" %}
</span>
{% endif %}
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 col-lg-4 order-1 order-md-2">
<div class="tracking-card">
<h3 class="h5 mb-4 fw-bold">{% trans "Documents" %}</h3>
<div class="document-list">
{% for document in candidate.candidatedocument_set.all %}
<div class="document-card card">
<div class="card-body"
hx-get="{% url 'candidate-view-file' document.id %}"
hx-target="#viewFile"
data-toggle="oh-modal-toggle"
data-target="#viewFileModal"
>
<div
class="d-flex justify-content-between align-items-center flex-wrap gap-2"
>
<div>
<h6 class="mb-1">{{document.title}}</h6>
<small class="text-muted" title="{{document.document_request_id.description}}"
>{{document.document_request_id.description|truncatechars:50}}</small
>
</div>
{% if document.document %}
{% if document.status == "approved" %}
<span class="badge text-success p-2"
>{% trans "Approved" %}</span
>
{% elif document.status == "rejected" %}
<span class="badge text-danger p-2"
>{% trans "Rejected" %}</span
>
{% else %}
<span class="badge text-info p-2"
>{% trans "Uploaded" %}</span
>
{% endif %}
{% else %}
<button
class="btn oh-btn--secondary border-0 px-3 py-1 small"
hx-get="{% url 'candidate-file-upload' document.id %}"
hx-target="#objectCreateModalTarget"
data-document-id="{{ document.id }}"
data-toggle="oh-modal-toggle"
data-target="#objectCreateModal"
onclick="event.stopPropagation()"
>
{% trans "Add" %}
</button>
{% endif %}
</div>
</div>
</div>
{% empty %}
<div class="d-flex justify-content-center align-items-center" style="height: 40vh;">
<div class="text-center">
<div style="width:30%; margin:10% auto;">
<img class="oh-404__image mb-3" style="width: 100%;" src="{% static 'images/ui/joiningchart.png' %}" alt="No documents">
</div>
<h5 class="oh-404__subtitle">{% trans "No document requests found." %}</h5>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-12 col-md-6 col-lg-4 order-2 order-md-1">
<div class="tracking-card">
<h3 class="h5 mb-4 fw-bold">{% trans "Scheduled Interviews" %}</h3>
<div class="interview-list">
{% for interview in interviews %}
<div class="interview-item">
<div class="interview-date">
{% trans "Date" %}: {{ interview.interview_date }}
</div>
<div class="interview-time">{% trans "Time" %}: {{ interview.interview_time }}</div>
<div class="interview-interviewer">
{% trans "Interviewer" %}:
<div class="mt-1">
{% for interviewer in interview.employee_id.all %}
<span class="interviewer-chip">
<i
class="fas fa-user-circle me-1"
></i>
{{ interviewer }}
</span>
{% endfor %}
</div>
</div>
<div class="interview-description">
{{ interview.description }}
</div>
{% if interview.completed %}
<div
class="d-flex align-items-center mt-2 status-completed"
>
<i
class="fas fa-check-circle interview-status-icon"
></i>
{% trans "Interview Completed" %}
</div>
{% elif interview.interview_date|date:"Y-m-d" < now %}
<div
class="d-flex align-items-center mt-2 status-completed text-danger"
>
<i
class="fas fa-close interview-status-icon"
></i>
{% trans "Expired Interview" %}
</div>
{% elif interview.interview_date|date:"Y-m-d" > now %}
<div
class="d-flex align-items-center mt-2 status-upcoming"
>
<i
class="fas fa-clock interview-status-icon"
></i>
{% trans "Upcoming Interview" %}
</div>
{% elif interview.interview_date|date:"Y-m-d" == now and not interview.completed %}
<div
class="d-flex align-items-center mt-2 status-today"
>
<i
class="fas fa-calendar-day interview-status-icon"
></i>
{% trans "Interview Today" %}
</div>
{% endif %}
</div>
{% empty %}
<div class="d-flex justify-content-center align-items-center" style="height: 40vh;">
<div>
<div style="width:30%; margin:10% auto;" class="oh-tracking-card">
<img style="width:100% " src="{% static 'images/ui/interview.png' %}"/>
</div>
<h5 class="oh-404__subtitle mt-4 ml-2">{% trans "No interviews are scheduled for this candidate" %}</h5>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-12 col-md-6 col-lg-4 order-3">
<div class="tracking-card" id="note">
{% if messages %}
<div class="oh-wrapper">
{% for message in messages %}
<div class="oh-alert-container">
<div class="oh-alert oh-alert--animated {{message.tags}}">
{{ message }}
</div>
</div>
{% endfor %}
<script>
setTimeout(function () {
$(".oh-modal__close").click();
}, 1000);
</script>
</div>
{% endif %}
<h3 class="h5 mb-4 fw-bold">{% trans "Notes" %}</h3>
<div class="mb-4">
<div class="form-group">
<form
hx-post="{% url 'candidate-add-notes' candidate.id %}"
hx-target="#note"
hx-select ="#note"
hx-swap = "outerHTML"
id="commentForm"
>
{% csrf_token %}
<input
type="text"
name="description"
class="form-control mb-2"
placeholder="Add notes"
/>
<button type="submit" id="commentButton" hidden class="btn btn-primary w-100">
{% trans "Add Note" %}
</button>
</form>
</div>
</div>
<div class="notes-list">
{% for note in candidate.stagenote_set.all reversed %}
{% if note.candidate_can_view %}
<div class="note-card card">
{% if request.user.is_authenticated or note.updated_user == candidate %}
<button class="note-close-btn"
aria-label="Close note"
hx-get="{% url 'note-delete-individual' note.id %}"
title="delete" hx-swap="delete"
hx-target="closest .card"
hx-on:click="setTimeout(() => {reloadMessage(this);},250);"
>
<ion-icon name="close-outline" style="font-size: 24px;color:red;" role="img" class="md hydrated" aria-label="close outline"></ion-icon>
</button>
{% endif %}
<div class="card-body">
<p class="mb-1">
{{ note.description }}
</p>
<small
class="text-muted d-flex align-items-center flex-wrap"
>
<i class="far fa-user me-1"></i>
{{ note.updated_user }}
<span>@</span>
{{note.stage_id }}
</small>
</div>
</div>
{% endif %}
{% empty %}
<div class="d-flex justify-content-center" style="height: 40vh;">
<div class="text-center">
<div style="width:25%; margin:10% auto;">
<img class="oh-404__image mb-3" style="width: 100%;" src="{% static 'images/ui/no_documents.png' %}" alt="No documents">
</div>
<h5 class="oh-404__subtitle">{% trans "No notes have been added for this candidate." %}</h5>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<div
class="oh-modal"
id="viewFileModal"
role="dialog"
aria-labelledby="viewFileModal"
aria-hidden="true"
>
<div class="oh-modal__dialog custom-dialog">
<div class="oh-modal__dialog-header">
<span class="oh-modal__dialog-title" id="viewFileModalLabel"
>{% trans "View File" %}</span
>
<button class="oh-modal__close" aria-label="Close">
<ion-icon
name="close-outline"
role="img"
class="md hydrated"
aria-label="close outline"
></ion-icon>
</button>
</div>
<div class="oh-modal__dialog-body" id="viewFile"></div>
</div>
</div>
<div
class="oh-modal"
id="objectCreateModal"
role="dialog"
aria-labelledby="objectCreateModal"
>
<div class="oh-modal__dialog" id="objectCreateModalTarget"></div>
</div>
<script src="{% static '/index/index.js' %}"></script>
<script src="{% static 'build/js/web.frontend.min.js' %}"></script>
<script src="{% static 'htmx/htmx.min.js' %}"></script>
<script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
</body>
</html>