working on translation
This commit is contained in:
parent
93eac071ab
commit
e1a55d7633
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -149,7 +149,7 @@ def candidate_user_required(view_func):
|
||||
|
||||
|
||||
def staff_user_required(view_func):
|
||||
|
||||
|
||||
"""Decorator to restrict view to staff users only."""
|
||||
return user_type_required(['staff'])(view_func)
|
||||
|
||||
|
||||
@ -1310,7 +1310,18 @@ class ScheduledInterview(Base):
|
||||
models.Index(fields=["application", "job"]),
|
||||
]
|
||||
|
||||
|
||||
@property
|
||||
def get_schedule_type(self):
|
||||
if self.schedule:
|
||||
return self.schedule.schedule_interview_type
|
||||
else:
|
||||
return self.interview_location.location_type
|
||||
@property
|
||||
def get_schedule_status(self):
|
||||
return self.status
|
||||
@property
|
||||
def get_meeting_details(self):
|
||||
return self.interview_location
|
||||
# --- 3. Interview Notes Model (Fixed) ---
|
||||
|
||||
class InterviewNote(Base):
|
||||
|
||||
@ -442,6 +442,11 @@ urlpatterns = [
|
||||
views.candidate_application_detail,
|
||||
name="candidate_application_detail",
|
||||
),
|
||||
# path(
|
||||
# "candidate/<slug:application_slug>/applications/<slug:person_slug>/detail/<slug:agency_slug>/",
|
||||
# views.candidate_application_detail,
|
||||
# name="candidate_application_detail",
|
||||
# ),
|
||||
path(
|
||||
"portal/dashboard/",
|
||||
views.agency_portal_dashboard,
|
||||
@ -661,4 +666,8 @@ urlpatterns = [
|
||||
# Detail View (assuming slug is on ScheduledInterview)
|
||||
path("interviews/meetings/<slug:slug>/", views.meeting_details, name="meeting_details"),
|
||||
|
||||
# Email invitation URLs
|
||||
path("interviews/meetings/<slug:slug>/send-candidate-invitation/", views.send_candidate_invitation, name="send_candidate_invitation"),
|
||||
path("interviews/meetings/<slug:slug>/send-participants-invitation/", views.send_participants_invitation, name="send_participants_invitation"),
|
||||
|
||||
]
|
||||
|
||||
@ -129,7 +129,6 @@ from .models import (
|
||||
Source,
|
||||
Message,
|
||||
Document,
|
||||
OnsiteLocationDetails,
|
||||
InterviewLocation,
|
||||
InterviewNote
|
||||
)
|
||||
@ -264,7 +263,7 @@ class ZoomMeetingCreateView(StaffRequiredMixin, CreateView):
|
||||
instance.save()
|
||||
messages.success(self.request, result["message"])
|
||||
|
||||
return redirect(reverse("list_meetings"))
|
||||
return redirect(reverse("list_meetings"))
|
||||
else:
|
||||
messages.error(self.request, result["message"])
|
||||
return redirect(
|
||||
@ -1964,8 +1963,6 @@ def candidate_update_status(request, slug):
|
||||
@staff_user_required
|
||||
def candidate_interview_view(request, slug):
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
|
||||
|
||||
context = {
|
||||
"job": job,
|
||||
"candidates": job.interview_candidates,
|
||||
@ -1975,8 +1972,6 @@ def candidate_interview_view(request, slug):
|
||||
return render(request, "recruitment/candidate_interview_view.html", context)
|
||||
|
||||
|
||||
|
||||
|
||||
@staff_user_required
|
||||
def candidate_document_review_view(request, slug):
|
||||
"""
|
||||
@ -4096,11 +4091,16 @@ def candidate_application_detail(request, slug):
|
||||
return redirect("account_login")
|
||||
|
||||
# Get candidate profile (Person record)
|
||||
try:
|
||||
candidate = request.user.person_profile
|
||||
except:
|
||||
messages.error(request, "No candidate profile found.")
|
||||
return redirect("account_login")
|
||||
agency = getattr(request.user,"agency_profile",None)
|
||||
if agency:
|
||||
candidate = get_object_or_404(Application,slug=slug)
|
||||
# if Application.objects.filter(person=candidate,hirin).exists()
|
||||
else:
|
||||
try:
|
||||
candidate = request.user.person_profile
|
||||
except:
|
||||
messages.error(request, "No candidate profile found.")
|
||||
return redirect("account_login")
|
||||
|
||||
# Get the specific application and verify it belongs to this candidate
|
||||
application = get_object_or_404(
|
||||
@ -4110,7 +4110,7 @@ def candidate_application_detail(request, slug):
|
||||
'scheduled_interviews' # Only prefetch interviews, not documents (Generic FK)
|
||||
),
|
||||
slug=slug,
|
||||
person=candidate
|
||||
person=candidate.person if agency else candidate
|
||||
)
|
||||
|
||||
# Get AI analysis data if available
|
||||
@ -4676,11 +4676,11 @@ def message_create(request):
|
||||
message.sender = request.user
|
||||
message.save()
|
||||
# Send email if message_type is 'email' and recipient has email
|
||||
|
||||
|
||||
if message.recipient and message.recipient.email:
|
||||
|
||||
|
||||
try:
|
||||
|
||||
|
||||
|
||||
email_result = async_task('recruitment.tasks._task_send_individual_email',
|
||||
subject=message.subject,
|
||||
@ -4693,23 +4693,23 @@ def message_create(request):
|
||||
if email_result:
|
||||
messages.success(request, "Message sent successfully via email!")
|
||||
else:
|
||||
|
||||
|
||||
messages.warning(request, f"email failed: {email_result.get('message', 'Unknown error')}")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
|
||||
messages.warning(request, f"Message saved but email sending failed: {str(e)}")
|
||||
else:
|
||||
|
||||
|
||||
messages.success(request, "Message sent successfully!")
|
||||
|
||||
|
||||
|
||||
return redirect("message_list")
|
||||
else:
|
||||
|
||||
|
||||
messages.error(request, "Please correct the errors below.")
|
||||
else:
|
||||
|
||||
|
||||
form = MessageForm(request.user)
|
||||
|
||||
context = {
|
||||
@ -4759,7 +4759,7 @@ def message_reply(request, message_id):
|
||||
if email_result:
|
||||
messages.success(request, "Message sent successfully via email!")
|
||||
else:
|
||||
|
||||
|
||||
messages.warning(request, f"email failed: {email_result.get('message', 'Unknown error')}")
|
||||
|
||||
except Exception as e:
|
||||
@ -4989,7 +4989,7 @@ def document_upload(request, slug):
|
||||
if upload_target == 'person':
|
||||
return redirect("candidate_portal_dashboard")
|
||||
else:
|
||||
return redirect("candidate_application_detail", slug=application.slug)
|
||||
return redirect("candidate_application_detail", application_slug=application.slug)
|
||||
|
||||
# Handle GET request for AJAX
|
||||
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
||||
@ -5763,7 +5763,7 @@ class MeetingListView(ListView):
|
||||
template_name = "meetings/list_meetings.html"
|
||||
context_object_name = "meetings"
|
||||
paginate_by = 100
|
||||
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
# Start with a base queryset, ensuring an InterviewLocation link exists.
|
||||
@ -5778,7 +5778,7 @@ class MeetingListView(ListView):
|
||||
)
|
||||
|
||||
# Note: Printing the queryset here can consume memory for large sets.
|
||||
|
||||
|
||||
# Get filters from GET request
|
||||
search_query = self.request.GET.get("q")
|
||||
status_filter = self.request.GET.get("status")
|
||||
@ -5790,11 +5790,11 @@ class MeetingListView(ListView):
|
||||
if type_filter:
|
||||
# Use .title() to handle case variations from URL (e.g., 'remote' -> 'Remote')
|
||||
normalized_type = type_filter.title()
|
||||
|
||||
|
||||
# Assuming InterviewLocation.LocationType is accessible/defined
|
||||
if normalized_type in ['Remote', 'Onsite']:
|
||||
queryset = queryset.filter(interview_location__location_type=normalized_type)
|
||||
|
||||
|
||||
# 3. Search by Topic (stored on InterviewLocation)
|
||||
if search_query:
|
||||
queryset = queryset.filter(interview_location__topic__icontains=search_query)
|
||||
@ -5876,8 +5876,8 @@ class MeetingListView(ListView):
|
||||
# model = InterviewLocation
|
||||
# template_name = "meetings/list_meetings.html"
|
||||
# context_object_name = "meetings"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# def get_queryset(self):
|
||||
# # Start with a base queryset, ensuring an InterviewLocation link exists.
|
||||
@ -5888,7 +5888,7 @@ class MeetingListView(ListView):
|
||||
# print(queryset)
|
||||
|
||||
# return queryset
|
||||
|
||||
|
||||
|
||||
def reschedule_onsite_meeting(request, slug, candidate_id, meeting_id):
|
||||
"""Handles the rescheduling of an Onsite Interview (updates OnsiteLocationDetails)."""
|
||||
@ -6108,3 +6108,152 @@ def meeting_details(request, slug):
|
||||
|
||||
return render(request, 'interviews/detail_interview.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def send_candidate_invitation(request, slug):
|
||||
"""Send invitation email to the candidate"""
|
||||
meeting = get_object_or_404(InterviewLocation, slug=slug)
|
||||
|
||||
try:
|
||||
interview = meeting.scheduled_interview
|
||||
except ScheduledInterview.DoesNotExist:
|
||||
raise Http404("No interview is associated with this meeting.")
|
||||
|
||||
candidate = interview.application
|
||||
job = interview.job
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
|
||||
# Simple email content
|
||||
subject = f"Interview Invitation - {job.title}"
|
||||
message = f"""
|
||||
Dear {candidate.person.first_name} {candidate.person.last_name},
|
||||
|
||||
You are invited for an interview for the position of {job.title}.
|
||||
|
||||
Meeting Details:
|
||||
- Date: {interview.interview_date}
|
||||
- Time: {interview.interview_time}
|
||||
- Duration: {meeting.duration or 60} minutes
|
||||
"""
|
||||
|
||||
# Add join URL if it's a Zoom meeting
|
||||
if hasattr(meeting, 'zoommeetingdetails') and meeting.zoommeetingdetails.join_url:
|
||||
message += f"- Join URL: {meeting.zoommeetingdetails.join_url}\n"
|
||||
|
||||
# Add physical address if it's an onsite meeting
|
||||
if hasattr(meeting, 'onsitelocationdetails') and meeting.onsitelocationdetails.physical_address:
|
||||
message += f"- Location: {meeting.onsitelocationdetails.physical_address}\n"
|
||||
if meeting.onsitelocationdetails.room_number:
|
||||
message += f"- Room: {meeting.onsitelocationdetails.room_number}\n"
|
||||
|
||||
message += """
|
||||
Please confirm your attendance.
|
||||
|
||||
Best regards,
|
||||
KAAUH Recruitment Team
|
||||
"""
|
||||
|
||||
# Send email
|
||||
send_mail(
|
||||
subject,
|
||||
message,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
[candidate.person.email],
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
messages.success(request, f"Invitation email sent to {candidate.person.email}")
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Failed to send invitation email: {str(e)}")
|
||||
|
||||
return redirect('meeting_details', slug=slug)
|
||||
|
||||
|
||||
@login_required
|
||||
def send_participants_invitation(request, slug):
|
||||
"""Send invitation email to all participants"""
|
||||
meeting = get_object_or_404(InterviewLocation, slug=slug)
|
||||
|
||||
try:
|
||||
interview = meeting.scheduled_interview
|
||||
except ScheduledInterview.DoesNotExist:
|
||||
raise Http404("No interview is associated with this meeting.")
|
||||
|
||||
candidate = interview.application
|
||||
job = interview.job
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
|
||||
# Get all participants
|
||||
participants = list(interview.participants.all())
|
||||
system_users = list(interview.system_users.all())
|
||||
all_participants = participants + system_users
|
||||
|
||||
if not all_participants:
|
||||
messages.warning(request, "No participants found to send invitation to.")
|
||||
return redirect('meeting_details', slug=slug)
|
||||
|
||||
# Simple email content
|
||||
subject = f"Interview Invitation - {job.title} with {candidate.person.first_name} {candidate.person.last_name}"
|
||||
message = f"""
|
||||
Dear Team Member,
|
||||
|
||||
You are invited to participate in an interview session.
|
||||
|
||||
Interview Details:
|
||||
- Candidate: {candidate.person.first_name} {candidate.person.last_name}
|
||||
- Position: {job.title}
|
||||
- Date: {interview.interview_date}
|
||||
- Time: {interview.interview_time}
|
||||
- Duration: {meeting.duration or 60} minutes
|
||||
"""
|
||||
|
||||
# Add join URL if it's a Zoom meeting
|
||||
if hasattr(meeting, 'zoommeetingdetails') and meeting.zoommeetingdetails.join_url:
|
||||
message += f"- Join URL: {meeting.zoommeetingdetails.join_url}\n"
|
||||
|
||||
# Add physical address if it's an onsite meeting
|
||||
if hasattr(meeting, 'onsitelocationdetails') and meeting.onsitelocationdetails.physical_address:
|
||||
message += f"- Location: {meeting.onsitelocationdetails.physical_address}\n"
|
||||
if meeting.onsitelocationdetails.room_number:
|
||||
message += f"- Room: {meeting.onsitelocationdetails.room_number}\n"
|
||||
|
||||
message += """
|
||||
Please confirm your availability.
|
||||
|
||||
Best regards,
|
||||
KAAUH Recruitment Team
|
||||
"""
|
||||
|
||||
# Get email addresses of all participants
|
||||
recipient_emails = []
|
||||
for participant in all_participants:
|
||||
if hasattr(participant, 'email') and participant.email:
|
||||
recipient_emails.append(participant.email)
|
||||
|
||||
if recipient_emails:
|
||||
# Send email to all participants
|
||||
send_mail(
|
||||
subject,
|
||||
message,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_emails,
|
||||
fail_silently=False,
|
||||
)
|
||||
|
||||
messages.success(request, f"Invitation emails sent to {len(recipient_emails)} participants")
|
||||
else:
|
||||
messages.warning(request, "No valid email addresses found for participants.")
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Failed to send invitation emails: {str(e)}")
|
||||
|
||||
return redirect('meeting_details', slug=slug)
|
||||
|
||||
@ -171,12 +171,12 @@
|
||||
title="{% trans 'Your account' %}">
|
||||
{% else %}
|
||||
<div class="profile-avatar" title="{% trans 'Your account' %}">
|
||||
{{ user.username|first|upper }}
|
||||
{{ user.first_name }} {{ user.last_name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</button>
|
||||
<ul
|
||||
|
||||
<ul
|
||||
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
|
||||
style="min-width: 240px;"
|
||||
>
|
||||
@ -242,10 +242,6 @@
|
||||
<span style="color:red;">{% trans "Sign Out" %}</span>
|
||||
</button>
|
||||
</form>
|
||||
{% comment %} <a class="d-inline text-decoration-none px-4 d-flex align-items-center border-0 bg-transparent text-start text-center" href={% url "account_logout" %}>
|
||||
<i class="fas fa-sign-out-alt me-3 fs-5 " style="color:red;"></i>
|
||||
<span style="color:red;">{% trans "Sign Out" %}</span>
|
||||
</a> {% endcomment %}
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
@ -226,7 +226,7 @@ body { background-color: #f0f2f5; font-family: 'Inter', sans-serif; }
|
||||
</div>
|
||||
|
||||
{# --- PARTICIPANTS --- #}
|
||||
<div class="row g-4 mt-1 mb-5">
|
||||
{% comment %} <div class="row g-4 mt-1 mb-5">
|
||||
<div class="col-lg-12">
|
||||
<div class="p-3 bg-white rounded shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
@ -275,7 +275,7 @@ body { background-color: #f0f2f5; font-family: 'Inter', sans-serif; }
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
|
||||
{# --- COMMENTS --- #}
|
||||
<div class="row g-4 mt-1">
|
||||
|
||||
@ -216,6 +216,15 @@ body {
|
||||
<a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary-teal btn-sm">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Meeting" %}
|
||||
</a>
|
||||
|
||||
{# Send Candidate Invitation Button #}
|
||||
<form method="post" action="{% url 'send_candidate_invitation' meeting.slug %}" style="display: inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-info btn-sm" onclick="return confirm('{% trans "Send invitation email to the candidate?" %}')">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Candidate Invitation" %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{# DELETE MEETING FORM #}
|
||||
<form method="post" action="{% url 'delete_meeting' meeting.slug %}" style="display: inline;">
|
||||
{% csrf_token %}
|
||||
@ -273,7 +282,7 @@ body {
|
||||
{% if meeting.join_url %}
|
||||
<div class="join-url-container pt-3">
|
||||
<div id="copy-message" class="text-white rounded px-2 py-1 small fw-bold mb-2 text-center" style="opacity: 0; transition: opacity 0.3s; position: absolute; right: 0; top: 5px; background-color: var(--kaauh-success); z-index: 10;">{% trans "Copied!" %}</div>
|
||||
|
||||
|
||||
<div class="join-url-display d-flex justify-content-between align-items-center position-relative">
|
||||
<div class="text-truncate me-2">
|
||||
<strong>{% trans "Join URL" %}:</strong>
|
||||
@ -298,7 +307,7 @@ body {
|
||||
|
||||
|
||||
{# --- PARTICIPANTS TABLE --- #}
|
||||
<div class="col-lg-12">
|
||||
{% comment %} <div class="col-lg-12">
|
||||
<div class="p-3 bg-white rounded shadow-sm">
|
||||
<div class="d-flex justify-content-between align-item-center" >
|
||||
<h2 class="text-start"><i class="fas fa-users-cog me-2"></i> {% trans "Assigned Participants" %}</h2>
|
||||
@ -310,6 +319,14 @@ body {
|
||||
<i class="fas fa-users-cog me-1"></i> {% trans "Manage Participants" %} ({{total_participants}})
|
||||
</button>
|
||||
|
||||
{# Send Participants Invitation Button #}
|
||||
<form method="post" action="{% url 'send_participants_invitation' meeting.slug %}" style="display: inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-info btn-sm me-2" onclick="return confirm('{% trans "Send invitation email to all participants?" %}')">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Participants Invitation" %}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<button type="button" class="btn btn-outline-info"
|
||||
data-bs-toggle="modal"
|
||||
title="Send Interview Emails"
|
||||
@ -352,7 +369,7 @@ body {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
</div>
|
||||
|
||||
{# ========================================================= #}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{% extends "portal_base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% if form.instance.pk %}Reply to Message{% else %}Compose Message{% endif %}{% endblock %}
|
||||
{% block title %}{% if form.instance.pk %}{% trans "Reply to Message"%}{% else %}{% trans"Compose Message"%}{% endif %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
|
||||
@ -194,7 +194,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
function updateCounter() {
|
||||
const remaining = maxLength - subjectField.value.length;
|
||||
counter.textContent = `${subjectField.value.length}/${maxLength} characters`;
|
||||
counter.textContent = `{% blocktrans %}{{ remaining }}/{{ maxLength }} characters{% endblocktrans %}`;
|
||||
if (remaining < 20) {
|
||||
counter.className = 'text-warning';
|
||||
} else {
|
||||
@ -236,3 +236,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -228,3 +228,4 @@ setInterval(() => {
|
||||
}, 30000);
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -264,6 +264,9 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'candidate_application_detail' candidate.slug %}" class="btn btn-sm btn-outline-primary" title="{% trans 'View Profile' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="editCandidate({{ candidate.id }})" title="{% trans 'Edit Candidate' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
/* Kaauh Theme Variables - Assuming these are defined in portal_base */
|
||||
:root {
|
||||
/* Assuming these are carried from your global CSS/base template */
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-primary-text: #343a40;
|
||||
@ -31,11 +31,11 @@
|
||||
.application-progress {
|
||||
position: relative;
|
||||
/* Use flexbox for layout */
|
||||
display: flex;
|
||||
display: flex;
|
||||
/* Use gap for consistent space between elements */
|
||||
gap: 1.5rem;
|
||||
gap: 1.5rem;
|
||||
/* Center the timeline content */
|
||||
justify-content: center;
|
||||
justify-content: center;
|
||||
margin: 2rem 0 3rem; /* Extra spacing below timeline */
|
||||
padding: 0 1rem;
|
||||
overflow-x: auto; /* Allow horizontal scroll for small screens */
|
||||
@ -46,9 +46,9 @@
|
||||
position: relative;
|
||||
text-align: center;
|
||||
/* Prevent shrinking */
|
||||
flex-shrink: 0;
|
||||
flex-shrink: 0;
|
||||
/* Added min-width for label spacing */
|
||||
min-width: 100px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
/* Timeline Connector Line */
|
||||
@ -57,7 +57,7 @@
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
/* Adjust position to connect centered steps */
|
||||
left: calc(-50% + 20px);
|
||||
left: calc(-50% + 20px);
|
||||
width: calc(100% - 40px);
|
||||
height: 2px;
|
||||
background: var(--kaauh-border);
|
||||
@ -75,7 +75,7 @@
|
||||
|
||||
.progress-step.active::before {
|
||||
/* Line leading up to the active step should be completed/success color */
|
||||
background: var(--kaauh-success);
|
||||
background: var(--kaauh-success);
|
||||
}
|
||||
|
||||
.progress-icon {
|
||||
@ -102,7 +102,7 @@
|
||||
background: var(--kaauh-teal);
|
||||
color: white;
|
||||
/* Add a subtle shadow for focus */
|
||||
box-shadow: 0 0 0 4px rgba(0, 99, 110, 0.2);
|
||||
box-shadow: 0 0 0 4px rgba(0, 99, 110, 0.2);
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
@ -178,7 +178,9 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="container-fluid">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
{% if not application.hiring_agency %}
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
@ -195,12 +197,13 @@
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="row mb-4">
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="kaauh-card">
|
||||
<div class="card-header bg-primary-theme text-white">
|
||||
<div class="card-header bg-primary-theme text-white">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-8">
|
||||
<h4 class="mb-1">
|
||||
@ -220,7 +223,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-5">
|
||||
<div class="card-body pt-5">
|
||||
<div class="application-progress">
|
||||
<div class="progress-step {% if application.stage != 'Applied' and application.stage != 'Rejected' %}completed{% else %}active{% endif %}" style="flex: 0 0 auto;">
|
||||
<div class="progress-icon">
|
||||
@ -275,9 +278,9 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="row mt-4 g-4">
|
||||
<div class="row mt-4 g-4">
|
||||
<div class="col-md-3">
|
||||
<div class="text-center p-3 bg-light rounded h-100 shadow-sm">
|
||||
<div class="text-center p-3 bg-light rounded h-100 shadow-sm">
|
||||
<i class="fas fa-calendar-alt text-primary-theme fa-2x mb-2"></i>
|
||||
<h6 class="text-muted small">{% trans "Applied Date" %}</h6>
|
||||
<p class="mb-0 fw-bold">{{ application.created_at|date:"M d, Y" }}</p>
|
||||
@ -309,8 +312,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-5">
|
||||
|
||||
<div class="row mb-5">
|
||||
<div class="col-md-6 col-6">
|
||||
<a href="{% url 'candidate_portal_dashboard' %}" class="text-decoration-none text-dark">
|
||||
<div class="kaauh-card h-50 shadow-sm action-card">
|
||||
@ -327,15 +330,15 @@
|
||||
</div>
|
||||
|
||||
{% if application.resume %}
|
||||
<div class="col-md-6 col-6">
|
||||
<a href="{{ application.resume.url }}"
|
||||
<div class="col-md-6 col-6">
|
||||
<a href="{{ application.resume.url }}"
|
||||
target="_blank" class="text-decoration-none text-dark">
|
||||
<div class="kaauh-card h-50 shadow-sm action-card">
|
||||
<div class="card-body text-center mb-4">
|
||||
<i class="fas fa-file-download fa-2x text-primary-theme mb-3"></i>
|
||||
<h6>{% trans "Download Resume" %}</h6>
|
||||
<p class="text-muted small">{% trans "Get your submitted file" %}</p>
|
||||
<a href="{{ application.resume.url }}"
|
||||
<a href="{{ application.resume.url }}"
|
||||
target="_blank"
|
||||
class="btn btn-main-action btn-sm w-100">
|
||||
<i class="fas fa-download me-2"></i>
|
||||
@ -346,25 +349,25 @@
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
{% if interviews %}
|
||||
<div class="row mb-5">
|
||||
<div class="row mb-5">
|
||||
<div class="col-12">
|
||||
<div class="kaauh-card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<div class="card-header bg-primary-theme text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-video me-2"></i>
|
||||
{% trans "Interview Schedule" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="card-body p-4">
|
||||
{% if interviews %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>{% trans "Date" %}</th>
|
||||
@ -381,28 +384,28 @@
|
||||
<td>{{ interview.interview_date|date:"M d, Y" }}</td>
|
||||
<td>{{ interview.interview_time|time:"H:i" }}</td>
|
||||
<td>
|
||||
{% if interview.zoom_meeting %}
|
||||
<span class="badge bg-primary-theme">
|
||||
{% if interview.get_schedule_type == 'Remote' %}
|
||||
<span class="badge bg-primary-theme">
|
||||
<i class="fas fa-laptop me-1"></i>
|
||||
{% trans "Remote" %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">
|
||||
<span class="badge bg-primary-theme">
|
||||
<i class="fas fa-building me-1"></i>
|
||||
{% trans "On-site" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-{{ interview.status|lower }} text-white">
|
||||
{{ interview.get_status_display }}
|
||||
<span class="badg ">
|
||||
{{ interview.get_schedule_status }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if interview.zoom_meeting and interview.zoom_meeting.join_url %}
|
||||
<a href="{{ interview.zoom_meeting.join_url }}"
|
||||
{% if interview.get_meeting_details and interview.get_schedule_type == 'Remote' %}
|
||||
<a href="{{ interview.get_meeting_details }}"
|
||||
target="_blank"
|
||||
class="btn btn-sm btn-primary">
|
||||
class="btn btn-sm bg-primary-theme text-white">
|
||||
<i class="fas fa-video me-1"></i>
|
||||
{% trans "Join" %}
|
||||
</a>
|
||||
@ -436,7 +439,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if application.stage == "Document Review" %}
|
||||
<div class="row mb-5">
|
||||
<div class="row mb-5">
|
||||
<div class="col-12">
|
||||
<div class="kaauh-card">
|
||||
<div class="card-header bg-primary-theme text-white">
|
||||
@ -455,7 +458,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="card-body p-4">
|
||||
{% if documents %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
@ -475,7 +478,7 @@
|
||||
{% if document.file %}
|
||||
<a href="{{ document.file.url }}"
|
||||
target="_blank"
|
||||
class="text-decoration-none text-primary-theme">
|
||||
class="text-decoration-none text-primary-theme">
|
||||
<i class="fas fa-file-pdf text-danger me-2"></i>
|
||||
{{ document.get_document_type_display }}
|
||||
</a>
|
||||
@ -533,6 +536,62 @@
|
||||
{% endif %}
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="kaauh-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-arrow-left fa-2x text-primary-theme mb-3"></i>
|
||||
<h6>{% trans "Back to Dashboard" %}</h6>
|
||||
<p class="text-muted small">{% trans "View all your applications" %}</p>
|
||||
{% if application.hiring_agency %}
|
||||
<a href="{% url 'agency_portal_dashboard' %}" class="btn btn-main-action w-100">
|
||||
{% trans "Go Back" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'candidate_portal_dashboard' %}" class="btn btn-main-action w-100">
|
||||
{% trans "Go to Dashboard" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if application.resume %}
|
||||
<div class="col-md-3 mb-3">
|
||||
<div class="kaauh-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-download fa-2x text-primary-theme mb-3"></i>
|
||||
<h6>{% trans "Download Resume" %}</h6>
|
||||
<p class="text-muted small">{% trans "Get your submitted resume" %}</p>
|
||||
<a href="{{ application.resume.url }}"
|
||||
target="_blank"
|
||||
class="btn btn-main-action w-100">
|
||||
<i class="fas fa-download me-2"></i>
|
||||
{% trans "Download" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% comment %} <div class="col-md-3 mb-3">
|
||||
<div class="kaauh-card h-100">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-edit fa-2x text-warning mb-3"></i>
|
||||
<h6>{% trans "Update Profile" %}</h6>
|
||||
<p class="text-muted small">{% trans "Edit your personal information" %}</p>
|
||||
<a href="" class="btn btn-main-action w-100">
|
||||
<i class="fas fa-edit me-2"></i>
|
||||
{% trans "Update" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
</div>
|
||||
|
||||
<!-- Next Steps Section -->
|
||||
{% comment %} <div class="row">
|
||||
<div class="col-12">
|
||||
<div class="kaauh-card">
|
||||
<div class="card-header bg-primary-theme text-white">
|
||||
@ -541,7 +600,7 @@
|
||||
{% trans "Next Steps" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div class="card-body p-4">
|
||||
{% if application.stage == 'Applied' %}
|
||||
<div class="alert bg-primary-theme text-white">
|
||||
<i class="fas fa-clock me-2"></i>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user