working on translation

This commit is contained in:
ismail 2025-11-20 18:55:11 +03:00
parent 93eac071ab
commit e1a55d7633
14 changed files with 959 additions and 20205 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -149,7 +149,7 @@ def candidate_user_required(view_func):
def staff_user_required(view_func): def staff_user_required(view_func):
"""Decorator to restrict view to staff users only.""" """Decorator to restrict view to staff users only."""
return user_type_required(['staff'])(view_func) return user_type_required(['staff'])(view_func)

View File

@ -1310,7 +1310,18 @@ class ScheduledInterview(Base):
models.Index(fields=["application", "job"]), 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) --- # --- 3. Interview Notes Model (Fixed) ---
class InterviewNote(Base): class InterviewNote(Base):

View File

@ -442,6 +442,11 @@ urlpatterns = [
views.candidate_application_detail, views.candidate_application_detail,
name="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( path(
"portal/dashboard/", "portal/dashboard/",
views.agency_portal_dashboard, views.agency_portal_dashboard,
@ -661,4 +666,8 @@ urlpatterns = [
# Detail View (assuming slug is on ScheduledInterview) # Detail View (assuming slug is on ScheduledInterview)
path("interviews/meetings/<slug:slug>/", views.meeting_details, name="meeting_details"), 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"),
] ]

View File

@ -129,7 +129,6 @@ from .models import (
Source, Source,
Message, Message,
Document, Document,
OnsiteLocationDetails,
InterviewLocation, InterviewLocation,
InterviewNote InterviewNote
) )
@ -264,7 +263,7 @@ class ZoomMeetingCreateView(StaffRequiredMixin, CreateView):
instance.save() instance.save()
messages.success(self.request, result["message"]) messages.success(self.request, result["message"])
return redirect(reverse("list_meetings")) return redirect(reverse("list_meetings"))
else: else:
messages.error(self.request, result["message"]) messages.error(self.request, result["message"])
return redirect( return redirect(
@ -1964,8 +1963,6 @@ def candidate_update_status(request, slug):
@staff_user_required @staff_user_required
def candidate_interview_view(request, slug): def candidate_interview_view(request, slug):
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
context = { context = {
"job": job, "job": job,
"candidates": job.interview_candidates, "candidates": job.interview_candidates,
@ -1975,8 +1972,6 @@ def candidate_interview_view(request, slug):
return render(request, "recruitment/candidate_interview_view.html", context) return render(request, "recruitment/candidate_interview_view.html", context)
@staff_user_required @staff_user_required
def candidate_document_review_view(request, slug): def candidate_document_review_view(request, slug):
""" """
@ -4096,11 +4091,16 @@ def candidate_application_detail(request, slug):
return redirect("account_login") return redirect("account_login")
# Get candidate profile (Person record) # Get candidate profile (Person record)
try: agency = getattr(request.user,"agency_profile",None)
candidate = request.user.person_profile if agency:
except: candidate = get_object_or_404(Application,slug=slug)
messages.error(request, "No candidate profile found.") # if Application.objects.filter(person=candidate,hirin).exists()
return redirect("account_login") 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 # Get the specific application and verify it belongs to this candidate
application = get_object_or_404( application = get_object_or_404(
@ -4110,7 +4110,7 @@ def candidate_application_detail(request, slug):
'scheduled_interviews' # Only prefetch interviews, not documents (Generic FK) 'scheduled_interviews' # Only prefetch interviews, not documents (Generic FK)
), ),
slug=slug, slug=slug,
person=candidate person=candidate.person if agency else candidate
) )
# Get AI analysis data if available # Get AI analysis data if available
@ -4676,11 +4676,11 @@ def message_create(request):
message.sender = request.user message.sender = request.user
message.save() message.save()
# Send email if message_type is 'email' and recipient has email # Send email if message_type is 'email' and recipient has email
if message.recipient and message.recipient.email: if message.recipient and message.recipient.email:
try: try:
email_result = async_task('recruitment.tasks._task_send_individual_email', email_result = async_task('recruitment.tasks._task_send_individual_email',
subject=message.subject, subject=message.subject,
@ -4693,23 +4693,23 @@ def message_create(request):
if email_result: if email_result:
messages.success(request, "Message sent successfully via email!") messages.success(request, "Message sent successfully via email!")
else: else:
messages.warning(request, f"email failed: {email_result.get('message', 'Unknown error')}") messages.warning(request, f"email failed: {email_result.get('message', 'Unknown error')}")
except Exception as e: except Exception as e:
messages.warning(request, f"Message saved but email sending failed: {str(e)}") messages.warning(request, f"Message saved but email sending failed: {str(e)}")
else: else:
messages.success(request, "Message sent successfully!") messages.success(request, "Message sent successfully!")
return redirect("message_list") return redirect("message_list")
else: else:
messages.error(request, "Please correct the errors below.") messages.error(request, "Please correct the errors below.")
else: else:
form = MessageForm(request.user) form = MessageForm(request.user)
context = { context = {
@ -4759,7 +4759,7 @@ def message_reply(request, message_id):
if email_result: if email_result:
messages.success(request, "Message sent successfully via email!") messages.success(request, "Message sent successfully via email!")
else: else:
messages.warning(request, f"email failed: {email_result.get('message', 'Unknown error')}") messages.warning(request, f"email failed: {email_result.get('message', 'Unknown error')}")
except Exception as e: except Exception as e:
@ -4989,7 +4989,7 @@ def document_upload(request, slug):
if upload_target == 'person': if upload_target == 'person':
return redirect("candidate_portal_dashboard") return redirect("candidate_portal_dashboard")
else: else:
return redirect("candidate_application_detail", slug=application.slug) return redirect("candidate_application_detail", application_slug=application.slug)
# Handle GET request for AJAX # Handle GET request for AJAX
if request.headers.get("X-Requested-With") == "XMLHttpRequest": if request.headers.get("X-Requested-With") == "XMLHttpRequest":
@ -5763,7 +5763,7 @@ class MeetingListView(ListView):
template_name = "meetings/list_meetings.html" template_name = "meetings/list_meetings.html"
context_object_name = "meetings" context_object_name = "meetings"
paginate_by = 100 paginate_by = 100
def get_queryset(self): def get_queryset(self):
# Start with a base queryset, ensuring an InterviewLocation link exists. # 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. # Note: Printing the queryset here can consume memory for large sets.
# Get filters from GET request # Get filters from GET request
search_query = self.request.GET.get("q") search_query = self.request.GET.get("q")
status_filter = self.request.GET.get("status") status_filter = self.request.GET.get("status")
@ -5790,11 +5790,11 @@ class MeetingListView(ListView):
if type_filter: if type_filter:
# Use .title() to handle case variations from URL (e.g., 'remote' -> 'Remote') # Use .title() to handle case variations from URL (e.g., 'remote' -> 'Remote')
normalized_type = type_filter.title() normalized_type = type_filter.title()
# Assuming InterviewLocation.LocationType is accessible/defined # Assuming InterviewLocation.LocationType is accessible/defined
if normalized_type in ['Remote', 'Onsite']: if normalized_type in ['Remote', 'Onsite']:
queryset = queryset.filter(interview_location__location_type=normalized_type) queryset = queryset.filter(interview_location__location_type=normalized_type)
# 3. Search by Topic (stored on InterviewLocation) # 3. Search by Topic (stored on InterviewLocation)
if search_query: if search_query:
queryset = queryset.filter(interview_location__topic__icontains=search_query) queryset = queryset.filter(interview_location__topic__icontains=search_query)
@ -5876,8 +5876,8 @@ class MeetingListView(ListView):
# model = InterviewLocation # model = InterviewLocation
# template_name = "meetings/list_meetings.html" # template_name = "meetings/list_meetings.html"
# context_object_name = "meetings" # context_object_name = "meetings"
# def get_queryset(self): # def get_queryset(self):
# # Start with a base queryset, ensuring an InterviewLocation link exists. # # Start with a base queryset, ensuring an InterviewLocation link exists.
@ -5888,7 +5888,7 @@ class MeetingListView(ListView):
# print(queryset) # print(queryset)
# return queryset # return queryset
def reschedule_onsite_meeting(request, slug, candidate_id, meeting_id): def reschedule_onsite_meeting(request, slug, candidate_id, meeting_id):
"""Handles the rescheduling of an Onsite Interview (updates OnsiteLocationDetails).""" """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) 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)

View File

@ -171,12 +171,12 @@
title="{% trans 'Your account' %}"> title="{% trans 'Your account' %}">
{% else %} {% else %}
<div class="profile-avatar" title="{% trans 'Your account' %}"> <div class="profile-avatar" title="{% trans 'Your account' %}">
{{ user.username|first|upper }} {{ user.first_name }} {{ user.last_name }}
</div> </div>
{% endif %} {% endif %}
</button> </button>
<ul
<ul
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3" class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
style="min-width: 240px;" style="min-width: 240px;"
> >
@ -242,10 +242,6 @@
<span style="color:red;">{% trans "Sign Out" %}</span> <span style="color:red;">{% trans "Sign Out" %}</span>
</button> </button>
</form> </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> </li>
{% endif %} {% endif %}
</ul> </ul>

View File

@ -226,7 +226,7 @@ body { background-color: #f0f2f5; font-family: 'Inter', sans-serif; }
</div> </div>
{# --- PARTICIPANTS --- #} {# --- 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="col-lg-12">
<div class="p-3 bg-white rounded shadow-sm"> <div class="p-3 bg-white rounded shadow-sm">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
@ -275,7 +275,7 @@ body { background-color: #f0f2f5; font-family: 'Inter', sans-serif; }
</table> </table>
</div> </div>
</div> </div>
</div> </div> {% endcomment %}
{# --- COMMENTS --- #} {# --- COMMENTS --- #}
<div class="row g-4 mt-1"> <div class="row g-4 mt-1">

View File

@ -216,6 +216,15 @@ body {
<a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary-teal btn-sm"> <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" %} <i class="fas fa-edit me-1"></i> {% trans "Edit Meeting" %}
</a> </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 #} {# DELETE MEETING FORM #}
<form method="post" action="{% url 'delete_meeting' meeting.slug %}" style="display: inline;"> <form method="post" action="{% url 'delete_meeting' meeting.slug %}" style="display: inline;">
{% csrf_token %} {% csrf_token %}
@ -273,7 +282,7 @@ body {
{% if meeting.join_url %} {% if meeting.join_url %}
<div class="join-url-container pt-3"> <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 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="join-url-display d-flex justify-content-between align-items-center position-relative">
<div class="text-truncate me-2"> <div class="text-truncate me-2">
<strong>{% trans "Join URL" %}:</strong> <strong>{% trans "Join URL" %}:</strong>
@ -298,7 +307,7 @@ body {
{# --- PARTICIPANTS TABLE --- #} {# --- PARTICIPANTS TABLE --- #}
<div class="col-lg-12"> {% comment %} <div class="col-lg-12">
<div class="p-3 bg-white rounded shadow-sm"> <div class="p-3 bg-white rounded shadow-sm">
<div class="d-flex justify-content-between align-item-center" > <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> <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}}) <i class="fas fa-users-cog me-1"></i> {% trans "Manage Participants" %} ({{total_participants}})
</button> </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" <button type="button" class="btn btn-outline-info"
data-bs-toggle="modal" data-bs-toggle="modal"
title="Send Interview Emails" title="Send Interview Emails"
@ -352,7 +369,7 @@ body {
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div> {% endcomment %}
</div> </div>
{# ========================================================= #} {# ========================================================= #}

View File

@ -1,7 +1,7 @@
{% extends "portal_base.html" %} {% extends "portal_base.html" %}
{% load static %} {% 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 %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">

View File

@ -194,7 +194,7 @@ document.addEventListener('DOMContentLoaded', function() {
function updateCounter() { function updateCounter() {
const remaining = maxLength - subjectField.value.length; const remaining = maxLength - subjectField.value.length;
counter.textContent = `${subjectField.value.length}/${maxLength} characters`; counter.textContent = `{% blocktrans %}{{ remaining }}/{{ maxLength }} characters{% endblocktrans %}`;
if (remaining < 20) { if (remaining < 20) {
counter.className = 'text-warning'; counter.className = 'text-warning';
} else { } else {
@ -236,3 +236,4 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -228,3 +228,4 @@ setInterval(() => {
}, 30000); }, 30000);
</script> </script>
{% endblock %} {% endblock %}

View File

@ -264,6 +264,9 @@
</div> </div>
</td> </td>
<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' %}"> <button class="btn btn-sm btn-outline-primary" onclick="editCandidate({{ candidate.id }})" title="{% trans 'Edit Candidate' %}">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>

View File

@ -8,7 +8,7 @@
/* Kaauh Theme Variables - Assuming these are defined in portal_base */ /* Kaauh Theme Variables - Assuming these are defined in portal_base */
:root { :root {
/* Assuming these are carried from your global CSS/base template */ /* Assuming these are carried from your global CSS/base template */
--kaauh-teal: #00636e; --kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53; --kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3; --kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40; --kaauh-primary-text: #343a40;
@ -31,11 +31,11 @@
.application-progress { .application-progress {
position: relative; position: relative;
/* Use flexbox for layout */ /* Use flexbox for layout */
display: flex; display: flex;
/* Use gap for consistent space between elements */ /* Use gap for consistent space between elements */
gap: 1.5rem; gap: 1.5rem;
/* Center the timeline content */ /* Center the timeline content */
justify-content: center; justify-content: center;
margin: 2rem 0 3rem; /* Extra spacing below timeline */ margin: 2rem 0 3rem; /* Extra spacing below timeline */
padding: 0 1rem; padding: 0 1rem;
overflow-x: auto; /* Allow horizontal scroll for small screens */ overflow-x: auto; /* Allow horizontal scroll for small screens */
@ -46,9 +46,9 @@
position: relative; position: relative;
text-align: center; text-align: center;
/* Prevent shrinking */ /* Prevent shrinking */
flex-shrink: 0; flex-shrink: 0;
/* Added min-width for label spacing */ /* Added min-width for label spacing */
min-width: 100px; min-width: 100px;
} }
/* Timeline Connector Line */ /* Timeline Connector Line */
@ -57,7 +57,7 @@
position: absolute; position: absolute;
top: 20px; top: 20px;
/* Adjust position to connect centered steps */ /* Adjust position to connect centered steps */
left: calc(-50% + 20px); left: calc(-50% + 20px);
width: calc(100% - 40px); width: calc(100% - 40px);
height: 2px; height: 2px;
background: var(--kaauh-border); background: var(--kaauh-border);
@ -75,7 +75,7 @@
.progress-step.active::before { .progress-step.active::before {
/* Line leading up to the active step should be completed/success color */ /* Line leading up to the active step should be completed/success color */
background: var(--kaauh-success); background: var(--kaauh-success);
} }
.progress-icon { .progress-icon {
@ -102,7 +102,7 @@
background: var(--kaauh-teal); background: var(--kaauh-teal);
color: white; color: white;
/* Add a subtle shadow for focus */ /* 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 { .progress-label {
@ -178,7 +178,9 @@
{% endblock %} {% endblock %}
{% block content %} {% 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"> <nav aria-label="breadcrumb" class="mb-4">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"> <li class="breadcrumb-item">
@ -195,12 +197,13 @@
</li> </li>
</ol> </ol>
</nav> </nav>
{% endif %}
<div class="row mb-4"> <div class="row mb-4">
<div class="col-12"> <div class="col-12">
<div class="kaauh-card"> <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="row align-items-center">
<div class="col-md-8"> <div class="col-md-8">
<h4 class="mb-1"> <h4 class="mb-1">
@ -220,7 +223,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-body pt-5"> <div class="card-body pt-5">
<div class="application-progress"> <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-step {% if application.stage != 'Applied' and application.stage != 'Rejected' %}completed{% else %}active{% endif %}" style="flex: 0 0 auto;">
<div class="progress-icon"> <div class="progress-icon">
@ -275,9 +278,9 @@
{% endif %} {% endif %}
</div> </div>
<div class="row mt-4 g-4"> <div class="row mt-4 g-4">
<div class="col-md-3"> <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> <i class="fas fa-calendar-alt text-primary-theme fa-2x mb-2"></i>
<h6 class="text-muted small">{% trans "Applied Date" %}</h6> <h6 class="text-muted small">{% trans "Applied Date" %}</h6>
<p class="mb-0 fw-bold">{{ application.created_at|date:"M d, Y" }}</p> <p class="mb-0 fw-bold">{{ application.created_at|date:"M d, Y" }}</p>
@ -309,8 +312,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-5"> <div class="row mb-5">
<div class="col-md-6 col-6"> <div class="col-md-6 col-6">
<a href="{% url 'candidate_portal_dashboard' %}" class="text-decoration-none text-dark"> <a href="{% url 'candidate_portal_dashboard' %}" class="text-decoration-none text-dark">
<div class="kaauh-card h-50 shadow-sm action-card"> <div class="kaauh-card h-50 shadow-sm action-card">
@ -327,15 +330,15 @@
</div> </div>
{% if application.resume %} {% if application.resume %}
<div class="col-md-6 col-6"> <div class="col-md-6 col-6">
<a href="{{ application.resume.url }}" <a href="{{ application.resume.url }}"
target="_blank" class="text-decoration-none text-dark"> target="_blank" class="text-decoration-none text-dark">
<div class="kaauh-card h-50 shadow-sm action-card"> <div class="kaauh-card h-50 shadow-sm action-card">
<div class="card-body text-center mb-4"> <div class="card-body text-center mb-4">
<i class="fas fa-file-download fa-2x text-primary-theme mb-3"></i> <i class="fas fa-file-download fa-2x text-primary-theme mb-3"></i>
<h6>{% trans "Download Resume" %}</h6> <h6>{% trans "Download Resume" %}</h6>
<p class="text-muted small">{% trans "Get your submitted file" %}</p> <p class="text-muted small">{% trans "Get your submitted file" %}</p>
<a href="{{ application.resume.url }}" <a href="{{ application.resume.url }}"
target="_blank" target="_blank"
class="btn btn-main-action btn-sm w-100"> class="btn btn-main-action btn-sm w-100">
<i class="fas fa-download me-2"></i> <i class="fas fa-download me-2"></i>
@ -346,25 +349,25 @@
</a> </a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if interviews %} {% if interviews %}
<div class="row mb-5"> <div class="row mb-5">
<div class="col-12"> <div class="col-12">
<div class="kaauh-card"> <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"> <h5 class="mb-0">
<i class="fas fa-video me-2"></i> <i class="fas fa-video me-2"></i>
{% trans "Interview Schedule" %} {% trans "Interview Schedule" %}
</h5> </h5>
</div> </div>
<div class="card-body p-4"> <div class="card-body p-4">
{% if interviews %} {% if interviews %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover align-middle"> <table class="table table-hover align-middle">
<thead class="table-light"> <thead class="table-light">
<tr> <tr>
<th>{% trans "Date" %}</th> <th>{% trans "Date" %}</th>
@ -381,28 +384,28 @@
<td>{{ interview.interview_date|date:"M d, Y" }}</td> <td>{{ interview.interview_date|date:"M d, Y" }}</td>
<td>{{ interview.interview_time|time:"H:i" }}</td> <td>{{ interview.interview_time|time:"H:i" }}</td>
<td> <td>
{% if interview.zoom_meeting %} {% if interview.get_schedule_type == 'Remote' %}
<span class="badge bg-primary-theme"> <span class="badge bg-primary-theme">
<i class="fas fa-laptop me-1"></i> <i class="fas fa-laptop me-1"></i>
{% trans "Remote" %} {% trans "Remote" %}
</span> </span>
{% else %} {% else %}
<span class="badge bg-secondary"> <span class="badge bg-primary-theme">
<i class="fas fa-building me-1"></i> <i class="fas fa-building me-1"></i>
{% trans "On-site" %} {% trans "On-site" %}
</span> </span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
<span class="badge bg-{{ interview.status|lower }} text-white"> <span class="badg ">
{{ interview.get_status_display }} {{ interview.get_schedule_status }}
</span> </span>
</td> </td>
<td> <td>
{% if interview.zoom_meeting and interview.zoom_meeting.join_url %} {% if interview.get_meeting_details and interview.get_schedule_type == 'Remote' %}
<a href="{{ interview.zoom_meeting.join_url }}" <a href="{{ interview.get_meeting_details }}"
target="_blank" 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> <i class="fas fa-video me-1"></i>
{% trans "Join" %} {% trans "Join" %}
</a> </a>
@ -436,7 +439,7 @@
{% endif %} {% endif %}
{% if application.stage == "Document Review" %} {% if application.stage == "Document Review" %}
<div class="row mb-5"> <div class="row mb-5">
<div class="col-12"> <div class="col-12">
<div class="kaauh-card"> <div class="kaauh-card">
<div class="card-header bg-primary-theme text-white"> <div class="card-header bg-primary-theme text-white">
@ -455,7 +458,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card-body p-4"> <div class="card-body p-4">
{% if documents %} {% if documents %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover align-middle"> <table class="table table-hover align-middle">
@ -475,7 +478,7 @@
{% if document.file %} {% if document.file %}
<a href="{{ document.file.url }}" <a href="{{ document.file.url }}"
target="_blank" 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> <i class="fas fa-file-pdf text-danger me-2"></i>
{{ document.get_document_type_display }} {{ document.get_document_type_display }}
</a> </a>
@ -533,6 +536,62 @@
{% endif %} {% endif %}
<div class="row mb-4"> <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="col-12">
<div class="kaauh-card"> <div class="kaauh-card">
<div class="card-header bg-primary-theme text-white"> <div class="card-header bg-primary-theme text-white">
@ -541,7 +600,7 @@
{% trans "Next Steps" %} {% trans "Next Steps" %}
</h5> </h5>
</div> </div>
<div class="card-body p-4"> <div class="card-body p-4">
{% if application.stage == 'Applied' %} {% if application.stage == 'Applied' %}
<div class="alert bg-primary-theme text-white"> <div class="alert bg-primary-theme text-white">
<i class="fas fa-clock me-2"></i> <i class="fas fa-clock me-2"></i>