transfered the relationships of participants to candidate instead of job

This commit is contained in:
Faheed 2025-11-06 17:40:26 +03:00
parent 6aa9ebd279
commit da05441f94
24 changed files with 1284 additions and 551 deletions

View File

@ -401,3 +401,11 @@ CKEDITOR_5_CONFIGS = {
# Define a constant in settings.py to specify file upload permissions # Define a constant in settings.py to specify file upload permissions
CKEDITOR_5_FILE_UPLOAD_PERMISSION = "staff" # Possible values: "staff", "authenticated", "any" CKEDITOR_5_FILE_UPLOAD_PERMISSION = "staff" # Possible values: "staff", "authenticated", "any"
from django.contrib.messages import constants as messages
MESSAGE_TAGS = {
messages.ERROR: 'danger',
}

View File

@ -224,138 +224,165 @@ def send_interview_invitation_email(candidate, job, meeting_details=None, recipi
logger.error(error_msg, exc_info=True) logger.error(error_msg, exc_info=True)
return {'success': False, 'error': error_msg} return {'success': False, 'error': error_msg}
from .models import Candidate
def send_bulk_email(subject, message, recipient_list, request=None, attachments=None, async_task_=False): def send_bulk_email(subject, message, recipient_list, request=None, attachments=None, async_task_=False):
""" """
Send bulk email to multiple recipients with HTML support and attachments. Send bulk email to multiple recipients with HTML support and attachments,
supporting synchronous or asynchronous dispatch.
Args:
subject: Email subject
message: Email message (can be HTML)
recipient_list: List of email addresses
request: Django request object (optional)
attachments: List of file attachments (optional)
async_task: Whether to run as background task (default: False)
Returns:
dict: Result with success status and error message if failed
""" """
# Handle async task execution
# Define messages (Placeholders)
all_candidate_emails = []
candidate_through_agency_emails=[]
participant_emails = []
agency_emails = []
left_candidate_emails = []
if not recipient_list:
return {'success': False, 'error': 'No recipients provided'}
for email in recipient_list:
email = email.strip().lower() # Clean input email
if email:
candidate = Candidate.objects.filter(email=email).first()
if candidate:
all_candidate_emails.append(email)
else:
participant_emails.append(email)
for email in all_candidate_emails:
candidate = Candidate.objects.filter(email=email).first()
if candidate:
if candidate.hiring_source == 'Agency' and hasattr(candidate, 'hiring_agency') and candidate.hiring_agency:
agency = candidate.hiring_agency
candidate_through_agency_emails.append(email)
if agency and agency.email:
agency_emails.append(agency.email)
else:
left_candidate_emails.append(email)
else:
left_candidate_emails.append(email)
# Determine unique recipients
unique_left_candidates = list(set(left_candidate_emails)) # Convert to list for async task
unique_agencies = list(agency_emails)
unique_participants = list(set(participant_emails))
total_recipients = len(unique_left_candidates) + len(unique_agencies) + len(unique_participants)
if total_recipients == 0:
return {'success': False, 'error': 'No valid email addresses found after categorization'}
# --- 3. Handle ASYNC Dispatch ---
if async_task_: if async_task_:
print("hereeeeeee") try:
from django_q.tasks import async_task from django_q.tasks import async_task
# Simple, serializable attachment format assumed: list of (filename, content, content_type) tuples
processed_attachments = attachments if attachments else []
task_ids = []
# Process attachments for background task serialization # Queue Left Candidates
# processed_attachments = [] if unique_left_candidates:
# if attachments: task_id = async_task(
# for attachment in attachments: 'recruitment.tasks.send_bulk_email_task',
# if hasattr(attachment, 'read'): subject,
# # File-like object - save to temporary file message,
# filename = getattr(attachment, 'name', 'attachment') recipient_list,
# content_type = getattr(attachment, 'content_type', 'application/octet-stream') processed_attachments, # Pass serializable data
hook='recruitment.tasks.email_success_hook' # Example hook
)
task_ids.append(task_id)
logger.info(f" email queued. ID: {task_id}")
# # Create temporary file return {
# with tempfile.NamedTemporaryFile(delete=False, suffix=f'_{filename}') as temp_file: 'success': True,
# content = attachment.read() 'async': True,
# temp_file.write(content) 'task_ids': task_ids,
# temp_file_path = temp_file.name 'message': f'Emails queued for background sending to {total_recipients} recipient(s)'
}
except ImportError:
logger.error("Async execution requested, but django_q or required modules not found. Defaulting to sync.")
async_task_ = False # Fallback to sync
except Exception as e:
logger.error(f"Failed to queue async tasks: {str(e)}", exc_info=True)
return {'success': False, 'error': f"Failed to queue async tasks: {str(e)}"}
# # Store file info for background task # --- 4. Handle SYNCHRONOUS Send (If async_task_=False or fallback) ---
# processed_attachments.append({
# 'file_path': temp_file_path,
# 'filename': filename,
# 'content_type': content_type
# })
# elif isinstance(attachment, tuple) and len(attachment) == 3:
# # (filename, content, content_type) tuple - can be serialized directly
# processed_attachments.append(attachment)
# Queue the email sending as a background task
task_id = async_task(
'recruitment.tasks.send_bulk_email_task',
subject,
message,
recipient_list,
request,
)
logger.info(f"Bulk email queued as background task with ID: {task_id}")
return {
'success': True,
'async': True,
'task_id': task_id,
'message': f'Email queued for background sending to {len(recipient_list)} recipient(s)'
}
# Synchronous execution (default behavior)
try: try:
if not recipient_list:
return {'success': False, 'error': 'No recipients provided'}
# Clean recipient list and remove duplicates
clean_recipients = []
seen_emails = set()
for recipient in recipient_list:
email = recipient.strip().lower()
if email and email not in seen_emails:
clean_recipients.append(email)
seen_emails.add(email)
if not clean_recipients:
return {'success': False, 'error': 'No valid email addresses found'}
# Prepare email content
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa') from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa')
# Check if message contains HTML tags
is_html = '<' in message and '>' in message is_html = '<' in message and '>' in message
successful_sends = 0
if is_html: # Helper Function for Sync Send
# Create HTML email with plain text fallback def send_individual_email(recipient, body_message):
plain_message = strip_tags(message) nonlocal successful_sends
if is_html:
plain_message = strip_tags(body_message)
email_obj = EmailMultiAlternatives(subject=subject, body=plain_message, from_email=from_email, to=[recipient])
email_obj.attach_alternative(body_message, "text/html")
else:
email_obj = EmailMultiAlternatives(subject=subject, body=body_message, from_email=from_email, to=[recipient])
# Attachment Logic
if attachments:
for attachment in attachments:
if hasattr(attachment, 'read'):
filename = getattr(attachment, 'name', 'attachment')
content = attachment.read()
content_type = getattr(attachment, 'content_type', 'application/octet-stream')
email_obj.attach(filename, content, content_type)
elif isinstance(attachment, tuple) and len(attachment) == 3:
filename, content, content_type = attachment
email_obj.attach(filename, content, content_type)
try:
# FIX: Added the critical .send() call
email_obj.send(fail_silently=False)
successful_sends += 1
except Exception as e:
logger.error(f"Failed to send email to {recipient}: {str(e)}", exc_info=True)
# Create email with both HTML and plain text versions
email = EmailMultiAlternatives(
subject=subject,
body=plain_message,
from_email=from_email,
to=clean_recipients,
)
email.attach_alternative(message, "text/html")
else:
# Plain text email
email = EmailMultiAlternatives(
subject=subject,
body=message,
from_email=from_email,
to=clean_recipients,
)
# Add attachments if provided # Send Emails
# if attachments: for email in unique_left_candidates:
# for attachment in attachments: candidate_name=Candidate.objects.filter(email=email).first().first_name
# if hasattr(attachment, 'read'): candidate_message = f"Hi, {candidate_name}"+"\n"+message
# # File-like object
# filename = getattr(attachment, 'name', 'attachment') send_individual_email(email, candidate_message)
# content = attachment.read()
# content_type = getattr(attachment, 'content_type', 'application/octet-stream') i=0
# email.attach(filename, content, content_type) for email in unique_agencies:
# elif isinstance(attachment, tuple) and len(attachment) == 3: candidate_name=Candidate.objects.filter(email=candidate_through_agency_emails[i]).first().first_name
# # (filename, content, content_type) tuple agency_message = f"Hi, {candidate_name}"+"\n"+message
# filename, content, content_type = attachment send_individual_email(email, agency_message)
# email.attach(filename, content, content_type)
# Send email for email in unique_participants:
email.send(fail_silently=False)
participant_message = "Hello Participant! This is a general notification for you."
send_individual_email(email, participant_message)
logger.info(f"Bulk email sent successfully to {len(clean_recipients)} recipients")
logger.info(f"Bulk email processing complete. Sent successfully to {successful_sends} out of {total_recipients} unique recipients.")
return { return {
'success': True, 'success': True,
'recipients_count': len(clean_recipients), 'recipients_count': successful_sends,
'message': f'Email sent successfully to {len(clean_recipients)} recipient(s)' 'message': f'Email processing complete. {successful_sends} email(s) were sent successfully to {total_recipients} unique intended recipients.'
} }
except Exception as e: except Exception as e:
error_msg = f"Failed to send bulk email: {str(e)}" error_msg = f"Failed to process bulk email send request: {str(e)}"
logger.error(error_msg, exc_info=True) logger.error(error_msg, exc_info=True)
return {'success': False, 'error': error_msg} return {'success': False, 'error': error_msg}

View File

@ -1270,7 +1270,7 @@ class CandidateEmailForm(forms.Form):
'class': 'form-check' 'class': 'form-check'
}), }),
label=_('Select Candidates'), # Use a descriptive label label=_('Select Candidates'), # Use a descriptive label
required=True required=False
) )
# to = forms.MultipleChoiceField( # to = forms.MultipleChoiceField(
@ -1308,7 +1308,7 @@ class CandidateEmailForm(forms.Form):
'class': 'form-check' 'class': 'form-check'
}), }),
label=_('Recipients'), label=_('Recipients'),
required=True required=False
) )
# include_candidate_info = forms.BooleanField( # include_candidate_info = forms.BooleanField(
@ -1334,15 +1334,18 @@ class CandidateEmailForm(forms.Form):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.job = job self.job = job
self.candidates=candidates self.candidates=candidates
stage=self.candidates.first().stage
# Get all participants and users for this job # Get all participants and users for this job
recipient_choices = [] recipient_choices = []
# Add job participants # Add job participants
for participant in job.participants.all(): #show particpants only in the interview stage
recipient_choices.append( if stage=='Interview':
(f'participant_{participant.id}', f'{participant.name} - {participant.designation} (Participant)') for participant in job.participants.all():
) recipient_choices.append(
(f'participant_{participant.id}', f'{participant.name} - {participant.designation} (Participant)')
)
# Add job users # Add job users
for user in job.users.all(): for user in job.users.all():
@ -1373,7 +1376,60 @@ class CandidateEmailForm(forms.Form):
def _get_initial_message(self): def _get_initial_message(self):
"""Generate initial message with candidate and meeting information""" """Generate initial message with candidate and meeting information"""
message_parts = ['hiiiiiiii'] candidate=self.candidates.first()
message_parts=[]
if candidate.stage == 'Applied':
message_parts = [
f"Than you, for your interest in the {self.job.title} role.",
f"We regret to inform you that you were not selected to move forward to the exam round at this time.",
f"We encourage you to check our career page for further updates and future opportunities:",
f"https://kaauh/careers",
f"Wishing you the best in your job search,",
f"The KAAUH Hiring team"
]
elif candidate.stage == 'Exam':
message_parts = [
f"Than you,for your interest in the {self.job.title} role.",
f"We're pleased to inform you that your initial screening was successful!",
f"The next step is the mandatory online assessment exam.",
f"Please complete the assessment by using the following link:",
f"https://kaauh/hire/exam",
f"We look forward to reviewing your results.",
f"Best regards, The KAAUH Hiring team"
]
elif candidate.stage == 'Exam':
message_parts = [
f"Than you, for your interest in the {self.job.title} role.",
f"We're pleased to inform you that your initial screening was successful!",
f"The next step is the mandatory online assessment exam.",
f"Please complete the assessment by using the following link:",
f"https://kaauh/hire/exam",
f"We look forward to reviewing your results.",
f"Best regards, The KAAUH Hiring team"
]
elif candidate.stage == 'Offer':
message_parts = [
f"Congratulations, ! We are delighted to inform you that we are extending a formal offer of employment for the {self.job.title} role.",
f"This is an exciting moment, and we look forward to having you join the KAAUH team.",
f"A detailed offer letter and compensation package will be sent to you via email within 24 hours.",
f"In the meantime, please contact our HR department at [HR Contact] if you have immediate questions.",
f"Welcome to the team!",
f"Best regards, The KAAUH Hiring team"
]
elif candidate.stage == 'Hired':
message_parts = [
f"Welcome aboard,!",
f"We are thrilled to officially confirm your employment as our new {self.job.title}.",
f"You will receive a separate email shortly with details regarding your start date, first-day instructions, and onboarding documents.",
f"We look forward to seeing you at KAAUH.",
f"If you have any questions before your start date, please contact [Onboarding Contact].",
f"Best regards, The KAAUH Hiring team"
]
# # Add candidate information # # Add candidate information
# if self.candidate: # if self.candidate:
@ -1394,12 +1450,12 @@ class CandidateEmailForm(forms.Form):
return '\n'.join(message_parts) return '\n'.join(message_parts)
def clean_recipients(self): # def clean_recipients(self):
"""Ensure at least one recipient is selected""" # """Ensure at least one recipient is selected"""
recipients = self.cleaned_data.get('recipients') # recipients = self.cleaned_data.get('recipients')
if not recipients: # if not recipients:
raise forms.ValidationError(_('Please select at least one recipient.')) # raise forms.ValidationError(_('Please select at least one recipient.'))
return recipients # return recipients
# def clean_to(self): # def clean_to(self):
# """Ensure at least one recipient is selected""" # """Ensure at least one recipient is selected"""
@ -1414,32 +1470,32 @@ class CandidateEmailForm(forms.Form):
email_addresses = [] email_addresses = []
recipients = self.cleaned_data.get('recipients', []) recipients = self.cleaned_data.get('recipients', [])
candidates=self.cleaned_data.get('to',[]) candidates=self.cleaned_data.get('to',[])
if recipients:
for recipient in recipients: for recipient in recipients:
if recipient.startswith('participant_'): if recipient.startswith('participant_'):
participant_id = recipient.split('_')[1] participant_id = recipient.split('_')[1]
try: try:
participant = Participants.objects.get(id=participant_id) participant = Participants.objects.get(id=participant_id)
email_addresses.append(participant.email) email_addresses.append(participant.email)
except Participants.DoesNotExist: except Participants.DoesNotExist:
continue continue
elif recipient.startswith('user_'): elif recipient.startswith('user_'):
user_id = recipient.split('_')[1] user_id = recipient.split('_')[1]
try: try:
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
email_addresses.append(user.email) email_addresses.append(user.email)
except User.DoesNotExist: except User.DoesNotExist:
continue continue
if candidates:
for candidate in candidates: for candidate in candidates:
if candidate.startswith('candidate_'): if candidate.startswith('candidate_'):
print("candidadte: {candidate}") print("candidadte: {candidate}")
candidate_id = candidate.split('_')[1] candidate_id = candidate.split('_')[1]
try: try:
candidate = Candidate.objects.get(id=candidate_id) candidate = Candidate.objects.get(id=candidate_id)
email_addresses.append(candidate.email) email_addresses.append(candidate.email)
except Candidate.DoesNotExist: except Candidate.DoesNotExist:
continue continue
return list(set(email_addresses)) # Remove duplicates return list(set(email_addresses)) # Remove duplicates

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.6 on 2025-10-30 10:22 # Generated by Django 5.2.7 on 2025-11-05 13:05
import django.core.validators import django.core.validators
import django.db.models.deletion import django.db.models.deletion

View File

@ -1763,3 +1763,5 @@ class Participants(Base):
def __str__(self): def __str__(self):
return f"{self.name} - {self.email}" return f"{self.name} - {self.email}"

View File

@ -560,7 +560,7 @@ def kaauh_career(request):
form_template__is_active=True form_template__is_active=True
) )
return render(request,'jobs/career.html',{'active_jobs':active_jobs}) return render(request,'applicant/career.html',{'active_jobs':active_jobs})
# job detail facing the candidate: # job detail facing the candidate:
def application_detail(request, slug): def application_detail(request, slug):
@ -3708,30 +3708,29 @@ def compose_candidate_email(request, job_slug):
candidate_ids=request.GET.getlist('candidate_ids') candidate_ids=request.GET.getlist('candidate_ids')
candidates=Candidate.objects.filter(id__in=candidate_ids) candidates=Candidate.objects.filter(id__in=candidate_ids)
print(candidates)
# candidate = get_object_or_404(Candidate, slug=candidate_slug, job=job)
if request.method == 'POST': if request.method == 'POST':
candidate_ids = request.POST.getlist('candidate_ids') candidate_ids = request.POST.getlist('candidate_ids')
print(f"inside the POST {candidate_ids}" )
candidates=Candidate.objects.filter(id__in=candidate_ids) candidates=Candidate.objects.filter(id__in=candidate_ids)
form = CandidateEmailForm(job, candidates, request.POST) form = CandidateEmailForm(job, candidates, request.POST)
print(form)
if form.is_valid(): if form.is_valid():
print("form is valid ...") print("form is valid ...")
# Get email addresses # Get email addresses
email_addresses = form.get_email_addresses() email_addresses = form.get_email_addresses()
print(f"hiii {email_addresses} ")
if not email_addresses: if not email_addresses:
messages.error(request, 'No valid email addresses found for selected recipients.') messages.error(request, 'No email selected')
return render(request, 'includes/email_compose_form.html', { referer = request.META.get('HTTP_REFERER')
'form': form,
'job': job, if referer:
'candidate': candidates # Redirect back to the referring page
}) return redirect(referer)
else:
return redirect('dashboard')
# Check if this is an interview invitation # Check if this is an interview invitation
subject = form.cleaned_data.get('subject', '').lower() subject = form.cleaned_data.get('subject', '').lower()
@ -3739,7 +3738,7 @@ def compose_candidate_email(request, job_slug):
if is_interview_invitation: if is_interview_invitation:
# Use HTML template for interview invitations # Use HTML template for interview invitations
meeting_details = None # meeting_details = None
# if form.cleaned_data.get('include_meeting_details'): # if form.cleaned_data.get('include_meeting_details'):
# # Try to get meeting details from candidate # # Try to get meeting details from candidate
# meeting_details = { # meeting_details = {
@ -3751,9 +3750,9 @@ def compose_candidate_email(request, job_slug):
from .email_service import send_interview_invitation_email from .email_service import send_interview_invitation_email
email_result = send_interview_invitation_email( email_result = send_interview_invitation_email(
candidate=candidates, candidates=candidates,
job=job, job=job,
meeting_details=meeting_details, # meeting_details=meeting_details,
recipient_list=email_addresses recipient_list=email_addresses
) )
else: else:
@ -3762,41 +3761,41 @@ def compose_candidate_email(request, job_slug):
subject = form.cleaned_data.get('subject') subject = form.cleaned_data.get('subject')
# Send emails using email service (no attachments, synchronous to avoid pickle issues) # Send emails using email service (no attachments, synchronous to avoid pickle issues)
email_result = send_bulk_email( email_result = send_bulk_email(
subject=subject, subject=subject,
message=message, message=message,
recipient_list=email_addresses, recipient_list=email_addresses,
request=request, request=request,
async_task_=False # Changed to False to avoid pickle issues async_task_=True # Changed to False to avoid pickle issues
) )
if email_result['success']: if email_result['success']:
messages.success(request, f'Email sent successfully to {len(email_addresses)} recipient(s).') messages.success(request, f'Email sent successfully to {len(email_addresses)} recipient(s).')
# # For HTMX requests, return success response # # For HTMX requests, return success response
# if 'HX-Request' in request.headers: # if 'HX-Request' in request.headers:
# return JsonResponse({ # return JsonResponse({
# 'success': True, # 'success': True,
# 'message': f'Email sent successfully to {len(email_addresses)} recipient(s).' # 'message': f'Email sent successfully to {len(email_addresses)} recipient(s).'
# }) # })
return redirect('candidate_interview_view', slug=job.slug) return redirect('candidate_interview_view', slug=job.slug)
else: else:
messages.error(request, f'Failed to send email: {email_result.get("message", "Unknown error")}') messages.error(request, f'Failed to send email: {email_result.get("message", "Unknown error")}')
# For HTMX requests, return error response # For HTMX requests, return error response
# if 'HX-Request' in request.headers: # if 'HX-Request' in request.headers:
# return JsonResponse({ # return JsonResponse({
# 'success': False, # 'success': False,
# 'error': email_result.get("message", "Failed to send email") # 'error': email_result.get("message", "Failed to send email")
# }) # })
return render(request, 'includes/email_compose_form.html', { return render(request, 'includes/email_compose_form.html', {
'form': form, 'form': form,
'job': job, 'job': job,
'candidate': candidates 'candidate': candidates
}) })
# except Exception as e: # except Exception as e:
# logger.error(f"Error sending candidate email: {e}") # logger.error(f"Error sending candidate email: {e}")

View File

@ -2,161 +2,421 @@
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "My Profile" %} - {{ block.super }}{% endblock %} {% block title %}{% trans "My Dashboard" %} - {{ block.super }}{% endblock %}
{% block customCSS %} {% block customCSS %}
<style>
/* ---------------------------------------------------------------------- */
/* THEME VARIABLES (Refined for Premium Look) */
/* ---------------------------------------------------------------------- */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-teal-accent: #008c9c; /* A slightly brighter, more vibrant teal for key links */
--kaauh-teal-light: #e6f7f8;
--kaauh-bg-subtle: #f9fbfb; /* Lighter, cleaner background */
--kaauh-border: #e0e5eb; /* Very subtle border color */
--kaauh-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05); /* Lightest shadow */
--kaauh-shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.08); /* Modern, lifted shadow */
--gray-text: #5e6c84; /* Deeper, more readable gray */
--success-subtle: #d4edda;
--danger-subtle: #f8d7da;
}
body {
background-color: var(--kaauh-bg-subtle);
}
.text-primary-theme { color: var(--kaauh-teal-accent) !important; }
.text-gray-subtle { color: var(--gray-text) !important; }
/* ---------------------------------------------------------------------- */
/* 1. PRIMARY BUTTONS & ACTIONS */
/* ---------------------------------------------------------------------- */
.btn-main-action {
background-color: var(--kaauh-teal);
color: white;
border: none;
transition: all 0.2s ease;
box-shadow: 0 4px 15px rgba(0, 99, 110, 0.4);
font-weight: 600;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(0, 99, 110, 0.5);
}
.btn-outline-secondary {
border-color: var(--kaauh-border);
color: var(--kaauh-teal-dark);
font-weight: 500;
transition: all 0.2s ease;
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
/* ---------------------------------------------------------------------- */
/* 2. CARDS & CONTAINERS (Increased Padding & Smoother Shadow) */
/* ---------------------------------------------------------------------- */
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 12px; /* Smoother corner radius */
box-shadow: var(--kaauh-shadow-sm);
transition: box-shadow 0.3s ease;
}
.kaauh-card:hover {
box-shadow: var(--kaauh-shadow-lg); /* Subtle lift on hover */
}
.profile-data-list li {
padding: 1rem 0; /* More vertical space */
border-bottom: 1px dashed var(--kaauh-border);
font-size: 0.95rem;
font-weight: 500;
}
.profile-data-list li strong {
font-weight: 700;
color: var(--kaauh-teal-dark);
min-width: 120px;
display: inline-block;
}
/* ---------------------------------------------------------------------- */
/* 3. TABS (Minimalist & Clean) */
/* ---------------------------------------------------------------------- */
.nav-tabs {
border-bottom: 1px solid var(--kaauh-border); /* Thinner border */
}
.nav-tabs .nav-link {
color: var(--gray-text); /* Use subdued text color for inactive tabs */
border: none;
border-bottom: 3px solid transparent; /* Thinner accent line */
padding: 1rem 1.75rem;
font-weight: 600;
}
.nav-tabs .nav-link:hover {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-light);
}
.nav-tabs .nav-link.active {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
background-color: transparent;
}
.nav-tabs .nav-link i {
color: var(--kaauh-teal-accent) !important;
}
.nav-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
white-space: nowrap;
}
.nav-scroll .nav-tabs { flex-wrap: nowrap; border-bottom: none; }
.nav-scroll .nav-tabs .nav-item { flex-shrink: 0; }
/* ---------------------------------------------------------------------- */
/* 4. APPLICATION TABLE (Refined Aesthetics) */
/* ---------------------------------------------------------------------- */
.application-table thead th {
background-color: var(--kaauh-teal-light); /* Light, subtle header */
color: var(--kaauh-teal-dark);
font-weight: 700;
border-bottom: 1px solid var(--kaauh-border);
padding: 1rem 1.5rem;
}
.application-table tbody tr {
transition: background-color 0.2s ease;
}
.application-table tbody tr:hover {
background-color: var(--kaauh-teal-light);
}
.badge-stage {
font-weight: 600;
padding: 0.4em 0.8em;
border-radius: 50rem;
}
.bg-success { background-color: #38a169 !important; }
.bg-warning { background-color: #f6ad55 !important; }
/* Responsive Table for Mobile (High contrast labels) */
@media (max-width: 767.98px) {
.application-table thead { display: none; }
.application-table tr {
margin-bottom: 1rem;
border: 1px solid var(--kaauh-border);
border-radius: 8px;
box-shadow: var(--kaauh-shadow-sm);
}
.application-table td {
text-align: right !important;
padding: 0.75rem 1rem;
padding-left: 50%;
position: relative;
}
.application-table td::before {
content: attr(data-label);
position: absolute;
left: 1rem;
width: 45%;
font-weight: 700;
color: var(--gray-text); /* Use muted gray for labels */
}
}
/* Document Management List */
.list-group-item {
border-radius: 8px;
margin-bottom: 0.5rem;
border: 1px solid var(--kaauh-border);
transition: all 0.2s ease;
}
.list-group-item:hover {
background-color: var(--kaauh-teal-light);
border-color: var(--kaauh-teal-accent);
}
</style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-5"> <div class="container py-4 py-md-5">
{# Profile Header #} {# Header: Larger, more dynamic on large screens. Stacks cleanly on mobile. #}
<div class="d-flex justify-content-between align-items-center mb-5 border-bottom pb-4"> <div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5">
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="display-6 display-md-5 fw-extrabold mb-3 mb-md-0" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-user-circle me-3 text-primary-theme"></i> {% trans "My Candidate Profile" %} {% trans "Your Candidate Dashboard" %}
</h1> </h1>
<a href="#" class="btn btn-main-action btn-lg"> <a href="#profile-details" data-bs-toggle="tab" class="btn btn-main-action btn-sm btn-md-lg px-4 py-2 rounded-pill shadow-sm shadow-md-lg">
<i class="fas fa-edit me-2"></i> {% trans "Edit Profile" %} <i class="fas fa-edit me-2"></i> {% trans "Update Profile" %}
</a> </a>
</div> </div>
{# Profile and Account Management Row #} {# Candidate Quick Overview Card: Use a softer background color #}
<div class="row g-5 mb-5"> <div class="card kaauh-card mb-5 p-4 bg-white">
<div class="d-flex align-items-center flex-column flex-sm-row text-center text-sm-start">
{# Candidate Details Card #} <img src="{% if candidate.profile_picture %}{{ candidate.profile_picture.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}"
<div class="col-lg-5"> alt="{% trans 'Profile Picture' %}"
<div class="card kaauh-card h-100 p-4"> class="rounded-circle me-sm-4 mb-3 mb-sm-0 shadow-lg"
<div class="card-body p-0"> style="width: 80px; height: 80px; object-fit: cover; border: 4px solid var(--kaauh-teal-accent);">
<div class="d-flex align-items-center mb-4 pb-4 border-bottom"> <div>
<img src="{% if candidate.profile_picture %}{{ candidate.profile_picture.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}" <h3 class="card-title mb-1 fw-bold text-dark">{{ candidate.name|default:"Candidate Name" }}</h3>
alt="{% trans 'Profile Picture' %}" <p class="text-gray-subtle mb-0">{{ candidate.email }}</p>
class="rounded-circle me-4 shadow-sm"
style="width: 100px; height: 100px; object-fit: cover; border: 4px solid var(--kaauh-teal-light);">
<div>
<h3 class="card-title mb-1" style="color: var(--kaauh-teal-dark); font-weight: 600;">{{ candidate.name|default:"N/A" }}</h3>
<p class="text-muted mb-0">{{ candidate.email }}</p>
</div>
</div>
<ul class="list-unstyled profile-data-list pt-2">
<li>
<i class="fas fa-phone-alt me-2 text-primary-theme"></i>
<strong>{% trans "Phone" %}</strong> {{ candidate.phone|default:"N/A" }}
</li>
<li>
<i class="fas fa-globe me-2 text-primary-theme"></i>
<strong>{% trans "Nationality" %}</strong> {{ candidate.get_nationality_display|default:"N/A" }}
</li>
<li>
<i class="fas fa-calendar-alt me-2 text-primary-theme"></i>
<strong>{% trans "Date of Birth" %}</strong> {{ candidate.date_of_birth|date:"M d, Y"|default:"N/A" }}
</li>
<li>
<i class="fas fa-file-alt me-2 text-primary-theme"></i>
<strong>{% trans "Resume" %}</strong>
{% if candidate.resume %}
<a href="#" target="_blank" class="text-primary-theme text-decoration-none fw-medium">
{% trans "View/Download" %} <i class="fas fa-external-link-alt small ms-1"></i>
</a>
{% else %}
<span class="text-muted">{% trans "Not uploaded" %}</span>
{% endif %}
</li>
</ul>
</div>
</div>
</div>
{# Account Management / Quick Actions Card #}
<div class="col-lg-7">
<div class="card kaauh-card h-100 p-4">
<div class="card-header bg-white border-0 p-0 mb-4 profile-header">
<h4 class="mb-0 py-2" style="color: var(--kaauh-teal-dark); font-weight: 600;"><i class="fas fa-cogs me-2"></i> {% trans "Account Settings" %}</h4>
</div>
<div class="card-body p-0">
<div class="row g-4">
<div class="col-md-6">
<a href="#" class="btn btn-outline-secondary w-100 py-3 d-flex align-items-center justify-content-center">
<i class="fas fa-key me-2"></i> {% trans "Change Password" %}
</a>
</div>
<div class="col-md-6">
<a href="#" class="btn btn-outline-secondary w-100 py-3 d-flex align-items-center justify-content-center">
<i class="fas fa-id-card me-2"></i> {% trans "Update Contact Info" %}
</a>
</div>
<div class="col-12">
<blockquote class="blockquote small text-muted mt-3 p-3" style="border-left: 3px solid var(--kaauh-teal-light); background-color: var(--kaauh-bg-subtle); border-radius: 0.5rem;">
<p class="mb-0">{% trans "Your profile is essential for the application process. Keep your resume and contact information up-to-date for timely communication." %}</p>
</blockquote>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
{# Application Tracking Section #} {# ================================================= #}
<h2 class="h4 mb-4" style="color: var(--kaauh-teal-dark); border-bottom: 1px solid var(--kaauh-border); padding-bottom: 0.5rem;"> {# MAIN TABBED INTERFACE #}
<i class="fas fa-list-alt me-2 text-primary-theme"></i> {% trans "My Applications History" %} {# ================================================= #}
</h2> <div class="card kaauh-card p-0 bg-white">
{% if applications %}
<div class="table-responsive kaauh-card shadow-sm">
<table class="table table-hover align-middle mb-0 application-table">
<thead>
<tr>
<th scope="col" style="min-width: 250px;">{% trans "Job Title" %}</th>
<th scope="col">{% trans "Applied On" %}</th>
<th scope="col">{% trans "Current Stage" %}</th>
<th scope="col">{% trans "Status" %}</th>
<th scope="col" class="text-end" style="min-width: 120px;">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for application in applications %}
<tr>
<td class="fw-medium">
<a href="{% url 'job_detail' application.job.slug %}" class="text-decoration-none text-primary-theme">
{{ application.job.title }}
</a>
</td>
<td>{{ application.applied_date|date:"d M Y" }}</td>
<td>
<span class="badge badge-stage">
{{ application.stage }}
</span>
</td>
<td>
{% if application.is_active %}
<span class="badge badge-success">{% trans "Active" %}</span>
{% else %}
<span class="badge badge-warning">{% trans "Closed" %}</span>
{% endif %}
</td>
<td class="text-end">
<a href="#" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-eye"></i> {% trans "Details" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# Placeholder for Pagination #} {# Tab Navigation: Used nav-scroll for responsiveness #}
{% comment %} {% include "includes/paginator.html" with page_obj=applications_page %} {% endcomment %} <div class="nav-scroll px-4 pt-3">
<ul class="nav nav-tabs" id="candidateTabs" role="tablist">
{% else %} <li class="nav-item" role="presentation">
<div class="alert alert-info text-center kaauh-card p-5" style="border: 1px dashed var(--kaauh-border);"> <button class="nav-link active" id="profile-tab" data-bs-toggle="tab" data-bs-target="#profile-details" type="button" role="tab" aria-controls="profile-details" aria-selected="true">
<i class="fas fa-info-circle fa-2x mb-3 text-primary-theme"></i> <i class="fas fa-user-circle me-2"></i> {% trans "Profile Details" %}
<h5 class="mb-3">{% trans "You have no active applications." %}</h5> </button>
<a href="{% url 'job_list' %}" class="ms-3 btn btn-main-action mt-2"> </li>
{% trans "View Available Jobs" %} <i class="fas fa-arrow-right ms-2"></i> <li class="nav-item" role="presentation">
</a> <button class="nav-link" id="applications-tab" data-bs-toggle="tab" data-bs-target="#applications-history" type="button" role="tab" aria-controls="applications-history" aria-selected="false">
<i class="fas fa-list-alt me-2"></i> {% trans "My Applications" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="documents-tab" data-bs-toggle="tab" data-bs-target="#document-management" type="button" role="tab" aria-controls="document-management" aria-selected="false">
<i class="fas fa-file-upload me-2"></i> {% trans "Documents" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="settings-tab" data-bs-toggle="tab" data-bs-target="#account-settings" type="button" role="tab" aria-controls="account-settings" aria-selected="false">
<i class="fas fa-cogs me-2"></i> {% trans "Settings" %}
</button>
</li>
</ul>
</div> </div>
{% endif %}
{# Tab Content #}
<div class="tab-content p-4 p-md-5" id="candidateTabsContent">
<div class="tab-pane fade show active" id="profile-details" role="tabpanel" aria-labelledby="profile-tab">
<h4 class="mb-4 fw-bold text-gray-subtle">{% trans "Personal Information" %}</h4>
<ul class="list-unstyled profile-data-list p-0">
<li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-phone-alt me-2 text-primary-theme"></i> <strong>{% trans "Phone" %}</strong></div>
<span class="text-end">{{ candidate.phone|default:"N/A" }}</span>
</li>
<li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-globe me-2 text-primary-theme"></i> <strong>{% trans "Nationality" %}</strong></div>
<span class="text-end">{{ candidate.get_nationality_display|default:"N/A" }}</span>
</li>
<li class="d-flex justify-content-between align-items-center">
<div><i class="fas fa-calendar-alt me-2 text-primary-theme"></i> <strong>{% trans "Date of Birth" %}</strong></div>
<span class="text-end">{{ candidate.date_of_birth|date:"M d, Y"|default:"N/A" }}</span>
</li>
<li class="small pt-3 text-muted border-bottom-0">{% trans "Use the 'Update Profile' button above to edit these details." %}</li>
</ul>
<hr class="my-5">
<h4 class="mb-4 fw-bold text-gray-subtle">{% trans "Quick Actions" %}</h4>
<div class="row g-3 g-md-4">
<div class="col-6 col-sm-4 col-md-4">
<a href="#applications-history" data-bs-toggle="tab" class="btn btn-action-tile w-100 d-grid text-center text-dark text-decoration-none">
<span class="action-tile-icon mb-2"><i class="fas fa-list-check"></i></span>
<span class="fw-bold">{% trans "Track Jobs" %}</span>
<span class="small text-muted d-none d-sm-block">{% trans "View stages" %}</span>
</a>
</div>
<div class="col-6 col-sm-4 col-md-4">
<a href="#document-management" data-bs-toggle="tab" class="btn btn-action-tile w-100 d-grid text-center text-dark text-decoration-none">
<span class="action-tile-icon mb-2"><i class="fas fa-cloud-upload-alt"></i></span>
<span class="fw-bold">{% trans "Manage Documents" %}</span>
<span class="small text-muted d-none d-sm-block">{% trans "Upload/View files" %}</span>
</a>
</div>
<div class="col-12 col-sm-4 col-md-4">
<a href="{% url 'kaauh_career' %}" class="btn btn-action-tile w-100 d-grid text-center text-dark text-decoration-none">
<span class="action-tile-icon mb-2"><i class="fas fa-search"></i></span>
<span class="fw-bold">{% trans "Find New Careers" %}</span>
<span class="small text-muted d-none d-sm-block">{% trans "Explore open roles" %}</span>
</a>
</div>
</div>
</div>
<div class="tab-pane fade" id="applications-history" role="tabpanel" aria-labelledby="applications-tab">
<h4 class="mb-4 fw-bold text-gray-subtle">{% trans "Application Tracking" %}</h4>
{% if applications %}
<div class="kaauh-card shadow-lg p-0">
<table class="table table-borderless align-middle mb-0 application-table">
<thead>
<tr>
<th scope="col" style="min-width: 250px;">{% trans "Job Title" %}</th>
<th scope="col">{% trans "Applied On" %}</th>
<th scope="col">{% trans "Current Stage" %}</th>
<th scope="col">{% trans "Status" %}</th>
<th scope="col" class="text-end" style="min-width: 140px;">{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% for application in applications %}
<tr>
<td class="fw-medium" data-label="{% trans 'Job Title' %}">
<a href="{% url 'job_detail' application.job.slug %}" class="text-decoration-none text-primary-theme">
{{ application.job.title }}
</a>
</td>
<td data-label="{% trans 'Applied On' %}" class="text-gray-subtle">{{ application.applied_date|date:"d M Y" }}</td>
<td data-label="{% trans 'Current Stage' %}">
<span class="badge badge-stage bg-info text-white">
{{ application.stage }}
</span>
</td>
<td data-label="{% trans 'Status' %}">
{% if application.is_active %}
<span class="badge badge-stage bg-success">{% trans "Active" %}</span>
{% else %}
<span class="badge badge-stage bg-warning text-dark">{% trans "Closed" %}</span>
{% endif %}
</td>
<td class="text-end" data-label="{% trans 'Action' %}">
<a href="#" class="btn btn-outline-secondary btn-sm rounded-pill px-3">
<i class="fas fa-eye me-1"></i> {% trans "Details" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info text-center p-5 rounded-3" style="border: 1px dashed var(--kaauh-border); background-color: var(--kaauh-teal-light);">
<i class="fas fa-info-circle fa-2x mb-3 text-primary-theme"></i>
<h5 class="mb-3 fw-bold text-primary-theme">{% trans "You haven't submitted any applications yet." %}</h5>
<a href="{% url 'kaauh_career' %}" class="ms-3 btn btn-main-action mt-2 rounded-pill px-4">
{% trans "View Available Jobs" %} <i class="fas fa-arrow-right ms-2"></i>
</a>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="document-management" role="tabpanel" aria-labelledby="documents-tab">
<h4 class="mb-4 fw-bold text-gray-subtle">{% trans "My Uploaded Documents" %}</h4>
<p class="text-gray-subtle">{% trans "You can upload and manage your resume, certificates, and professional documents here. These documents will be attached to your applications." %}</p>
<a href="#" class="btn btn-main-action rounded-pill px-4 me-3 d-block d-sm-inline-block w-100 w-sm-auto mb-4">
<i class="fas fa-cloud-upload-alt me-2"></i> {% trans "Upload New Document" %}
</a>
<hr class="my-5">
{# Example Document List (Refined structure) #}
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center bg-white p-3">
<div class="mb-2 mb-sm-0 fw-medium">
<i class="fas fa-file-pdf me-2 text-primary-theme"></i> **{% trans "Resume" %}** <span class="text-muted small">(CV\_John\_Doe\_2024.pdf)</span>
</div>
<div class="d-flex align-items-center">
<span class="text-muted small me-3">{% trans "Uploaded: 10 Jan 2024" %}</span>
<a href="#" class="btn btn-sm btn-outline-secondary me-2"><i class="fas fa-eye"></i></a>
<a href="#" class="btn btn-sm btn-outline-danger"><i class="fas fa-trash-alt"></i></a>
</div>
</li>
<li class="list-group-item d-flex flex-column flex-sm-row justify-content-between align-items-start align-items-sm-center bg-white p-3">
<div class="mb-2 mb-sm-0 fw-medium">
<i class="fas fa-file-alt me-2 text-primary-theme"></i> **{% trans "Medical Certificate" %}** <span class="text-muted small">(Cert\_KSA\_MED.jpg)</span>
</div>
<div class="d-flex align-items-center">
<span class="text-muted small me-3">{% trans "Uploaded: 22 Feb 2023" %}</span>
<a href="#" class="btn btn-sm btn-outline-secondary me-2"><i class="fas fa-eye"></i></a>
<a href="#" class="btn btn-sm btn-outline-danger"><i class="fas fa-trash-alt"></i></a>
</div>
</li>
</ul>
</div>
<div class="tab-pane fade" id="account-settings" role="tabpanel" aria-labelledby="settings-tab">
<h4 class="mb-4 fw-bold text-gray-subtle">{% trans "Security & Preferences" %}</h4>
<div class="row g-4">
<div class="col-12 col-md-6">
<div class="card kaauh-card p-4 h-100 bg-white">
<h5 class="fw-bold"><i class="fas fa-key me-2 text-primary-theme"></i> {% trans "Password Security" %}</h5>
<p class="text-muted small">{% trans "Update your password regularly to keep your account secure." %}</p>
<a href="#" class="btn btn-outline-secondary mt-auto w-100 py-2 fw-medium">
{% trans "Change Password" %}
</a>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card kaauh-card p-4 h-100 bg-white">
<h5 class="fw-bold"><i class="fas fa-envelope me-2 text-primary-theme"></i> {% trans "Email Preferences" %}</h5>
<p class="text-muted small">{% trans "Manage subscriptions and job alert settings." %}</p>
<a href="#" class="btn btn-outline-secondary mt-auto w-100 py-2 fw-medium">
{% trans "Manage Alerts" %}
</a>
</div>
</div>
</div>
<div class="alert mt-5 py-3" style="background-color: var(--danger-subtle); color: #842029; border: 1px solid #f5c2c7; border-radius: 8px;">
<i class="fas fa-exclamation-triangle me-2"></i> {% trans "To delete your profile, please contact HR support." %}
</div>
</div>
</div>
</div>
{# ================================================= #}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,5 +1,6 @@
{% extends 'applicant/partials/candidate_facing_base.html'%} {% extends 'applicant/partials/candidate_facing_base.html'%}
{% load static i18n %} {% load static i18n %}
{% block content %} {% block content %}
<nav id="bottomNavbar" class="navbar navbar-expand-lg sticky-top" style="background-color: var(--kaauh-teal); z-index: 1030;"> <nav id="bottomNavbar" class="navbar navbar-expand-lg sticky-top" style="background-color: var(--kaauh-teal); z-index: 1030;">
@ -35,8 +36,8 @@
<div class="col-lg-4 order-lg-2 order-1 d-none d-lg-block"> <div class="col-lg-4 order-lg-2 order-1 d-none d-lg-block">
<div class="card shadow-sm sticky-top"> <div class="card shadow-sm sticky-top">
<div class="card-header bg-kaauh-teal-dark text-white"> <div class="card-header bg-kaauh-teal-dark bg-white p-3">
<h5 class="mb-0"><i class="fas fa-file-signature me-2"></i>{% trans "Ready to Apply?" %}</h5> <h5 class="mb-0 fw-bold"><i class="fas fa-file-signature me-2" style="color: var(--kaauh-teal);"></i>{% trans "Ready to Apply?" %}</h5>
</div> </div>
<div class="card-body text-center"> <div class="card-body text-center">
<p class="text-muted">{% trans "Review the job details, then apply below." %}</p> <p class="text-muted">{% trans "Review the job details, then apply below." %}</p>
@ -52,14 +53,15 @@
<div class="col-lg-8 order-lg-1 order-2"> <div class="col-lg-8 order-lg-1 order-2">
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-header bg-kaauh-teal-dark text-white d-flex justify-content-between align-items-center">
<h2 class="h3 mb-0 fw-bold">{{ job.title }}</h2> <div class="card-header bg-white border-bottom p-4">
<h1 class="h3 mb-0 fw-bold" style="color: var(--kaauh-teal);">{{ job.title }}</h1>
</div> </div>
<div class="card-body"> <div class="card-body p-4">
<h4 class="mb-3" style="color: var(--kaauh-teal-dark);">{% trans "Job Overview" %}</h4> <h4 class="mb-3 fw-bold" style="color: var(--kaauh-teal-dark);">{% trans "Job Overview" %}</h4>
<div class="row row-cols-1 row-cols-md-2 g-3 mb-4 small text-secondary"> <div class="row row-cols-1 row-cols-md-2 g-3 mb-5 small text-secondary border p-3 rounded">
{% if job.salary_range %} {% if job.salary_range %}
<div class="col"> <div class="col">
<i class="fas fa-money-bill-wave text-success me-2"></i> <i class="fas fa-money-bill-wave text-success me-2"></i>
@ -88,21 +90,79 @@
<div class="col"> <i class="fas fa-desktop text-muted me-2"></i> <strong>{% trans "Workplace:" %}</strong> {{ job.get_workplace_type_display }} </div> <div class="col"> <i class="fas fa-desktop text-muted me-2"></i> <strong>{% trans "Workplace:" %}</strong> {{ job.get_workplace_type_display }} </div>
</div> </div>
{% if job.has_description_content %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-info-circle me-2"></i>{% trans "Job Description" %}</h5><div class="text-secondary">{{ job.description|safe }}</div></div>{% endif %}
{% if job.has_qualifications_content %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-graduation-cap me-2"></i>{% trans "Qualifications" %}</h5><div class="text-secondary">{{ job.qualifications|safe }}</div></div>{% endif %}
{% if job.has_benefits_content %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-hand-holding-usd me-2"></i>{% trans "Benefits" %}</h5><div class="text-secondary">{{ job.benefits|safe }}</div></div>{% endif %}
{% if job.has_application_instructions_content %}<hr class="my-4"><div class="mb-4"><h5 class="fw-bold" style="color: var(--kaauh-teal-dark);"><i class="fas fa-file-alt me-2"></i>{% trans "Application Instructions" %}</h5><div class="text-secondary">{{ job.application_instructions|safe }}</div></div>{% endif %}
</div> <div class="accordion" id="jobDetailAccordion">
{% if job.has_description_content %}
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button fw-bold fs-5 text-primary-theme" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
<i class="fas fa-info-circle me-3"></i> {% trans "Job Description" %}
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#jobDetailAccordion">
<div class="accordion-body text-secondary p-4">
{{ job.description|safe }}
</div>
</div>
</div>
{% endif %}
{% if job.has_qualifications_content %}
<div class="accordion-item">
<h2 class="accordion-header" id="headingTwo">
<button class="accordion-button collapsed fw-bold fs-5 text-primary-theme" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
<i class="fas fa-graduation-cap me-3"></i> {% trans "Qualifications" %}
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#jobDetailAccordion">
<div class="accordion-body text-secondary p-4">
{{ job.qualifications|safe }}
</div>
</div>
</div>
{% endif %}
{% if job.has_benefits_content %}
<div class="accordion-item">
<h2 class="accordion-header" id="headingThree">
<button class="accordion-button collapsed fw-bold fs-5 text-primary-theme" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
<i class="fas fa-hand-holding-usd me-3"></i> {% trans "Benefits" %}
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#jobDetailAccordion">
<div class="accordion-body text-secondary p-4">
{{ job.benefits|safe }}
</div>
</div>
</div>
{% endif %}
{% if job.has_application_instructions_content %}
<div class="accordion-item">
<h2 class="accordion-header" id="headingFour">
<button class="accordion-button collapsed fw-bold fs-5 text-primary-theme" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
<i class="fas fa-file-alt me-3"></i> {% trans "Application Instructions" %}
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingFour" data-bs-parent="#jobDetailAccordion">
<div class="accordion-body text-secondary p-4">
{{ job.application_instructions|safe }}
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="mobile-fixed-apply-bar d-lg-none"> <div class="mobile-fixed-apply-bar d-lg-none text-center">
{% if job.form_template %} {% if job.form_template %}
<a href="{% url 'application_submit_form' job.form_template.pk %}" class="btn btn-main-action btn-lg w-100"> <a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %} <i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
</a> </a>
{% endif %} {% endif %}

View File

@ -0,0 +1,156 @@
{% extends 'applicant/partials/candidate_facing_base.html'%}
{% load static i18n %}
{% block title %}{% trans "My Profile" %} - {{ block.super }}{% endblock %}
{% block content %}
<div class="main-content-area">
<header class="hero-section">
<div class="container">
<div class="row">
<div class="col-lg-12 col-xl-10">
<h1 class="hero-title mb-4">
{% translate "Your Career in Health & Academia starts here." %}
</h1>
<p class="lead mb-5">
{% translate "Join KAAUH, a national leader in patient care, research, and education. We are building the future of healthcare." %}
</p>
<a href="#filterSidebar" class="btn btn-hero-action me-3 mb-3 mb-lg-0">
<i class="fas fa-compass me-2"></i> {% translate "Find Your Path" %}
</a>
<a href="https://kaauh.edu.sa/about-us" class="btn btn-outline-light rounded-pill px-4 btn-lg">
{% translate "About US" %}
</a>
</div>
</div>
</div>
</header>
<section class="py-5 job-listing-section">
<div class="container">
<div class="row g-5">
<div class="col-lg-3 col-xl-3">
<button class="btn btn-outline-dark filter-toggle-button d-lg-none" type="button"
data-bs-toggle="collapse" data-bs-target="#filterSidebar" aria-expanded="false" aria-controls="filterSidebar">
<i class="fas fa-filter me-2"></i> {% translate "Filter Jobs" %}
</button>
<div class="collapse d-lg-block filter-sidebar-collapse" id="filterSidebar">
<div class="card sticky-top-filters p-4 bg-white">
<h4 class="fw-bold mb-4 text-primary-theme">
{% translate "Refine Your Search" %}
</h4>
<div class="d-grid gap-3">
<select class="form-select" aria-label="Department filter">
<option selected>{% translate "Department (Faculty/Admin)" %}</option>
<option value="1">{% translate "Clinical Services" %}</option>
<option value="2">{% translate "Research Labs" %}</option>
<option value="3">{% translate "Training & Education" %}</option>
</select>
<select class="form-select" aria-label="Employment Type filter">
<option selected>{% translate "Employment Type" %}</option>
<option value="1">{% translate "Full-Time" %}</option>
<option value="2">{% translate "Part-Time" %}</option>
</select>
<select class="form-select" aria-label="Specialty filter">
<option selected>{% translate "Specialty / Focus" %}</option>
<option value="1">{% translate "Women's Health" %}</option>
<option value="2">{% translate "Child Growth & Dev" %}</option>
</select>
<button class="btn btn-main-action rounded-pill mt-3">{% translate "Apply Filters" %}</button>
</div>
</div>
</div>
</div>
<div class="col-lg-9 col-xl-9">
<div class="sticky-filter-bar">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h3 class="fw-bold mb-0 text-dark fs-5">
{% comment %}
Assuming a 'job_count' context variable exists, otherwise
this remains static as in the original template.
{% endcomment %}
{% translate "Showing 37 Open Roles" %}
</h3>
<div class="d-flex flex-wrap gap-2">
<span class="filter-chip">
{% translate "Specialty: Women's Health" %}
<i class="fas fa-times text-xs ms-2 cursor-pointer" role="button" aria-label="Remove filter: Women's Health"></i>
</span>
</div>
</div>
</div>
<div class="mt-4 d-grid gap-3">
{% for job in active_jobs %}
{# The original card structure, now dynamically filled with job data #}
<a href="{% url 'application_detail' job.slug %}" class="card d-block text-decoration-none text-dark job-listing-card bg-white">
<div class="d-flex justify-content-between align-items-start">
<h4 class="h5 fw-bold mb-1 text-primary-theme-hover">
{{ job.title }}
</h4>
{# NOTE: You will need to define how job.category or job.tag is determined for the badge logic #}
<span class="badge bg-primary-theme job-tag">
{% comment %} Placeholder: Use job.tag or implement conditional logic {% endcomment %}
{% if job.tag_slug == 'clinical' %}{% translate "Clinical" %}
{% elif job.tag_slug == 'research' %}{% translate "Research/Contract" %}
{% else %}{% translate "General" %}{% endif %}
</span>
</div>
{# NOTE: Assuming job.department and job.location exist in your context #}
<p class="text-muted small mb-3">{{ job.department }}</p>
<div class="d-flex flex-wrap gap-4 small text-muted">
<span class="d-flex align-items-center">
<i class="fas fa-map-marker-alt me-2 job-detail-icon"></i>
{{ job.location|default:"Riyadh, KSA" }}
</span>
<span class="d-flex align-items-center">
<i class="fas fa-user-md me-2 job-detail-icon"></i>
{{ job.focus|default:"High Reliability Focus" }}
</span>
<span class="d-flex align-items-center">
<i class="fas fa-calendar-alt me-2 job-detail-icon"></i>
{{ job.employment_type|default:"Full-Time" }}
</span>
</div>
</a>
{% empty %}
<div class="alert alert-info" role="alert">
{% translate "We currently have no open roles that match your search. Please check back soon!" %}
</div>
{% endfor %}
<div class="text-center mt-5 mb-3">
<button class="btn btn-main-action btn-lg rounded-pill px-5 shadow-sm">
{% translate "Load More Jobs" %}
</button>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
{% endblock content %}

View File

@ -8,7 +8,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% translate "Application Form" %}</title> <title>{% trans "Careers" %} - {% block title %}{% translate "Application Form" %}{% endblock %}</title>
{% comment %} Load the correct Bootstrap CSS file for RTL/LTR {% endcomment %} {% comment %} Load the correct Bootstrap CSS file for RTL/LTR {% endcomment %}
{% if LANGUAGE_CODE == 'ar' %} {% if LANGUAGE_CODE == 'ar' %}
@ -25,13 +25,17 @@
:root { :root {
--kaauh-teal: #00636e; --kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53; --kaauh-teal-dark: #004a53;
--kaauh-teal-light: #e6f7f8; /* Very light teal for hover/background */
--success: #198754; --success: #198754;
--danger: #dc3545; --danger: #dc3545;
--light-bg: #f8f9fa; --light-bg: #f8f9fa;
--gray-text: #6c757d; --gray-text: #6c757d;
--kaauh-border: #eaeff3; /* Added for dropdown styling */ --kaauh-border: #d0d7de; /* Cleaner border color */
--kaauh-shadow: 0 15px 35px rgba(0, 0, 0, 0.1); /* Deeper shadow for premium look */
--kaauh-dark-bg: #0d0d0d;
--kaauh-dark-contrast: #1c1c1c;
/* CALCULATED STICKY HEIGHTS */ /* CALCULATED STICKY HEIGHTS (As provided in base) */
--navbar-height: 56px; --navbar-height: 56px;
--navbar-gap: 16px; --navbar-gap: 16px;
--sticky-navbar-total-height: 128px; --sticky-navbar-total-height: 128px;
@ -39,208 +43,142 @@
body { body {
min-height: 100vh; min-height: 100vh;
background-color: #f0f0f5; /* Light gray background for contrast */ background-color: #f0f0f5;
padding-top: 0; padding-top: 0;
} }
.text-primary-theme { color: var(--kaauh-teal) !important; }
.text-primary-theme-hover:hover { color: var(--kaauh-teal-dark) !important; }
.btn-main-action { .btn-main-action {
background-color: var(--kaauh-teal); background-color: var(--kaauh-teal);
color: white; color: white;
border: none; border: none;
transition: background-color 0.3s ease, transform 0.2s ease; transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 99, 110, 0.3); box-shadow: 0 6px 15px rgba(0, 99, 110, 0.4); /* Stronger shadow */
font-weight: 600;
letter-spacing: 0.02em;
} }
.btn-main-action:hover { .btn-main-action:hover {
background-color: var(--kaauh-teal-dark); background-color: var(--kaauh-teal-dark);
color: white; color: white;
transform: translateY(-1px); transform: translateY(-2px); /* More pronounced lift */
box-shadow: 0 10px 20px rgba(0, 99, 110, 0.5);
} }
.bg-kaauh-teal-dark {
background-color: var(--kaauh-teal-dark) !important;
}
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
/* NEW: MESSAGES STYLING */ /* 1. DARK HERO STYLING (High Contrast) */
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
.message-container { .hero-section {
/* Position right below the sticky navbar (56px) with a small top margin */ background: linear-gradient(135deg, var(--kaauh-dark-contrast) 0%, var(--kaauh-dark-bg) 100%);
margin-top: calc(var(--navbar-height) + 10px); padding: 4rem 0; /* Reduced from 8rem to 4rem */
margin-top: -1px;
color: white;
position: relative;
overflow: hidden;
} }
.alert { .hero-title {
padding: 0.75rem 1.25rem; font-size: 2.5rem; /* Reduced from 3.5rem to 2.5rem */
border-radius: 0.5rem; font-weight: 800; /* Extra bold */
font-weight: 500; line-height: 1.1;
box-shadow: 0 4px 8px rgba(0,0,0,0.08); letter-spacing: -0.05em;
margin-bottom: 0; /* Handled by container margin */ max-width: 900px;
border-left: 5px solid; /* Feature highlight */
} }
.alert-success { .hero-section .lead {
color: var(--success); font-size: 1.35rem; /* Larger lead text */
background-color: #d1e7dd;
border-color: var(--success);
} }
.alert-error, .alert-danger { .btn-hero-action {
color: var(--danger); /* Primary CTA: Retained strong teal look */
background-color: #f8d7da; background-color: var(--kaauh-teal);
border-color: var(--danger);
}
.alert-info {
color: var(--kaauh-teal-dark);
background-color: #cff4fc;
border-color: var(--kaauh-teal); border-color: var(--kaauh-teal);
font-weight: 600;
padding: 0.8rem 2.2rem;
border-radius: 50rem;
box-shadow: 0 4px 10px rgba(0, 99, 110, 0.4);
}
@media (min-width: 992px) {
.hero-section {
padding: 10rem 0;
}
.hero-title {
font-size: 5.5rem;
}
} }
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
/* LANGUAGE TOGGLE STYLES (COPIED FROM MAIN LAYOUT) */ /* 2. PATH CARDS SECTION (New Segmented Entry) */
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
.language-toggle-btn { .path-card-section {
color: var(--gray-text) !important; /* Use secondary color */ background-color: white;
background: none !important; margin-top: -80px; /* Pulls the section up over the dark hero for a modern layered look */
border: none !important; position: relative;
display: flex; z-index: 50;
align-items: center; padding-top: 5rem;
gap: 0.3rem; padding-bottom: 5rem;
padding: 0.5rem 0.75rem !important; border-top-left-radius: 2rem;
font-weight: 500; border-top-right-radius: 2rem;
transition: all 0.2s ease; box-shadow: var(--kaauh-shadow); /* Defines the separation */
} }
.language-toggle-btn:hover { .path-card {
background: var(--light-bg) !important; padding: 2rem;
border-radius: 4px; border-radius: 1rem;
color: var(--kaauh-teal) !important;
}
/* Dropdown Menu styling for language */
.dropdown-menu {
backdrop-filter: blur(4px);
background-color: rgba(255, 255, 255, 0.98);
border: 1px solid var(--kaauh-border); border: 1px solid var(--kaauh-border);
box-shadow: 0 6px 20px rgba(0,0,0,0.12); transition: all 0.3s ease;
border-radius: 8px; height: 100%;
padding: 0.5rem 0; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
min-width: 150px;
} }
.dropdown-item { .path-card:hover {
padding: 0.5rem 1.25rem; border-color: var(--kaauh-teal);
transition: background-color 0.15s; transform: translateY(-5px);
text-align: inherit; /* Ensure text alignment is controlled by dir="rtl" */ box-shadow: 0 15px 30px rgba(0, 99, 110, 0.1);
} }
.path-card-icon {
/* Use button as dropdown-item inside form for full click area */ color: var(--kaauh-teal);
.dropdown-item[type="submit"] { font-size: 2.5rem;
width: 100%; margin-bottom: 1rem;
text-align: inherit; }
border: none; .path-card h5 {
font-weight: 700;
color: var(--kaauh-teal-dark);
margin-bottom: 0.5rem;
} }
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
/* LAYOUT & STICKY POSITIONING FIXES (Desktop/Tablet) */ /* 3. JOB LISTING & FILTER (Refined) */
/* ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- */
.job-listing-section {
#topNavbar { background-color: #f0f0f5; /* Separates the job list from the white path section */
z-index: 1040; /* Higher than the bottom bar */ padding-top: 3rem;
}
.job-listing-card {
border: 1px solid var(--kaauh-border);
border-left: 6px solid var(--kaauh-teal);
border-radius: 0.75rem;
padding: 2rem !important;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); /* Lighter default shadow */
}
.job-listing-card:hover {
transform: translateY(-3px); /* Increased lift */
box-shadow: 0 12px 25px rgba(0, 99, 110, 0.15); /* Stronger hover shadow */
background-color: var(--kaauh-teal-light);
}
.card.sticky-top-filters {
box-shadow: var(--kaauh-shadow); /* Uses the deeper card shadow */
} }
/* 1. Position the dark navbar below the white navbar + gap */ /* RTL Corrections (retained) */
#bottomNavbar { html[dir="rtl"] .alert { border-right: 5px solid; border-left: none; }
/* 56px (white nav) + 16px (mb-3) = 72px */ html[dir="rtl"] .job-listing-card { border-right: 6px solid var(--kaauh-teal); border-left: 1px solid var(--kaauh-border); }
top: calc(var(--navbar-height) + var(--navbar-gap)); </style>
z-index: 1030;
}
/* 2. Pushes the main content down so it's not hidden under the navbars */
.main-content-area {
/* Total Sticky Height (128px) + Extra Margin (12px) = 140px */
margin-top: calc(var(--sticky-navbar-total-height) + 12px);
}
/* 3. Positions the sticky sidebar correctly */
.card.sticky-top {
/* Start scrolling the sidebar just below the two navbars + a small gap */
top: calc(var(--sticky-navbar-total-height) + 15px);
}
/* ---------------------------------------------------------------------- */
/* RTL / ARABIC SUPPORT - Optimized */
/* ---------------------------------------------------------------------- */
html[dir="rtl"] {
text-align: right;
}
/* Flip Margin Utilities (m-end and m-start) */
html[dir="rtl"] .ms-auto { margin-right: auto !important; margin-left: 0 !important; }
html[dir="rtl"] .me-auto { margin-left: auto !important; margin-right: 0 !important; }
html[dir="rtl"] .ms-2 { margin-right: 0.5rem !important; margin-left: 0 !important; }
html[dir="rtl"] .me-2 { margin-left: 0.5rem !important; margin-right: 0 !important; }
html[dir="rtl"] .me-1 { margin-left: 0.25rem !important; margin-right: 0 !important; } /* For the globe icon */
/* Flip alignment for text-end/text-start */
html[dir="rtl"] .text-end { text-align: left !important; }
html[dir="rtl"] .text-start { text-align: right !important; }
/* Flip border-left for RTL alerts */
html[dir="rtl"] .alert {
border-right: 5px solid;
border-left: none;
}
/* ---------------------------------------------------------------------- */
/* MOBILE RESPONSIVE STYLES (Below 992px) */
/* ---------------------------------------------------------------------- */
@media (max-width: 991.98px) {
/* Ensures dropdown items in mobile menu align correctly */
html[dir="rtl"] .navbar-collapse .dropdown-menu {
text-align: right;
left: auto;
right: 0;
}
/* On mobile, the top navbar is generally only 56px tall when collapsed. */
#bottomNavbar {
top: calc(var(--navbar-height) + var(--navbar-gap));
}
.main-content-area {
/* Reduced margin-top for smaller screens */
margin-top: calc(var(--sticky-navbar-total-height) / 2);
}
/* Mobile Fixed Footer Bar for Application */
.mobile-fixed-apply-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
width: 100%;
padding: 15px;
background-color: var(--light-bg);
border-top: 1px solid #ddd;
z-index: 1000;
box-shadow: 0 -4px 10px rgba(0, 0, 0, 0.08);
}
/* Add padding to the bottom of the body content to prevent it from hiding under the fixed bar */
body {
padding-bottom: 90px;
}
/* Fix job overview grid responsiveness (ensures 1 column) */
.row-cols-md-2 > .col {
flex: 0 0 100%;
max-width: 100%;
}
}
</style>
</head> </head>
<body> <body>
<nav id="topNavbar" class="navbar navbar-expand-lg sticky-top" style="background-color: white; z-index: 1040;"> <nav id="topNavbar" class="navbar navbar-expand-lg sticky-top bg-white border-bottom" style="z-index: 1040;">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand text-white fw-bold" href="{% url 'kaauh_career' %}"> <a class="navbar-brand text-dark fw-bold" href="{% url 'kaauh_career' %}">
<img src="{% static 'image/kaauh.jpeg' %}" alt="{% translate 'KAAUH IMAGE' %}" style="height: 50px; margin-right: 10px;"> <img src="{% static 'image/kaauh.jpeg' %}" alt="{% translate 'KAAUH IMAGE' %}" style="height: 50px; margin-right: 10px;">
KAAUH
</a> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
@ -249,11 +187,11 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item"> {% comment %} <li class="nav-item">
<a class="nav-link text-secondary" href="{% url 'applicant_profile' %}">{% translate "Applications" %}</a> <a class="nav-link text-secondary" href="{% url 'applicant_profile' %}">{% translate "Applications" %}</a>
</li> </li> {% endcomment %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-secondary" href="/profile/">{% translate "Profile" %}</a> <a class="nav-link text-secondary" href="{% url 'applicant_profile' %}">{% translate "Profile" %}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-secondary" href="{% url 'kaauh_career' %}">{% translate "Careers" %}</a> <a class="nav-link text-secondary" href="{% url 'kaauh_career' %}">{% translate "Careers" %}</a>
@ -269,7 +207,6 @@
<ul class="dropdown-menu {% if LANGUAGE_CODE == 'ar' %}dropdown-menu-start{% else %}dropdown-menu-end{% endif %}" aria-labelledby="navbarLanguageDropdown"> <ul class="dropdown-menu {% if LANGUAGE_CODE == 'ar' %}dropdown-menu-start{% else %}dropdown-menu-end{% endif %}" aria-labelledby="navbarLanguageDropdown">
{% comment %} English Button {% endcomment %}
<li> <li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %} <form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}"> <input name="next" type="hidden" value="{{ request.get_full_path }}">
@ -279,7 +216,6 @@
</form> </form>
</li> </li>
{% comment %} Arabic Button {% endcomment %}
<li> <li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %} <form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}"> <input name="next" type="hidden" value="{{ request.get_full_path }}">
@ -296,9 +232,13 @@
</nav> </nav>
{% block content %} {% block content %}
{% endblock content %}
{% endblock content %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
{% block customJS %}
{% endblock %}
</body> </body>
</html> </html>

View File

@ -290,14 +290,15 @@
<main class="container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;"> <main class="container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;">
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show mt-2" role="alert"> <div class="alert alert-{{ message.tags }} alert-dismissible fade show mt-2" role="alert">
{{ message }} {{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% block content %} {% block content %}
{% endblock %} {% endblock %}
</main> </main>

View File

@ -1,8 +1,19 @@
{% load i18n %} {% load i18n %}
<div class="container-fluid">
<div class="row"> <div class="row">
<div class="container-fluid">
<div class="col-12"> <div class="col-12">
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
{{ message }}
</li>
{% endfor %}
</ul>
{% endif %}
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
@ -32,7 +43,7 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-bold"> <label class="form-label fw-bold">
{% trans "Recipients" %} {% trans "Participants" %}
</label> </label>
<div class="border rounded p-3 bg-light" style="max-height: 200px; overflow-y: auto;"> <div class="border rounded p-3 bg-light" style="max-height: 200px; overflow-y: auto;">
{% for choice in form.recipients %} {% for choice in form.recipients %}

View File

@ -229,6 +229,17 @@
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %} <i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
</button> </button>
<button type="button" class="btn btn-outline-info btn-sm"
data-bs-toggle="modal"
hx-boost='true'
data-bs-target="#emailModal"
hx-get="{% url 'compose_candidate_email' job.slug %}"
hx-target="#emailModalBody"
hx-include="#candidate-form"
title="Email Participants">
<i class="fas fa-envelope"></i>
</button>
</div> </div>
</form> </form>
</div> </div>
@ -358,6 +369,28 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Email Modal -->
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content kaauh-card">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="emailModalLabel" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-envelope me-2"></i>{% trans "Compose Email" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="emailModalBody" class="modal-body">
<div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Loading email form..." %}
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -225,10 +225,63 @@
</div> </div>
<div class="kaauh-card shadow-sm p-3"> <div class="kaauh-card shadow-sm p-3">
{% if candidates %}
<div class="bulk-action-bar p-3 bg-light border-bottom">
<form hx-boost="true" hx-include="#candidate-form" action="{% url 'candidate_update_status' job.slug %}" method="post" class="action-group">
{% csrf_token %}
{# MODIFIED: Using d-flex for horizontal alignment and align-items-end to align everything based on the baseline of the button/select #}
<div class="d-flex align-items-end gap-3">
{# Select Input Group #}
<div>
<select name="mark_as" id="update_status" class="form-select form-select-sm" style="min-width: 150px;">
<option selected>
----------
</option>
<option value="Offer">
{% trans "Offer Stage" %}
</option>
{# Include other options here, such as Interview, Offer, Rejected, etc. #}
</select>
</div>
{# Button #}
<button type="submit" class="btn btn-main-action btn-sm">
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
</button>
{# email button#}
<button type="button" class="btn btn-outline-info btn-sm"
data-bs-toggle="modal"
hx-boost='true'
data-bs-target="#emailModal"
hx-get="{% url 'compose_candidate_email' job.slug %}"
hx-target="#emailModalBody"
hx-include="#candidate-form"
title="Email Participants">
<i class="fas fa-envelope"></i>
</button>
</div>
</form>
</div>
{% endif %}
<div class="table-responsive"> <div class="table-responsive">
<form id="candidate-form" action="{% url 'candidate_update_status' job.slug %}" method="get">
{% csrf_token %}
<table class="table candidate-table align-middle"> <table class="table candidate-table align-middle">
<thead> <thead>
<tr> <tr>
<th style="width: 2%">
{% if candidates %}
<div class="form-check">
<input
type="checkbox" class="form-check-input" id="selectAllCheckbox">
</div>
{% endif %}
</th>
<th style="width: 15%"><i class="fas fa-user me-1"></i> {% trans "Name" %}</th> <th style="width: 15%"><i class="fas fa-user me-1"></i> {% trans "Name" %}</th>
<th style="width: 15%"><i class="fas fa-phone me-1"></i> {% trans "Contact" %}</th> <th style="width: 15%"><i class="fas fa-phone me-1"></i> {% trans "Contact" %}</th>
<th style="width: 15%"><i class="fas fa-briefcase me-1"></i> {% trans "Applied Position" %}</th> <th style="width: 15%"><i class="fas fa-briefcase me-1"></i> {% trans "Applied Position" %}</th>
@ -240,6 +293,12 @@
<tbody> <tbody>
{% for candidate in candidates %} {% for candidate in candidates %}
<tr> <tr>
<td>
<div class="form-check">
<input name="candidate_ids" value="{{ candidate.id }}" type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}">
</div>
</td>
<td> <td>
<div class="candidate-name"> <div class="candidate-name">
{{ candidate.name }} {{ candidate.name }}
@ -288,12 +347,15 @@
title="View Resume Template"> title="View Resume Template">
<i class="fas fa-file-alt"></i> <i class="fas fa-file-alt"></i>
</a> </a>
</div> </div>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</form>
{% if not candidates %} {% if not candidates %}
<div class="alert alert-info text-center mt-3 mb-0" role="alert"> <div class="alert alert-info text-center mt-3 mb-0" role="alert">
<i class="fas fa-info-circle me-1"></i> <i class="fas fa-info-circle me-1"></i>
@ -347,13 +409,76 @@
</div> </div>
</div> </div>
<!-- Email Modal -->
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content kaauh-card">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="emailModalLabel" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-envelope me-2"></i>{% trans "Compose Email" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="emailModalBody" class="modal-body">
<div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Loading email form..." %}
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block customJS %} {% block customJS %}
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// Add any specific JavaScript for hired candidates view if needed const selectAllCheckbox = document.getElementById('selectAllCheckbox');
console.log('Hired candidates view loaded'); const rowCheckboxes = document.querySelectorAll('.rowCheckbox');
if (selectAllCheckbox) {
// Function to safely update the header checkbox state
function updateSelectAllState() {
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
const totalCount = rowCheckboxes.length;
if (checkedCount === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
} else if (checkedCount === totalCount) {
selectAllCheckbox.checked = true;
selectAllCheckbox.indeterminate = false;
} else {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = true;
}
}
// 1. Logic for the 'Select All' checkbox (Clicking it updates all rows)
selectAllCheckbox.addEventListener('change', function () {
const isChecked = selectAllCheckbox.checked;
rowCheckboxes.forEach(checkbox => checkbox.removeEventListener('change', updateSelectAllState));
rowCheckboxes.forEach(function (checkbox) {
checkbox.checked = isChecked;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
});
rowCheckboxes.forEach(checkbox => checkbox.addEventListener('change', updateSelectAllState));
updateSelectAllState();
});
// 2. Logic to update 'Select All' state based on row checkboxes
rowCheckboxes.forEach(function (checkbox) {
checkbox.addEventListener('change', updateSelectAllState);
});
// Initial check to set the correct state on load (in case items are pre-checked)
updateSelectAllState();
}
}); });
function syncHiredCandidates() { function syncHiredCandidates() {

View File

@ -249,7 +249,7 @@
hx-include="#candidate-form" hx-include="#candidate-form"
title="Email Participants"> title="Email Participants">
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</button> </button>
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -414,7 +414,7 @@
data-modal-title="{% trans 'Schedule Interview' %}" data-modal-title="{% trans 'Schedule Interview' %}"
title="Schedule Interview"> title="Schedule Interview">
<i class="fas fa-calendar-plus"></i> <i class="fas fa-calendar-plus"></i>
</button> </button>
{% endif %} {% endif %}
</td> </td>

View File

@ -221,10 +221,24 @@
<button type="submit" class="btn btn-main-action btn-sm"> <button type="submit" class="btn btn-main-action btn-sm">
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %} <i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
</button> </button>
</form> </form>
{# Separator (Vertical Rule) #} {# Separator (Vertical Rule) #}
<div class="vr" style="height: 28px;"></div> <div class="vr" style="height: 28px;"></div>
<button type="button" class="btn btn-outline-info btn-sm"
data-bs-toggle="modal"
hx-boost='true'
data-bs-target="#emailModal"
hx-get="{% url 'compose_candidate_email' job.slug %}"
hx-target="#emailModalBody"
hx-include="#candidate-form"
title="Email Participants">
<i class="fas fa-envelope"></i>
</button>
</div> </div>
</div> </div>
@ -338,6 +352,28 @@
</div> </div>
</div> </div>
<!-- Email Modal -->
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content kaauh-card">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="emailModalLabel" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-envelope me-2"></i>{% trans "Compose Email" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="emailModalBody" class="modal-body">
<div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Loading email form..." %}
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block customJS %} {% block customJS %}

View File

@ -162,18 +162,6 @@
font-size: 0.8rem !important; /* Slightly smaller font */ font-size: 0.8rem !important; /* Slightly smaller font */
} }
<<<<<<< HEAD
.kaats-spinner {
animation: kaats-spinner-rotate 1.5s linear infinite; /* Faster rotation */
width: 40px; /* Standard size */
height: 40px;
display: inline-block; /* Useful for table cells */
vertical-align: middle;
}
=======
>>>>>>> f71a202ed3606d299f9ac6515247662b6d3370b4
.kaats-spinner .path { .kaats-spinner .path {
stroke: var(--kaauh-teal, #00636e); /* Use Teal color, fallback to dark teal */ stroke: var(--kaauh-teal, #00636e); /* Use Teal color, fallback to dark teal */
@ -340,6 +328,17 @@
<button type="submit" class="btn btn-main-action btn-sm"> <button type="submit" class="btn btn-main-action btn-sm">
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %} <i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
</button> </button>
{# email button#}
<button type="button" class="btn btn-outline-info btn-sm"
data-bs-toggle="modal"
hx-boost='true'
data-bs-target="#emailModal"
hx-get="{% url 'compose_candidate_email' job.slug %}"
hx-target="#emailModalBody"
hx-include="#candidate-form"
title="Email Participants">
<i class="fas fa-envelope"></i>
</button>
</div> </div>
</form> </form>
@ -498,6 +497,26 @@
</div> </div>
</div> </div>
</div> </div>
!-- Email Modal -->
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content kaauh-card">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="emailModalLabel" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-envelope me-2"></i>{% trans "Compose Email" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="emailModalBody" class="modal-body">
<div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Loading email form..." %}
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}