transfered the relationships of participants to candidate instead of job
This commit is contained in:
parent
6aa9ebd279
commit
da05441f94
Binary file not shown.
@ -401,3 +401,11 @@ CKEDITOR_5_CONFIGS = {
|
||||
|
||||
# Define a constant in settings.py to specify file upload permissions
|
||||
CKEDITOR_5_FILE_UPLOAD_PERMISSION = "staff" # Possible values: "staff", "authenticated", "any"
|
||||
|
||||
|
||||
|
||||
|
||||
from django.contrib.messages import constants as messages
|
||||
MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger',
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -224,138 +224,165 @@ def send_interview_invitation_email(candidate, job, meeting_details=None, recipi
|
||||
logger.error(error_msg, exc_info=True)
|
||||
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):
|
||||
"""
|
||||
Send bulk email to multiple recipients with HTML support and attachments.
|
||||
|
||||
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
|
||||
Send bulk email to multiple recipients with HTML support and attachments,
|
||||
supporting synchronous or asynchronous dispatch.
|
||||
"""
|
||||
# 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_:
|
||||
print("hereeeeeee")
|
||||
from django_q.tasks import async_task
|
||||
try:
|
||||
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
|
||||
# processed_attachments = []
|
||||
# if attachments:
|
||||
# for attachment in attachments:
|
||||
# if hasattr(attachment, 'read'):
|
||||
# # File-like object - save to temporary file
|
||||
# filename = getattr(attachment, 'name', 'attachment')
|
||||
# content_type = getattr(attachment, 'content_type', 'application/octet-stream')
|
||||
# Queue Left Candidates
|
||||
if unique_left_candidates:
|
||||
task_id = async_task(
|
||||
'recruitment.tasks.send_bulk_email_task',
|
||||
subject,
|
||||
message,
|
||||
recipient_list,
|
||||
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
|
||||
# with tempfile.NamedTemporaryFile(delete=False, suffix=f'_{filename}') as temp_file:
|
||||
# content = attachment.read()
|
||||
# temp_file.write(content)
|
||||
# temp_file_path = temp_file.name
|
||||
return {
|
||||
'success': True,
|
||||
'async': True,
|
||||
'task_ids': task_ids,
|
||||
'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
|
||||
# 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)
|
||||
# --- 4. Handle SYNCHRONOUS Send (If async_task_=False or fallback) ---
|
||||
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')
|
||||
|
||||
# Check if message contains HTML tags
|
||||
is_html = '<' in message and '>' in message
|
||||
successful_sends = 0
|
||||
|
||||
if is_html:
|
||||
# Create HTML email with plain text fallback
|
||||
plain_message = strip_tags(message)
|
||||
# Helper Function for Sync Send
|
||||
def send_individual_email(recipient, body_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
|
||||
# if attachments:
|
||||
# for attachment in attachments:
|
||||
# if hasattr(attachment, 'read'):
|
||||
# # File-like object
|
||||
# filename = getattr(attachment, 'name', 'attachment')
|
||||
# content = attachment.read()
|
||||
# content_type = getattr(attachment, 'content_type', 'application/octet-stream')
|
||||
# email.attach(filename, content, content_type)
|
||||
# elif isinstance(attachment, tuple) and len(attachment) == 3:
|
||||
# # (filename, content, content_type) tuple
|
||||
# filename, content, content_type = attachment
|
||||
# email.attach(filename, content, content_type)
|
||||
# Send Emails
|
||||
for email in unique_left_candidates:
|
||||
candidate_name=Candidate.objects.filter(email=email).first().first_name
|
||||
candidate_message = f"Hi, {candidate_name}"+"\n"+message
|
||||
|
||||
send_individual_email(email, candidate_message)
|
||||
|
||||
i=0
|
||||
for email in unique_agencies:
|
||||
candidate_name=Candidate.objects.filter(email=candidate_through_agency_emails[i]).first().first_name
|
||||
agency_message = f"Hi, {candidate_name}"+"\n"+message
|
||||
send_individual_email(email, agency_message)
|
||||
|
||||
# Send email
|
||||
email.send(fail_silently=False)
|
||||
for email in unique_participants:
|
||||
|
||||
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 {
|
||||
'success': True,
|
||||
'recipients_count': len(clean_recipients),
|
||||
'message': f'Email sent successfully to {len(clean_recipients)} recipient(s)'
|
||||
'recipients_count': successful_sends,
|
||||
'message': f'Email processing complete. {successful_sends} email(s) were sent successfully to {total_recipients} unique intended recipients.'
|
||||
}
|
||||
|
||||
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)
|
||||
return {'success': False, 'error': error_msg}
|
||||
return {'success': False, 'error': error_msg}
|
||||
@ -1270,7 +1270,7 @@ class CandidateEmailForm(forms.Form):
|
||||
'class': 'form-check'
|
||||
}),
|
||||
label=_('Select Candidates'), # Use a descriptive label
|
||||
required=True
|
||||
required=False
|
||||
)
|
||||
|
||||
# to = forms.MultipleChoiceField(
|
||||
@ -1308,7 +1308,7 @@ class CandidateEmailForm(forms.Form):
|
||||
'class': 'form-check'
|
||||
}),
|
||||
label=_('Recipients'),
|
||||
required=True
|
||||
required=False
|
||||
)
|
||||
|
||||
# include_candidate_info = forms.BooleanField(
|
||||
@ -1334,15 +1334,18 @@ class CandidateEmailForm(forms.Form):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.job = job
|
||||
self.candidates=candidates
|
||||
stage=self.candidates.first().stage
|
||||
|
||||
# Get all participants and users for this job
|
||||
recipient_choices = []
|
||||
|
||||
# Add job participants
|
||||
for participant in job.participants.all():
|
||||
recipient_choices.append(
|
||||
(f'participant_{participant.id}', f'{participant.name} - {participant.designation} (Participant)')
|
||||
)
|
||||
#show particpants only in the interview stage
|
||||
if stage=='Interview':
|
||||
for participant in job.participants.all():
|
||||
recipient_choices.append(
|
||||
(f'participant_{participant.id}', f'{participant.name} - {participant.designation} (Participant)')
|
||||
)
|
||||
|
||||
# Add job users
|
||||
for user in job.users.all():
|
||||
@ -1373,7 +1376,60 @@ class CandidateEmailForm(forms.Form):
|
||||
|
||||
def _get_initial_message(self):
|
||||
"""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
|
||||
# if self.candidate:
|
||||
@ -1394,12 +1450,12 @@ class CandidateEmailForm(forms.Form):
|
||||
|
||||
return '\n'.join(message_parts)
|
||||
|
||||
def clean_recipients(self):
|
||||
"""Ensure at least one recipient is selected"""
|
||||
recipients = self.cleaned_data.get('recipients')
|
||||
if not recipients:
|
||||
raise forms.ValidationError(_('Please select at least one recipient.'))
|
||||
return recipients
|
||||
# def clean_recipients(self):
|
||||
# """Ensure at least one recipient is selected"""
|
||||
# recipients = self.cleaned_data.get('recipients')
|
||||
# if not recipients:
|
||||
# raise forms.ValidationError(_('Please select at least one recipient.'))
|
||||
# return recipients
|
||||
|
||||
# def clean_to(self):
|
||||
# """Ensure at least one recipient is selected"""
|
||||
@ -1414,32 +1470,32 @@ class CandidateEmailForm(forms.Form):
|
||||
email_addresses = []
|
||||
recipients = self.cleaned_data.get('recipients', [])
|
||||
candidates=self.cleaned_data.get('to',[])
|
||||
|
||||
for recipient in recipients:
|
||||
if recipient.startswith('participant_'):
|
||||
participant_id = recipient.split('_')[1]
|
||||
try:
|
||||
participant = Participants.objects.get(id=participant_id)
|
||||
email_addresses.append(participant.email)
|
||||
except Participants.DoesNotExist:
|
||||
continue
|
||||
elif recipient.startswith('user_'):
|
||||
user_id = recipient.split('_')[1]
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
email_addresses.append(user.email)
|
||||
except User.DoesNotExist:
|
||||
continue
|
||||
|
||||
for candidate in candidates:
|
||||
if candidate.startswith('candidate_'):
|
||||
print("candidadte: {candidate}")
|
||||
candidate_id = candidate.split('_')[1]
|
||||
try:
|
||||
candidate = Candidate.objects.get(id=candidate_id)
|
||||
email_addresses.append(candidate.email)
|
||||
except Candidate.DoesNotExist:
|
||||
continue
|
||||
if recipients:
|
||||
for recipient in recipients:
|
||||
if recipient.startswith('participant_'):
|
||||
participant_id = recipient.split('_')[1]
|
||||
try:
|
||||
participant = Participants.objects.get(id=participant_id)
|
||||
email_addresses.append(participant.email)
|
||||
except Participants.DoesNotExist:
|
||||
continue
|
||||
elif recipient.startswith('user_'):
|
||||
user_id = recipient.split('_')[1]
|
||||
try:
|
||||
user = User.objects.get(id=user_id)
|
||||
email_addresses.append(user.email)
|
||||
except User.DoesNotExist:
|
||||
continue
|
||||
if candidates:
|
||||
for candidate in candidates:
|
||||
if candidate.startswith('candidate_'):
|
||||
print("candidadte: {candidate}")
|
||||
candidate_id = candidate.split('_')[1]
|
||||
try:
|
||||
candidate = Candidate.objects.get(id=candidate_id)
|
||||
email_addresses.append(candidate.email)
|
||||
except Candidate.DoesNotExist:
|
||||
continue
|
||||
|
||||
|
||||
return list(set(email_addresses)) # Remove duplicates
|
||||
|
||||
@ -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.db.models.deletion
|
||||
|
||||
@ -1763,3 +1763,5 @@ class Participants(Base):
|
||||
def __str__(self):
|
||||
return f"{self.name} - {self.email}"
|
||||
|
||||
|
||||
|
||||
|
||||
@ -560,7 +560,7 @@ def kaauh_career(request):
|
||||
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:
|
||||
def application_detail(request, slug):
|
||||
@ -3708,30 +3708,29 @@ def compose_candidate_email(request, job_slug):
|
||||
candidate_ids=request.GET.getlist('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':
|
||||
candidate_ids = request.POST.getlist('candidate_ids')
|
||||
print(f"inside the POST {candidate_ids}" )
|
||||
candidates=Candidate.objects.filter(id__in=candidate_ids)
|
||||
form = CandidateEmailForm(job, candidates, request.POST)
|
||||
|
||||
|
||||
print(form)
|
||||
|
||||
if form.is_valid():
|
||||
print("form is valid ...")
|
||||
# Get email addresses
|
||||
email_addresses = form.get_email_addresses()
|
||||
print(f"hiii {email_addresses} ")
|
||||
|
||||
|
||||
if not email_addresses:
|
||||
messages.error(request, 'No valid email addresses found for selected recipients.')
|
||||
return render(request, 'includes/email_compose_form.html', {
|
||||
'form': form,
|
||||
'job': job,
|
||||
'candidate': candidates
|
||||
})
|
||||
messages.error(request, 'No email selected')
|
||||
referer = request.META.get('HTTP_REFERER')
|
||||
|
||||
if referer:
|
||||
# Redirect back to the referring page
|
||||
return redirect(referer)
|
||||
else:
|
||||
|
||||
return redirect('dashboard')
|
||||
|
||||
|
||||
|
||||
# Check if this is an interview invitation
|
||||
subject = form.cleaned_data.get('subject', '').lower()
|
||||
@ -3739,7 +3738,7 @@ def compose_candidate_email(request, job_slug):
|
||||
|
||||
if is_interview_invitation:
|
||||
# Use HTML template for interview invitations
|
||||
meeting_details = None
|
||||
# meeting_details = None
|
||||
# if form.cleaned_data.get('include_meeting_details'):
|
||||
# # Try to get meeting details from candidate
|
||||
# meeting_details = {
|
||||
@ -3751,9 +3750,9 @@ def compose_candidate_email(request, job_slug):
|
||||
|
||||
from .email_service import send_interview_invitation_email
|
||||
email_result = send_interview_invitation_email(
|
||||
candidate=candidates,
|
||||
candidates=candidates,
|
||||
job=job,
|
||||
meeting_details=meeting_details,
|
||||
# meeting_details=meeting_details,
|
||||
recipient_list=email_addresses
|
||||
)
|
||||
else:
|
||||
@ -3762,41 +3761,41 @@ def compose_candidate_email(request, job_slug):
|
||||
subject = form.cleaned_data.get('subject')
|
||||
|
||||
# Send emails using email service (no attachments, synchronous to avoid pickle issues)
|
||||
|
||||
|
||||
email_result = send_bulk_email(
|
||||
subject=subject,
|
||||
message=message,
|
||||
recipient_list=email_addresses,
|
||||
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']:
|
||||
messages.success(request, f'Email sent successfully to {len(email_addresses)} recipient(s).')
|
||||
if email_result['success']:
|
||||
messages.success(request, f'Email sent successfully to {len(email_addresses)} recipient(s).')
|
||||
|
||||
# # For HTMX requests, return success response
|
||||
# if 'HX-Request' in request.headers:
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'message': f'Email sent successfully to {len(email_addresses)} recipient(s).'
|
||||
# })
|
||||
# # For HTMX requests, return success response
|
||||
# if 'HX-Request' in request.headers:
|
||||
# return JsonResponse({
|
||||
# 'success': True,
|
||||
# 'message': f'Email sent successfully to {len(email_addresses)} recipient(s).'
|
||||
# })
|
||||
|
||||
return redirect('candidate_interview_view', slug=job.slug)
|
||||
else:
|
||||
messages.error(request, f'Failed to send email: {email_result.get("message", "Unknown error")}')
|
||||
return redirect('candidate_interview_view', slug=job.slug)
|
||||
else:
|
||||
messages.error(request, f'Failed to send email: {email_result.get("message", "Unknown error")}')
|
||||
|
||||
# For HTMX requests, return error response
|
||||
# if 'HX-Request' in request.headers:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'error': email_result.get("message", "Failed to send email")
|
||||
# })
|
||||
# For HTMX requests, return error response
|
||||
# if 'HX-Request' in request.headers:
|
||||
# return JsonResponse({
|
||||
# 'success': False,
|
||||
# 'error': email_result.get("message", "Failed to send email")
|
||||
# })
|
||||
|
||||
return render(request, 'includes/email_compose_form.html', {
|
||||
'form': form,
|
||||
'job': job,
|
||||
'candidate': candidates
|
||||
})
|
||||
return render(request, 'includes/email_compose_form.html', {
|
||||
'form': form,
|
||||
'job': job,
|
||||
'candidate': candidates
|
||||
})
|
||||
|
||||
# except Exception as e:
|
||||
# logger.error(f"Error sending candidate email: {e}")
|
||||
|
||||
@ -2,161 +2,421 @@
|
||||
{% load static i18n %}
|
||||
|
||||
|
||||
{% block title %}{% trans "My Profile" %} - {{ block.super }}{% endblock %}
|
||||
{% block title %}{% trans "My Dashboard" %} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% 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 %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-5">
|
||||
<div class="container py-4 py-md-5">
|
||||
|
||||
{# Profile Header #}
|
||||
<div class="d-flex justify-content-between align-items-center mb-5 border-bottom pb-4">
|
||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-user-circle me-3 text-primary-theme"></i> {% trans "My Candidate Profile" %}
|
||||
{# Header: Larger, more dynamic on large screens. Stacks cleanly on mobile. #}
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5">
|
||||
<h1 class="display-6 display-md-5 fw-extrabold mb-3 mb-md-0" style="color: var(--kaauh-teal-dark);">
|
||||
{% trans "Your Candidate Dashboard" %}
|
||||
</h1>
|
||||
<a href="#" class="btn btn-main-action btn-lg">
|
||||
<i class="fas fa-edit me-2"></i> {% trans "Edit Profile" %}
|
||||
<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 "Update Profile" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{# Profile and Account Management Row #}
|
||||
<div class="row g-5 mb-5">
|
||||
|
||||
{# Candidate Details Card #}
|
||||
<div class="col-lg-5">
|
||||
<div class="card kaauh-card h-100 p-4">
|
||||
<div class="card-body p-0">
|
||||
<div class="d-flex align-items-center mb-4 pb-4 border-bottom">
|
||||
<img src="{% if candidate.profile_picture %}{{ candidate.profile_picture.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}"
|
||||
alt="{% trans 'Profile Picture' %}"
|
||||
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>
|
||||
{# Candidate Quick Overview Card: Use a softer background color #}
|
||||
<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">
|
||||
<img src="{% if candidate.profile_picture %}{{ candidate.profile_picture.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}"
|
||||
alt="{% trans 'Profile Picture' %}"
|
||||
class="rounded-circle me-sm-4 mb-3 mb-sm-0 shadow-lg"
|
||||
style="width: 80px; height: 80px; object-fit: cover; border: 4px solid var(--kaauh-teal-accent);">
|
||||
<div>
|
||||
<h3 class="card-title mb-1 fw-bold text-dark">{{ candidate.name|default:"Candidate Name" }}</h3>
|
||||
<p class="text-gray-subtle mb-0">{{ candidate.email }}</p>
|
||||
</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;">
|
||||
<i class="fas fa-list-alt me-2 text-primary-theme"></i> {% trans "My Applications History" %}
|
||||
</h2>
|
||||
|
||||
{% 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>
|
||||
{# ================================================= #}
|
||||
{# MAIN TABBED INTERFACE #}
|
||||
{# ================================================= #}
|
||||
<div class="card kaauh-card p-0 bg-white">
|
||||
|
||||
{# Placeholder for Pagination #}
|
||||
{% comment %} {% include "includes/paginator.html" with page_obj=applications_page %} {% endcomment %}
|
||||
|
||||
{% else %}
|
||||
<div class="alert alert-info text-center kaauh-card p-5" style="border: 1px dashed var(--kaauh-border);">
|
||||
<i class="fas fa-info-circle fa-2x mb-3 text-primary-theme"></i>
|
||||
<h5 class="mb-3">{% trans "You have no active applications." %}</h5>
|
||||
<a href="{% url 'job_list' %}" class="ms-3 btn btn-main-action mt-2">
|
||||
{% trans "View Available Jobs" %} <i class="fas fa-arrow-right ms-2"></i>
|
||||
</a>
|
||||
{# Tab Navigation: Used nav-scroll for responsiveness #}
|
||||
<div class="nav-scroll px-4 pt-3">
|
||||
<ul class="nav nav-tabs" id="candidateTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<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-user-circle me-2"></i> {% trans "Profile Details" %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<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>
|
||||
{% 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>
|
||||
{% endblock %}
|
||||
@ -1,5 +1,6 @@
|
||||
{% extends 'applicant/partials/candidate_facing_base.html'%}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<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="card shadow-sm sticky-top">
|
||||
<div class="card-header bg-kaauh-teal-dark text-white">
|
||||
<h5 class="mb-0"><i class="fas fa-file-signature me-2"></i>{% trans "Ready to Apply?" %}</h5>
|
||||
<div class="card-header bg-kaauh-teal-dark bg-white p-3">
|
||||
<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 class="card-body text-center">
|
||||
<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="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 class="card-body">
|
||||
<div class="card-body p-4">
|
||||
|
||||
<h4 class="mb-3" 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">
|
||||
<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-5 small text-secondary border p-3 rounded">
|
||||
{% if job.salary_range %}
|
||||
<div class="col">
|
||||
<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>
|
||||
|
||||
{% 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 class="mobile-fixed-apply-bar d-lg-none">
|
||||
<div class="mobile-fixed-apply-bar d-lg-none text-center">
|
||||
{% 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" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
156
templates/applicant/career.html
Normal file
156
templates/applicant/career.html
Normal 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 %}
|
||||
@ -8,7 +8,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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 %}
|
||||
{% if LANGUAGE_CODE == 'ar' %}
|
||||
@ -25,13 +25,17 @@
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-teal-light: #e6f7f8; /* Very light teal for hover/background */
|
||||
--success: #198754;
|
||||
--danger: #dc3545;
|
||||
--light-bg: #f8f9fa;
|
||||
--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-gap: 16px;
|
||||
--sticky-navbar-total-height: 128px;
|
||||
@ -39,208 +43,142 @@
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
background-color: #f0f0f5; /* Light gray background for contrast */
|
||||
background-color: #f0f0f5;
|
||||
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 {
|
||||
background-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
border: none;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
box-shadow: 0 4px 12px rgba(0, 99, 110, 0.3);
|
||||
transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
|
||||
box-shadow: 0 6px 15px rgba(0, 99, 110, 0.4); /* Stronger shadow */
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.btn-main-action:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
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 {
|
||||
/* Position right below the sticky navbar (56px) with a small top margin */
|
||||
margin-top: calc(var(--navbar-height) + 10px);
|
||||
.hero-section {
|
||||
background: linear-gradient(135deg, var(--kaauh-dark-contrast) 0%, var(--kaauh-dark-bg) 100%);
|
||||
padding: 4rem 0; /* Reduced from 8rem to 4rem */
|
||||
margin-top: -1px;
|
||||
color: white;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.alert {
|
||||
padding: 0.75rem 1.25rem;
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.08);
|
||||
margin-bottom: 0; /* Handled by container margin */
|
||||
border-left: 5px solid; /* Feature highlight */
|
||||
.hero-title {
|
||||
font-size: 2.5rem; /* Reduced from 3.5rem to 2.5rem */
|
||||
font-weight: 800; /* Extra bold */
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.05em;
|
||||
max-width: 900px;
|
||||
}
|
||||
.alert-success {
|
||||
color: var(--success);
|
||||
background-color: #d1e7dd;
|
||||
border-color: var(--success);
|
||||
.hero-section .lead {
|
||||
font-size: 1.35rem; /* Larger lead text */
|
||||
}
|
||||
.alert-error, .alert-danger {
|
||||
color: var(--danger);
|
||||
background-color: #f8d7da;
|
||||
border-color: var(--danger);
|
||||
}
|
||||
.alert-info {
|
||||
color: var(--kaauh-teal-dark);
|
||||
background-color: #cff4fc;
|
||||
.btn-hero-action {
|
||||
/* Primary CTA: Retained strong teal look */
|
||||
background-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 {
|
||||
color: var(--gray-text) !important; /* Use secondary color */
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.5rem 0.75rem !important;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
.path-card-section {
|
||||
background-color: white;
|
||||
margin-top: -80px; /* Pulls the section up over the dark hero for a modern layered look */
|
||||
position: relative;
|
||||
z-index: 50;
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 5rem;
|
||||
border-top-left-radius: 2rem;
|
||||
border-top-right-radius: 2rem;
|
||||
box-shadow: var(--kaauh-shadow); /* Defines the separation */
|
||||
}
|
||||
.language-toggle-btn:hover {
|
||||
background: var(--light-bg) !important;
|
||||
border-radius: 4px;
|
||||
color: var(--kaauh-teal) !important;
|
||||
}
|
||||
|
||||
/* Dropdown Menu styling for language */
|
||||
.dropdown-menu {
|
||||
backdrop-filter: blur(4px);
|
||||
background-color: rgba(255, 255, 255, 0.98);
|
||||
.path-card {
|
||||
padding: 2rem;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid var(--kaauh-border);
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem 0;
|
||||
min-width: 150px;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.dropdown-item {
|
||||
padding: 0.5rem 1.25rem;
|
||||
transition: background-color 0.15s;
|
||||
text-align: inherit; /* Ensure text alignment is controlled by dir="rtl" */
|
||||
.path-card:hover {
|
||||
border-color: var(--kaauh-teal);
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 30px rgba(0, 99, 110, 0.1);
|
||||
}
|
||||
|
||||
/* Use button as dropdown-item inside form for full click area */
|
||||
.dropdown-item[type="submit"] {
|
||||
width: 100%;
|
||||
text-align: inherit;
|
||||
border: none;
|
||||
.path-card-icon {
|
||||
color: var(--kaauh-teal);
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.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) */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
#topNavbar {
|
||||
z-index: 1040; /* Higher than the bottom bar */
|
||||
.job-listing-section {
|
||||
background-color: #f0f0f5; /* Separates the job list from the white path section */
|
||||
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 */
|
||||
#bottomNavbar {
|
||||
/* 56px (white nav) + 16px (mb-3) = 72px */
|
||||
top: calc(var(--navbar-height) + var(--navbar-gap));
|
||||
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>
|
||||
/* RTL Corrections (retained) */
|
||||
html[dir="rtl"] .alert { border-right: 5px solid; border-left: none; }
|
||||
html[dir="rtl"] .job-listing-card { border-right: 6px solid var(--kaauh-teal); border-left: 1px solid var(--kaauh-border); }
|
||||
</style>
|
||||
</head>
|
||||
<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">
|
||||
<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;">
|
||||
KAAUH
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
|
||||
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
@ -249,11 +187,11 @@
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<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>
|
||||
</li>
|
||||
</li> {% endcomment %}
|
||||
<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 class="nav-item">
|
||||
<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">
|
||||
|
||||
{% comment %} English Button {% endcomment %}
|
||||
<li>
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
@ -279,7 +216,6 @@
|
||||
</form>
|
||||
</li>
|
||||
|
||||
{% comment %} Arabic Button {% endcomment %}
|
||||
<li>
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
@ -296,9 +232,13 @@
|
||||
</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>
|
||||
|
||||
{% block customJS %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@ -290,14 +290,15 @@
|
||||
|
||||
|
||||
<main class="container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show mt-2" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show mt-2" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
@ -1,8 +1,19 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<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-body">
|
||||
@ -32,7 +43,7 @@
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
{% trans "Recipients" %}
|
||||
{% trans "Participants" %}
|
||||
</label>
|
||||
<div class="border rounded p-3 bg-light" style="max-height: 200px; overflow-y: auto;">
|
||||
{% for choice in form.recipients %}
|
||||
|
||||
@ -229,6 +229,17 @@
|
||||
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
|
||||
</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>
|
||||
@ -358,6 +369,28 @@
|
||||
</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 %}
|
||||
|
||||
|
||||
|
||||
@ -225,10 +225,63 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<form id="candidate-form" action="{% url 'candidate_update_status' job.slug %}" method="get">
|
||||
{% csrf_token %}
|
||||
<table class="table candidate-table align-middle">
|
||||
<thead>
|
||||
<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-phone me-1"></i> {% trans "Contact" %}</th>
|
||||
<th style="width: 15%"><i class="fas fa-briefcase me-1"></i> {% trans "Applied Position" %}</th>
|
||||
@ -240,6 +293,12 @@
|
||||
<tbody>
|
||||
{% for candidate in candidates %}
|
||||
<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>
|
||||
<div class="candidate-name">
|
||||
{{ candidate.name }}
|
||||
@ -288,12 +347,15 @@
|
||||
title="View Resume Template">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</a>
|
||||
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
{% if not candidates %}
|
||||
<div class="alert alert-info text-center mt-3 mb-0" role="alert">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
@ -347,13 +409,76 @@
|
||||
</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 %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Add any specific JavaScript for hired candidates view if needed
|
||||
console.log('Hired candidates view loaded');
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
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() {
|
||||
|
||||
@ -249,7 +249,7 @@
|
||||
hx-include="#candidate-form"
|
||||
title="Email Participants">
|
||||
<i class="fas fa-envelope"></i>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -414,7 +414,7 @@
|
||||
data-modal-title="{% trans 'Schedule Interview' %}"
|
||||
title="Schedule Interview">
|
||||
<i class="fas fa-calendar-plus"></i>
|
||||
</button>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
|
||||
@ -221,10 +221,24 @@
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
|
||||
</button>
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
{# Separator (Vertical Rule) #}
|
||||
<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>
|
||||
@ -338,6 +352,28 @@
|
||||
</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 %}
|
||||
|
||||
{% block customJS %}
|
||||
|
||||
@ -162,18 +162,6 @@
|
||||
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 {
|
||||
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">
|
||||
<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>
|
||||
@ -498,6 +497,26 @@
|
||||
</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 %}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user