diff --git a/NorahUniversity/__pycache__/settings.cpython-312.pyc b/NorahUniversity/__pycache__/settings.cpython-312.pyc index d539c86..b1e307c 100644 Binary files a/NorahUniversity/__pycache__/settings.cpython-312.pyc and b/NorahUniversity/__pycache__/settings.cpython-312.pyc differ diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index d741e46..a402e87 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -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', +} diff --git a/recruitment/__pycache__/admin.cpython-312.pyc b/recruitment/__pycache__/admin.cpython-312.pyc index 86a871d..1ada161 100644 Binary files a/recruitment/__pycache__/admin.cpython-312.pyc and b/recruitment/__pycache__/admin.cpython-312.pyc differ diff --git a/recruitment/__pycache__/forms.cpython-312.pyc b/recruitment/__pycache__/forms.cpython-312.pyc index 20e2903..67ec042 100644 Binary files a/recruitment/__pycache__/forms.cpython-312.pyc and b/recruitment/__pycache__/forms.cpython-312.pyc differ diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc index a2a7d03..e86961d 100644 Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ diff --git a/recruitment/__pycache__/signals.cpython-312.pyc b/recruitment/__pycache__/signals.cpython-312.pyc index 05801f6..cd94916 100644 Binary files a/recruitment/__pycache__/signals.cpython-312.pyc and b/recruitment/__pycache__/signals.cpython-312.pyc differ diff --git a/recruitment/__pycache__/urls.cpython-312.pyc b/recruitment/__pycache__/urls.cpython-312.pyc index 7f41aa6..e4baf7c 100644 Binary files a/recruitment/__pycache__/urls.cpython-312.pyc and b/recruitment/__pycache__/urls.cpython-312.pyc differ diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc index 80c6e30..336d5a1 100644 Binary files a/recruitment/__pycache__/views.cpython-312.pyc and b/recruitment/__pycache__/views.cpython-312.pyc differ diff --git a/recruitment/email_service.py b/recruitment/email_service.py index a048cf9..8395d13 100644 --- a/recruitment/email_service.py +++ b/recruitment/email_service.py @@ -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} \ No newline at end of file diff --git a/recruitment/forms.py b/recruitment/forms.py index 4142ebe..b553d1e 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -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 diff --git a/recruitment/migrations/0001_initial.py b/recruitment/migrations/0001_initial.py index 306ceb7..6e83eef 100644 --- a/recruitment/migrations/0001_initial.py +++ b/recruitment/migrations/0001_initial.py @@ -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 diff --git a/recruitment/models.py b/recruitment/models.py index ad673b9..cc73897 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -1763,3 +1763,5 @@ class Participants(Base): def __str__(self): return f"{self.name} - {self.email}" + + diff --git a/recruitment/views.py b/recruitment/views.py index 44c510c..7473177 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -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}") diff --git a/templates/applicant/applicant_profile.html b/templates/applicant/applicant_profile.html index 87f05c2..9acbcc4 100644 --- a/templates/applicant/applicant_profile.html +++ b/templates/applicant/applicant_profile.html @@ -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 %} + {% endblock %} {% block content %} -
+
- {# Profile Header #} -
-

- {% trans "My Candidate Profile" %} + {# Header: Larger, more dynamic on large screens. Stacks cleanly on mobile. #} +
+

+ {% trans "Your Candidate Dashboard" %}

- - {% trans "Edit Profile" %} + + {% trans "Update Profile" %}
- {# Profile and Account Management Row #} -
- - {# Candidate Details Card #} -
-
-
-
- {% trans 'Profile Picture' %} -
-

{{ candidate.name|default:"N/A" }}

-

{{ candidate.email }}

-
-
- -
    -
  • - - {% trans "Phone" %} {{ candidate.phone|default:"N/A" }} -
  • -
  • - - {% trans "Nationality" %} {{ candidate.get_nationality_display|default:"N/A" }} -
  • -
  • - - {% trans "Date of Birth" %} {{ candidate.date_of_birth|date:"M d, Y"|default:"N/A" }} -
  • -
  • - - {% trans "Resume" %} - {% if candidate.resume %} - - {% trans "View/Download" %} - - {% else %} - {% trans "Not uploaded" %} - {% endif %} -
  • -
-
-
-
- - {# Account Management / Quick Actions Card #} -
-
-
-

{% trans "Account Settings" %}

-
-
-
- - -
-
-

{% trans "Your profile is essential for the application process. Keep your resume and contact information up-to-date for timely communication." %}

-
-
-
-
+ {# Candidate Quick Overview Card: Use a softer background color #} +
+
+ {% trans 'Profile Picture' %} +
+

{{ candidate.name|default:"Candidate Name" }}

+

{{ candidate.email }}

- {# Application Tracking Section #} -

- {% trans "My Applications History" %} -

- - {% if applications %} -
- - - - - - - - - - - - {% for application in applications %} - - - - - - - - {% endfor %} - -
{% trans "Job Title" %}{% trans "Applied On" %}{% trans "Current Stage" %}{% trans "Status" %}{% trans "Action" %}
- - {{ application.job.title }} - - {{ application.applied_date|date:"d M Y" }} - - {{ application.stage }} - - - {% if application.is_active %} - {% trans "Active" %} - {% else %} - {% trans "Closed" %} - {% endif %} - - - {% trans "Details" %} - -
-
+ {# ================================================= #} + {# MAIN TABBED INTERFACE #} + {# ================================================= #} +
- {# Placeholder for Pagination #} - {% comment %} {% include "includes/paginator.html" with page_obj=applications_page %} {% endcomment %} - - {% else %} -
- -
{% trans "You have no active applications." %}
- - {% trans "View Available Jobs" %} - + {# Tab Navigation: Used nav-scroll for responsiveness #} + - {% endif %} + + {# Tab Content #} +
+ +
+

{% trans "Personal Information" %}

+
    +
  • +
    {% trans "Phone" %}
    + {{ candidate.phone|default:"N/A" }} +
  • +
  • +
    {% trans "Nationality" %}
    + {{ candidate.get_nationality_display|default:"N/A" }} +
  • +
  • +
    {% trans "Date of Birth" %}
    + {{ candidate.date_of_birth|date:"M d, Y"|default:"N/A" }} +
  • +
  • {% trans "Use the 'Update Profile' button above to edit these details." %}
  • +
+ +
+ +

{% trans "Quick Actions" %}

+ +
+ +
+

{% trans "Application Tracking" %}

+ + {% if applications %} +
+ + + + + + + + + + + + {% for application in applications %} + + + + + + + + {% endfor %} + +
{% trans "Job Title" %}{% trans "Applied On" %}{% trans "Current Stage" %}{% trans "Status" %}{% trans "Action" %}
+ + {{ application.job.title }} + + {{ application.applied_date|date:"d M Y" }} + + {{ application.stage }} + + + {% if application.is_active %} + {% trans "Active" %} + {% else %} + {% trans "Closed" %} + {% endif %} + + + {% trans "Details" %} + +
+
+ + {% else %} +
+ +
{% trans "You haven't submitted any applications yet." %}
+ + {% trans "View Available Jobs" %} + +
+ {% endif %} +
+ +
+

{% trans "My Uploaded Documents" %}

+ +

{% trans "You can upload and manage your resume, certificates, and professional documents here. These documents will be attached to your applications." %}

+ + + {% trans "Upload New Document" %} + + +
+ + {# Example Document List (Refined structure) #} +
    +
  • +
    + **{% trans "Resume" %}** (CV\_John\_Doe\_2024.pdf) +
    +
    + {% trans "Uploaded: 10 Jan 2024" %} + + +
    +
  • +
  • +
    + **{% trans "Medical Certificate" %}** (Cert\_KSA\_MED.jpg) +
    +
    + {% trans "Uploaded: 22 Feb 2023" %} + + +
    +
  • +
+ +
+ +
+

{% trans "Security & Preferences" %}

+ +
+
+
+
{% trans "Password Security" %}
+

{% trans "Update your password regularly to keep your account secure." %}

+ + {% trans "Change Password" %} + +
+
+
+
+
{% trans "Email Preferences" %}
+

{% trans "Manage subscriptions and job alert settings." %}

+ + {% trans "Manage Alerts" %} + +
+
+
+ +
+ {% trans "To delete your profile, please contact HR support." %} +
+
+ +
+
+ {# ================================================= #} +
{% endblock %} \ No newline at end of file diff --git a/templates/applicant/application_detail.html b/templates/applicant/application_detail.html index a574903..64465e6 100644 --- a/templates/applicant/application_detail.html +++ b/templates/applicant/application_detail.html @@ -1,5 +1,6 @@ {% extends 'applicant/partials/candidate_facing_base.html'%} {% load static i18n %} + {% block content %}