done with the refractoring 2 in the branch ai_overview for candidate to pplication still lot left

This commit is contained in:
Faheed 2025-11-26 19:28:47 +03:00
parent 1ad85b82e1
commit 8b51416e10
37 changed files with 810 additions and 772 deletions

View File

@ -35,7 +35,7 @@ class CandidateSyncService:
} }
# Get all hired candidates for this job # Get all hired candidates for this job
hired_candidates = list(job.hired_candidates.select_related('job')) hired_candidates = list(job.hired_applications.select_related('job'))
results['total_candidates'] = len(hired_candidates) results['total_candidates'] = len(hired_candidates)

View File

@ -132,7 +132,7 @@ class JobPosting(Base):
# Application Information ---job detail apply link for the candidates # Application Information ---job detail apply link for the candidates
application_url = models.URLField( application_url = models.URLField(
validators=[URLValidator()], validators=[URLValidator()],
help_text="URL where candidates apply", help_text="URL where applicants apply",
null=True, null=True,
blank=True, blank=True,
) )
@ -222,7 +222,7 @@ class JobPosting(Base):
related_name="jobs", related_name="jobs",
verbose_name=_("Hiring Agency"), verbose_name=_("Hiring Agency"),
help_text=_( help_text=_(
"External agency responsible for sourcing candidates for this role" "External agency responsible for sourcing applicants for this role"
), ),
) )
cancel_reason = models.TextField( cancel_reason = models.TextField(
@ -365,7 +365,7 @@ class JobPosting(Base):
@property @property
def current_applications_count(self): def current_applications_count(self):
"""Returns the current number of candidates associated with this job.""" """Returns the current number of applications associated with this job."""
return self.applications.count() return self.applications.count()
@property @property
@ -377,7 +377,7 @@ class JobPosting(Base):
return self.current_applications_count >= self.max_applications return self.current_applications_count >= self.max_applications
@property @property
def all_candidates(self): def all_applications(self):
# 1. Define the safe JSON extraction and conversion expression # 1. Define the safe JSON extraction and conversion expression
safe_score_expression = Cast( safe_score_expression = Cast(
Coalesce( Coalesce(
@ -397,68 +397,70 @@ class JobPosting(Base):
).order_by("-sortable_score") ).order_by("-sortable_score")
@property @property
def screening_candidates(self): def screening_applications(self):
return self.all_candidates.filter(stage="Applied") return self.all_applications.filter(stage="Applied")
@property @property
def exam_candidates(self): def exam_applications(self):
return self.all_candidates.filter(stage="Exam") return self.all_applications.filter(stage="Exam")
@property @property
def interview_candidates(self): def interview_applications(self):
return self.all_candidates.filter(stage="Interview") return self.all_applications.filter(stage="Interview")
@property @property
def document_review_candidates(self): def document_review_applications(self):
return self.all_candidates.filter(stage="Document Review") return self.all_applications.filter(stage="Document Review")
@property @property
def offer_candidates(self): def offer_applications(self):
return self.all_candidates.filter(stage="Offer") return self.all_applications.filter(stage="Offer")
@property @property
def accepted_candidates(self): def accepted_applications(self):
return self.all_candidates.filter(offer_status="Accepted") return self.all_applications.filter(offer_status="Accepted")
@property @property
def hired_candidates(self): def hired_applications(self):
return self.all_candidates.filter(stage="Hired") return self.all_applications.filter(stage="Hired")
# counts # counts
@property @property
def all_candidates_count(self): def all_applications_count(self):
return self.all_candidates.count() return self.all_applications.count()
@property @property
def screening_candidates_count(self): def screening_applications_count(self):
return self.all_candidates.filter(stage="Applied").count() or 0 return self.all_applications.filter(stage="Applied").count() or 0
@property @property
def exam_candidates_count(self): def exam_applications_count(self):
return self.all_candidates.filter(stage="Exam").count() or 0 return self.all_applications.filter(stage="Exam").count() or 0
@property @property
def interview_candidates_count(self): def interview_applications_count(self):
return self.all_candidates.filter(stage="Interview").count() or 0 return self.all_applications.filter(stage="Interview").count() or 0
@property @property
def document_review_candidates_count(self): def document_review_applications_count(self):
return self.all_candidates.filter(stage="Document Review").count() or 0 return self.all_applications.filter(stage="Document Review").count() or 0
@property @property
def offer_candidates_count(self): def offer_applications_count(self):
return self.all_candidates.filter(stage="Offer").count() or 0 return self.all_applications.filter(stage="Offer").count() or 0
@property @property
def hired_candidates_count(self): def hired_applications_count(self):
return self.all_candidates.filter(stage="Hired").count() or 0 return self.all_applications.filter(stage="Hired").count() or 0
@property @property
def vacancy_fill_rate(self): def vacancy_fill_rate(self):
total_positions = self.open_positions total_positions = self.open_positions
print(total_positions)
no_of_positions_filled = self.applications.filter(stage__in=["HIRED"]).count() no_of_positions_filled = self.applications.filter(stage__in=["Hired"]).count()
print(no_of_positions_filled)
if total_positions > 0: if total_positions > 0:
vacancy_fill_rate = no_of_positions_filled / total_positions vacancy_fill_rate = no_of_positions_filled / total_positions

View File

@ -866,7 +866,7 @@ def sync_hired_candidates_task(job_slug):
# action=IntegrationLog.ActionChoices.SYNC, # action=IntegrationLog.ActionChoices.SYNC,
# endpoint="multi_source_sync", # endpoint="multi_source_sync",
# method="BACKGROUND_TASK", # method="BACKGROUND_TASK",
# request_data={"job_slug": job_slug, "candidate_count": job.accepted_candidates.count()}, # request_data={"job_slug": job_slug, "candidate_count": job.accepted_applications.count()},
# response_data=results, # response_data=results,
# status_code="SUCCESS" if results.get('summary', {}).get('failed', 0) == 0 else "PARTIAL", # status_code="SUCCESS" if results.get('summary', {}).get('failed', 0) == 0 else "PARTIAL",
# ip_address="127.0.0.1", # Background task # ip_address="127.0.0.1", # Background task

View File

@ -452,13 +452,13 @@ def edit_job(request, slug):
if form.is_valid(): if form.is_valid():
try: try:
form.save() form.save()
messages.success(request, f'Job "{job.title}" updated successfully!') messages.success(request, _('Job "%(title)s" updated successfully!') % {'title': job.title})
return redirect("job_list") return redirect("job_list")
except Exception as e: except Exception as e:
logger.error(f"Error updating job: {e}") logger.error(f"Error updating job: {e}")
messages.error(request, f"Error updating job: {e}") messages.error(request, _('Error updating job: %(error)s') % {'error': e})
else: else:
messages.error(request, "Please correct the errors below.") messages.error(request, _("Please correct the errors below."))
else: else:
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
form = JobPostingForm(instance=job) form = JobPostingForm(instance=job)
@ -478,18 +478,18 @@ def job_detail(request, slug):
"""View details of a specific job""" """View details of a specific job"""
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
# Get all applications for this job, ordered by most recent # Get all applications for this job, ordered by most recent
applicants = job.applications.all().order_by("-created_at") applications = job.applications.all().order_by("-created_at")
# Count applications by stage for summary statistics # Count applications by stage for summary statistics
total_applicant = applicants.count() total_applications = applications.count()
applied_count = applicants.filter(stage="Applied").count() applied_count = applications.filter(stage="Applied").count()
exam_count = applicants.filter(stage="Exam").count() exam_count = applications.filter(stage="Exam").count()
interview_count = applicants.filter(stage="Interview").count() interview_count = applications.filter(stage="Interview").count()
offer_count = applicants.filter(stage="Offer").count() offer_count = applications.filter(stage="Offer").count()
status_form = JobPostingStatusForm(instance=job) status_form = JobPostingStatusForm(instance=job)
linkedin_content_form = LinkedPostContentForm(instance=job) linkedin_content_form = LinkedPostContentForm(instance=job)
@ -528,8 +528,8 @@ def job_detail(request, slug):
# --- 2. Quality Metrics (JSON Aggregation) --- # --- 2. Quality Metrics (JSON Aggregation) ---
candidates_with_score = applicants.filter(is_resume_parsed=True) applications_with_score = applications.filter(is_resume_parsed=True)
total_candidates = candidates_with_score.count() # For context total_applications_ = applications_with_score.count() # For context
# Define the queryset for applications that have been parsed # Define the queryset for applications that have been parsed
score_expression = Cast( score_expression = Cast(
@ -544,34 +544,34 @@ def job_detail(request, slug):
) )
# 2. ANNOTATE the queryset with the new field # 2. ANNOTATE the queryset with the new field
candidates_with_score = candidates_with_score.annotate( applications_with_score = applications_with_score.annotate(
annotated_match_score=score_expression annotated_match_score=score_expression
) )
avg_match_score_result = candidates_with_score.aggregate( avg_match_score_result = applications_with_score.aggregate(
avg_score=Avg('annotated_match_score') avg_score=Avg('annotated_match_score')
) )
avg_match_score = avg_match_score_result.get("avg_score") or 0.0 avg_match_score = avg_match_score_result.get("avg_score") or 0.0
high_potential_count = candidates_with_score.filter( high_potential_count = applications_with_score.filter(
annotated_match_score__gte=HIGH_POTENTIAL_THRESHOLD annotated_match_score__gte=HIGH_POTENTIAL_THRESHOLD
).count() ).count()
high_potential_ratio = ( high_potential_ratio = (
round((high_potential_count / total_candidates) * 100, 1) round((high_potential_count / total_applications_) * 100, 1)
if total_candidates > 0 if total_applications_ > 0
else 0 else 0
) )
# --- 3. Time Metrics (Duration Aggregation) --- # --- 3. Time Metrics (Duration Aggregation) ---
# Metric: Average Time from Applied to Interview (T2I) # Metric: Average Time from Applied to Interview (T2I)
t2i_candidates = applicants.filter(interview_date__isnull=False).annotate( t2i_applications = applications.filter(interview_date__isnull=False).annotate(
time_to_interview=ExpressionWrapper( time_to_interview=ExpressionWrapper(
F("interview_date") - F("created_at"), output_field=DurationField() F("interview_date") - F("created_at"), output_field=DurationField()
) )
) )
avg_t2i_duration = t2i_candidates.aggregate(avg_t2i=Avg("time_to_interview"))[ avg_t2i_duration = t2i_applications.aggregate(avg_t2i=Avg("time_to_interview"))[
"avg_t2i" "avg_t2i"
] ]
@ -583,14 +583,14 @@ def job_detail(request, slug):
) )
# Metric: Average Time in Exam Stage # Metric: Average Time in Exam Stage
t_in_exam_candidates = applicants.filter( t_in_exam_applications = applications.filter(
exam_date__isnull=False, interview_date__isnull=False exam_date__isnull=False, interview_date__isnull=False
).annotate( ).annotate(
time_in_exam=ExpressionWrapper( time_in_exam=ExpressionWrapper(
F("interview_date") - F("exam_date"), output_field=DurationField() F("interview_date") - F("exam_date"), output_field=DurationField()
) )
) )
avg_t_in_exam_duration = t_in_exam_candidates.aggregate( avg_t_in_exam_duration = t_in_exam_applications.aggregate(
avg_t_in_exam=Avg("time_in_exam") avg_t_in_exam=Avg("time_in_exam")
)["avg_t_in_exam"] )["avg_t_in_exam"]
@ -602,26 +602,27 @@ def job_detail(request, slug):
) )
category_data = ( category_data = (
applicants.filter(ai_analysis_data__analysis_data__category__isnull=False) applications.filter(ai_analysis_data__analysis_data_en__category__isnull=False)
.values("ai_analysis_data__analysis_data__category") .values("ai_analysis_data__analysis_data_en__category")
.annotate( .annotate(
candidate_count=Count("id"), application_count=Count("id"),
category=Cast( category=Cast(
"ai_analysis_data__analysis_data__category", output_field=CharField() "ai_analysis_data__analysis_data_en__category", output_field=CharField()
), ),
) )
.order_by("ai_analysis_data__analysis_data__category") .order_by("ai_analysis_data__analysis_data_en__category")
) )
# Prepare data for Chart.js # Prepare data for Chart.js
print(category_data)
categories = [item["category"] for item in category_data] categories = [item["category"] for item in category_data]
candidate_counts = [item["candidate_count"] for item in category_data]
applications_count = [item["application_count"] for item in category_data]
# avg_scores = [round(item['avg_match_score'], 2) if item['avg_match_score'] is not None else 0 for item in category_data] # avg_scores = [round(item['avg_match_score'], 2) if item['avg_match_score'] is not None else 0 for item in category_data]
context = { context = {
"job": job, "job": job,
"applicants": applicants, "applications": applications,
"total_applicants": total_applicant, # This was total_candidates in the prompt, using total_applicant for consistency "total_applications": total_applications, # This was total_candidates in the prompt, using total_applicant for consistency
"applied_count": applied_count, "applied_count": applied_count,
"exam_count": exam_count, "exam_count": exam_count,
"interview_count": interview_count, "interview_count": interview_count,
@ -629,7 +630,7 @@ def job_detail(request, slug):
"status_form": status_form, "status_form": status_form,
"image_upload_form": image_upload_form, "image_upload_form": image_upload_form,
"categories": categories, "categories": categories,
"candidate_counts": candidate_counts, "applications_count": applications_count,
# 'avg_scores': avg_scores, # 'avg_scores': avg_scores,
# New statistics # New statistics
"avg_match_score": avg_match_score, "avg_match_score": avg_match_score,
@ -1765,7 +1766,7 @@ def applications_screening_view(request, slug):
Manage candidate tiers and stage transitions Manage candidate tiers and stage transitions
""" """
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
candidates = job.screening_candidates applications = job.screening_applications
# Get filter parameters # Get filter parameters
min_ai_score_str = request.GET.get("min_ai_score") min_ai_score_str = request.GET.get("min_ai_score")
@ -1805,31 +1806,31 @@ def applications_screening_view(request, slug):
# Apply filters # Apply filters
if min_ai_score > 0: if min_ai_score > 0:
candidates = candidates.filter( applications = applications.filter(
ai_analysis_data__analysis_data__match_score__gte=min_ai_score ai_analysis_data__analysis_data_en__match_score__gte=min_ai_score
) )
if min_experience > 0: if min_experience > 0:
candidates = candidates.filter( applications = applications.filter(
ai_analysis_data__analysis_data__years_of_experience__gte=min_experience ai_analysis_data__analysis_data_en__years_of_experience__gte=min_experience
) )
if screening_rating: if screening_rating:
candidates = candidates.filter( applications = applications.filter(
ai_analysis_data__analysis_data__screening_stage_rating=screening_rating ai_analysis_data__analysis_data_en__screening_stage_rating=screening_rating
) )
if gpa: if gpa:
candidates = candidates.filter( applications = applications.filter(
person__gpa__gt= gpa person__gpa__gt= gpa
) )
print(candidates) print(applications)
if tier1_count > 0: if tier1_count > 0:
candidates = candidates[:tier1_count] applications = applications[:tier1_count]
context = { context = {
"job": job, "job": job,
"candidates": candidates, "applications": applications,
"min_ai_score": min_ai_score, "min_ai_score": min_ai_score,
"min_experience": min_experience, "min_experience": min_experience,
"screening_rating": screening_rating, "screening_rating": screening_rating,
@ -1838,7 +1839,7 @@ def applications_screening_view(request, slug):
"current_stage": "Applied", "current_stage": "Applied",
} }
return render(request, "recruitment/candidate_screening_view.html", context) return render(request, "recruitment/applications_screening_view.html", context)
@staff_user_required @staff_user_required
@ -1847,8 +1848,8 @@ def applications_exam_view(request, slug):
Manage candidate tiers and stage transitions Manage candidate tiers and stage transitions
""" """
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
context = {"job": job, "candidates": job.exam_candidates, "current_stage": "Exam"} context = {"job": job, "applications": job.exam_applications, "current_stage": "Exam"}
return render(request, "recruitment/candidate_exam_view.html", context) return render(request, "recruitment/applications_exam_view.html", context)
@staff_user_required @staff_user_required
@ -1888,9 +1889,9 @@ def bulk_update_application_exam_status(request, slug):
def application_criteria_view_htmx(request, pk): def application_criteria_view_htmx(request, pk):
candidate = get_object_or_404(Application, pk=pk) application = get_object_or_404(Application, pk=pk)
return render( return render(
request, "includes/candidate_modal_body.html", {"candidate": candidate} request, "includes/application_modal_body.html", {"application": application}
) )
@ -1989,11 +1990,11 @@ def applications_interview_view(request, slug):
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
context = { context = {
"job": job, "job": job,
"candidates": job.interview_candidates, "applications": job.interview_applications,
"current_stage": "Interview", "current_stage": "Interview",
} }
return render(request, "recruitment/candidate_interview_view.html", context) return render(request, "recruitment/applications_interview_view.html", context)
@staff_user_required @staff_user_required
@ -2004,12 +2005,12 @@ def applications_document_review_view(request, slug):
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
# Get candidates from Interview stage who need document review # Get candidates from Interview stage who need document review
candidates = job.document_review_candidates.select_related('person') applications = job.document_review_applications.select_related('person')
print(candidates)
# Get search query for filtering # Get search query for filtering
search_query = request.GET.get('q', '') search_query = request.GET.get('q', '')
if search_query: if search_query:
candidates = candidates.filter( applications = applications.filter(
Q(person__first_name__icontains=search_query) | Q(person__first_name__icontains=search_query) |
Q(person__last_name__icontains=search_query) | Q(person__last_name__icontains=search_query) |
Q(person__email__icontains=search_query) Q(person__email__icontains=search_query)
@ -2017,11 +2018,11 @@ def applications_document_review_view(request, slug):
context = { context = {
"job": job, "job": job,
"candidates": candidates, "applications": applications,
"current_stage": "Document Review", "current_stage": "Document Review",
"search_query": search_query, "search_query": search_query,
} }
return render(request, "recruitment/candidate_document_review_view.html", context) return render(request, "recruitment/applications_document_review_view.html", context)
@staff_user_required @staff_user_required
@ -2066,7 +2067,7 @@ def reschedule_meeting_for_application(request, slug, candidate_id, meeting_id):
@staff_user_required @staff_user_required
def schedule_meeting_for_application(request, slug, candidate_pk, meeting_id): def schedule_meeting_for_application(request, slug, candidate_pk, meeting_id):
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
candidate = get_object_or_404(Application, pk=candidate_pk) application = get_object_or_404(Application, pk=candidate_pk)
meeting = get_object_or_404(ZoomMeetingDetails, pk=meeting_id) meeting = get_object_or_404(ZoomMeetingDetails, pk=meeting_id)
if request.method == "POST": if request.method == "POST":
result = delete_zoom_meeting(meeting.meeting_id) result = delete_zoom_meeting(meeting.meeting_id)
@ -2082,7 +2083,7 @@ def schedule_meeting_for_application(request, slug, candidate_pk, meeting_id):
context = { context = {
"job": job, "job": job,
"candidate": candidate, "application": application,
"meeting": meeting, "meeting": meeting,
"delete_url": reverse( "delete_url": reverse(
"schedule_meeting_for_application", "schedule_meeting_for_application",
@ -2767,7 +2768,7 @@ def schedule_meeting_for_application(request, slug, candidate_pk):
Handles POST to process the form, create a meeting, and redirect back. Handles POST to process the form, create a meeting, and redirect back.
""" """
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
candidate = get_object_or_404(Application, pk=candidate_pk, job=job) application = get_object_or_404(Application, pk=candidate_pk, job=job)
if request.method == "POST": if request.method == "POST":
form = ZoomMeetingForm(request.POST) form = ZoomMeetingForm(request.POST)
@ -2778,7 +2779,7 @@ def schedule_meeting_for_application(request, slug, candidate_pk):
# Use a default topic if not provided # Use a default topic if not provided
if not topic_val: if not topic_val:
topic_val = f"Interview: {job.title} with {candidate.name}" topic_val = f"Interview: {job.title} with {application.name}"
# Ensure start_time is in the future # Ensure start_time is in the future
if start_time_val <= timezone.now(): if start_time_val <= timezone.now():
@ -2788,7 +2789,7 @@ def schedule_meeting_for_application(request, slug, candidate_pk):
# return render(request, "recruitment/schedule_meeting_form.html", { # return render(request, "recruitment/schedule_meeting_form.html", {
# 'form': form, # 'form': form,
# 'job': job, # 'job': job,
# 'candidate': candidate, # 'application': application,
# 'initial_topic': topic_val, # 'initial_topic': topic_val,
# 'initial_start_time': start_time_val.strftime('%Y-%m-%dT%H:%M') if start_time_val else '', # 'initial_start_time': start_time_val.strftime('%Y-%m-%dT%H:%M') if start_time_val else '',
# 'initial_duration': duration_val # 'initial_duration': duration_val
@ -2821,14 +2822,14 @@ def schedule_meeting_for_application(request, slug, candidate_pk):
) )
# Create a ScheduledInterview record # Create a ScheduledInterview record
ScheduledInterview.objects.create( ScheduledInterview.objects.create(
application=candidate, application=application,
job=job, job=job,
interview_location=zoom_meeting_instance, interview_location=zoom_meeting_instance,
interview_date=start_time_val.date(), interview_date=start_time_val.date(),
interview_time=start_time_val.time(), interview_time=start_time_val.time(),
status="scheduled", status="scheduled",
) )
messages.success(request, f"Meeting scheduled with {candidate.name}.") messages.success(request, f"Meeting scheduled with {application.name}.")
return redirect("applications_interview_view", slug=job.slug) return redirect("applications_interview_view", slug=job.slug)
else: else:
messages.error( messages.error(
@ -2842,7 +2843,7 @@ def schedule_meeting_for_application(request, slug, candidate_pk):
{ {
"form": form, "form": form,
"job": job, "job": job,
"candidate": candidate, "application": application,
"initial_topic": topic_val, "initial_topic": topic_val,
"initial_start_time": start_time_val.strftime("%Y-%m-%dT%H:%M") "initial_start_time": start_time_val.strftime("%Y-%m-%dT%H:%M")
if start_time_val if start_time_val
@ -2858,9 +2859,9 @@ def schedule_meeting_for_application(request, slug, candidate_pk):
{ {
"form": form, "form": form,
"job": job, "job": job,
"candidate": candidate, "application": application,
"initial_topic": request.POST.get( "initial_topic": request.POST.get(
"topic", f"Interview: {job.title} with {candidate.name}" "topic", f"Interview: {job.title} with {application.name}"
), ),
"initial_start_time": request.POST.get("start_time", ""), "initial_start_time": request.POST.get("start_time", ""),
"initial_duration": request.POST.get("duration", 60), "initial_duration": request.POST.get("duration", 60),
@ -2868,7 +2869,7 @@ def schedule_meeting_for_application(request, slug, candidate_pk):
) )
else: # GET request else: # GET request
initial_data = { initial_data = {
"topic": f"Interview: {job.title} with {candidate.name}", "topic": f"Interview: {job.title} with {application.name}",
"start_time": (timezone.now() + timedelta(hours=1)).strftime( "start_time": (timezone.now() + timedelta(hours=1)).strftime(
"%Y-%m-%dT%H:%M" "%Y-%m-%dT%H:%M"
), # Default to 1 hour from now ), # Default to 1 hour from now
@ -2878,7 +2879,7 @@ def schedule_meeting_for_application(request, slug, candidate_pk):
return render( return render(
request, request,
"meetings/schedule_meeting_form.html", "meetings/schedule_meeting_form.html",
{"form": form, "job": job, "candidate": candidate}, {"form": form, "job": job, "application": application},
) )

View File

@ -30,6 +30,7 @@ from django.shortcuts import render
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
import json import json
from django.utils.translation import gettext_lazy as _
# Add imports for user type restrictions # Add imports for user type restrictions
from recruitment.decorators import StaffRequiredMixin, staff_user_required from recruitment.decorators import StaffRequiredMixin, staff_user_required
@ -91,7 +92,7 @@ class JobUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin,
form_class = forms.JobPostingForm form_class = forms.JobPostingForm
template_name = 'jobs/edit_job.html' template_name = 'jobs/edit_job.html'
success_url = reverse_lazy('job_list') success_url = reverse_lazy('job_list')
success_message = 'Job updated successfully.' success_message = _('Job updated successfully.')
slug_url_kwarg = 'slug' slug_url_kwarg = 'slug'
@ -99,12 +100,12 @@ class JobDeleteView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin,
model = models.JobPosting model = models.JobPosting
template_name = 'jobs/partials/delete_modal.html' template_name = 'jobs/partials/delete_modal.html'
success_url = reverse_lazy('job_list') success_url = reverse_lazy('job_list')
success_message = 'Job deleted successfully.' success_message = _('Job deleted successfully.')
slug_url_kwarg = 'slug' slug_url_kwarg = 'slug'
class JobApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView): class JobApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
model = models.Application model = models.Application
template_name = 'jobs/job_candidates_list.html' template_name = 'jobs/job_applications_list.html'
context_object_name = 'applications' context_object_name = 'applications'
paginate_by = 10 paginate_by = 10
@ -146,7 +147,7 @@ class JobApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView): class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
model = models.Application model = models.Application
template_name = 'recruitment/candidate_list.html' template_name = 'recruitment/applications_list.html'
context_object_name = 'applications' context_object_name = 'applications'
paginate_by = 100 paginate_by = 100
@ -186,9 +187,9 @@ class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
class ApplicationCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, CreateView): class ApplicationCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, CreateView):
model = models.Application model = models.Application
form_class = forms.ApplicationForm form_class = forms.ApplicationForm
template_name = 'recruitment/candidate_create.html' template_name = 'recruitment/application_create.html'
success_url = reverse_lazy('application_list') success_url = reverse_lazy('application_list')
success_message = 'Candidate created successfully.' success_message = _('Application created successfully.')
def get_initial(self): def get_initial(self):
initial = super().get_initial() initial = super().get_initial()
@ -215,17 +216,17 @@ class ApplicationCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessa
class ApplicationUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView): class ApplicationUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
model = models.Application model = models.Application
form_class = forms.ApplicationForm form_class = forms.ApplicationForm
template_name = 'recruitment/candidate_update.html' template_name = 'recruitment/application_update.html'
success_url = reverse_lazy('application_list') success_url = reverse_lazy('application_list')
success_message = 'Candidate updated successfully.' success_message = _('Application updated successfully.')
slug_url_kwarg = 'slug' slug_url_kwarg = 'slug'
class ApplicationDeleteView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, DeleteView): class ApplicationDeleteView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, DeleteView):
model = models.Application model = models.Application
template_name = 'recruitment/candidate_delete.html' template_name = 'recruitment/application_delete.html'
success_url = reverse_lazy('application_list') success_url = reverse_lazy('application_list')
success_message = 'Candidate deleted successfully.' success_message = _('Application deleted successfully.')
slug_url_kwarg = 'slug' slug_url_kwarg = 'slug'
@ -255,9 +256,9 @@ def training_list(request):
@staff_user_required @staff_user_required
def application_detail(request, slug): def application_detail(request, slug):
from rich.json import JSON from rich.json import JSON
candidate = get_object_or_404(models.Application, slug=slug) application = get_object_or_404(models.Application, slug=slug)
try: try:
parsed = ast.literal_eval(candidate.parsed_summary) parsed = ast.literal_eval(application.parsed_summary)
except: except:
parsed = {} parsed = {}
@ -271,7 +272,7 @@ def application_detail(request, slug):
# parsed = JSON(json.dumps(parsed), indent=2, highlight=True, skip_keys=False, ensure_ascii=False, check_circular=True, allow_nan=True, default=None, sort_keys=False) # parsed = JSON(json.dumps(parsed), indent=2, highlight=True, skip_keys=False, ensure_ascii=False, check_circular=True, allow_nan=True, default=None, sort_keys=False)
# parsed = json_to_markdown_table([parsed]) # parsed = json_to_markdown_table([parsed])
return render(request, 'recruitment/application_detail.html', { return render(request, 'recruitment/application_detail.html', {
'candidate': candidate, 'application': application,
'parsed': parsed, 'parsed': parsed,
'stage_form': stage_form, 'stage_form': stage_form,
}) })
@ -287,7 +288,7 @@ def application_resume_template_view(request, slug):
messages.error(request, _("You don't have permission to view this page.")) messages.error(request, _("You don't have permission to view this page."))
return redirect('application_list') return redirect('application_list')
return render(request, 'recruitment/candidate_resume_template.html', { return render(request, 'recruitment/application_resume_template.html', {
'application': application 'application': application
}) })
@ -301,7 +302,7 @@ def application_update_stage(request, slug):
stage_value = form.cleaned_data['stage'] stage_value = form.cleaned_data['stage']
application.stage = stage_value application.stage = stage_value
application.save(update_fields=['stage']) application.save(update_fields=['stage'])
messages.success(request,"application Stage Updated") messages.success(request,_("application Stage Updated"))
return redirect("application_detail",slug=application.slug) return redirect("application_detail",slug=application.slug)
class TrainingListView(LoginRequiredMixin, StaffRequiredMixin, ListView): class TrainingListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
@ -386,7 +387,7 @@ def dashboard_view(request):
# --- 1. BASE QUERYSETS & GLOBAL METRICS (UNFILTERED) --- # --- 1. BASE QUERYSETS & GLOBAL METRICS (UNFILTERED) ---
all_jobs_queryset = models.JobPosting.objects.all().order_by('-created_at') all_jobs_queryset = models.JobPosting.objects.all().order_by('-created_at')
all_candidates_queryset = models.Application.objects.all() all_applications_queryset = models.Application.objects.all()
# Global KPI Card Metrics # Global KPI Card Metrics
total_jobs_global = all_jobs_queryset.count() total_jobs_global = all_jobs_queryset.count()
@ -400,7 +401,7 @@ def dashboard_view(request):
# --- 2. TIME SERIES: GLOBAL DAILY APPLICANTS --- # --- 2. TIME SERIES: GLOBAL DAILY APPLICANTS ---
# Group ALL candidates by creation date # Group ALL candidates by creation date
global_daily_applications_qs = all_candidates_queryset.annotate( global_daily_applications_qs = all_applications_queryset.annotate(
date=TruncDate('created_at') date=TruncDate('created_at')
).values('date').annotate( ).values('date').annotate(
count=Count('pk') count=Count('pk')
@ -412,14 +413,14 @@ def dashboard_view(request):
# --- 3. FILTERING LOGIC: Determine the scope for scoped metrics --- # --- 3. FILTERING LOGIC: Determine the scope for scoped metrics ---
candidate_queryset = all_candidates_queryset application_queryset = all_applications_queryset
job_scope_queryset = all_jobs_queryset job_scope_queryset = all_jobs_queryset
interview_queryset = models.ScheduledInterview.objects.all() interview_queryset = models.ScheduledInterview.objects.all()
current_job = None current_job = None
if selected_job_pk: if selected_job_pk:
# Filter all base querysets # Filter all base querysets
candidate_queryset = candidate_queryset.filter(job__pk=selected_job_pk) application_queryset = application_queryset.filter(job__pk=selected_job_pk)
interview_queryset = interview_queryset.filter(job__pk=selected_job_pk) interview_queryset = interview_queryset.filter(job__pk=selected_job_pk)
try: try:
@ -434,7 +435,7 @@ def dashboard_view(request):
scoped_dates = [] scoped_dates = []
scoped_counts = [] scoped_counts = []
if selected_job_pk: if selected_job_pk:
scoped_daily_applications_qs = candidate_queryset.annotate( scoped_daily_applications_qs = application_queryset.annotate(
date=TruncDate('created_at') date=TruncDate('created_at')
).values('date').annotate( ).values('date').annotate(
count=Count('pk') count=Count('pk')
@ -446,7 +447,7 @@ def dashboard_view(request):
# --- 5. SCOPED CORE AGGREGATIONS (FILTERED OR ALL) --- # --- 5. SCOPED CORE AGGREGATIONS (FILTERED OR ALL) ---
total_candidates = candidate_queryset.count() total_applications = application_queryset.count()
score_expression = Cast( score_expression = Cast(
@ -461,7 +462,7 @@ def dashboard_view(request):
) )
# 2. ANNOTATE the queryset with the new field # 2. ANNOTATE the queryset with the new field
candidates_with_score_query = candidate_queryset.annotate( applications_with_score_query = application_queryset.annotate(
annotated_match_score=score_expression annotated_match_score=score_expression
) )
@ -493,24 +494,24 @@ def dashboard_view(request):
# A. Pipeline & Volume Metrics (Scoped) # A. Pipeline & Volume Metrics (Scoped)
total_active_jobs = job_scope_queryset.filter(status="ACTIVE").count() total_active_jobs = job_scope_queryset.filter(status="ACTIVE").count()
last_week = timezone.now() - timedelta(days=7) last_week = timezone.now() - timedelta(days=7)
new_candidates_7days = candidate_queryset.filter(created_at__gte=last_week).count() new_applications_7days = application_queryset.filter(created_at__gte=last_week).count()
open_positions_agg = job_scope_queryset.filter(status="ACTIVE").aggregate(total_open=Sum('open_positions')) open_positions_agg = job_scope_queryset.filter(status="ACTIVE").aggregate(total_open=Sum('open_positions'))
total_open_positions = open_positions_agg['total_open'] or 0 total_open_positions = open_positions_agg['total_open'] or 0
average_applications_result = job_scope_queryset.annotate( average_applications_result = job_scope_queryset.annotate(
candidate_count=Count('applications', distinct=True) applications_count=Count('applications', distinct=True)
).aggregate(avg_apps=Avg('candidate_count'))['avg_apps'] ).aggregate(avg_apps=Avg('applications_count'))['avg_apps']
average_applications = round(average_applications_result or 0, 2) average_applications = round(average_applications_result or 0, 2)
# B. Efficiency & Conversion Metrics (Scoped) # B. Efficiency & Conversion Metrics (Scoped)
hired_candidates = candidate_queryset.filter( hired_applications = application_queryset.filter(
stage='Hired' stage='Hired'
) )
lst=[c.time_to_hire_days for c in hired_candidates] lst=[c.time_to_hire_days for c in hired_applications]
time_to_hire_query = hired_candidates.annotate( time_to_hire_query = hired_applications.annotate(
time_diff=ExpressionWrapper( time_diff=ExpressionWrapper(
F('join_date') - F('created_at__date'), F('join_date') - F('created_at__date'),
output_field=fields.DurationField() output_field=fields.DurationField()
@ -527,11 +528,11 @@ def dashboard_view(request):
) )
print(avg_time_to_hire_days) print(avg_time_to_hire_days)
applied_count = candidate_queryset.filter(stage='Applied').count() applied_count = application_queryset.filter(stage='Applied').count()
advanced_count = candidate_queryset.filter(stage__in=['Exam', 'Interview', 'Offer']).count() advanced_count = application_queryset.filter(stage__in=['Exam', 'Interview', 'Offer']).count()
screening_pass_rate = round( (advanced_count / applied_count) * 100, 1 ) if applied_count > 0 else 0 screening_pass_rate = round( (advanced_count / applied_count) * 100, 1 ) if applied_count > 0 else 0
offers_extended_count = candidate_queryset.filter(stage='Offer').count() offers_extended_count = application_queryset.filter(stage='Offer').count()
offers_accepted_count = candidate_queryset.filter(offer_status='Accepted').count() offers_accepted_count = application_queryset.filter(offer_status='Accepted').count()
offers_accepted_rate = round( (offers_accepted_count / offers_extended_count) * 100, 1 ) if offers_extended_count > 0 else 0 offers_accepted_rate = round( (offers_accepted_count / offers_extended_count) * 100, 1 ) if offers_extended_count > 0 else 0
filled_positions = offers_accepted_count filled_positions = offers_accepted_count
vacancy_fill_rate = round( (filled_positions / total_open_positions) * 100, 1 ) if total_open_positions > 0 else 0 vacancy_fill_rate = round( (filled_positions / total_open_positions) * 100, 1 ) if total_open_positions > 0 else 0
@ -542,21 +543,21 @@ def dashboard_view(request):
meetings_scheduled_this_week = interview_queryset.filter( meetings_scheduled_this_week = interview_queryset.filter(
interview_date__week=current_week, interview_date__year=current_year interview_date__week=current_week, interview_date__year=current_year
).count() ).count()
avg_match_score_result = candidates_with_score_query.aggregate(avg_score=Avg('annotated_match_score'))['avg_score'] avg_match_score_result = applications_with_score_query.aggregate(avg_score=Avg('annotated_match_score'))['avg_score']
avg_match_score = round(avg_match_score_result or 0, 1) avg_match_score = round(avg_match_score_result or 0, 1)
high_potential_count = candidates_with_score_query.filter(annotated_match_score__gte=HIGH_POTENTIAL_THRESHOLD).count() high_potential_count = applications_with_score_query.filter(annotated_match_score__gte=HIGH_POTENTIAL_THRESHOLD).count()
high_potential_ratio = round( (high_potential_count / total_candidates) * 100, 1 ) if total_candidates > 0 else 0 high_potential_ratio = round( (high_potential_count / total_applications) * 100, 1 ) if total_applications > 0 else 0
total_scored_candidates = candidates_with_score_query.count() total_scored_candidates = applications_with_score_query.count()
scored_ratio = round( (total_scored_candidates / total_candidates) * 100, 1 ) if total_candidates > 0 else 0 scored_ratio = round( (total_scored_candidates / total_applications) * 100, 1 ) if total_applications > 0 else 0
# --- 6. CHART DATA PREPARATION --- # --- 6. CHART DATA PREPARATION ---
# A. Pipeline Funnel (Scoped) # A. Pipeline Funnel (Scoped)
stage_counts = candidate_queryset.values('stage').annotate(count=Count('stage')) stage_counts = application_queryset.values('stage').annotate(count=Count('stage'))
stage_map = {item['stage']: item['count'] for item in stage_counts} stage_map = {item['stage']: item['count'] for item in stage_counts}
candidate_stage = ['Applied', 'Exam', 'Interview', 'Offer', 'Hired'] application_stage = ['Applied', 'Exam', 'Interview', 'Offer', 'Hired']
candidates_count = [ application_count = [
stage_map.get('Applied', 0), stage_map.get('Exam', 0), stage_map.get('Interview', 0), stage_map.get('Applied', 0), stage_map.get('Exam', 0), stage_map.get('Interview', 0),
stage_map.get('Offer', 0), stage_map.get('Hired',0) stage_map.get('Offer', 0), stage_map.get('Hired',0)
] ]
@ -570,9 +571,9 @@ def dashboard_view(request):
rotation_degrees_final = round(min(rotation_degrees, 180), 1) # Ensure max 180 degrees rotation_degrees_final = round(min(rotation_degrees, 180), 1) # Ensure max 180 degrees
# #
hiring_source_counts = candidate_queryset.values('hiring_source').annotate(count=Count('stage')) hiring_source_counts = application_queryset.values('hiring_source').annotate(count=Count('stage'))
source_map= {item['hiring_source']: item['count'] for item in hiring_source_counts} source_map= {item['hiring_source']: item['count'] for item in hiring_source_counts}
candidates_count_in_each_source = [ applications_count_in_each_source = [
source_map.get('Public', 0), source_map.get('Internal', 0), source_map.get('Agency', 0), source_map.get('Public', 0), source_map.get('Internal', 0), source_map.get('Agency', 0),
] ]
@ -589,8 +590,8 @@ def dashboard_view(request):
# Scoped KPIs # Scoped KPIs
'total_active_jobs': total_active_jobs, 'total_active_jobs': total_active_jobs,
'total_candidates': total_candidates, 'total_applications': total_applications,
'new_candidates_7days': new_candidates_7days, 'new_applications_7days': new_applications_7days,
'total_open_positions': total_open_positions, 'total_open_positions': total_open_positions,
'average_applications': average_applications, 'average_applications': average_applications,
'avg_time_to_hire_days': avg_time_to_hire_days, 'avg_time_to_hire_days': avg_time_to_hire_days,
@ -604,8 +605,8 @@ def dashboard_view(request):
'scored_ratio': scored_ratio, 'scored_ratio': scored_ratio,
# Chart Data # Chart Data
'candidate_stage': json.dumps(candidate_stage), 'application_stage': json.dumps(application_stage),
'candidates_count': json.dumps(candidates_count), 'application_count': json.dumps(application_count),
'job_titles': json.dumps(job_titles), 'job_titles': json.dumps(job_titles),
'job_app_counts': json.dumps(job_app_counts), 'job_app_counts': json.dumps(job_app_counts),
# 'source_volume_chart_data' is intentionally REMOVED # 'source_volume_chart_data' is intentionally REMOVED
@ -628,7 +629,7 @@ def dashboard_view(request):
'current_job': current_job, 'current_job': current_job,
'candidates_count_in_each_source': json.dumps(candidates_count_in_each_source), 'applications_count_in_each_source': json.dumps(applications_count_in_each_source),
'all_hiring_sources': json.dumps(all_hiring_sources), 'all_hiring_sources': json.dumps(all_hiring_sources),
} }
@ -642,95 +643,95 @@ def applications_offer_view(request, slug):
job = get_object_or_404(models.JobPosting, slug=slug) job = get_object_or_404(models.JobPosting, slug=slug)
# Filter candidates for this specific job and stage # Filter candidates for this specific job and stage
candidates = job.offer_candidates applications = job.offer_applications
# Handle search # Handle search
search_query = request.GET.get('search', '') search_query = request.GET.get('search', '')
if search_query: if search_query:
candidates = candidates.filter( applications = applications.filter(
Q(first_name__icontains=search_query) | Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query) | Q(last_name__icontains=search_query) |
Q(email__icontains=search_query) | Q(email__icontains=search_query) |
Q(phone__icontains=search_query) Q(phone__icontains=search_query)
) )
candidates = candidates.order_by('-created_at') applications = applications.order_by('-created_at')
context = { context = {
'job': job, 'job': job,
'candidates': candidates, 'applications': applications,
'search_query': search_query, 'search_query': search_query,
'current_stage': 'Offer', 'current_stage': 'Offer',
} }
return render(request, 'recruitment/candidate_offer_view.html', context) return render(request, 'recruitment/applications_offer_view.html', context)
@login_required @login_required
@staff_user_required @staff_user_required
def applications_hired_view(request, slug): def applications_hired_view(request, slug):
"""View for hired candidates""" """View for hired applications"""
job = get_object_or_404(models.JobPosting, slug=slug) job = get_object_or_404(models.JobPosting, slug=slug)
# Filter candidates with offer_status = 'Accepted' # Filter applications with offer_status = 'Accepted'
candidates = job.hired_candidates applications = job.hired_applications
# Handle search # Handle search
search_query = request.GET.get('search', '') search_query = request.GET.get('search', '')
if search_query: if search_query:
candidates = candidates.filter( applications = applications.filter(
Q(first_name__icontains=search_query) | Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query) | Q(last_name__icontains=search_query) |
Q(email__icontains=search_query) | Q(email__icontains=search_query) |
Q(phone__icontains=search_query) Q(phone__icontains=search_query)
) )
candidates = candidates.order_by('-created_at') applications = applications.order_by('-created_at')
context = { context = {
'job': job, 'job': job,
'candidates': candidates, 'applications': applications,
'search_query': search_query, 'search_query': search_query,
'current_stage': 'Hired', 'current_stage': 'Hired',
} }
return render(request, 'recruitment/candidate_hired_view.html', context) return render(request, 'recruitment/applications_hired_view.html', context)
@login_required @login_required
@staff_user_required @staff_user_required
def update_application_status(request, job_slug, candidate_slug, stage_type, status): def update_application_status(request, job_slug, application_slug, stage_type, status):
"""Handle exam/interview/offer status updates""" """Handle exam/interview/offer status updates"""
from django.utils import timezone from django.utils import timezone
job = get_object_or_404(models.JobPosting, slug=job_slug) job = get_object_or_404(models.JobPosting, slug=job_slug)
candidate = get_object_or_404(models.Application, slug=candidate_slug, job=job) application = get_object_or_404(models.Application, slug=application_slug, job=job)
if request.method == "POST": if request.method == "POST":
if stage_type == 'exam': if stage_type == 'exam':
status = request.POST.get("exam_status") status = request.POST.get("exam_status")
score = request.POST.get("exam_score") score = request.POST.get("exam_score")
candidate.exam_status = status application.exam_status = status
candidate.exam_score = score application.exam_score = score
candidate.exam_date = timezone.now() application.exam_date = timezone.now()
candidate.save(update_fields=['exam_status','exam_score', 'exam_date']) application.save(update_fields=['exam_status','exam_score', 'exam_date'])
return render(request,'recruitment/partials/exam-results.html',{'candidate':candidate,'job':job}) return render(request,'recruitment/partials/exam-results.html',{'application':application,'job':job})
elif stage_type == 'interview': elif stage_type == 'interview':
candidate.interview_status = status application.interview_status = status
candidate.interview_date = timezone.now() application.interview_date = timezone.now()
candidate.save(update_fields=['interview_status', 'interview_date']) application.save(update_fields=['interview_status', 'interview_date'])
return render(request,'recruitment/partials/interview-results.html',{'candidate':candidate,'job':job}) return render(request,'recruitment/partials/interview-results.html',{'application':application,'job':job})
elif stage_type == 'offer': elif stage_type == 'offer':
candidate.offer_status = status application.offer_status = status
candidate.offer_date = timezone.now() application.offer_date = timezone.now()
candidate.save(update_fields=['offer_status', 'offer_date']) application.save(update_fields=['offer_status', 'offer_date'])
return render(request,'recruitment/partials/offer-results.html',{'candidate':candidate,'job':job}) return render(request,'recruitment/partials/offer-results.html',{'application':application,'job':job})
return redirect('application_detail', candidate.slug) return redirect('application_detail', application.slug)
else: else:
if stage_type == 'exam': if stage_type == 'exam':
return render(request,"includes/candidate_update_exam_form.html",{'candidate':candidate,'job':job}) return render(request,"includes/applications_update_exam_form.html",{'application':application,'job':job})
elif stage_type == 'interview': elif stage_type == 'interview':
return render(request,"includes/candidate_update_interview_form.html",{'candidate':candidate,'job':job}) return render(request,"includes/applications_update_interview_form.html",{'application':application,'job':job})
elif stage_type == 'offer': elif stage_type == 'offer':
return render(request,"includes/candidate_update_offer_form.html",{'candidate':candidate,'job':job}) return render(request,"includes/applications_update_offer_form.html",{'application':application,'job':job})
# Stage configuration for CSV export # Stage configuration for CSV export
@ -766,7 +767,7 @@ STAGE_CONFIG = {
@login_required @login_required
@staff_user_required @staff_user_required
def export_applications_csv(request, job_slug, stage): def export_applications_csv(request, job_slug, stage):
"""Export candidates for a specific stage as CSV""" """Export applications for a specific stage as CSV"""
job = get_object_or_404(models.JobPosting, slug=job_slug) job = get_object_or_404(models.JobPosting, slug=job_slug)
# Validate stage # Validate stage
@ -776,23 +777,23 @@ def export_applications_csv(request, job_slug, stage):
config = STAGE_CONFIG[stage] config = STAGE_CONFIG[stage]
# Filter candidates based on stage # Filter applications based on stage
if stage == 'hired': if stage == 'hired':
candidates = job.applications.filter(**config['filter']) applications = job.applications.filter(**config['filter'])
else: else:
candidates = job.applications.filter(**config['filter']) applications = job.applications.filter(**config['filter'])
# Handle search if provided # Handle search if provided
search_query = request.GET.get('search', '') search_query = request.GET.get('search', '')
if search_query: if search_query:
candidates = candidates.filter( applications = applications.filter(
Q(first_name__icontains=search_query) | Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query) | Q(last_name__icontains=search_query) |
Q(email__icontains=search_query) | Q(email__icontains=search_query) |
Q(phone__icontains=search_query) Q(phone__icontains=search_query)
) )
candidates = candidates.order_by('-created_at') applications = applications.order_by('-created_at')
# Create CSV response # Create CSV response
response = HttpResponse(content_type='text/csv') response = HttpResponse(content_type='text/csv')
@ -809,87 +810,87 @@ def export_applications_csv(request, job_slug, stage):
headers.extend(['Job Title', 'Department']) headers.extend(['Job Title', 'Department'])
writer.writerow(headers) writer.writerow(headers)
# Write candidate data # Write application data
for candidate in candidates: for application in applications:
row = [] row = []
# Extract data based on stage configuration # Extract data based on stage configuration
for field in config['fields']: for field in config['fields']:
if field == 'name': if field == 'name':
row.append(candidate.name) row.append(application.name)
elif field == 'email': elif field == 'email':
row.append(candidate.email) row.append(application.email)
elif field == 'phone': elif field == 'phone':
row.append(candidate.phone) row.append(application.phone)
elif field == 'created_at': elif field == 'created_at':
row.append(candidate.created_at.strftime('%Y-%m-%d %H:%M') if candidate.created_at else '') row.append(application.created_at.strftime('%Y-%m-%d %H:%M') if application.created_at else '')
elif field == 'stage': elif field == 'stage':
row.append(candidate.stage or '') row.append(application.stage or '')
elif field == 'exam_status': elif field == 'exam_status':
row.append(candidate.exam_status or '') row.append(application.exam_status or '')
elif field == 'exam_date': elif field == 'exam_date':
row.append(candidate.exam_date.strftime('%Y-%m-%d %H:%M') if candidate.exam_date else '') row.append(application.exam_date.strftime('%Y-%m-%d %H:%M') if application.exam_date else '')
elif field == 'interview_status': elif field == 'interview_status':
row.append(candidate.interview_status or '') row.append(application.interview_status or '')
elif field == 'interview_date': elif field == 'interview_date':
row.append(candidate.interview_date.strftime('%Y-%m-%d %H:%M') if candidate.interview_date else '') row.append(application.interview_date.strftime('%Y-%m-%d %H:%M') if application.interview_date else '')
elif field == 'offer_status': elif field == 'offer_status':
row.append(candidate.offer_status or '') row.append(application.offer_status or '')
elif field == 'offer_date': elif field == 'offer_date':
row.append(candidate.offer_date.strftime('%Y-%m-%d %H:%M') if candidate.offer_date else '') row.append(application.offer_date.strftime('%Y-%m-%d %H:%M') if application.offer_date else '')
elif field == 'ai_score': elif field == 'ai_score':
# Extract AI score using model property # Extract AI score using model property
try: try:
score = candidate.match_score score = application.match_score
row.append(f"{score}%" if score else '') row.append(f"{score}%" if score else '')
except: except:
row.append('') row.append('')
elif field == 'years_experience': elif field == 'years_experience':
# Extract years of experience using model property # Extract years of experience using model property
try: try:
years = candidate.years_of_experience years = application.years_of_experience
row.append(f"{years}" if years else '') row.append(f"{years}" if years else '')
except: except:
row.append('') row.append('')
elif field == 'screening_rating': elif field == 'screening_rating':
# Extract screening rating using model property # Extract screening rating using model property
try: try:
rating = candidate.screening_stage_rating rating = application.screening_stage_rating
row.append(rating if rating else '') row.append(rating if rating else '')
except: except:
row.append('') row.append('')
elif field == 'professional_category': elif field == 'professional_category':
# Extract professional category using model property # Extract professional category using model property
try: try:
category = candidate.professional_category category = application.professional_category
row.append(category if category else '') row.append(category if category else '')
except: except:
row.append('') row.append('')
elif field == 'top_skills': elif field == 'top_skills':
# Extract top 3 skills using model property # Extract top 3 skills using model property
try: try:
skills = candidate.top_3_keywords skills = application.top_3_keywords
row.append(', '.join(skills) if skills else '') row.append(', '.join(skills) if skills else '')
except: except:
row.append('') row.append('')
elif field == 'strengths': elif field == 'strengths':
# Extract strengths using model property # Extract strengths using model property
try: try:
strengths = candidate.strengths strengths = application.strengths
row.append(strengths if strengths else '') row.append(strengths if strengths else '')
except: except:
row.append('') row.append('')
elif field == 'weaknesses': elif field == 'weaknesses':
# Extract weaknesses using model property # Extract weaknesses using model property
try: try:
weaknesses = candidate.weaknesses weaknesses = application.weaknesses
row.append(weaknesses if weaknesses else '') row.append(weaknesses if weaknesses else '')
except: except:
row.append('') row.append('')
elif field == 'join_date': elif field == 'join_date':
row.append(candidate.join_date.strftime('%Y-%m-%d') if candidate.join_date else '') row.append(application.join_date.strftime('%Y-%m-%d') if application.join_date else '')
else: else:
row.append(getattr(candidate, field, '')) row.append(getattr(application, field, ''))
# Add job information # Add job information
row.extend([job.title, job.department or '']) row.extend([job.title, job.department or ''])

View File

@ -2,7 +2,7 @@
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% if LANGUAGE_CODE == 'en' %} {% if LANGUAGE_CODE == 'en' %}
<h5> {% trans "AI Score" %}: <span class="badge bg-success"><i class="fas fa-robot me-1"></i> {{ candidate.match_score }}%</span> <span class="badge bg-success"><i class="fas fa-graduation-cap me-1"></i> {{ candidate.professional_category }} </span></h5> <h5> {% trans "AI Score" %}: <span class="badge bg-success"><i class="fas fa-robot me-1"></i> {{ application.match_score }}%</span> <span class="badge bg-success"><i class="fas fa-graduation-cap me-1"></i> {{ application.professional_category }} </span></h5>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
@ -10,7 +10,7 @@
<i class="fas fa-briefcase me-2 text-primary"></i> <i class="fas fa-briefcase me-2 text-primary"></i>
<small class="text-muted">{% trans "Job Fit" %}</small> <small class="text-muted">{% trans "Job Fit" %}</small>
</div> </div>
<p class="mb-1">{{ candidate.job_fit_narrative }}</p> <p class="mb-1">{{ application.job_fit_narrative }}</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="d-flex align-items-center mb-2"> <div class="d-flex align-items-center mb-2">
@ -18,7 +18,7 @@
<small class="text-muted">{% trans "Top Keywords" %}</small> <small class="text-muted">{% trans "Top Keywords" %}</small>
</div> </div>
<div class="d-flex flex-wrap gap-1"> <div class="d-flex flex-wrap gap-1">
{% for keyword in candidate.top_3_keywords %} {% for keyword in application.top_3_keywords %}
<span class="badge bg-info text-dark me-1">{{ keyword }}</span> <span class="badge bg-info text-dark me-1">{{ keyword }}</span>
{% endfor %} {% endfor %}
</div> </div>
@ -31,18 +31,18 @@
<i class="fas fa-clock me-2 text-info"></i> <i class="fas fa-clock me-2 text-info"></i>
<small class="text-muted">{% trans "Experience" %}</small> <small class="text-muted">{% trans "Experience" %}</small>
</div> </div>
<p class="mb-1"><strong>{{ candidate.years_of_experience }}</strong> {% trans "years" %}</p> <p class="mb-1"><strong>{{ application.years_of_experience }}</strong> {% trans "years" %}</p>
<p class="mb-0"><strong>{% trans "Recent Role:" %}</strong> {{ candidate.most_recent_job_title }}</p> <p class="mb-0"><strong>{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title }}</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="d-flex align-items-center mb-2"> <div class="d-flex align-items-center mb-2">
<i class="fas fa-chart-line me-2 text-success"></i> <i class="fas fa-chart-line me-2 text-success"></i>
<small class="text-muted">{% trans "Skills" %}</small> <small class="text-muted">{% trans "Skills" %}</small>
</div> </div>
<p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ candidate.soft_skills_score }}%</p> <p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
<p class="mb-0"><strong>{% trans "Industry Match:" %}</strong> <p class="mb-0"><strong>{% trans "Industry Match:" %}</strong>
<span class="badge {% if candidate.industry_match_score >= 70 %}bg-success{% elif candidate.industry_match_score >= 40 %}bg-warning{% else %}bg-danger{% endif %}"> <span class="badge {% if application.industry_match_score >= 70 %}bg-success{% elif application.industry_match_score >= 40 %}bg-warning{% else %}bg-danger{% endif %}">
{{ candidate.industry_match_score }}% {{ application.industry_match_score }}%
</span> </span>
</p> </p>
</div> </div>
@ -50,17 +50,17 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label> <label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label>
<textarea class="form-control" rows="6" readonly>{{ candidate.recommendation }}</textarea> <textarea class="form-control" rows="6" readonly>{{ application.recommendation }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label> <label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label>
<textarea class="form-control" rows="4" readonly>{{ candidate.strengths }}</textarea> <textarea class="form-control" rows="4" readonly>{{ application.strengths }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label> <label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label>
<textarea class="form-control" rows="4" readonly>{{ candidate.weaknesses }}</textarea> <textarea class="form-control" rows="4" readonly>{{ application.weaknesses }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@ -74,7 +74,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for criterion, status in candidate.criteria_checklist.items %} {% for criterion, status in application.criteria_checklist.items %}
<tr> <tr>
<td>{{ criterion }}</td> <td>{{ criterion }}</td>
<td> <td>
@ -99,7 +99,7 @@
<i class="fas fa-check-circle me-2 text-success"></i> <i class="fas fa-check-circle me-2 text-success"></i>
<small class="text-muted">{% trans "Minimum Requirements" %}</small> <small class="text-muted">{% trans "Minimum Requirements" %}</small>
</div> </div>
{% if candidate.min_requirements_met %} {% if application.min_requirements_met %}
<span class="badge bg-success">{% trans "Met" %}</span> <span class="badge bg-success">{% trans "Met" %}</span>
{% else %} {% else %}
<span class="badge bg-danger">{% trans "Not Met" %}</span> <span class="badge bg-danger">{% trans "Not Met" %}</span>
@ -110,15 +110,15 @@
<i class="fas fa-star me-2 text-warning"></i> <i class="fas fa-star me-2 text-warning"></i>
<small class="text-muted">{% trans "Screening Rating" %}</small> <small class="text-muted">{% trans "Screening Rating" %}</small>
</div> </div>
<span class="badge bg-secondary">{{ candidate.screening_stage_rating }}</span> <span class="badge bg-secondary">{{ application.screening_stage_rating }}</span>
</div> </div>
</div> </div>
{% if candidate.language_fluency %} {% if application.language_fluency %}
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label> <label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label>
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
{% for language in candidate.language_fluency %} {% for language in application.language_fluency %}
<span class="badge bg-light text-dark">{{ language }}</span> <span class="badge bg-light text-dark">{{ language }}</span>
{% endfor %} {% endfor %}
</div> </div>
@ -126,7 +126,7 @@
{% endif %} {% endif %}
{% else %} {% else %}
<h5> {% trans "AI Score" %}: <span class="badge bg-success"><i class="fas fa-robot me-1"></i> {{ candidate.match_score }}%</span> <span class="badge bg-success"><i class="fas fa-graduation-cap me-1"></i> {{ candidate.professional_category_ar }} </span></h5> <h5> {% trans "AI Score" %}: <span class="badge bg-success"><i class="fas fa-robot me-1"></i> {{ application.match_score }}%</span> <span class="badge bg-success"><i class="fas fa-graduation-cap me-1"></i> {{ application.professional_category_ar }} </span></h5>
<div class="row mb-3"> <div class="row mb-3">
<div class="col-md-6"> <div class="col-md-6">
@ -134,7 +134,7 @@
<i class="fas fa-briefcase me-2 text-primary"></i> <i class="fas fa-briefcase me-2 text-primary"></i>
<small class="text-muted">{% trans "Job Fit" %}</small> <small class="text-muted">{% trans "Job Fit" %}</small>
</div> </div>
<p class="mb-1">{{ candidate.job_fit_narrative_ar }}</p> <p class="mb-1">{{ application.job_fit_narrative_ar }}</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="d-flex align-items-center mb-2"> <div class="d-flex align-items-center mb-2">
@ -142,7 +142,7 @@
<small class="text-muted">{% trans "Top Keywords" %}</small> <small class="text-muted">{% trans "Top Keywords" %}</small>
</div> </div>
<div class="d-flex flex-wrap gap-1"> <div class="d-flex flex-wrap gap-1">
{% for keyword in candidate.top_3_keywords_ar %} {% for keyword in application.top_3_keywords_ar %}
<span class="badge bg-info text-dark me-1">{{ keyword }}</span> <span class="badge bg-info text-dark me-1">{{ keyword }}</span>
{% endfor %} {% endfor %}
</div> </div>
@ -155,18 +155,18 @@
<i class="fas fa-clock me-2 text-info"></i> <i class="fas fa-clock me-2 text-info"></i>
<small class="text-muted">{% trans "Experience" %}</small> <small class="text-muted">{% trans "Experience" %}</small>
</div> </div>
<p class="mb-1"><strong>{{ candidate.years_of_experience }}</strong> {% trans "years" %}</p> <p class="mb-1"><strong>{{ application.years_of_experience }}</strong> {% trans "years" %}</p>
<p class="mb-0"><strong>{% trans "Recent Role:" %}</strong> {{ candidate.most_recent_job_title_ar }}</p> <p class="mb-0"><strong>{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title_ar }}</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="d-flex align-items-center mb-2"> <div class="d-flex align-items-center mb-2">
<i class="fas fa-chart-line me-2 text-success"></i> <i class="fas fa-chart-line me-2 text-success"></i>
<small class="text-muted">{% trans "Skills" %}</small> <small class="text-muted">{% trans "Skills" %}</small>
</div> </div>
<p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ candidate.soft_skills_score }}%</p> <p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
<p class="mb-0"><strong>{% trans "Industry Match:" %}</strong> <p class="mb-0"><strong>{% trans "Industry Match:" %}</strong>
<span class="badge {% if candidate.industry_match_score >= 70 %}bg-success{% elif candidate.industry_match_score >= 40 %}bg-warning{% else %}bg-danger{% endif %}"> <span class="badge {% if application.industry_match_score >= 70 %}bg-success{% elif application.industry_match_score >= 40 %}bg-warning{% else %}bg-danger{% endif %}">
{{ candidate.industry_match_score }}% {{ application.industry_match_score }}%
</span> </span>
</p> </p>
</div> </div>
@ -174,17 +174,17 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label> <label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label>
<textarea class="form-control" rows="6" readonly>{{ candidate.recommendation_ar }}</textarea> <textarea class="form-control" rows="6" readonly>{{ application.recommendation_ar }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label> <label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label>
<textarea class="form-control" rows="4" readonly>{{ candidate.strengths_ar }}</textarea> <textarea class="form-control" rows="4" readonly>{{ application.strengths_ar }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label> <label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label>
<textarea class="form-control" rows="4" readonly>{{ candidate.weaknesses_ar }}</textarea> <textarea class="form-control" rows="4" readonly>{{ application.weaknesses_ar }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@ -198,7 +198,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for criterion, status in candidate.criteria_checklist_ar.items %} {% for criterion, status in application.criteria_checklist_ar.items %}
<tr> <tr>
<td>{{ criterion }}</td> <td>{{ criterion }}</td>
<td> <td>
@ -223,7 +223,7 @@
<i class="fas fa-check-circle me-2 text-success"></i> <i class="fas fa-check-circle me-2 text-success"></i>
<small class="text-muted">{% trans "Minimum Requirements" %}</small> <small class="text-muted">{% trans "Minimum Requirements" %}</small>
</div> </div>
{% if candidate.min_requirements_met_ar %} {% if application.min_requirements_met_ar %}
<span class="badge bg-success">{% trans "Met" %}</span> <span class="badge bg-success">{% trans "Met" %}</span>
{% else %} {% else %}
<span class="badge bg-danger">{% trans "Not Met" %}</span> <span class="badge bg-danger">{% trans "Not Met" %}</span>
@ -234,15 +234,15 @@
<i class="fas fa-star me-2 text-warning"></i> <i class="fas fa-star me-2 text-warning"></i>
<small class="text-muted">{% trans "Screening Rating" %}</small> <small class="text-muted">{% trans "Screening Rating" %}</small>
</div> </div>
<span class="badge bg-secondary">{{ candidate.screening_stage_rating_ar }}</span> <span class="badge bg-secondary">{{ application.screening_stage_rating_ar }}</span>
</div> </div>
</div> </div>
{% if candidate.language_fluency_ar %} {% if application.language_fluency_ar %}
<div class="mb-3"> <div class="mb-3">
<label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label> <label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label>
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
{% for language in candidate.language_fluency_ar %} {% for language in application.language_fluency_ar %}
<span class="badge bg-light text-dark">{{ language }}</span> <span class="badge bg-light text-dark">{{ language }}</span>
{% endfor %} {% endfor %}
</div> </div>

View File

@ -1,15 +1,15 @@
{% load i18n %} {% load i18n %}
<form id="exam-update-form" hx-post="{% url 'update_application_status' job.slug candidate.slug 'exam' 'Failed' %}" hx-swap='outerHTML' hx-target="#status-result-{{ candidate.pk }}" <form id="exam-update-form" hx-post="{% url 'update_application_status' job.slug application.slug 'exam' 'Failed' %}" hx-swap='outerHTML' hx-target="#status-result-{{ application.pk }}"
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }"> hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
<div class="d-flex justify-content-center align-items-center gap-2"> <div class="d-flex justify-content-center align-items-center gap-2">
<div class="form-check d-flex align-items-center gap-2"> <div class="form-check d-flex align-items-center gap-2">
<input class="form-check-input" type="radio" name="exam_status" id="exam_passed" value="Passed" {% if candidate.exam_status == 'Passed' %}checked{% endif %}> <input class="form-check-input" type="radio" name="exam_status" id="exam_passed" value="Passed" {% if application.exam_status == 'Passed' %}checked{% endif %}>
<label class="form-check-label" for="exam_passed"> <label class="form-check-label" for="exam_passed">
<i class="fas fa-check me-1"></i> {% trans "Passed" %} <i class="fas fa-check me-1"></i> {% trans "Passed" %}
</label> </label>
</div> </div>
<div class="form-check d-flex align-items-center gap-2"> <div class="form-check d-flex align-items-center gap-2">
<input class="form-check-input" type="radio" name="exam_status" id="exam_failed" value="Failed" {% if candidate.exam_status == 'Failed' %}checked{% endif %}> <input class="form-check-input" type="radio" name="exam_status" id="exam_failed" value="Failed" {% if application.exam_status == 'Failed' %}checked{% endif %}>
<label class="form-check-label" for="exam_failed"> <label class="form-check-label" for="exam_failed">
<i class="fas fa-times me-1"></i> {% trans "Failed" %} <i class="fas fa-times me-1"></i> {% trans "Failed" %}
</label> </label>
@ -20,7 +20,7 @@
<label for="exam_score" class="form-label small text-muted">{% trans "Exam Score" %}</label> <label for="exam_score" class="form-label small text-muted">{% trans "Exam Score" %}</label>
</div> </div>
<div class="w-25"> <div class="w-25">
<input type="number" class="form-control form-control-sm" id="exam_score" name="exam_score" min="0" max="100" required value="{{ candidate.exam_score }}"> <input type="number" class="form-control form-control-sm" id="exam_score" name="exam_score" min="0" max="100" required value="{{ application.exam_score }}">
</div> </div>
<div class="w-25 text-start ps-none"> <div class="w-25 text-start ps-none">
</div> </div>

View File

@ -1,10 +1,10 @@
{% load i18n %} {% load i18n %}
<div class="d-flex justify-content-center align-items-center gap-2" hx-swap='outerHTML' hx-target="#interview-result-{{ candidate.pk }}" <div class="d-flex justify-content-center align-items-center gap-2" hx-swap='outerHTML' hx-target="#interview-result-{{ application.pk }}"
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }"> hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
<a hx-post="{% url 'update_application_status' job.slug candidate.slug 'interview' 'Passed' %}" class="btn btn-outline-secondary"> <a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Passed' %}" class="btn btn-outline-secondary">
<i class="fas fa-check me-1"></i> {% trans "Passed" %} <i class="fas fa-check me-1"></i> {% trans "Passed" %}
</a> </a>
<a hx-post="{% url 'update_application_status' job.slug candidate.slug 'interview' 'Failed' %}" class="btn btn-danger"> <a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Failed' %}" class="btn btn-danger">
<i class="fas fa-times me-1"></i> {% trans "Failed" %} <i class="fas fa-times me-1"></i> {% trans "Failed" %}
</a> </a>
</div> </div>

View File

@ -1,10 +1,10 @@
{% load i18n %} {% load i18n %}
<div class="d-flex justify-content-center align-items-center gap-2" hx-swap='outerHTML' hx-target="#status-result-{{ candidate.pk }}" <div class="d-flex justify-content-center align-items-center gap-2" hx-swap='outerHTML' hx-target="#status-result-{{ application.pk }}"
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }"> hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
<a hx-post="{% url 'update_application_status' job.slug candidate.slug 'offer' 'Accepted' %}" class="btn btn-outline-secondary"> <a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" class="btn btn-outline-secondary">
<i class="fas fa-check me-1"></i> {% trans "Accepted" %} <i class="fas fa-check me-1"></i> {% trans "Accepted" %}
</a> </a>
<a hx-post="{% url 'update_application_status' job.slug candidate.slug 'offer' 'Rejected' %}" class="btn btn-danger"> <a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Rejected' %}" class="btn btn-danger">
<i class="fas fa-times me-1"></i> {% trans "Rejected" %} <i class="fas fa-times me-1"></i> {% trans "Rejected" %}
</a> </a>
</div> </div>

View File

@ -26,7 +26,7 @@
<form <form
method="post" method="post"
enctype="multipart/form-data" enctype="multipart/form-data"
hx-post="{% url 'document_upload' candidate.id %}" hx-post="{% url 'document_upload' application.id %}"
hx-target="#documents-pane" hx-target="#documents-pane"
hx-select="#documents-pane" hx-select="#documents-pane"
hx-swap="outerHTML" hx-swap="outerHTML"
@ -108,7 +108,7 @@
<i class="fas fa-download"></i> <i class="fas fa-download"></i>
</a> </a>
{% if user.is_superuser or candidate.job.assigned_to == user %} {% if user.is_superuser or application.job.assigned_to == user %}
<button <button
type="button" type="button"
class="btn btn-sm btn-outline-danger" class="btn btn-sm btn-outline-danger"
@ -125,7 +125,7 @@
<div class="text-center py-5 text-muted"> <div class="text-center py-5 text-muted">
<i class="fas fa-file-alt fa-3x mb-3"></i> <i class="fas fa-file-alt fa-3x mb-3"></i>
<p class="mb-2">{% trans "No documents uploaded yet." %}</p> <p class="mb-2">{% trans "No documents uploaded yet." %}</p>
<p class="small">{% trans "Click \"Upload Document\" to add files for this candidate." %}</p> <p class="small">{% trans "Click \"Upload Document\" to add files for this application." %}</p>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n %}
{% block title %}Create New Job Post - {{ block.super }}{% endblock %} {% block title %}{% trans "Create New Job Post" %} - {{ block.super }}{% endblock %}
{% block customCSS %} {% block customCSS %}

View File

@ -150,12 +150,12 @@
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-decoration-none text-secondary">Home</a></li> <li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-decoration-none text-secondary">{% trans "Home" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-decoration-none text-secondary">Jobs</a></li> <li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-decoration-none text-secondary">{% trans "Jobs" %}</a></li>
<li class="breadcrumb-item active" aria-current="page" style=" <li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */ color: #F43B5E; /* Rosy Accent Color */
font-weight: 600; font-weight: 600;
">Job Detail</li> ">{% trans "Job Detail" %}</li>
</ol> </ol>
</nav> </nav>
<div class="row g-4"> <div class="row g-4">
@ -218,19 +218,19 @@
<div class="card-body"> <div class="card-body">
<h5 class="text-muted mb-3">{% trans "Administrative & Location" %} <h5 class="text-muted mb-3">{% trans "Administrative & Location" %}
<a href="{% url 'job_update' job.slug %}" class="btn btn-main-action btn-sm"><li class="fa fa-edit"></li>{% trans "Edit JOb" %}</a> <a href="{% url 'job_update' job.slug %}" class="btn btn-main-action btn-sm"><li class="fa fa-edit"></li>{% trans "Edit Job" %}</a>
<div class="float-end"> <div class="float-end">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<i class="fas fa-user-tie me-2 text-primary"></i> <strong>{% trans "Assigned to :" %} </strong> {{ job.assigned_to|default:"N/A" }} <i class="fas fa-user-tie me-2 text-primary"></i> <strong>{% trans "Assigned to :" %} </strong> {{ job.assigned_to|default:"" }}
</div> </div>
</div> </div>
</h5> </h5>
<div class="row g-3 mb-4 border-bottom pb-3 small text-secondary"> <div class="row g-3 mb-4 border-bottom pb-3 small text-secondary">
<div class="col-md-6"> <div class="col-md-6">
<i class="fas fa-building me-2 text-primary"></i> <strong>{% trans "Department:" %}</strong> {{ job.department|default:"N/A" }} <i class="fas fa-building me-2 text-primary"></i> <strong>{% trans "Department:" %}</strong> {{ job.department|default:"" }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<i class="fas fa-hashtag me-2 text-primary"></i> <strong>{% trans "Position No:" %}</strong> {{ job.position_number|default:"N/A" }} <i class="fas fa-hashtag me-2 text-primary"></i> <strong>{% trans "Position No:" %}</strong> {{ job.position_number|default:"" }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<i class="fas fa-briefcase me-2 text-primary"></i> <strong>{% trans "Job Type:" %}</strong> {{ job.get_job_type_display }} <i class="fas fa-briefcase me-2 text-primary"></i> <strong>{% trans "Job Type:" %}</strong> {{ job.get_job_type_display }}
@ -242,16 +242,16 @@
<i class="fas fa-globe me-2 text-primary"></i> <strong>{% trans "Location:" %}</strong> {{ job.get_location_display }} <i class="fas fa-globe me-2 text-primary"></i> <strong>{% trans "Location:" %}</strong> {{ job.get_location_display }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<i class="fa-solid fa-money-bill me-2 text-primary"></i> <strong>{% trans "Salary:" %}</strong> {{ job.salary_range |default:"N/A" }} <i class="fa-solid fa-money-bill me-2 text-primary"></i> <strong>{% trans "Salary:" %}</strong> {{ job.salary_range |default:"" }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<i class="fas fa-user-tie me-2 text-primary"></i> <strong>{% trans "Created By:" %}</strong> {{ job.created_by|default:"N/A" }} <i class="fas fa-user-tie me-2 text-primary"></i> <strong>{% trans "Created By:" %}</strong> {{ job.created_by|default:"" }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<i class="fas fa-plus me-2 text-primary"></i> <strong>{% trans "Created At:" %}</strong> {{ job.created_at|default:"N/A" }} <i class="fas fa-plus me-2 text-primary"></i> <strong>{% trans "Created At:" %}</strong> {{ job.created_at|default:"" }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<i class="fas fa-edit me-2 text-primary"></i> <strong>{% trans "Updated At:" %}</strong> {{ job.updated_at|default:"N/A" }} <i class="fas fa-edit me-2 text-primary"></i> <strong>{% trans "Updated At:" %}</strong> {{ job.updated_at|default:"" }}
</div> </div>
</div> </div>
@ -293,7 +293,7 @@
<ul class="nav nav-tabs" id="rightJobTabs" role="tablist"> <ul class="nav nav-tabs" id="rightJobTabs" role="tablist">
<li class="nav-item flex-fill" role="presentation"> <li class="nav-item flex-fill" role="presentation">
<button class="nav-link active" id="applicants-tab" data-bs-toggle="tab" data-bs-target="#applicants-pane" type="button" role="tab" aria-controls="applicants-pane" aria-selected="true"> <button class="nav-link active" id="applicants-tab" data-bs-toggle="tab" data-bs-target="#applicants-pane" type="button" role="tab" aria-controls="applicants-pane" aria-selected="true">
<i class="fas fa-users me-1"></i> {% trans "Applicants" %} <i class="fas fa-users me-1"></i> {% trans "Applications" %}
</button> </button>
</li> </li>
<li class="nav-item flex-fill" role="presentation"> <li class="nav-item flex-fill" role="presentation">
@ -322,14 +322,14 @@
{# TAB 1: APPLICANTS CONTENT #} {# TAB 1: APPLICANTS CONTENT #}
<div class="tab-pane fade show active" id="applicants-pane" role="tabpanel" aria-labelledby="applicants-tab"> <div class="tab-pane fade show active" id="applicants-pane" role="tabpanel" aria-labelledby="applicants-tab">
<h5 class="mb-3">{% trans "Total Applicants" %} (<span id="total_candidates">{{ total_applicants }}</span>)</h5> <h5 class="mb-3">{% trans "Total Applications" %} (<span id="total_candidates">{{ total_applications }}</span>)</h5>
<div class="d-grid gap-3"> <div class="d-grid gap-3">
<a href="{% url 'application_create_for_job' job.slug %}" class="btn btn-main-action"> <a href="{% url 'application_create_for_job' job.slug %}" class="btn btn-main-action">
<i class="fas fa-user-plus me-1"></i> {% trans "Create Applicant" %} <i class="fas fa-user-plus me-1"></i> {% trans "Create Application" %}
</a> </a>
<a href="{% url 'applications_screening_view' job.slug %}" class="btn btn-main-action"> <a href="{% url 'applications_screening_view' job.slug %}" class="btn btn-main-action">
<i class="fas fa-layer-group me-1"></i> {% trans "Manage Applicants" %} <i class="fas fa-layer-group me-1"></i> {% trans "Manage Applications" %}
</a> </a>
<a href="{% url 'request_cvs_download' job.slug %}" class="btn btn-main-action"> <a href="{% url 'request_cvs_download' job.slug %}" class="btn btn-main-action">
@ -345,7 +345,7 @@
{# TAB 2: TRACKING CONTENT #} {# TAB 2: TRACKING CONTENT #}
<div class="tab-pane fade" id="tracking-pane" role="tabpanel" aria-labelledby="tracking-tab"> <div class="tab-pane fade" id="tracking-pane" role="tabpanel" aria-labelledby="tracking-tab">
<h5 class="mb-3"><i class="fas fa-project-diagram me-2 text-primary"></i>{% trans "Applicant Stages" %}</h5> <h5 class="mb-3"><i class="fas fa-project-diagram me-2 text-primary"></i>{% trans "Applications Stages" %}</h5>
{% include 'jobs/partials/applicant_tracking.html' %} {% include 'jobs/partials/applicant_tracking.html' %}
<p class="text-muted small"> <p class="text-muted small">
{% trans "The applicant tracking flow is defined by the attached Form Template. View the Form Template tab to manage stages and fields." %} {% trans "The applicant tracking flow is defined by the attached Form Template. View the Form Template tab to manage stages and fields." %}
@ -491,12 +491,12 @@
</div> </div>
</div> </div>
{# Card 2: Candidate Category Chart #} {# Card 2: Application Category Chart #}
<div class="card shadow-sm no-hover mb-4"> <div class="card shadow-sm no-hover mb-4">
<div class="card-header"> <div class="card-header">
<h6 class="mb-0"> <h6 class="mb-0">
<i class="fas fa-chart-pie me-2 text-primary"></i> <i class="fas fa-chart-pie me-2 text-primary"></i>
{% trans "Candidate Categories & Scores" %} {% trans "Application Categories & Scores" %}
</h6> </h6>
</div> </div>
<div class="card-body p-4"> <div class="card-body p-4">
@ -535,7 +535,7 @@
<div class="card-body p-2"> <div class="card-body p-2">
<i class="fas fa-trophy text-success mb-1 d-block" style="font-size: 1.2rem;"></i> <i class="fas fa-trophy text-success mb-1 d-block" style="font-size: 1.2rem;"></i>
<div class="h4 mb-0 text-success fw-bold">{{ high_potential_count }}</div> <div class="h4 mb-0 text-success fw-bold">{{ high_potential_count }}</div>
<small class="text-muted d-block">{% trans "High Potential" %}</small> <small class="text-muted d-block">{% trans "High Potential (score>=75)" %}</small>
</div> </div>
</div> </div>
</div> </div>
@ -616,14 +616,24 @@
{% endblock %} {% endblock %}
{# Translated JS strings used by the inline scripts below. Using `as` + `escapejs` ensures safe JS embedding. #}
{% trans "Copy failed. Please copy the URL manually:" as copy_failed_msg %}
{% trans "Number of Applications" as chart_label_applications %}
{% trans " application(s)" as chart_tooltip_suffix %}
{% trans "No application category data available for this job." as no_chart_data_msg %}
{% block customJS%} {% block customJS%}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script> <script>
const COPY_FAILED_MSG = "{{ copy_failed_msg|escapejs }}";
const CHART_LABEL_NUMBER_OF_APPLICATIONS = "{{ chart_label_applications|escapejs }}";
const CHART_TOOLTIP_SUFFIX = "{{ chart_tooltip_suffix|escapejs }}";
const NO_CHART_DATA_MSG = "{{ no_chart_data_msg|escapejs }}";
// Pass data from Django to JavaScript safely // Pass data from Django to JavaScript safely
window.jobChartData = { window.jobChartData = {
categories: {{ categories|safe|default:"[]" }}, categories: {{ categories|safe|default:"[]" }},
candidate_counts: {{ candidate_counts|safe|default:"[]" }}, applications_count: {{ applications_count|safe|default:"[]" }},
avg_scores: {{ avg_scores|safe|default:"[]" }} avg_scores: {{ avg_scores|safe|default:"[]" }}
}; };
@ -646,7 +656,7 @@
}).catch(err => { }).catch(err => {
// Fallback for older browsers or security issues // Fallback for older browsers or security issues
console.error('Could not copy text: ', err); console.error('Could not copy text: ', err);
alert("Copy failed. Please copy the URL manually: " + urlToCopy); alert(COPY_FAILED_MSG + ' ' + urlToCopy);
}); });
}); });
@ -661,10 +671,10 @@
// Safely get job_category_data from Django context // Safely get job_category_data from Django context
// Using window.jobChartData to avoid template parsing issues // Using window.jobChartData to avoid template parsing issues
const jobChartData = window.jobChartData || { categories: [], candidate_counts: [], avg_scores: []}; const jobChartData = window.jobChartData || { categories: [], applications_count: [], avg_scores: []};
const categories = jobChartData.categories || []; const categories = jobChartData.categories || [];
const candidateCounts = jobChartData.candidate_counts || []; const candidateCounts = jobChartData.applications_count || [];
const avgScores = jobChartData.avg_scores || []; const avgScores = jobChartData.avg_scores || [];
if (categories.length > 0) { // Only render if there's data if (categories.length > 0) { // Only render if there's data
@ -672,9 +682,9 @@
type: 'doughnut', type: 'doughnut',
data: { data: {
labels: categories, labels: categories,
datasets: [ datasets: [
{ {
label: 'Number of Candidates', label: CHART_LABEL_NUMBER_OF_APPLICATIONS,
data: candidateCounts, data: candidateCounts,
backgroundColor: [ backgroundColor: [
'rgba(0, 99, 110, 0.7)', // --kaauh-teal 'rgba(0, 99, 110, 0.7)', // --kaauh-teal
@ -715,7 +725,7 @@
if (label) { if (label) {
label += ': '; label += ': ';
} }
label += context.parsed + ' candidate(s)'; label += context.parsed + CHART_TOOLTIP_SUFFIX;
return label; return label;
} }
} }
@ -724,8 +734,8 @@
} }
}); });
} else { } else {
// Display a message if no data is available // Display a message if no data is available (translated)
chartCtx.canvas.parentNode.innerHTML = '<p class="text-center text-muted mt-4">No candidate category data available for this job.</p>'; chartCtx.canvas.parentNode.innerHTML = '<p class="text-center text-muted mt-4">' + NO_CHART_DATA_MSG + '</p>';
} }
}); });
</script> </script>

View File

@ -330,12 +330,12 @@
</td> </td>
{# CANDIDATE MANAGEMENT DATA #} {# CANDIDATE MANAGEMENT DATA #}
<td class="candidate-data-cell text-primary-theme"><a href="{% url 'applications_screening_view' job.slug %}" class="text-primary-theme">{% if job.all_candidates.count %}{{ job.all_candidates.count }}{% else %}-{% endif %}</a></td> <td class="candidate-data-cell text-primary-theme"><a href="{% url 'applications_screening_view' job.slug %}" class="text-primary-theme">{% if job.all_applications.count %}{{ job.all_applications.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-info"><a href="{% url 'applications_screening_view' job.slug %}" class="text-info">{% if job.screening_candidates.count %}{{ job.screening_candidates.count }}{% else %}-{% endif %}</a></td> <td class="candidate-data-cell text-info"><a href="{% url 'applications_screening_view' job.slug %}" class="text-info">{% if job.screening_applications.count %}{{ job.screening_applications.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'applications_exam_view' job.slug %}" class="text-success">{% if job.exam_candidates.count %}{{ job.exam_candidates.count }}{% else %}-{% endif %}</a></td> <td class="candidate-data-cell text-success"><a href="{% url 'applications_exam_view' job.slug %}" class="text-success">{% if job.exam_applications.count %}{{ job.exam_applications.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'applications_interview_view' job.slug %}" class="text-success">{% if job.interview_candidates.count %}{{ job.interview_candidates.count }}{% else %}-{% endif %}</a></td> <td class="candidate-data-cell text-success"><a href="{% url 'applications_interview_view' job.slug %}" class="text-success">{% if job.interview_applications.count %}{{ job.interview_applications.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'applications_document_review_view' job.slug %}" class="text-success">{% if job.document_review_candidates.count %}{{ job.document_review_candidates.count }}{% else %}-{% endif %}</a></td> <td class="candidate-data-cell text-success"><a href="{% url 'applications_document_review_view' job.slug %}" class="text-success">{% if job.document_review_applications.count %}{{ job.document_review_applications.count }}{% else %}-{% endif %}</a></td>
<td class="candidate-data-cell text-success"><a href="{% url 'applications_offer_view' job.slug %}" class="text-success">{% if job.offer_candidates.count %}{{ job.offer_candidates.count }}{% else %}-{% endif %}</a></td> <td class="candidate-data-cell text-success"><a href="{% url 'applications_offer_view' job.slug %}" class="text-success">{% if job.offer_applications.count %}{{ job.offer_applications.count }}{% else %}-{% endif %}</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -360,8 +360,8 @@
<ul class="list-unstyled small mb-3"> <ul class="list-unstyled small mb-3">
<li><i class="fas fa-calendar-alt text-primary-theme me-2"></i>{% trans "Deadline" %}: {{ job.application_deadline|date:"d-m-Y" }}</li> <li><i class="fas fa-calendar-alt text-primary-theme me-2"></i>{% trans "Deadline" %}: {{ job.application_deadline|date:"d-m-Y" }}</li>
<li><i class="fas fa-users text-primary-theme me-2"></i>{% trans "Total Applicants" %}: {{ job.all_candidates.count|default:"0" }}</li> <li><i class="fas fa-users text-primary-theme me-2"></i>{% trans "Total Applicants" %}: {{ job.all_applications.count|default:"0" }}</li>
<li><i class="fas fa-clipboard-check text-success me-2"></i> {% trans "Offers Made" %}: {{ job.offer_candidates.count|default:"0" }}</li> <li><i class="fas fa-clipboard-check text-success me-2"></i> {% trans "Offers Made" %}: {{ job.offer_applications.count|default:"0" }}</li>
<li><i class="fas fa-file-alt text-info me-2"></i> {% trans "Form" %}: <li><i class="fas fa-file-alt text-info me-2"></i> {% trans "Form" %}:
{% if job.form_template %} {% if job.form_template %}
<a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="text-info">{{ job.form_template.name }} ({{ job.form_template.submissions.count }} submissions)</a> <a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="text-info">{{ job.form_template.name }} ({{ job.form_template.submissions.count }} submissions)</a>

View File

@ -109,7 +109,7 @@
<i class="fas fa-file-signature cd_screening"></i> <i class="fas fa-file-signature cd_screening"></i>
</div> </div>
<div class="stage-label cd_screening">{% trans "Screened" %}</div> <div class="stage-label cd_screening">{% trans "Screened" %}</div>
<div class="stage-count">{{ job.screening_candidates.count|default:"0" }}</div> <div class="stage-count">{{ job.screening_applications.count|default:"0" }}</div>
</a> </a>
{% comment %} CONNECTOR 1 -> 2 {% endcomment %} {% comment %} CONNECTOR 1 -> 2 {% endcomment %}
@ -123,7 +123,7 @@
<i class="fas fa-clipboard-check cd_exam"></i> <i class="fas fa-clipboard-check cd_exam"></i>
</div> </div>
<div class="stage-label cd_exam">{% trans "Exam" %}</div> <div class="stage-label cd_exam">{% trans "Exam" %}</div>
<div class="stage-count ">{{ job.exam_candidates.count|default:"0" }}</div> <div class="stage-count ">{{ job.exam_applications.count|default:"0" }}</div>
</a> </a>
{% comment %} CONNECTOR 2 -> 3 {% endcomment %} {% comment %} CONNECTOR 2 -> 3 {% endcomment %}
@ -137,7 +137,7 @@
<i class="fas fa-comments cd_interview"></i> <i class="fas fa-comments cd_interview"></i>
</div> </div>
<div class="stage-label cd_interview">{% trans "Interview" %}</div> <div class="stage-label cd_interview">{% trans "Interview" %}</div>
<div class="stage-count">{{ job.interview_candidates.count|default:"0" }}</div> <div class="stage-count">{{ job.interview_applications.count|default:"0" }}</div>
</a> </a>
{% comment %} CONNECTOR 3 -> 4 {% endcomment %} {% comment %} CONNECTOR 3 -> 4 {% endcomment %}
@ -151,7 +151,7 @@
<i class="fas fa-file-alt"></i> <i class="fas fa-file-alt"></i>
</div> </div>
<div class="stage-label">{% trans "Document Review" %}</div> <div class="stage-label">{% trans "Document Review" %}</div>
<div class="stage-count">{{ job.document_review_candidates.count|default:"0" }}</div> <div class="stage-count">{{ job.document_review_applications.count|default:"0" }}</div>
</a> </a>
{% comment %} CONNECTOR 4 -> 5 {% endcomment %} {% comment %} CONNECTOR 4 -> 5 {% endcomment %}
@ -165,7 +165,7 @@
<i class="fas fa-handshake"></i> <i class="fas fa-handshake"></i>
</div> </div>
<div class="stage-label">{% trans "Offer" %}</div> <div class="stage-label">{% trans "Offer" %}</div>
<div class="stage-count">{{ job.offer_candidates.count|default:"0" }}</div> <div class="stage-count">{{ job.offer_applications.count|default:"0" }}</div>
</a> </a>
{% comment %} CONNECTOR 5 -> 6 {% endcomment %} {% comment %} CONNECTOR 5 -> 6 {% endcomment %}
@ -179,7 +179,7 @@
<i class="fas fa-trophy"></i> <i class="fas fa-trophy"></i>
</div> </div>
<div class="stage-label">{% trans "Hired" %}</div> <div class="stage-label">{% trans "Hired" %}</div>
<div class="stage-count">{{ job.hired_candidates.count|default:"0" }}</div> <div class="stage-count">{{ job.hired_applications.count|default:"0" }}</div>
</a> </a>
</div> </div>
</div> </div>

View File

@ -0,0 +1,35 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}{% trans "Delete Application" %} - {{ block.super }}{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h1>
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Delete Application" %}: {{ object.candidate.full_name }}
</h1>
<a href="{% url 'applications_list' %}" class="btn btn-secondary">{% trans "Back to List" %}</a>
</div>
<p>{% trans "Are you sure you want to delete this application for" %} "{{ object.candidate.full_name }}" {% trans "for the job" %} "{{ object.job.title }}"? {% trans "This action cannot be undone." %}</p>
{% if object.job %}
<p><strong>{% trans "Job:" %}</strong> {{ object.job.title }}</p>
{% endif %}
{% if object.candidate %}
<p><strong>{% trans "Candidate:" %}</strong> {{ object.candidate.full_name }}</p>
{% endif %}
<p><strong>{% trans "Application Date:" %}</strong> {{ object.created_at|date:"M d, Y" }}</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Yes, Delete Application" %}
</button>
</form>
</div>
{% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static humanize i18n %} {% load static i18n humanize%}
{% block title %}{{ candidate.name }} - {{ block.super }}{% endblock %} {% block title %}{{ application.name }} - {{ block.super }}{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
@ -269,7 +269,7 @@
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary text-decoration-none">Home</a></li> <li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary text-decoration-none">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-secondary text-decoration-none">Job:({{candidate.job.title}})</a></li> <li class="breadcrumb-item"><a href="{% url 'job_detail' application.job.slug %}" class="text-secondary text-decoration-none">Job:({{application.job.title}})</a></li>
<li class="breadcrumb-item active" aria-current="page" class="text-secondary" style=" <li class="breadcrumb-item active" aria-current="page" class="text-secondary" style="
color: #F43B5E; /* Rosy Accent Color */ color: #F43B5E; /* Rosy Accent Color */
font-weight: 600; font-weight: 600;
@ -277,7 +277,7 @@
</ol> </ol>
</nav> </nav>
{# LEFT COLUMN: MAIN CANDIDATE DETAILS AND TABS #} {# LEFT COLUMN: MAIN application DETAILS AND TABS #}
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card shadow-sm no-hover"> <div class="card shadow-sm no-hover">
@ -285,16 +285,16 @@
<div class="candidate-header-card"> <div class="candidate-header-card">
<div class="d-flex justify-content-between align-items-start flex-wrap"> <div class="d-flex justify-content-between align-items-start flex-wrap">
<div> <div>
<h1 class="h3 mb-2">{{ candidate.name }}</h1> <h1 class="h3 mb-2">{{ application.name }}</h1>
<div class="d-flex align-items-center gap-2 mb-2"> <div class="d-flex align-items-center gap-2 mb-2">
<span id="stageDisplay" class="badge"> <span id="stageDisplay" class="badge">
{% trans "Stage:" %} {% trans "Stage:" %}
<span>{{ candidate.stage }}</span> <span>{{ application.stage }}</span>
</span> </span>
</div> </div>
<small class="text-white opacity-75"> <small class="text-white opacity-75">
{% trans "Applied for:" %} <strong>{{ candidate.job.title }}</strong> {% trans "Applied for:" %} <strong>{{ application.job.title }}</strong>
</small> </small>
</div> </div>
{# Change Stage button #} {# Change Stage button #}
@ -341,7 +341,7 @@
<i class="fas fa-envelope fa-2x text-muted me-3"></i> <i class="fas fa-envelope fa-2x text-muted me-3"></i>
<div> <div>
<small class="text-muted d-block">{% trans "Email" %}</small> <small class="text-muted d-block">{% trans "Email" %}</small>
<strong>{{ candidate.email }}</strong> <strong>{{ application.email }}</strong>
</div> </div>
</div> </div>
</div> </div>
@ -350,7 +350,7 @@
<i class="fas fa-briefcase fa-2x text-muted me-3"></i> <i class="fas fa-briefcase fa-2x text-muted me-3"></i>
<div> <div>
<small class="text-muted d-block">{% trans "Position Applied" %}</small> <small class="text-muted d-block">{% trans "Position Applied" %}</small>
<strong>{{ candidate.job.title }}</strong> <strong>{{ application.job.title }}</strong>
</div> </div>
</div> </div>
</div> </div>
@ -360,10 +360,10 @@
<div> <div>
<small class="text-muted d-block">{% trans "Applied Date" %}</small> <small class="text-muted d-block">{% trans "Applied Date" %}</small>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<strong>{{ candidate.created_at|date:"M d, Y H:i" }}</strong> <strong>{{ application.created_at|date:"M d, Y H:i" }}</strong>
<span class="badge bg-light text-dark"> <span class="badge bg-light text-dark">
<i class="far fa-clock me-1"></i> <i class="far fa-clock me-1"></i>
{{ candidate.created_at|naturaltime }} {{ application.created_at|naturaltime }}
</span> </span>
</div> </div>
</div> </div>
@ -380,15 +380,15 @@
{# ENHANCED: CANDIDATE JOURNEY TIMELINE CARD #} {# ENHANCED: CANDIDATE JOURNEY TIMELINE CARD #}
<div class="card shadow-sm timeline-card"> <div class="card shadow-sm timeline-card">
<div class="card-header bg-white border-bottom py-3"> <div class="card-header bg-white border-bottom py-3">
<h5 class="mb-0 text-muted"><i class="fas fa-route me-2"></i>{% trans "Candidate Journey" %}</h5> <h5 class="mb-0 text-muted"><i class="fas fa-route me-2"></i>{% trans "Application Journey" %}</h5>
</div> </div>
<div class="card-body p-4"> <div class="card-body p-4">
<h6 class="text-uppercase text-secondary mb-3">{% trans "Current Stage" %}</h6> <h6 class="text-uppercase text-secondary mb-3">{% trans "Current Stage" %}</h6>
<div class="p-3 mb-4 rounded current-stage"> <div class="p-3 mb-4 rounded current-stage">
<p class="mb-0 fw-bold fs-5 text-primary">{{ candidate.stage }}</p> <p class="mb-0 fw-bold fs-5 text-primary">{{ application.stage }}</p>
<small class="text-muted d-block mt-1"> <small class="text-muted d-block mt-1">
{% trans "Latest status update:" %} {{ candidate.updated_at|date:"M d, Y" }} {% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
</small> </small>
</div> </div>
@ -402,50 +402,50 @@
<div class="timeline-content"> <div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Application Submitted" %}</p> <p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Application Submitted" %}</p>
<small class="text-muted"> <small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.created_at|date:"M d, Y" }} <i class="far fa-calendar-alt me-1"></i> {{ application.created_at|date:"M d, Y" }}
<span class="ms-2">|</span> <span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.created_at|date:"h:i A" }} <i class="far fa-clock ms-2 me-1"></i> {{ application.created_at|date:"h:i A" }}
</small> </small>
</div> </div>
</div> </div>
{% if candidate.exam_date %} {% if application.exam_date %}
<div class="timeline-item"> <div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fa-clipboard-check"></i></div> <div class="timeline-icon timeline-bg-applied"><i class="fas fa-clipboard-check"></i></div>
<div class="timeline-content"> <div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Exam" %}</p> <p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Exam" %}</p>
<small class="text-muted"> <small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.exam_date|date:"M d, Y" }} <i class="far fa-calendar-alt me-1"></i> {{ application.exam_date|date:"M d, Y" }}
<span class="ms-2">|</span> <span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.exam_date|date:"h:i A" }} <i class="far fa-clock ms-2 me-1"></i> {{ application.exam_date|date:"h:i A" }}
</small> </small>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if candidate.get_interview_date %} {% if application.get_interview_date %}
<div class="timeline-item"> <div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-comments"></i></div> <div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-comments"></i></div>
<div class="timeline-content"> <div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Interview" %}</p> <p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Interview" %}</p>
<small class="text-muted"> <small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.get_interview_date}} <i class="far fa-calendar-alt me-1"></i> {{ application.get_interview_date}}
<span class="ms-2">|</span> <span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.get_interview_time}} <i class="far fa-clock ms-2 me-1"></i> {{ application.get_interview_time}}
</small> </small>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if candidate.offer_date %} {% if application.offer_date %}
<div class="timeline-item"> <div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-handshake"></i></div> <div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-handshake"></i></div>
<div class="timeline-content"> <div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p> <p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
<small class="text-muted"> <small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.offer_date|date:"M d, Y" }} <i class="far fa-calendar-alt me-1"></i> {{ application.offer_date|date:"M d, Y" }}
</small> </small>
</div> </div>
@ -453,13 +453,13 @@
</div> </div>
{% endif %} {% endif %}
{% if candidate.hired_date %} {% if application.hired_date %}
<div class="timeline-item"> <div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-handshake"></i></div> <div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-handshake"></i></div>
<div class="timeline-content"> <div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p> <p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
<small class="text-muted"> <small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.hired_date|date:"M d, Y" }} <i class="far fa-calendar-alt me-1"></i> {{ application.hired_date|date:"M d, Y" }}
</small> </small>
</div> </div>
@ -473,28 +473,28 @@
{# TAB 4 CONTENT: DOCUMENTS #} {# TAB 4 CONTENT: DOCUMENTS #}
<div class="tab-pane fade" id="documents-pane" role="tabpanel" aria-labelledby="documents-tab"> <div class="tab-pane fade" id="documents-pane" role="tabpanel" aria-labelledby="documents-tab">
{% with documents=candidate.documents %} {% with documents=application.documents %}
{% include 'includes/document_list.html' %} {% include 'includes/document_list.html' %}
{% endwith %} {% endwith %}
</div> </div>
{# TAB 5 CONTENT: PARSED SUMMARY #} {# TAB 5 CONTENT: PARSED SUMMARY #}
{% if candidate.parsed_summary %} {% if application.parsed_summary %}
<div class="tab-pane fade" id="summary-pane" role="tabpanel" aria-labelledby="summary-tab"> <div class="tab-pane fade" id="summary-pane" role="tabpanel" aria-labelledby="summary-tab">
<h5 class="text-primary mb-4">{% trans "AI Generated Summary" %}</h5> <h5 class="text-primary mb-4">{% trans "AI Generated Summary" %}</h5>
<div class="border-start border-primary ps-3 pt-1 pb-1"> <div class="border-start border-primary ps-3 pt-1 pb-1">
{% include 'includes/candidate_modal_body.html' %} {% include 'includes/application_modal_body.html' %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{# TAB 5 CONTENT: AI ANALYSIS #} {# TAB 5 CONTENT: AI ANALYSIS #}
{% if candidate.is_resume_parsed %} {% if application.is_resume_parsed %}
<div class="tab-pane fade" id="analysis-pane" role="tabpanel" aria-labelledby="analysis-tab"> <div class="tab-pane fade" id="analysis-pane" role="tabpanel" aria-labelledby="analysis-tab">
<h5 class="text-primary mb-4">{% trans "AI Analysis Report" %}</h5> <h5 class="text-primary mb-4">{% trans "AI Analysis Report" %}</h5>
<div class="border-start border-primary ps-3 pt-1 pb-1"> <div class="border-start border-primary ps-3 pt-1 pb-1">
{% with analysis=candidate.ai_analysis_data %} {% with analysis=application.ai_analysis_data %}
{# Match Score Card #} {# Match Score Card #}
<div class="mb-4 p-3 rounded" style="background-color: {% if analysis.match_score >= 70 %}rgba(40, 167, 69, 0.1){% elif analysis.match_score >= 40 %}rgba(255, 193, 7, 0.1){% else %}rgba(220, 53, 69, 0.1){% endif %}"> <div class="mb-4 p-3 rounded" style="background-color: {% if analysis.match_score >= 70 %}rgba(40, 167, 69, 0.1){% elif analysis.match_score >= 40 %}rgba(255, 193, 7, 0.1){% else %}rgba(220, 53, 69, 0.1){% endif %}">
<div class="d-flex justify-content-between align-items-center mb-2"> <div class="d-flex justify-content-between align-items-center mb-2">
@ -626,7 +626,7 @@
<span class="visually-hidden">{% trans "Loading..." %}</span> <span class="visually-hidden">{% trans "Loading..." %}</span>
</div> </div>
<h5 class="text-primary">{% trans "Resume is being parsed" %}</h5> <h5 class="text-primary">{% trans "Resume is being parsed" %}</h5>
<p class="text-muted">{% trans "Our AI is analyzing the candidate's resume to generate insights. This may take a few moments." %}</p> <p class="text-muted">{% trans "Our AI is analyzing the application's resume to generate insights. This may take a few moments." %}</p>
<div class="progress mt-3" style="height: 6px;"> <div class="progress mt-3" style="height: 6px;">
<div class="progress-bar progress-bar-striped progress-bar-animated" <div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 75%"></div> role="progressbar" style="width: 75%"></div>
@ -648,28 +648,28 @@
<div class="card shadow-sm mb-2 p-2"> <div class="card shadow-sm mb-2 p-2">
<h5 class="text-muted mb-3"><i class="fas fa-cog me-2"></i>{% trans "Management Actions" %}</h5> <h5 class="text-muted mb-3"><i class="fas fa-cog me-2"></i>{% trans "Management Actions" %}</h5>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
{% comment %} <a href="{% url 'application_update' candidate.slug %}" class="btn btn-outline-primary"> {% comment %} <a href="{% url 'application_update' application.slug %}" class="btn btn-outline-primary">
<i class="fas fa-edit"></i> {% trans "Edit Details" %} <i class="fas fa-edit"></i> {% trans "Edit Details" %}
</a> {% endcomment %} </a> {% endcomment %}
{% comment %} <a href="{% url 'application_delete' candidate.slug %}" class="btn btn-outline-danger" onclick="return confirm('{% trans "Are you sure you want to delete this candidate?" %}')"> {% comment %} <a href="{% url 'application_delete' application.slug %}" class="btn btn-outline-danger" onclick="return confirm('{% trans "Are you sure you want to delete this candidate?" %}')">
<i class="fas fa-trash-alt"></i> {% trans "Delete Candidate" %} <i class="fas fa-trash-alt"></i> {% trans "Delete Candidate" %}
</a> {% endcomment %} </a> {% endcomment %}
<a href="{% url 'application_list' %}" class="btn btn-outline-secondary"> <a href="{% url 'application_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> {% trans "Back to List" %} <i class="fas fa-arrow-left"></i> {% trans "Back to List" %}
</a> </a>
{% if candidate.resume %} {% if application.resume %}
{% comment %} <a href="{{ candidate.resume.url }}" target="_blank" class="btn btn-outline-primary"> {% comment %} <a href="{{ application.resume.url }}" target="_blank" class="btn btn-outline-primary">
<i class="fas fa-eye me-1"></i> <i class="fas fa-eye me-1"></i>
{% trans "View Actual Resume" %} {% trans "View Actual Resume" %}
</a> {% endcomment %} </a> {% endcomment %}
<a href="{{ candidate.resume.url }}" download class="btn btn-outline-primary"> <a href="{{ application.resume.url }}" download class="btn btn-outline-primary">
<i class="fas fa-download me-1"></i> <i class="fas fa-download me-1"></i>
{% trans "Download Resume" %} {% trans "Download Resume" %}
</a> </a>
{% comment %} <a href="{% url 'application_resume_template' candidate.slug %}" class="btn btn-outline-info"> {% comment %} <a href="{% url 'application_resume_template' application.slug %}" class="btn btn-outline-info">
<i class="fas fa-file-alt me-1"></i> <i class="fas fa-file-alt me-1"></i>
{% trans "View Resume AI Overview" %} {% trans "View Resume AI Overview" %}
</a> {% endcomment %} </a> {% endcomment %}
@ -681,7 +681,7 @@
<div class="card shadow-sm mb-4 p-2"> <div class="card shadow-sm mb-4 p-2">
<h5 class="text-muted mb-3"><i class="fas fa-clock me-2"></i>{% trans "Time to Hire:" %} <h5 class="text-muted mb-3"><i class="fas fa-clock me-2"></i>{% trans "Time to Hire:" %}
{% with days=candidate.time_to_hire_days %} {% with days=application.time_to_hire_days %}
{% if days > 0 %} {% if days > 0 %}
{{ days }} day{{ days|pluralize }} {{ days }} day{{ days|pluralize }}
{% else %} {% else %}
@ -699,10 +699,10 @@
</div> </div>
<div class="resume-parsed-section"> <div class="resume-parsed-section">
{% if candidate.is_resume_parsed %} {% if application.is_resume_parsed %}
{% include 'recruitment/candidate_resume_template.html' %} {% include 'recruitment/application_resume_template.html' %}
{% else %} {% else %}
{% if candidate.scoring_timeout %} {% if application.scoring_timeout %}
<div style="display: flex; justify-content: center; align-items: center; height: 100%;" class="mb-2"> <div style="display: flex; justify-content: center; align-items: center; height: 100%;" class="mb-2">
<div class="ai-loading-container"> <div class="ai-loading-container">
<i class="fas fa-robot ai-robot-icon"></i> <i class="fas fa-robot ai-robot-icon"></i>
@ -711,7 +711,7 @@
</div> </div>
{% else %} {% else %}
<div style="display: flex; justify-content: center; align-items: center; height: 100%;"> <div style="display: flex; justify-content: center; align-items: center; height: 100%;">
<button type="submit" class="btn btn-sm btn-main-action" hx-get="{% url 'application_retry_scoring' candidate.slug %}" hx-select=".resume-parsed-section" hx-target=".resume-parsed-section" hx-swap="outerHTML" hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume , Please Wait.. <i class='fa-solid fa-spinner fa-spin'></i>`"> <button type="submit" class="btn btn-sm btn-main-action" hx-get="{% url 'application_retry_scoring' application.slug %}" hx-select=".resume-parsed-section" hx-target=".resume-parsed-section" hx-swap="outerHTML" hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume , Please Wait.. <i class='fa-solid fa-spinner fa-spin'></i>`">
<i class="fas fa-redo-alt me-1"></i> <i class="fas fa-redo-alt me-1"></i>
{% trans "Unable to Parse Resume , click to retry" %} {% trans "Unable to Parse Resume , click to retry" %}
</button> </button>
@ -724,6 +724,6 @@
{% if user.is_staff %} {% if user.is_staff %}
{% include "recruitment/partials/stage_update_modal.html" with candidate=candidate form=stage_form %} {% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -7,9 +7,9 @@
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% if LANGUAGE_CODE == 'ar' %} {% if LANGUAGE_CODE == 'ar' %}
<title>{{ candidate.resume_data_ar.full_name|default:"Candidate" }} - Candidate Profile</title> <title>{{ application.resume_data_ar.full_name|default:"Application" }} - Application Profile</title>
{% else %} {% else %}
<title>{{ candidate.resume_data_en.full_name|default:"Candidate" }} - Candidate Profile</title> <title>{{ application.resume_data_en.full_name|default:"Application" }} - Application Profile</title>
{% endif %} {% endif %}
<!-- Use a modern icon set --> <!-- Use a modern icon set -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
@ -532,40 +532,40 @@
<!-- Header Section --> <!-- Header Section -->
<header class="header-box"> <header class="header-box">
<div class="header-info"> <div class="header-info">
<h1>{{ candidate.resume_data.full_name|default:"Candidate Name" }}</h1> <h1>{{ application.resume_data.full_name|default:"Application Name" }}</h1>
<p>{{ candidate.resume_data.current_title|default:"Professional Title" }}</p> <p>{{ application.resume_data.current_title|default:"Professional Title" }}</p>
<div class="contact-details"> <div class="contact-details">
<div class="contact-item"> <div class="contact-item">
<i class="fas fa-map-marker-alt"></i> <i class="fas fa-map-marker-alt"></i>
<span>{{ candidate.resume_data.location|default:"Location" }}</span> <span>{{ application.resume_data.location|default:"Location" }}</span>
</div> </div>
<!-- Displaying the raw contact string (which contains both phone and email in the example) --> <!-- Displaying the raw contact string (which contains both phone and email in the example) -->
<div class="contact-item"> <div class="contact-item">
<i class="fas fa-id-card"></i> <i class="fas fa-id-card"></i>
<span title="Contact Information: Phone and Email">{{ candidate.resume_data.contact|default:"Contact Information" }}</span> <span title="Contact Information: Phone and Email">{{ application.resume_data.contact|default:"Contact Information" }}</span>
</div> </div>
<!-- GitHub and LinkedIn links for quick access (null in example but included for completeness) --> <!-- GitHub and LinkedIn links for quick access (null in example but included for completeness) -->
{% if candidate.resume_data.linkedin %} {% if application.resume_data.linkedin %}
<div class="contact-item"> <div class="contact-item">
<a href="{{ candidate.resume_data.linkedin }}" target="_blank"><i class="fab fa-linkedin text-white"></i></a> <a href="{{ application.resume_data.linkedin }}" target="_blank"><i class="fab fa-linkedin text-white"></i></a>
</div> </div>
{% endif %} {% endif %}
{% if candidate.resume_data.github %} {% if application.resume_data.github %}
<div class="contact-item"> <div class="contact-item">
<a href="{{ candidate.resume_data.github }}" target="_blank"><i class="fab fa-github text-white"></i></a> <a href="{{ application.resume_data.github }}" target="_blank"><i class="fab fa-github text-white"></i></a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="score-box"> <div class="score-box">
<div class="score-value">{{ candidate.analysis_data.match_score|default:0 }}%</div> <div class="score-value">{{ application.analysis_data.match_score|default:0 }}%</div>
<div class="score-text">Match Score</div> <div class="score-text">Match Score</div>
<div class="assessment-rating <div class="assessment-rating
{% if candidate.analysis_data.match_score|default:0 < 50 %}score-red{% elif candidate.analysis_data.match_score|default:0 < 75 %}score-yellow{% else %}score-green{% endif %}"> {% if application.analysis_data.match_score|default:0 < 50 %}score-red{% elif application.analysis_data.match_score|default:0 < 75 %}score-yellow{% else %}score-green{% endif %}">
<!-- scoring_data.screening_stage_rating --> <!-- scoring_data.screening_stage_rating -->
{{ candidate.analysis_data.screening_stage_rating|default:"Assessment" }} {{ application.analysis_data.screening_stage_rating|default:"Assessment" }}
</div> </div>
</div> </div>
</header> </header>
@ -582,8 +582,8 @@
Summary Summary
</h2> </h2>
<p class="summary-text"> <p class="summary-text">
<!-- candidate.resume_data.summary, falling back to scoring_data.job_fit_narrative --> <!-- application.resume_data.summary, falling back to scoring_data.job_fit_narrative -->
{{ candidate.resume_data.summary|default:"Professional summary not available." }} {{ application.resume_data.summary|default:"Professional summary not available." }}
</p> </p>
</section> </section>
@ -593,7 +593,7 @@
<i class="fas fa-briefcase"></i> <i class="fas fa-briefcase"></i>
Experience Experience
</h2> </h2>
{% for experience in candidate.resume_data.experience %} {% for experience in application.resume_data.experience %}
<div class="experience-item"> <div class="experience-item">
<div class="experience-header"> <div class="experience-header">
<div> <div>
@ -608,7 +608,7 @@
<i class="fas fa-calendar-alt"></i> <i class="fas fa-calendar-alt"></i>
{% if experience.start_date %}{{ experience.start_date }}{% endif %} - {% if experience.start_date %}{{ experience.start_date }}{% endif %} -
{% if experience.end_date and experience.end_date != "Present" %}{{ experience.end_date }}{% else %}Present{% endif %} {% if experience.end_date and experience.end_date != "Present" %}{{ experience.end_date }}{% else %}Present{% endif %}
<!-- candidate.resume_data.experience[].location --> <!-- application.resume_data.experience[].location -->
{% if experience.location %}<span style="margin-left: 1rem;"><i class="fas fa-map-pin"></i>{{ experience.location }}</span>{% endif %} {% if experience.location %}<span style="margin-left: 1rem;"><i class="fas fa-map-pin"></i>{{ experience.location }}</span>{% endif %}
</p> </p>
{% if experience.key_achievements %} {% if experience.key_achievements %}
@ -628,7 +628,7 @@
<i class="fas fa-graduation-cap"></i> <i class="fas fa-graduation-cap"></i>
Education Education
</h2> </h2>
{% for education in candidate.resume_data.education %} {% for education in application.resume_data.education %}
<div class="education-item"> <div class="education-item">
<div class="icon-badge"> <div class="icon-badge">
<i class="fas fa-certificate"></i> <i class="fas fa-certificate"></i>
@ -642,7 +642,7 @@
{% if education.gpa %} {% if education.gpa %}
<p class="meta"><i class="fas fa-award"></i> GPA: {{ education.gpa }}</p> <p class="meta"><i class="fas fa-award"></i> GPA: {{ education.gpa }}</p>
{% endif %} {% endif %}
<!-- candidate.resume_data.education[].relevant_courses --> <!-- application.resume_data.education[].relevant_courses -->
{% if education.relevant_courses %} {% if education.relevant_courses %}
<p class="meta" style="margin-top: 0.25rem;">Courses: {{ education.relevant_courses|join:", " }}</p> <p class="meta" style="margin-top: 0.25rem;">Courses: {{ education.relevant_courses|join:", " }}</p>
{% endif %} {% endif %}
@ -657,7 +657,7 @@
<i class="fas fa-project-diagram"></i> <i class="fas fa-project-diagram"></i>
Projects Projects
</h2> </h2>
{% for project in candidate.resume_data.projects %} {% for project in application.resume_data.projects %}
<div class="project-item"> <div class="project-item">
<h3>{{ project.name }}</h3> <h3>{{ project.name }}</h3>
<p class="description">{{ project.brief_description }}</p> <p class="description">{{ project.brief_description }}</p>
@ -670,37 +670,37 @@
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}
{% if not candidate.resume_data.projects %} {% if not application.resume_data.projects %}
<p style="color: var(--color-gray-500); font-size: 0.875rem;">No projects detailed in the resume.</p> <p style="color: var(--color-gray-500); font-size: 0.875rem;">No projects detailed in the resume.</p>
{% endif %} {% endif %}
</section> </section>
<!-- Keywords Section (NOW IN THE LEFT COLUMN) --> <!-- Keywords Section (NOW IN THE LEFT COLUMN) -->
{% if candidate.analysis_data.top_3_keywords or candidate.analysis_data.cultural_fit_keywords %} {% if application.analysis_data.top_3_keywords or application.analysis_data.cultural_fit_keywords %}
<section class="card-section" style="margin-top: 0;"> <section class="card-section" style="margin-top: 0;">
<h2 class="section-title"> <h2 class="section-title">
<i class="fas fa-tags"></i> <i class="fas fa-tags"></i>
Keywords Keywords
</h2> </h2>
{% if candidate.analysis_data.top_3_keywords %} {% if application.analysis_data.top_3_keywords %}
<div style="margin-bottom: 1rem;"> <div style="margin-bottom: 1rem;">
<h3 class="keyword-subheader"><i class="fas fa-key"></i>Top Keywords (Job Match)</h3> <h3 class="keyword-subheader"><i class="fas fa-key"></i>Top Keywords (Job Match)</h3>
<div class="tag-list"> <div class="tag-list">
<!-- scoring_data.top_3_keywords --> <!-- scoring_data.top_3_keywords -->
{% for keyword in candidate.analysis_data.top_3_keywords %} {% for keyword in application.analysis_data.top_3_keywords %}
<span class="keyword-tag">{{ keyword }}</span> <span class="keyword-tag">{{ keyword }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.cultural_fit_keywords %} {% if application.analysis_data.cultural_fit_keywords %}
<div> <div>
<h3 class="keyword-subheader"><i class="fas fa-users"></i>Cultural Fit Keywords</h3> <h3 class="keyword-subheader"><i class="fas fa-users"></i>Cultural Fit Keywords</h3>
<div class="tag-list"> <div class="tag-list">
<!-- scoring_data.cultural_fit_keywords --> <!-- scoring_data.cultural_fit_keywords -->
{% for keyword in candidate.analysis_data.cultural_fit_keywords %} {% for keyword in application.analysis_data.cultural_fit_keywords %}
<span class="cultural-tag">{{ keyword }}</span> <span class="cultural-tag">{{ keyword }}</span>
{% endfor %} {% endfor %}
</div> </div>
@ -719,57 +719,57 @@
Analysis Analysis
</h2> </h2>
{% if candidate.analysis_data.category %} {% if application.analysis_data.category %}
<div class="analysis-metric" style="margin-bottom: 0.75rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--color-gray-100);"> <div class="analysis-metric" style="margin-bottom: 0.75rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--color-gray-100);">
<span class="metric-title">Target Role Category:</span> <span class="metric-title">Target Role Category:</span>
<!-- scoring_data.category --> <!-- scoring_data.category -->
<span class="metric-value" style="color: var(--kaauh-teal);">{{ candidate.analysis_data.category }}</span> <span class="metric-value" style="color: var(--kaauh-teal);">{{ application.analysis_data.category }}</span>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.red_flags %} {% if application.analysis_data.red_flags %}
<div class="narrative-box"> <div class="narrative-box">
<h3 class="flag-title red"><i class="fas fa-flag"></i>Red Flags</h3> <h3 class="flag-title red"><i class="fas fa-flag"></i>Red Flags</h3>
<!-- scoring_data.red_flags --> <!-- scoring_data.red_flags -->
<p class="narrative-text">{{ candidate.analysis_data.red_flags|join:". "|default:"None." }}</p> <p class="narrative-text">{{ application.analysis_data.red_flags|join:". "|default:"None." }}</p>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.strengths %} {% if application.analysis_data.strengths %}
<div class="narrative-box strength-box"> <div class="narrative-box strength-box">
<h3 class="flag-title green"><i class="fas fa-circle-check"></i>Strengths</h3> <h3 class="flag-title green"><i class="fas fa-circle-check"></i>Strengths</h3>
<!-- scoring_data.strengths --> <!-- scoring_data.strengths -->
<p class="narrative-text">{{ candidate.analysis_data.strengths }}</p> <p class="narrative-text">{{ application.analysis_data.strengths }}</p>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.weaknesses %} {% if application.analysis_data.weaknesses %}
<div class="narrative-box" style="margin-bottom: 1rem;"> <div class="narrative-box" style="margin-bottom: 1rem;">
<h3 class="flag-title red"><i class="fas fa-triangle-exclamation"></i>Weaknesses</h3> <h3 class="flag-title red"><i class="fas fa-triangle-exclamation"></i>Weaknesses</h3>
<!-- scoring_data.weaknesses --> <!-- scoring_data.weaknesses -->
<p class="narrative-text">{{ candidate.analysis_data.weaknesses }}</p> <p class="narrative-text">{{ application.analysis_data.weaknesses }}</p>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.recommendation %} {% if application.analysis_data.recommendation %}
<div class="analysis-summary"> <div class="analysis-summary">
<h3 style="font-size: 0.875rem;">Recommendation</h3> <h3 style="font-size: 0.875rem;">Recommendation</h3>
<!-- scoring_data.recommendation --> <!-- scoring_data.recommendation -->
<p style="font-size: 0.875rem;">{{ candidate.analysis_data.recommendation }}</p> <p style="font-size: 0.875rem;">{{ application.analysis_data.recommendation }}</p>
</div> </div>
{% endif %} {% endif %}
</section> </section>
<!-- Criteria Checklist Section --> <!-- Criteria Checklist Section -->
{% if candidate.analysis_data.criteria_checklist %} {% if application.analysis_data.criteria_checklist %}
<section class="card-section"> <section class="card-section">
<h2 class="section-title"> <h2 class="section-title">
<i class="fas fa-list-check"></i> <i class="fas fa-list-check"></i>
Required Criteria Check Required Criteria Check
</h2> </h2>
<div style="margin-top: 0.75rem;"> <div style="margin-top: 0.75rem;">
{% for criterion, status in candidate.analysis_data.criteria_checklist.items %} {% for criterion, status in application.analysis_data.criteria_checklist.items %}
<div class="criteria-item"> <div class="criteria-item">
<span class="text-gray-700">{{ criterion }}</span> <span class="text-gray-700">{{ criterion }}</span>
<span class="metric-value" style="font-size: 0.875rem;"> <span class="metric-value" style="font-size: 0.875rem;">
@ -790,10 +790,10 @@
<i class="fas fa-tools"></i> <i class="fas fa-tools"></i>
Skills Skills
</h2> </h2>
{% if candidate.resume_data.skills %} {% if application.resume_data.skills %}
{% for category, skills in candidate.resume_data.skills.items %} {% for category, skills in application.resume_data.skills.items %}
<div style="margin-bottom: 1rem;"> <div style="margin-bottom: 1rem;">
<!-- candidate.resume_data.skills --> <!-- application.resume_data.skills -->
<h3 class="keyword-subheader" style="color: var(--color-gray-700);"><i class="fas fa-list-alt" style="color: transparent;"></i>{{ category|cut:"_"|title }}</h3> <h3 class="keyword-subheader" style="color: var(--color-gray-700);"><i class="fas fa-list-alt" style="color: transparent;"></i>{{ category|cut:"_"|title }}</h3>
<div class="tag-list"> <div class="tag-list">
{% for skill in skills %} {% for skill in skills %}
@ -813,8 +813,8 @@
<i class="fas fa-language"></i> <i class="fas fa-language"></i>
Languages Languages
</h2> </h2>
{% if candidate.analysis_data.language_fluency %} {% if application.analysis_data.language_fluency %}
{% for language in candidate.analysis_data.language_fluency %} {% for language in application.analysis_data.language_fluency %}
<div style="margin-bottom: 0.75rem;"> <div style="margin-bottom: 0.75rem;">
<div class="analysis-metric" style="margin-bottom: 0.25rem; border-bottom: none;"> <div class="analysis-metric" style="margin-bottom: 0.25rem; border-bottom: none;">
<!-- scoring_data.language_fluency --> <!-- scoring_data.language_fluency -->
@ -842,69 +842,69 @@
Key Metrics Key Metrics
</h2> </h2>
<div style="margin-top: 0.75rem;"> <div style="margin-top: 0.75rem;">
{% if candidate.analysis_data.min_req_met_bool is not none %} {% if application.analysis_data.min_req_met_bool is not none %}
<div class="analysis-metric"> <div class="analysis-metric">
<span class="metric-label"><i class="fas fa-shield-halved"></i>Min Requirements Met:</span> <span class="metric-label"><i class="fas fa-shield-halved"></i>Min Requirements Met:</span>
<!-- scoring_data.min_req_met_bool --> <!-- scoring_data.min_req_met_bool -->
<span class="metric-value {% if candidate.analysis_data.min_req_met_bool %}text-green-check{% else %}text-red-x{% endif %}"> <span class="metric-value {% if application.analysis_data.min_req_met_bool %}text-green-check{% else %}text-red-x{% endif %}">
{% if candidate.analysis_data.min_req_met_bool %}<i class="fas fa-check-circle"></i> Yes{% else %}<i class="fas fa-times-circle"></i> No{% endif %} {% if application.analysis_data.min_req_met_bool %}<i class="fas fa-check-circle"></i> Yes{% else %}<i class="fas fa-times-circle"></i> No{% endif %}
</span> </span>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.years_of_experience is not none %} {% if application.analysis_data.years_of_experience is not none %}
<div class="analysis-metric"> <div class="analysis-metric">
<span class="metric-label"><i class="fas fa-clock"></i>Total Experience:</span> <span class="metric-label"><i class="fas fa-clock"></i>Total Experience:</span>
<!-- scoring_data.years_of_experience --> <!-- scoring_data.years_of_experience -->
<span class="metric-value">{{ candidate.analysis_data.years_of_experience|floatformat:1 }} years</span> <span class="metric-value">{{ application.analysis_data.years_of_experience|floatformat:1 }} years</span>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.most_recent_job_title %} {% if application.analysis_data.most_recent_job_title %}
<div class="analysis-metric"> <div class="analysis-metric">
<span class="metric-label"><i class="fas fa-id-badge"></i>Most Recent Title (Scoring):</span> <span class="metric-label"><i class="fas fa-id-badge"></i>Most Recent Title (Scoring):</span>
<!-- scoring_data.most_recent_job_title (explicitly added) --> <!-- scoring_data.most_recent_job_title (explicitly added) -->
<span class="metric-value max-w-50-percent" style="text-align: right;">{{ candidate.analysis_data.most_recent_job_title }}</span> <span class="metric-value max-w-50-percent" style="text-align: right;">{{ application.analysis_data.most_recent_job_title }}</span>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.seniority_level_match is not none %} {% if application.analysis_data.seniority_level_match is not none %}
<div class="analysis-metric"> <div class="analysis-metric">
<span class="metric-label"><i class="fas fa-user-tie"></i>Seniority Match:</span> <span class="metric-label"><i class="fas fa-user-tie"></i>Seniority Match:</span>
<!-- scoring_data.seniority_level_match --> <!-- scoring_data.seniority_level_match -->
<span class="metric-value">{{ candidate.analysis_data.seniority_level_match|default:0 }}/100</span> <span class="metric-value">{{ application.analysis_data.seniority_level_match|default:0 }}/100</span>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.soft_skills_score is not none %} {% if application.analysis_data.soft_skills_score is not none %}
<div class="analysis-metric"> <div class="analysis-metric">
<span class="metric-label"><i class="fas fa-handshake"></i>Soft Skills Score:</span> <span class="metric-label"><i class="fas fa-handshake"></i>Soft Skills Score:</span>
<!-- scoring_data.soft_skills_score --> <!-- scoring_data.soft_skills_score -->
<span class="metric-value">{{ candidate.analysis_data.soft_skills_score|default:0 }}/100</span> <span class="metric-value">{{ application.analysis_data.soft_skills_score|default:0 }}/100</span>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.employment_stability_score is not none %} {% if application.analysis_data.employment_stability_score is not none %}
<div class="analysis-metric"> <div class="analysis-metric">
<span class="metric-label"><i class="fas fa-anchor"></i>Stability Score:</span> <span class="metric-label"><i class="fas fa-anchor"></i>Stability Score:</span>
<!-- scoring_data.employment_stability_score --> <!-- scoring_data.employment_stability_score -->
<span class="metric-value">{{ candidate.analysis_data.employment_stability_score|default:0 }}/100</span> <span class="metric-value">{{ application.analysis_data.employment_stability_score|default:0 }}/100</span>
</div> </div>
{% endif %} {% endif %}
{% if candidate.analysis_data.experience_industry_match is not none %} {% if application.analysis_data.experience_industry_match is not none %}
<div class="analysis-metric" style="border-bottom: none; padding-bottom: 0;"> <div class="analysis-metric" style="border-bottom: none; padding-bottom: 0;">
<span class="metric-label"><i class="fas fa-industry"></i>Industry Match:</span> <span class="metric-label"><i class="fas fa-industry"></i>Industry Match:</span>
<!-- scoring_data.experience_industry_match --> <!-- scoring_data.experience_industry_match -->
<span class="metric-value">{{ candidate.analysis_data.experience_industry_match|default:0 }}/100</span> <span class="metric-value">{{ application.analysis_data.experience_industry_match|default:0 }}/100</span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if candidate.analysis_data.transferable_skills_narrative %} {% if application.analysis_data.transferable_skills_narrative %}
<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-gray-100); font-size: 0.875rem; color: var(--color-gray-500);"> <div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--color-gray-100); font-size: 0.875rem; color: var(--color-gray-500);">
<i class="fas fa-puzzle-piece" style="margin-right: 0.25rem;"></i> Transferable Skills: <i class="fas fa-puzzle-piece" style="margin-right: 0.25rem;"></i> Transferable Skills:
<!-- scoring_data.transferable_skills_narrative --> <!-- scoring_data.transferable_skills_narrative -->
{{ candidate.analysis_data.transferable_skills_narrative }} {{ application.analysis_data.transferable_skills_narrative }}
</div> </div>
{% endif %} {% endif %}
</section> </section>
@ -914,7 +914,7 @@
{% if LANGUAGE_CODE == 'ar' %} {% if LANGUAGE_CODE == 'ar' %}
{% with data_source=candidate.resume_data_ar analysis_source=candidate.analysis_data_ar %} {% with data_source=application.resume_data_ar analysis_source=application.analysis_data_ar %}
<div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto; direction: rtl; text-align: right;"> <div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto; direction: rtl; text-align: right;">
{% include 'recruitment/partials/ai_overview_breadcromb.html' %} {% include 'recruitment/partials/ai_overview_breadcromb.html' %}
@ -1263,13 +1263,13 @@
{% endwith %} {% endwith %}
{% else %} {% else %}
{% with data_source=candidate.resume_data_en analysis_source=candidate.analysis_data_en %} {% with data_source=application.resume_data_en analysis_source=application.analysis_data_en %}
<div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;"> <div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;">
{% include 'recruitment/partials/ai_overview_breadcromb.html' %} {% include 'recruitment/partials/ai_overview_breadcromb.html' %}
<header class="header-box"> <header class="header-box">
<div class="header-info"> <div class="header-info">
<h1>{{ data_source.full_name|default:"Candidate Name" }}</h1> <h1>{{ data_source.full_name|default:"Application Name" }}</h1>
<p>{{ data_source.current_title|default:"Professional Title" }}</p> <p>{{ data_source.current_title|default:"Professional Title" }}</p>
<div class="contact-details"> <div class="contact-details">
<div class="contact-item"> <div class="contact-item">

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}Document Review - {{ job.title }} - University ATS{% endblock %} {% block title %}{% blocktrans %} Document Review - {{ job.title }} - University ATS{%endblocktrans %}{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
/* KAAT-S UI Variables */ /* KAAT-S UI Variables */
@ -60,8 +60,8 @@
border-color: var(--kaauh-teal-dark); border-color: var(--kaauh-teal-dark);
} }
/* 3. Candidate Table Styling (Aligned with KAAT-S) */ /* 3. Application Table Styling (Aligned with KAAT-S) */
.candidate-table { .application-table {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
border-collapse: separate; border-collapse: separate;
@ -70,10 +70,10 @@
border-radius: 0.5rem; border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
} }
.candidate-table thead { .application-table thead {
background-color: var(--kaauh-border); background-color: var(--kaauh-border);
} }
.candidate-table th { .application-table th {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
font-weight: 600; font-weight: 600;
color: var(--kaauh-teal-dark); color: var(--kaauh-teal-dark);
@ -81,24 +81,24 @@
font-size: 0.9rem; font-size: 0.9rem;
vertical-align: middle; vertical-align: middle;
} }
.candidate-table td { .application-table td {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-bottom: 1px solid var(--kaauh-border); border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle; vertical-align: middle;
font-size: 0.9rem; font-size: 0.9rem;
} }
.candidate-table tbody tr:hover { .application-table tbody tr:hover {
background-color: #f1f3f4; background-color: #f1f3f4;
} }
.candidate-table thead th:nth-child(1) { width: 40px; } .application-table thead th:nth-child(1) { width: 40px; }
.candidate-table thead th:nth-child(4) { width: 10%; } .application-table thead th:nth-child(4) { width: 10%; }
.candidate-table thead th:nth-child(7) { width: 100px; } .application-table thead th:nth-child(7) { width: 100px; }
.candidate-name { .application-name {
font-weight: 600; font-weight: 600;
color: var(--kaauh-primary-text); color: var(--kaauh-primary-text);
} }
.candidate-details { .application-details {
font-size: 0.8rem; font-size: 0.8rem;
color: #6c757d; color: #6c757d;
} }
@ -118,7 +118,7 @@
font-weight: 700; font-weight: 700;
} }
.bg-applicant { background-color: #6c757d !important; color: white; } .bg-applicant { background-color: #6c757d !important; color: white; }
.bg-candidate { background-color: var(--kaauh-success) !important; color: white; } .bg-application { background-color: var(--kaauh-success) !important; color: white; }
/* Stage Badges */ /* Stage Badges */
.stage-badge { .stage-badge {
@ -213,7 +213,7 @@
<div class="d-flex gap-2"> <div class="d-flex gap-2">
{% comment %} <a href="{% url 'export_applications_csv' job.slug 'document_review' %}" {% comment %} <a href="{% url 'export_applications_csv' job.slug 'document_review' %}"
class="btn btn-outline-secondary" class="btn btn-outline-secondary"
title="{% trans 'Export document review candidates to CSV' %}"> title="{% trans 'Export document review applications to CSV' %}">
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %} <i class="fas fa-download me-1"></i> {% trans "Export CSV" %}
</a> {% endcomment %} </a> {% endcomment %}
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
@ -229,7 +229,7 @@
<!-- Search and Filter Controls --> <!-- Search and Filter Controls -->
<div class="filter-controls"> <div class="filter-controls">
<h4 class="h6 mb-3 fw-bold"> <h4 class="h6 mb-3 fw-bold">
<i class="fas fa-search me-1"></i> {% trans "Search Candidates" %} <i class="fas fa-search me-1"></i> {% trans "Search Applications" %}
</h4> </h4>
<form method="GET" class="mb-0"> <form method="GET" class="mb-0">
<div class="row g-3 align-items-end"> <div class="row g-3 align-items-end">
@ -251,18 +251,18 @@
</div> </div>
<h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);"> <h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);">
<i class="fas fa-users me-1"></i> {% trans "Candidates Ready for Document Review" %} <i class="fas fa-users me-1"></i> {% trans "Applications Ready for Document Review" %}
<span class="badge bg-primary-theme ms-2">{{ candidates|length }}</span> <span class="badge bg-primary-theme ms-2">{{ applications|length }}</span>
</h2> </h2>
<div class="kaauh-card p-3"> <div class="kaauh-card p-3">
{% if candidates %} {% if applications %}
<div class="bulk-action-bar p-3 bg-light border-bottom"> <div class="bulk-action-bar p-3 bg-light border-bottom">
{# Use d-flex to align the entire contents (two forms and the separator) horizontally #} {# Use d-flex to align the entire contents (two forms and the separator) horizontally #}
<div class="d-flex align-items-end gap-3"> <div class="d-flex align-items-end gap-3">
{# Form 1: Status Update #} {# Form 1: Status Update #}
<form hx-boost="true" hx-include="#candidate-form" action="{% url 'application_update_status' job.slug %}" method="post" class="d-flex align-items-end gap-2 action-group"> <form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="d-flex align-items-end gap-2 action-group">
{% csrf_token %} {% csrf_token %}
{# Select Input Group - No label needed for this one, so we just flex the select and button #} {# Select Input Group - No label needed for this one, so we just flex the select and button #}
@ -292,7 +292,7 @@
data-bs-target="#emailModal" data-bs-target="#emailModal"
hx-get="{% url 'compose_application_email' job.slug %}" hx-get="{% url 'compose_application_email' job.slug %}"
hx-target="#emailModalBody" hx-target="#emailModalBody"
hx-include="#candidate-form" hx-include="#application-form"
title="Email Participants"> title="Email Participants">
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</button> </button>
@ -300,12 +300,12 @@
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<form id="candidate-form" action="{% url 'application_update_status' job.slug %}" method="get"> <form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get">
<table class="table candidate-table align-middle"> <table class="table application-table align-middle">
<thead> <thead>
<tr> <tr>
<th scope="col" style="width: 2%;"> <th scope="col" style="width: 2%;">
{% if candidates %} {% if applications %}
<div class="form-check"> <div class="form-check">
<input type="checkbox" class="form-check-input" id="selectAllCheckbox"> <input type="checkbox" class="form-check-input" id="selectAllCheckbox">
</div> </div>
@ -329,21 +329,21 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for candidate in candidates %} {% for application in applications %}
<tr> <tr>
<td> <td>
<div class="form-check"> <div class="form-check">
<input name="candidate_ids" value="{{ candidate.id }}" type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}"> <input name="candidate_ids" value="{{ application.id }}" type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
</div> </div>
</td> </td>
<td> <td>
<div class="candidate-name"> <div class="application-name">
{{ candidate.person.first_name }} {{ candidate.person.last_name }} {{ application.person.first_name }} {{ application.person.last_name }}
</div> </div>
</td> </td>
<td><div class="candidate-details"> <td><div class="application-details">
<i class="fas fa-envelope me-1"></i> {{ candidate.person.email }}<br> <i class="fas fa-envelope me-1"></i> {{ application.person.email }}<br>
<i class="fas fa-phone me-1"></i> {{ candidate.person.phone|default:"--" }} <i class="fas fa-phone me-1"></i> {{ application.person.phone|default:"--" }}
</div></td> </div></td>
<td> <td>
<span class="stage-badge stage-Interview"> <span class="stage-badge stage-Interview">
@ -351,7 +351,7 @@
</span> </span>
</td> </td>
<td> <td>
{% with documents=candidate.documents.all %} {% with documents=application.documents.all %}
{% if documents %} {% if documents %}
<table class="table table-sm table-hover"> <table class="table table-sm table-hover">
<thead> <thead>
@ -398,7 +398,7 @@
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'application_criteria_view_htmx' candidate.pk %}" hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="View Profile"> title="View Profile">
<i class="fas fa-eye ms-1"></i> <i class="fas fa-eye ms-1"></i>
@ -412,7 +412,7 @@
{% else %} {% else %}
<div class="alert alert-info text-center" role="alert"> <div class="alert alert-info text-center" role="alert">
<i class="fas fa-info-circle me-1"></i> <i class="fas fa-info-circle me-1"></i>
{% trans "No candidates are currently ready for document review." %} {% trans "No applications are currently ready for document review." %}
</div> </div>
{% endif %} {% endif %}
</form> </form>
@ -425,7 +425,7 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content kaauh-card"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="modal-content kaauh-card"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);"> <h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
{% trans "Candidate Details / Bulk Action Form" %} {% trans "Application Details / Bulk Action Form" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}Candidate Tier Management - {{ job.title }} - ATS{% endblock %} {% block title %}{% blocktrans %}Application Tier Management - {{ job.title }} - ATS {% endblocktrans %}{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
/* KAAT-S UI Variables */ /* KAAT-S UI Variables */
@ -71,8 +71,8 @@
border-color: #00363e; border-color: #00363e;
} }
/* 3. Candidate Table Styling (Aligned with KAAT-S) */ /* 3. Application Table Styling (Aligned with KAAT-S) */
.candidate-table { .application-table {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
border-collapse: separate; border-collapse: separate;
@ -81,10 +81,10 @@
border-radius: 0.5rem; border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
} }
.candidate-table thead { .application-table thead {
background-color: var(--kaauh-border); background-color: var(--kaauh-border);
} }
.candidate-table th { .application-table th {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
font-weight: 600; font-weight: 600;
color: var(--kaauh-teal-dark); color: var(--kaauh-teal-dark);
@ -92,24 +92,24 @@
font-size: 0.9rem; font-size: 0.9rem;
vertical-align: middle; vertical-align: middle;
} }
.candidate-table td { .application-table td {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-bottom: 1px solid var(--kaauh-border); border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle; vertical-align: middle;
font-size: 0.9rem; font-size: 0.9rem;
} }
.candidate-table tbody tr:hover { .application-table tbody tr:hover {
background-color: #f1f3f4; background-color: #f1f3f4;
} }
.candidate-table thead th:nth-child(1) { width: 40px; } .application-table thead th:nth-child(1) { width: 40px; }
.candidate-table thead th:nth-child(4) { width: 10%; } .application-table thead th:nth-child(4) { width: 10%; }
.candidate-table thead th:nth-child(7) { width: 100px; } .application-table thead th:nth-child(7) { width: 100px; }
.candidate-name { .application-name {
font-weight: 600; font-weight: 600;
color: var(--kaauh-primary-text); color: var(--kaauh-primary-text);
} }
.candidate-details { .application-details {
font-size: 0.8rem; font-size: 0.8rem;
color: #6c757d; color: #6c757d;
} }
@ -129,7 +129,7 @@
font-weight: 700; font-weight: 700;
} }
.bg-applicant { background-color: #6c757d !important; color: white; } .bg-applicant { background-color: #6c757d !important; color: white; }
.bg-candidate { background-color: var(--kaauh-success) !important; color: white; } .bg-application { background-color: var(--kaauh-success) !important; color: white; }
/* Stage Badges */ /* Stage Badges */
.stage-badge { .stage-badge {
@ -174,13 +174,13 @@
{% trans "Exam Management" %} - {{ job.title }} {% trans "Exam Management" %} - {{ job.title }}
</h1> </h1>
<h2 class="h5 text-muted mb-0"> <h2 class="h5 text-muted mb-0">
{% trans "Candidates in Exam Stage:" %} <span class="fw-bold">{{ total_candidates }}</span> {% trans "Applications in Exam Stage:" %} <span class="fw-bold">{{ total_candidates }}</span>
</h2> </h2>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="{% url 'export_applications_csv' job.slug 'exam' %}" <a href="{% url 'export_applications_csv' job.slug 'exam' %}"
class="btn btn-outline-secondary" class="btn btn-outline-secondary"
title="{% trans 'Export exam candidates to CSV' %}"> title="{% trans 'Export exam applications to CSV' %}">
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %} <i class="fas fa-download me-1"></i> {% trans "Export CSV" %}
</a> </a>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
@ -194,15 +194,15 @@
</div> </div>
<h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);"> <h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);">
{% trans "Candidate List" %} {% trans "Application List" %}
<span class="badge bg-primary-theme ms-2">{{ candidates|length }} / {{ total_candidates }} Total</span> <span class="badge bg-primary-theme ms-2">{{ applications|length }} / {{ total_candidates }} Total</span>
<small class="text-muted fw-normal ms-2">({% trans "Sorted by AI Score" %})</small> <small class="text-muted fw-normal ms-2">({% trans "Sorted by AI Score" %})</small>
</h2> </h2>
<div class="kaauh-card shadow-sm p-3"> <div class="kaauh-card shadow-sm p-3">
{% if candidates %} {% if applications %}
<div class="bulk-action-bar p-3 bg-light border-bottom"> <div class="bulk-action-bar p-3 bg-light border-bottom">
<form hx-boost="true" hx-include="#candidate-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group"> <form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group">
{% csrf_token %} {% csrf_token %}
{# Using d-flex for horizontal alignment and align-items-end to align items to the bottom baseline #} {# Using d-flex for horizontal alignment and align-items-end to align items to the bottom baseline #}
@ -235,7 +235,7 @@
data-bs-target="#emailModal" data-bs-target="#emailModal"
hx-get="{% url 'compose_application_email' job.slug %}" hx-get="{% url 'compose_application_email' job.slug %}"
hx-target="#emailModalBody" hx-target="#emailModalBody"
hx-include="#candidate-form" hx-include="#application-form"
title="Email Participants"> title="Email Participants">
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</button> </button>
@ -245,13 +245,13 @@
</div> </div>
{% endif %} {% endif %}
<div class="table-responsive"> <div class="table-responsive">
<form id="candidate-form" method="post"> <form id="application-form" method="post">
{% csrf_token %} {% csrf_token %}
<table class="table candidate-table align-middle"> <table class="table application-table align-middle">
<thead> <thead>
<tr> <tr>
<th style="width: 2%;"> <th style="width: 2%;">
{% if candidates %} {% if applications %}
<div class="form-check"> <div class="form-check">
<input <input
type="checkbox" class="form-check-input" id="selectAllCheckbox"> type="checkbox" class="form-check-input" id="selectAllCheckbox">
@ -268,56 +268,56 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for candidate in candidates %} {% for application in applications %}
<tr> <tr>
<td> <td>
<div class="form-check"> <div class="form-check">
<input <input
name="candidate_ids" name="candidate_ids"
value="{{ candidate.id }}" value="{{ application.id }}"
type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}"> type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
</div> </div>
</td> </td>
<td> <td>
<div class="candidate-name"> <div class="application-name">
{{ candidate.name }} {{ application.name }}
</div> </div>
</td> </td>
<td> <td>
<div class="candidate-details"> <div class="application-details">
<i class="fas fa-envelope me-1"></i> {{ candidate.email }}<br> <i class="fas fa-envelope me-1"></i> {{ application.email }}<br>
<i class="fas fa-phone me-1"></i> {{ candidate.phone }} <i class="fas fa-phone me-1"></i> {{ application.phone }}
</div> </div>
</td> </td>
<td class="text-center"> <td class="text-center">
<span class="badge ai-score-badge">{{ candidate.match_score|default:"0" }}%</span> <span class="badge ai-score-badge">{{ application.match_score|default:"0" }}%</span>
</td> </td>
<td> <td>
{{candidate.exam_date|date:"d-m-Y h:i A"|default:"--"}} {{application.exam_date|date:"d-m-Y h:i A"|default:"--"}}
</td> </td>
<td id="exam-score-{{ candidate.pk}}"> <td id="exam-score-{{ application.pk}}">
{{candidate.exam_score|default:"--"}} {{application.exam_score|default:"--"}}
</td> </td>
<td class="text-center" id="status-result-{{ candidate.pk}}"> <td class="text-center" id="status-result-{{ application.pk}}">
{% if not candidate.exam_status %} {% if not application.exam_status %}
<button type="button" class="btn btn-warning btn-sm" <button type="button" class="btn btn-warning btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'exam' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
</button> </button>
{% else %} {% else %}
{% if candidate.exam_status %} {% if application.exam_status %}
<button type="button" class="btn btn-{% if candidate.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm" <button type="button" class="btn btn-{% if application.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'exam' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
{{ candidate.exam_status }} {{ application.exam_status }}
</button> </button>
{% else %} {% else %}
-- --
@ -329,7 +329,7 @@
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'application_criteria_view_htmx' candidate.pk %}" hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="View Profile"> title="View Profile">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
@ -339,10 +339,10 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if not candidates %} {% if not applications %}
<div class="alert alert-info text-center mt-3 mb-0" role="alert"> <div class="alert alert-info text-center mt-3 mb-0" role="alert">
<i class="fas fa-info-circle me-1"></i> <i class="fas fa-info-circle me-1"></i>
{% trans "No candidates are currently in the Exam stage for this job." %} {% trans "No applications are currently in the Exam stage for this job." %}
</div> </div>
{% endif %} {% endif %}
</form> </form>
@ -355,14 +355,14 @@
<div class="modal-content kaauh-card"> <div class="modal-content kaauh-card">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);"> <h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
{% trans "Candidate Details & Exam Update" %} {% trans "Application Details & Exam Update" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div id="candidateviewModalBody" class="modal-body"> <div id="candidateviewModalBody" class="modal-body">
<div class="text-center py-5 text-muted"> <div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br> <i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Loading candidate data..." %} {% trans "Loading application data..." %}
</div> </div>
</div> </div>
<div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);"> <div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);">

View File

@ -60,8 +60,8 @@
border-color: var(--kaauh-teal-dark); border-color: var(--kaauh-teal-dark);
} }
/* 3. Candidate Table Styling (Aligned with KAAT-S) */ /* 3. Application Table Styling (Aligned with KAAT-S) */
.candidate-table { .application-table {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
border-collapse: separate; border-collapse: separate;
@ -70,10 +70,10 @@
border-radius: 0.5rem; border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
} }
.candidate-table thead { .application-table thead {
background-color: var(--kaauh-border); background-color: var(--kaauh-border);
} }
.candidate-table th { .application-table th {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
font-weight: 600; font-weight: 600;
color: var(--kaauh-teal-dark); color: var(--kaauh-teal-dark);
@ -81,24 +81,24 @@
font-size: 0.9rem; font-size: 0.9rem;
vertical-align: middle; vertical-align: middle;
} }
.candidate-table td { .application-table td {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-bottom: 1px solid var(--kaauh-border); border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle; vertical-align: middle;
font-size: 0.9rem; font-size: 0.9rem;
} }
.candidate-table tbody tr:hover { .application-table tbody tr:hover {
background-color: #f1f3f4; background-color: #f1f3f4;
} }
.candidate-table thead th:nth-child(1) { width: 40px; } .application-table thead th:nth-child(1) { width: 40px; }
.candidate-table thead th:nth-child(4) { width: 10%; } .application-table thead th:nth-child(4) { width: 10%; }
.candidate-table thead th:nth-child(7) { width: 100px; } .application-table thead th:nth-child(7) { width: 100px; }
.candidate-name { .application-name {
font-weight: 600; font-weight: 600;
color: var(--kaauh-primary-text); color: var(--kaauh-primary-text);
} }
.candidate-details { .application-details {
font-size: 0.8rem; font-size: 0.8rem;
color: #6c757d; color: #6c757d;
} }
@ -118,7 +118,7 @@
font-weight: 700; font-weight: 700;
} }
.bg-applicant { background-color: #6c757d !important; color: white; } .bg-applicant { background-color: #6c757d !important; color: white; }
.bg-candidate { background-color: var(--kaauh-success) !important; color: white; } .bg-application { background-color: var(--kaauh-success) !important; color: white; }
/* Stage Badges */ /* Stage Badges */
.stage-badge { .stage-badge {
@ -189,22 +189,22 @@
<div> <div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-trophy me-2"></i> <i class="fas fa-trophy me-2"></i>
{% trans "Hired Candidates" %} - {{ job.title }} {% trans "Hired Applications" %} - {{ job.title }}
</h1> </h1>
<h2 class="h5 text-muted mb-0"> <h2 class="h5 text-muted mb-0">
{% trans "Successfully Hired:" %} <span class="fw-bold">{{ candidates|length }}</span> {% trans "Successfully Hired:" %} <span class="fw-bold">{{ applications|length }}</span>
</h2> </h2>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<button type="button" <button type="button"
class="btn btn-main-action" class="btn btn-main-action"
onclick="syncHiredCandidates()" onclick="syncHiredCandidates()"
title="{% trans 'Sync hired candidates to external sources' %}"> title="{% trans 'Sync hired applications to external sources' %}">
<i class="fas fa-sync me-1"></i> {% trans "Sync to Sources" %} <i class="fas fa-sync me-1"></i> {% trans "Sync to Sources" %}
</button> </button>
<a href="{% url 'export_applications_csv' job.slug 'hired' %}" <a href="{% url 'export_applications_csv' job.slug 'hired' %}"
class="btn btn-outline-secondary" class="btn btn-outline-secondary"
title="{% trans 'Export hired candidates to CSV' %}"> title="{% trans 'Export hired applications to CSV' %}">
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %} <i class="fas fa-download me-1"></i> {% trans "Export CSV" %}
</a> </a>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
@ -217,7 +217,7 @@
<div class="success-header"> <div class="success-header">
<i class="fas fa-check-circle fa-3x mb-3"></i> <i class="fas fa-check-circle fa-3x mb-3"></i>
<h3 class="mb-2">{% trans "Congratulations!" %}</h3> <h3 class="mb-2">{% trans "Congratulations!" %}</h3>
<p class="mb-0">{% trans "These candidates have successfully completed the hiring process and joined your team." %}</p> <p class="mb-0">{% trans "These applications have successfully completed the hiring process and joined your team." %}</p>
</div> </div>
<div class="applicant-tracking-timeline"> <div class="applicant-tracking-timeline">
@ -225,9 +225,9 @@
</div> </div>
<div class="kaauh-card shadow-sm p-3"> <div class="kaauh-card shadow-sm p-3">
{% if candidates %} {% if applications %}
<div class="bulk-action-bar p-3 bg-light border-bottom"> <div class="bulk-action-bar p-3 bg-light border-bottom">
<form hx-boost="true" hx-include="#candidate-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group"> <form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group">
{% csrf_token %} {% csrf_token %}
{# MODIFIED: Using d-flex for horizontal alignment and align-items-end to align everything based on the baseline of the button/select #} {# MODIFIED: Using d-flex for horizontal alignment and align-items-end to align everything based on the baseline of the button/select #}
@ -258,7 +258,7 @@
data-bs-target="#emailModal" data-bs-target="#emailModal"
hx-get="{% url 'compose_application_email' job.slug %}" hx-get="{% url 'compose_application_email' job.slug %}"
hx-target="#emailModalBody" hx-target="#emailModalBody"
hx-include="#candidate-form" hx-include="#application-form"
title="Email Participants"> title="Email Participants">
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</button> </button>
@ -269,13 +269,13 @@
{% endif %} {% endif %}
<div class="table-responsive"> <div class="table-responsive">
<form id="candidate-form" action="{% url 'application_update_status' job.slug %}" method="get"> <form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get">
{% csrf_token %} {% csrf_token %}
<table class="table candidate-table align-middle"> <table class="table application-table align-middle">
<thead> <thead>
<tr> <tr>
<th style="width: 2%"> <th style="width: 2%">
{% if candidates %} {% if applications %}
<div class="form-check"> <div class="form-check">
<input <input
type="checkbox" class="form-check-input" id="selectAllCheckbox"> type="checkbox" class="form-check-input" id="selectAllCheckbox">
@ -291,36 +291,36 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for candidate in candidates %} {% for application in applications %}
<tr> <tr>
<td> <td>
<div class="form-check"> <div class="form-check">
<input name="candidate_ids" value="{{ candidate.id }}" type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}"> <input name="candidate_ids" value="{{ application.id }}" type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
</div> </div>
</td> </td>
<td> <td>
<div class="candidate-name"> <div class="application-name">
{{ candidate.name }} {{ application.name }}
</div> </div>
</td> </td>
<td> <td>
<div class="candidate-details"> <div class="application-details">
<i class="fas fa-envelope me-1"></i> {{ candidate.email }}<br> <i class="fas fa-envelope me-1"></i> {{ application.email }}<br>
<i class="fas fa-phone me-1"></i> {{ candidate.phone }} <i class="fas fa-phone me-1"></i> {{ application.phone }}
</div> </div>
</td> </td>
<td> <td>
<div class="candidate-details"> <div class="application-details">
<strong>{{ job.title }}</strong><br> <strong>{{ job.title }}</strong><br>
<small class="text-muted">{{ job.department }}</small> <small class="text-muted">{{ job.department }}</small>
</div> </div>
</td> </td>
<td class="text-center"> <td class="text-center">
<div class="hired-date"> <div class="hired-date">
{% if candidate.offer_date %} {% if application.offer_date %}
<i class="fas fa-calendar me-1"></i> <i class="fas fa-calendar me-1"></i>
{{ candidate.offer_date|date:"M d, Y" }} {{ application.offer_date|date:"M d, Y" }}
{% else %} {% else %}
<span class="text-muted">--</span> <span class="text-muted">--</span>
{% endif %} {% endif %}
@ -337,12 +337,12 @@
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'application_criteria_view_htmx' candidate.pk %}" hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="View Profile"> title="View Profile">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</button> </button>
<a href="{% url 'application_resume_template' candidate.slug %}" <a href="{% url 'application_resume_template' application.slug %}"
class="btn btn-outline-primary btn-sm" class="btn btn-outline-primary btn-sm"
title="View Resume Template"> title="View Resume Template">
<i class="fas fa-file-alt"></i> <i class="fas fa-file-alt"></i>
@ -356,10 +356,10 @@
</tbody> </tbody>
</table> </table>
</form> </form>
{% if not candidates %} {% if not applications %}
<div class="alert alert-info text-center mt-3 mb-0" role="alert"> <div class="alert alert-info text-center mt-3 mb-0" role="alert">
<i class="fas fa-info-circle me-1"></i> <i class="fas fa-info-circle me-1"></i>
{% trans "No candidates have been hired for this position yet." %} {% trans "No applications have been hired for this position yet." %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -372,7 +372,7 @@
<div class="modal-content kaauh-card"> <div class="modal-content kaauh-card">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);"> <h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
{% trans "Hired Candidate Details" %} {% trans "Hired Application Details" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
@ -399,7 +399,7 @@
<div id="syncResultsModalBody" class="modal-body"> <div id="syncResultsModalBody" class="modal-body">
<div class="text-center py-5 text-muted"> <div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br> <i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Syncing candidates..." %} {% trans "Syncing applications..." %}
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@ -491,8 +491,8 @@
<div class="spinner-border text-primary mb-3" role="status"> <div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
<h5>{% trans "Syncing hired candidates..." %}</h5> <h5>{% trans "Syncing hired applications..." %}</h5>
<p class="text-muted">{% trans "Please wait while we sync candidates to external sources." %}</p> <p class="text-muted">{% trans "Please wait while we sync applications to external sources." %}</p>
</div> </div>
`; `;
@ -553,7 +553,7 @@
<strong>{% trans "Failed:" %}</strong> <span class="text-danger">${results.failed_syncs}</span> <strong>{% trans "Failed:" %}</strong> <span class="text-danger">${results.failed_syncs}</span>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<strong>{% trans "Candidates Synced:" %}</strong> ${results.total_candidates} <strong>{% trans "Applications Synced:" %}</strong> ${results.total_candidates}
</div> </div>
</div> </div>
</div> </div>
@ -579,7 +579,7 @@
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<small class="text-muted">{% trans "Candidates Processed:" %}</small> <small class="text-muted">{% trans "Applications Processed:" %}</small>
<div class="fw-bold">${source.candidates_processed}</div> <div class="fw-bold">${source.candidates_processed}</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}- {{ job.title }} - ATS{% endblock %} {% block title %}{% blocktrans %}Application Tier Management - {{ job.title }} - ATS {% endblocktrans %}{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
/* KAAT-S UI Variables */ /* KAAT-S UI Variables */
@ -71,8 +71,8 @@
border-color: #00363e; border-color: #00363e;
} }
/* 3. Candidate Table Styling (Aligned with KAAT-S) */ /* 3. Application Table Styling (Aligned with KAAT-S) */
.candidate-table { .application-table {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
border-collapse: separate; border-collapse: separate;
@ -81,10 +81,10 @@
border-radius: 0.5rem; border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
} }
.candidate-table thead { .application-table thead {
background-color: var(--kaauh-border); background-color: var(--kaauh-border);
} }
.candidate-table th { .application-table th {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
font-weight: 600; font-weight: 600;
color: var(--kaauh-teal-dark); color: var(--kaauh-teal-dark);
@ -92,24 +92,24 @@
font-size: 0.9rem; font-size: 0.9rem;
vertical-align: middle; vertical-align: middle;
} }
.candidate-table td { .application-table td {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-bottom: 1px solid var(--kaauh-border); border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle; vertical-align: middle;
font-size: 0.9rem; font-size: 0.9rem;
} }
.candidate-table tbody tr:hover { .application-table tbody tr:hover {
background-color: #f1f3f4; background-color: #f1f3f4;
} }
.candidate-table thead th:nth-child(1) { width: 40px; } .application-table thead th:nth-child(1) { width: 40px; }
.candidate-table thead th:nth-child(4) { width: 10%; } .application-table thead th:nth-child(4) { width: 10%; }
.candidate-table thead th:nth-child(7) { width: 100px; } .application-table thead th:nth-child(7) { width: 100px; }
.candidate-name { .application-name {
font-weight: 600; font-weight: 600;
color: var(--kaauh-primary-text); color: var(--kaauh-primary-text);
} }
.candidate-details { .application-details {
font-size: 0.8rem; font-size: 0.8rem;
color: #6c757d; color: #6c757d;
} }
@ -129,7 +129,7 @@
font-weight: 700; font-weight: 700;
} }
.bg-applicant { background-color: #6c757d !important; color: white; } .bg-applicant { background-color: #6c757d !important; color: white; }
.bg-candidate { background-color: var(--kaauh-success) !important; color: white; } .bg-application { background-color: var(--kaauh-success) !important; color: white; }
/* Stage Badges */ /* Stage Badges */
.stage-badge { .stage-badge {
@ -177,13 +177,13 @@
{% trans "Interview Management" %} - {{ job.title }} {% trans "Interview Management" %} - {{ job.title }}
</h1> </h1>
<h2 class="h5 text-muted mb-0"> <h2 class="h5 text-muted mb-0">
{% trans "Candidates in Interview Stage:" %} <span class="fw-bold">{{ candidates|length }}</span> {% trans "Applications in Interview Stage:" %} <span class="fw-bold">{{ applications|length }}</span>
</h2> </h2>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="{% url 'export_applications_csv' job.slug 'interview' %}" <a href="{% url 'export_applications_csv' job.slug 'interview' %}"
class="btn btn-outline-secondary" class="btn btn-outline-secondary"
title="{% trans 'Export interview candidates to CSV' %}"> title="{% trans 'Export interview applications to CSV' %}">
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %} <i class="fas fa-download me-1"></i> {% trans "Export CSV" %}
</a> </a>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
@ -196,13 +196,13 @@
</div> </div>
<div class="kaauh-card shadow-sm p-3"> <div class="kaauh-card shadow-sm p-3">
{% if candidates %} {% if applications %}
<div class="bulk-action-bar p-3 bg-light border-bottom"> <div class="bulk-action-bar p-3 bg-light border-bottom">
{# Use d-flex to align the entire contents (two forms and the separator) horizontally #} {# Use d-flex to align the entire contents (two forms and the separator) horizontally #}
<div class="d-flex align-items-end gap-3"> <div class="d-flex align-items-end gap-3">
{# Form 1: Status Update #} {# Form 1: Status Update #}
<form hx-boost="true" hx-include="#candidate-form" action="{% url 'application_update_status' job.slug %}" method="post" class="d-flex align-items-end gap-2 action-group"> <form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="d-flex align-items-end gap-2 action-group">
{% csrf_token %} {% csrf_token %}
{# Select Input Group - No label needed for this one, so we just flex the select and button #} {# Select Input Group - No label needed for this one, so we just flex the select and button #}
@ -227,7 +227,7 @@
<div class="vr" style="height: 28px;"></div> <div class="vr" style="height: 28px;"></div>
{# Form 2: Schedule Interviews #} {# Form 2: Schedule Interviews #}
<form hx-boost="true" hx-include="#candidate-form" action="{% url 'schedule_interviews' job.slug %}" method="get" class="action-group"> <form hx-boost="true" hx-include="#application-form" action="{% url 'schedule_interviews' job.slug %}" method="get" class="action-group">
<button type="submit" class="btn btn-main-action btn-sm"> <button type="submit" class="btn btn-main-action btn-sm">
<i class="fas fa-calendar-plus me-1"></i> {% trans "Schedule Interviews" %} <i class="fas fa-calendar-plus me-1"></i> {% trans "Schedule Interviews" %}
</button> </button>
@ -241,7 +241,7 @@
data-bs-target="#emailModal" data-bs-target="#emailModal"
hx-get="{% url 'compose_application_email' job.slug %}" hx-get="{% url 'compose_application_email' job.slug %}"
hx-target="#emailModalBody" hx-target="#emailModalBody"
hx-include="#candidate-form" hx-include="#application-form"
title="Email Participants"> title="Email Participants">
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</button> </button>
@ -251,13 +251,13 @@
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<form id="candidate-form" action="{% url 'application_update_status' job.slug %}" method="get"> <form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get">
{% csrf_token %} {% csrf_token %}
<table class="table candidate-table align-middle"> <table class="table application-table align-middle">
<thead> <thead>
<tr> <tr>
<th style="width: 2%"> <th style="width: 2%">
{% if candidates %} {% if applications %}
<div class="form-check"> <div class="form-check">
<input <input
type="checkbox" class="form-check-input" id="selectAllCheckbox"> type="checkbox" class="form-check-input" id="selectAllCheckbox">
@ -276,11 +276,11 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for candidate in candidates %} {% for application in applications %}
<tr> <tr>
<td> <td>
<div class="form-check"> <div class="form-check">
<input name="candidate_ids" value="{{ candidate.id }}" type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}"> <input name="candidate_ids" value="{{ application.id }}" type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
</div> </div>
</td> </td>
@ -288,35 +288,35 @@
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'application_criteria_view_htmx' candidate.pk %}" hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="View Profile"> title="View Profile">
{{ candidate.name }} {{ application.name }}
</button> </button>
</td> </td>
<td> <td>
<div class="candidate-details"> <div class="application-details">
<i class="fas fa-envelope me-1"></i> {{ candidate.email }}<br> <i class="fas fa-envelope me-1"></i> {{ application.email }}<br>
<i class="fas fa-phone me-1"></i> {{ candidate.phone }} <i class="fas fa-phone me-1"></i> {{ application.phone }}
</div> </div>
</td> </td>
<td class="candidate-details text-muted"> <td class="application-details text-muted">
{% if candidate.get_latest_meeting %} {% if application.get_latest_meeting %}
{{ candidate.get_latest_meeting }} {{ application.get_latest_meeting }}
{% else %} {% else %}
-- --
{% endif %} {% endif %}
</td> </td>
<td class="candidate-details text-muted"><div class="d-block"> <td class="application-details text-muted"><div class="d-block">
{% if candidate.get_latest_meeting.duration %} {% if application.get_latest_meeting.duration %}
{{ candidate.get_latest_meeting.duration }} {% trans _("Minutes") %} {{ application.get_latest_meeting.duration }} {% trans _("Minutes") %}
{% else %} {% else %}
-- --
{% endif %} {% endif %}
</div></td> </div></td>
<td class="candidate-details text-muted"> <td class="application-details text-muted">
{% with latest_meeting=candidate.get_latest_meeting %} {% with latest_meeting=application.get_latest_meeting %}
{% if latest_meeting %} {% if latest_meeting %}
{{ latest_meeting.start_time|date:"d-m-Y h:i A" }} {{ latest_meeting.start_time|date:"d-m-Y h:i A" }}
{% else %} {% else %}
@ -325,7 +325,7 @@
{% endwith %} {% endwith %}
</td> </td>
<td> <td>
{% with latest_meeting=candidate.get_latest_meeting %} {% with latest_meeting=application.get_latest_meeting %}
{% if latest_meeting and latest_meeting.details_url %} {% if latest_meeting and latest_meeting.details_url %}
<a href="{{ latest_meeting.details_url }}" target="_blank" class="btn btn-sm bg-primary-theme text-white" title="Join Interview" <a href="{{ latest_meeting.details_url }}" target="_blank" class="btn btn-sm bg-primary-theme text-white" title="Join Interview"
{% if latest_meeting.status == 'ended' %}disabled{% endif %}> {% if latest_meeting.status == 'ended' %}disabled{% endif %}>
@ -339,7 +339,7 @@
</td> </td>
<td> <td>
{{ latest_meeting.status }} {{ latest_meeting.status }}
{% with latest_meeting=candidate.get_latest_meeting %} {% with latest_meeting=application.get_latest_meeting %}
{% if latest_meeting %} {% if latest_meeting %}
<span class="badge {% if latest_meeting.status == 'waiting' %}bg-warning{% elif latest_meeting.status == 'started' %}bg-success{% elif latest_meeting.status == 'ended' %}bg-danger{% endif %}"> <span class="badge {% if latest_meeting.status == 'waiting' %}bg-warning{% elif latest_meeting.status == 'started' %}bg-success{% elif latest_meeting.status == 'ended' %}bg-danger{% endif %}">
{% if latest_meeting.status == 'started' %} {% if latest_meeting.status == 'started' %}
@ -352,25 +352,25 @@
{% endif %} {% endif %}
{% endwith %} {% endwith %}
</td> </td>
<td class="text-center" id="interview-result-{{ candidate.pk }}"> <td class="text-center" id="interview-result-{{ application.pk }}">
{% if not candidate.interview_status %} {% if not application.interview_status %}
<button type="button" class="btn btn-warning btn-sm" <button type="button" class="btn btn-warning btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'interview' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
</button> </button>
{% else %} {% else %}
{% if candidate.interview_status %} {% if application.interview_status %}
<button type="button" class="btn btn-{% if candidate.interview_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm" <button type="button" class="btn btn-{% if application.interview_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'interview' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
{{ candidate.interview_status }} {{ application.interview_status }}
</button> </button>
{% else %} {% else %}
-- --
@ -379,13 +379,13 @@
</td> </td>
<td> <td>
{% if candidate.get_latest_meeting %} {% if application.get_latest_meeting %}
{% if candidate.get_latest_meeting.location_type == 'Remote'%} {% if application.get_latest_meeting.location_type == 'Remote'%}
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'reschedule_meeting_for_application' job.slug candidate.pk candidate.get_latest_meeting.pk %}" hx-get="{% url 'reschedule_meeting_for_application' job.slug application.pk application.get_latest_meeting.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Reschedule"> title="Reschedule">
<i class="fas fa-redo-alt"></i> <i class="fas fa-redo-alt"></i>
@ -394,7 +394,7 @@
<button type="button" class="btn btn-outline-danger btn-sm" <button type="button" class="btn btn-outline-danger btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'schedule_meeting_for_application' job.slug candidate.pk candidate.get_latest_meeting.pk %}" hx-get="{% url 'schedule_meeting_for_application' job.slug application.pk application.get_latest_meeting.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Delete Meeting"> title="Delete Meeting">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
@ -404,7 +404,7 @@
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'reschedule_onsite_meeting' job.slug candidate.pk candidate.get_latest_meeting.pk %}" hx-get="{% url 'reschedule_onsite_meeting' job.slug application.pk application.get_latest_meeting.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Reschedule"> title="Reschedule">
<i class="fas fa-redo-alt"></i> <i class="fas fa-redo-alt"></i>
@ -413,7 +413,7 @@
<button type="button" class="btn btn-outline-danger btn-sm" <button type="button" class="btn btn-outline-danger btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'delete_onsite_meeting_for_application' job.slug candidate.pk candidate.get_latest_meeting.pk %}" hx-get="{% url 'delete_onsite_meeting_for_application' job.slug application.pk application.get_latest_meeting.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Delete Meeting"> title="Delete Meeting">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
@ -425,7 +425,7 @@
<button type="button" class="btn btn-main-action btn-sm" <button type="button" class="btn btn-main-action btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'schedule_meeting_for_application' job.slug candidate.pk %}" hx-get="{% url 'schedule_meeting_for_application' job.slug application.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
data-modal-title="{% trans 'Schedule Interview' %}" data-modal-title="{% trans 'Schedule Interview' %}"
title="Schedule Interview"> title="Schedule Interview">
@ -435,7 +435,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
{# UPDATED: Points to the specific Onsite scheduling URL #} {# UPDATED: Points to the specific Onsite scheduling URL #}
hx-get="{% url 'schedule_onsite_meeting_for_application' job.slug candidate.pk %}" hx-get="{% url 'schedule_onsite_meeting_for_application' job.slug application.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
data-modal-title="{% trans 'Schedule Onsite Interview' %}" data-modal-title="{% trans 'Schedule Onsite Interview' %}"
title="Schedule Onsite Interview"> title="Schedule Onsite Interview">
@ -448,10 +448,10 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if not candidates %} {% if not applications %}
<div class="alert alert-info text-center mt-3 mb-0" role="alert"> <div class="alert alert-info text-center mt-3 mb-0" role="alert">
<i class="fas fa-info-circle me-1"></i> <i class="fas fa-info-circle me-1"></i>
{% trans "No candidates are currently in the Interview stage for this job." %} {% trans "No applications are currently in the Interview stage for this job." %}
</div> </div>
{% endif %} {% endif %}
</form> </form>
@ -463,7 +463,7 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content kaauh-card"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="modal-content kaauh-card"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);"> <h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
{% trans "Candidate Details / Bulk Action Form" %} {% trans "Application Details / Bulk Action Form" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
@ -598,7 +598,7 @@
</div> </div>
`; `;
// Reset the modal title to its default state for next use // Reset the modal title to its default state for next use
const defaultTitle = "{% trans "Candidate Details / Bulk Action Form" %}"; const defaultTitle = "{% trans "Application Details / Bulk Action Form" %}";
candidateviewModalLabel.textContent = defaultTitle; candidateviewModalLabel.textContent = defaultTitle;
} }
}); });

View File

@ -1,7 +1,6 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% blocktrans %}Application Tier Management - {{ job.title }} - ATS {% endblocktrans %}{% endblock %}
{% block title %}- {{ job.title }} - ATS{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
/* KAAT-S UI Variables */ /* KAAT-S UI Variables */
@ -71,8 +70,8 @@
border-color: #00363e; border-color: #00363e;
} }
/* 3. Candidate Table Styling (Aligned with KAAT-S) */ /* 3. Application Table Styling (Aligned with KAAT-S) */
.candidate-table { .application-table {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
border-collapse: separate; border-collapse: separate;
@ -81,10 +80,10 @@
border-radius: 0.5rem; border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
} }
.candidate-table thead { .application-table thead {
background-color: var(--kaauh-border); background-color: var(--kaauh-border);
} }
.candidate-table th { .application-table th {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
font-weight: 600; font-weight: 600;
color: var(--kaauh-teal-dark); color: var(--kaauh-teal-dark);
@ -92,24 +91,24 @@
font-size: 0.9rem; font-size: 0.9rem;
vertical-align: middle; vertical-align: middle;
} }
.candidate-table td { .application-table td {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-bottom: 1px solid var(--kaauh-border); border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle; vertical-align: middle;
font-size: 0.9rem; font-size: 0.9rem;
} }
.candidate-table tbody tr:hover { .application-table tbody tr:hover {
background-color: #f1f3f4; background-color: #f1f3f4;
} }
.candidate-table thead th:nth-child(1) { width: 40px; } .application-table thead th:nth-child(1) { width: 40px; }
.candidate-table thead th:nth-child(4) { width: 10%; } .application-table thead th:nth-child(4) { width: 10%; }
.candidate-table thead th:nth-child(7) { width: 100px; } .application-table thead th:nth-child(7) { width: 100px; }
.candidate-name { .application-name {
font-weight: 600; font-weight: 600;
color: var(--kaauh-primary-text); color: var(--kaauh-primary-text);
} }
.candidate-details { .application-details {
font-size: 0.8rem; font-size: 0.8rem;
color: #6c757d; color: #6c757d;
} }
@ -129,7 +128,7 @@
font-weight: 700; font-weight: 700;
} }
.bg-applicant { background-color: #6c757d !important; color: white; } .bg-applicant { background-color: #6c757d !important; color: white; }
.bg-candidate { background-color: var(--kaauh-success) !important; color: white; } .bg-application { background-color: var(--kaauh-success) !important; color: white; }
/* Stage Badges */ /* Stage Badges */
.stage-badge { .stage-badge {
@ -176,13 +175,13 @@
{% trans "Offer Management" %} - {{ job.title }} {% trans "Offer Management" %} - {{ job.title }}
</h1> </h1>
<h2 class="h5 text-muted mb-0"> <h2 class="h5 text-muted mb-0">
{% trans "Candidates in Offer Stage:" %} <span class="fw-bold">{{ candidates|length }}</span> {% trans "Applications in Offer Stage:" %} <span class="fw-bold">{{ applications|length }}</span>
</h2> </h2>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="{% url 'export_applications_csv' job.slug 'offer' %}" <a href="{% url 'export_applications_csv' job.slug 'offer' %}"
class="btn btn-outline-secondary" class="btn btn-outline-secondary"
title="{% trans 'Export offer candidates to CSV' %}"> title="{% trans 'Export offer applications to CSV' %}">
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %} <i class="fas fa-download me-1"></i> {% trans "Export CSV" %}
</a> </a>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
@ -195,13 +194,13 @@
</div> </div>
<div class="kaauh-card shadow-sm p-3"> <div class="kaauh-card shadow-sm p-3">
{% if candidates %} {% if applications %}
<div class="bulk-action-bar p-3 bg-light border-bottom"> <div class="bulk-action-bar p-3 bg-light border-bottom">
{# Use d-flex and align-items-end on the container to align the form and the separator #} {# Use d-flex and align-items-end on the container to align the form and the separator #}
<div class="d-flex align-items-end gap-3"> <div class="d-flex align-items-end gap-3">
{# Form: Hired/Rejected Status Update #} {# Form: Hired/Rejected Status Update #}
<form hx-boost="true" hx-include="#candidate-form" action="{% url 'application_update_status' job.slug %}" method="post" class="d-flex align-items-end gap-2 action-group"> <form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="d-flex align-items-end gap-2 action-group">
{% csrf_token %} {% csrf_token %}
{# Select element #} {# Select element #}
@ -238,7 +237,7 @@
data-bs-target="#emailModal" data-bs-target="#emailModal"
hx-get="{% url 'compose_application_email' job.slug %}" hx-get="{% url 'compose_application_email' job.slug %}"
hx-target="#emailModalBody" hx-target="#emailModalBody"
hx-include="#candidate-form" hx-include="#application-form"
title="Email Participants"> title="Email Participants">
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</button> </button>
@ -247,13 +246,13 @@
</div> </div>
{% endif %} {% endif %}
<div class="table-responsive"> <div class="table-responsive">
<form id="candidate-form" action="{% url 'application_update_status' job.slug %}" method="get"> <form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get">
{% csrf_token %} {% csrf_token %}
<table class="table candidate-table align-middle"> <table class="table application-table align-middle">
<thead> <thead>
<tr> <tr>
<th style="width: 2%"> <th style="width: 2%">
{% if candidates %} {% if applications %}
<div class="form-check"> <div class="form-check">
<input <input
type="checkbox" class="form-check-input" id="selectAllCheckbox"> type="checkbox" class="form-check-input" id="selectAllCheckbox">
@ -270,43 +269,43 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for candidate in candidates %} {% for application in applications %}
<tr> <tr>
<td> <td>
<div class="form-check"> <div class="form-check">
<input name="candidate_ids" value="{{ candidate.id }}" type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}"> <input name="candidate_ids" value="{{ application.id }}" type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
</div> </div>
</td> </td>
<td> <td>
<div class="candidate-name"> <div class="application-name">
{{ candidate.name }} {{ application.name }}
</div> </div>
</td> </td>
<td> <td>
<div class="candidate-details"> <div class="application-details">
<i class="fas fa-envelope me-1"></i> {{ candidate.email }}<br> <i class="fas fa-envelope me-1"></i> {{ application.email }}<br>
<i class="fas fa-phone me-1"></i> {{ candidate.phone }} <i class="fas fa-phone me-1"></i> {{ application.phone }}
</div> </div>
</td> </td>
<td class="text-center" id="status-result-{{ candidate.pk}}"> <td class="text-center" id="status-result-{{ application.pk}}">
{% if not candidate.offer_status %} {% if not application.offer_status %}
<button type="button" class="btn btn-warning btn-sm" <button type="button" class="btn btn-warning btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'offer' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'offer' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
</button> </button>
{% else %} {% else %}
{% if candidate.offer_status %} {% if application.offer_status %}
<button type="button" class="btn btn-{% if candidate.offer_status == 'Accepted' %}success{% else %}danger{% endif %} btn-sm" <button type="button" class="btn btn-{% if application.offer_status == 'Accepted' %}success{% else %}danger{% endif %} btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'offer' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'offer' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
{{ candidate.offer_status }} {{ application.offer_status }}
</button> </button>
{% else %} {% else %}
-- --
@ -314,7 +313,7 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% with documents=candidate.documents.all %} {% with documents=application.documents.all %}
{% if documents %} {% if documents %}
<table class="table table-sm table-hover"> <table class="table table-sm table-hover">
<thead> <thead>
@ -361,7 +360,7 @@
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'application_criteria_view_htmx' candidate.pk %}" hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="View Profile"> title="View Profile">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
@ -371,10 +370,10 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if not candidates %} {% if not applications %}
<div class="alert alert-info text-center mt-3 mb-0" role="alert"> <div class="alert alert-info text-center mt-3 mb-0" role="alert">
<i class="fas fa-info-circle me-1"></i> <i class="fas fa-info-circle me-1"></i>
{% trans "No candidates are currently in the Offer stage for this job." %} {% trans "No applications are currently in the Offer stage for this job." %}
</div> </div>
{% endif %} {% endif %}
</form> </form>
@ -387,7 +386,7 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content kaauh-card"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="modal-content kaauh-card"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);"> <h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
{% trans "Candidate Details / Bulk Action Form" %} {% trans "Application Details / Bulk Action Form" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}Candidate Management - {{ job.title }} - University ATS{% endblock %} {% block title %}Application Management - {{ job.title }} - University ATS{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
/* KAAT-S UI Variables */ /* KAAT-S UI Variables */
@ -71,8 +71,8 @@
border-color: #00363e; border-color: #00363e;
} }
/* 3. Candidate Table Styling (Aligned with KAAT-S) */ /* 3. Application Table Styling (Aligned with KAAT-S) */
.candidate-table { .application-table {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
border-collapse: separate; border-collapse: separate;
@ -81,10 +81,10 @@
border-radius: 0.5rem; border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
} }
.candidate-table thead { .application-table thead {
background-color: var(--kaauh-border); background-color: var(--kaauh-border);
} }
.candidate-table th { .application-table th {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
font-weight: 600; font-weight: 600;
color: var(--kaauh-teal-dark); color: var(--kaauh-teal-dark);
@ -92,24 +92,24 @@
font-size: 0.9rem; font-size: 0.9rem;
vertical-align: middle; vertical-align: middle;
} }
.candidate-table td { .application-table td {
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-bottom: 1px solid var(--kaauh-border); border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle; vertical-align: middle;
font-size: 0.9rem; font-size: 0.9rem;
} }
.candidate-table tbody tr:hover { .application-table tbody tr:hover {
background-color: #f1f3f4; background-color: #f1f3f4;
} }
.candidate-table thead th:nth-child(1) { width: 40px; } .application-table thead th:nth-child(1) { width: 40px; }
.candidate-table thead th:nth-child(4) { width: 10%; } .application-table thead th:nth-child(4) { width: 10%; }
.candidate-table thead th:nth-child(7) { width: 100px; } .application-table thead th:nth-child(7) { width: 100px; }
.candidate-name { .application-name {
font-weight: 600; font-weight: 600;
color: var(--kaauh-primary-text); color: var(--kaauh-primary-text);
} }
.candidate-details { .application-details {
font-size: 0.8rem; font-size: 0.8rem;
color: #6c757d; color: #6c757d;
} }
@ -129,7 +129,7 @@
font-weight: 700; font-weight: 700;
} }
.bg-applicant { background-color: #6c757d !important; color: white; } .bg-applicant { background-color: #6c757d !important; color: white; }
.bg-candidate { background-color: var(--kaauh-success) !important; color: white; } .bg-application { background-color: var(--kaauh-success) !important; color: white; }
/* Stage Badges */ /* Stage Badges */
.stage-badge { .stage-badge {
@ -216,7 +216,7 @@
<div> <div>
<h1 class="h3 mb-1 page-header"> <h1 class="h3 mb-1 page-header">
<i class="fas fa-layer-group me-2"></i> <i class="fas fa-layer-group me-2"></i>
{% trans "Applicant Screening" %} {% trans "Application Screening" %}
</h1> </h1>
<h2 class="h5 text-muted mb-0"> <h2 class="h5 text-muted mb-0">
{% trans "Job:" %} {{ job.title }} {% trans "Job:" %} {{ job.title }}
@ -226,7 +226,7 @@
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="{% url 'export_applications_csv' job.slug 'screening' %}" <a href="{% url 'export_applications_csv' job.slug 'screening' %}"
class="btn btn-outline-secondary" class="btn btn-outline-secondary"
title="{% trans 'Export screening candidates to CSV' %}"> title="{% trans 'Export screening applications to CSV' %}">
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %} <i class="fas fa-download me-1"></i> {% trans "Export CSV" %}
</a> </a>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
@ -241,7 +241,7 @@
<div class="filter-controls"> <div class="filter-controls">
<h4 class="h6 mb-3 fw-bold"> <h4 class="h6 mb-3 fw-bold">
<i class="fas fa-sort-numeric-up me-1"></i> {% trans "AI Scoring & Top Candidate Filter" %} <i class="fas fa-sort-numeric-up me-1"></i> {% trans "AI Scoring & Top Application Filter" %}
</h4> </h4>
<form method="GET" class="mb-0"> <form method="GET" class="mb-0">
@ -296,10 +296,10 @@
<div class="col-auto"> <div class="col-auto">
<label for="tier1_count" class="form-label small text-muted mb-1"> <label for="tier1_count" class="form-label small text-muted mb-1">
{% trans "Top N Candidates" %} {% trans "Top N applications" %}
</label> </label>
<input type="number" name="tier1_count" id="tier1_count" class="form-control form-control-sm" <input type="number" name="tier1_count" id="tier1_count" class="form-control form-control-sm"
value="{{ tier1_count }}" min="1" max="{{ total_candidates }}" style="width: 120px;"> value="{{ tier1_count }}" min="1" max="{{ total_applications }}" style="width: 120px;">
</div> </div>
<div class="col-auto"> <div class="col-auto">
@ -312,14 +312,14 @@
</div> </div>
<h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);"> <h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);">
<i class="fas fa-users me-1"></i> {% trans "Candidate List" %} <i class="fas fa-users me-1"></i> {% trans "Application List" %}
<span class="badge bg-primary-theme ms-2">{{ candidates|length }} / {{ total_candidates }} Total</span> <span class="badge bg-primary-theme ms-2">{{ applications|length }} / {{ total_applications }} {% trans "Total" %}</span>
</h2> </h2>
<div class="kaauh-card p-3"> <div class="kaauh-card p-3">
{% if candidates %} {% if applications %}
<div class="bulk-action-bar p-3 bg-light border-bottom"> <div class="bulk-action-bar p-3 bg-light border-bottom">
<form hx-boost="true" hx-include="#candidate-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group"> <form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group">
{% csrf_token %} {% csrf_token %}
{# MODIFIED: Using d-flex for horizontal alignment and align-items-end to align everything based on the baseline of the button/select #} {# MODIFIED: Using d-flex for horizontal alignment and align-items-end to align everything based on the baseline of the button/select #}
@ -350,7 +350,7 @@
data-bs-target="#emailModal" data-bs-target="#emailModal"
hx-get="{% url 'compose_application_email' job.slug %}" hx-get="{% url 'compose_application_email' job.slug %}"
hx-target="#emailModalBody" hx-target="#emailModalBody"
hx-include="#candidate-form" hx-include="#application-form"
title="Email Participants"> title="Email Participants">
<i class="fas fa-envelope"></i> <i class="fas fa-envelope"></i>
</button> </button>
@ -361,13 +361,13 @@
{% endif %} {% endif %}
<div class="table-responsive"> <div class="table-responsive">
<form id="candidate-form" action="{% url 'application_update_status' job.slug %}" method="post"> <form id="application-form" action="{% url 'application_update_status' job.slug %}" method="post">
{% csrf_token %} {% csrf_token %}
<table class="table candidate-table align-middle"> <table class="table application-table align-middle">
<thead> <thead>
<tr> <tr>
<th scope="col" style="width: 2%;"> <th scope="col" style="width: 2%;">
{% if candidates %} {% if applications %}
<div class="form-check"> <div class="form-check">
<input <input
type="checkbox" class="form-check-input" id="selectAllCheckbox"> type="checkbox" class="form-check-input" id="selectAllCheckbox">
@ -401,33 +401,33 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for candidate in candidates %} {% for application in applications %}
<tr> <tr>
<td> <td>
<div class="form-check"> <div class="form-check">
<input <input
name="candidate_ids" name="candidate_ids"
value="{{ candidate.id }}" value="{{ application.id }}"
type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}"> type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
</div> </div>
</td> </td>
<td> <td>
<a href="#" class="candidate-name text-primary-theme text-decoration-none"> <a href="#" class="application-name text-primary-theme text-decoration-none">
{{ candidate.name }} {{ application.name }}
</a> </a>
</td> </td>
<td> <td>
<div class="candidate-details"> <div class="application-details">
<i class="fas fa-envelope me-1"></i> {{ candidate.email }}<br> <i class="fas fa-envelope me-1"></i> {{ application.email }}<br>
<i class="fas fa-phone me-1"></i> {{ candidate.phone }} <i class="fas fa-phone me-1"></i> {{ application.phone }}
</div> </div>
</td> </td>
<td class="text-center">{{candidate.person.gpa|default:"0"}}</td> <td class="text-center">{{application.person.gpa|default:"0"}}</td>
<td class="text-center"> <td class="text-center">
{% if candidate.is_resume_parsed %} {% if application.is_resume_parsed %}
{% if candidate.match_score %} {% if application.match_score %}
<span class="badge ai-score-badge"> <span class="badge ai-score-badge">
{{ candidate.match_score|default:"0" }}% {{ application.match_score|default:"0" }}%
</span> </span>
{% endif %} {% endif %}
{% else %} {% else %}
@ -442,23 +442,23 @@
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="text-center">
{% if candidate.screening_stage_rating %} {% if application.screening_stage_rating %}
<span class="badge ai-score-badge d-inline-block text-wrap"> <span class="badge ai-score-badge d-inline-block text-wrap">
{{ candidate.screening_stage_rating|default:"--" }} {{ application.screening_stage_rating|default:"--" }}
</span> </span>
{% endif %} {% endif %}
</td> </td>
<td class="text-center"> <td class="text-center">
{% if candidate.professional_category %} {% if application.professional_category %}
<span class="badge ai-score-badge"> <span class="badge ai-score-badge">
{{ candidate.professional_category }} {{ application.professional_category }}
</span> </span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if candidate.top_3_keywords %} {% if application.top_3_keywords %}
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
{% for skill in candidate.top_3_keywords %} {% for skill in application.top_3_keywords %}
<span class="badge ai-score-badge" style="font-size: smaller;"> <span class="badge ai-score-badge" style="font-size: smaller;">
{{ skill }} {{ skill }}
</span> </span>
@ -471,9 +471,9 @@
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'application_criteria_view_htmx' candidate.pk %}" hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="View Candidate Profile and Criteria"> title="View Application Profile and Criteria">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</button> </button>
</td> </td>
@ -481,10 +481,10 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if not candidates %} {% if not applications %}
<div class="alert alert-info text-center mt-3 mb-0" role="alert"> <div class="alert alert-info text-center mt-3 mb-0" role="alert">
<i class="fas fa-info-circle me-1"></i> <i class="fas fa-info-circle me-1"></i>
{% trans "No candidates match the current stage and filter criteria." %} {% trans "No applications match the current stage and filter criteria." %}
</div> </div>
{% endif %} {% endif %}
</form> </form>
@ -498,14 +498,14 @@
<div class="modal-content kaauh-card"> <div class="modal-content kaauh-card">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);"> <h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
{% trans "Candidate Criteria Review" %} {% trans "Application Criteria Review" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div id="candidateviewModalBody" class="modal-body"> <div id="candidateviewModalBody" class="modal-body">
<div class="text-center py-5 text-muted"> <div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br> <i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Loading candidate data..." %} {% trans "Loading application data..." %}
</div> </div>
</div> </div>
<div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);"> <div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);">

View File

@ -1,11 +0,0 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Delete Candidate - {{ block.super }}{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h1>
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2

View File

@ -167,7 +167,7 @@
} }
/* Funnel Specific Styles */ /* Funnel Specific Styles */
#candidate_funnel_chart { #application_funnel_chart {
max-height: 400px; max-height: 400px;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
@ -224,7 +224,7 @@
<div class="card-header"> <div class="card-header">
<h2> <h2>
<i class="fas fa-chart-area stat-icon"></i> <i class="fas fa-chart-area stat-icon"></i>
{% trans "Daily Candidate Applications Trend" %} {% trans "Daily Applications Trend" %}
</h2> </h2>
</div> </div>
<div class="chart-container"> <div class="chart-container">
@ -261,7 +261,7 @@
</h2> </h2>
</div> </div>
<div class="chart-container d-flex justify-content-center align-items-center"> <div class="chart-container d-flex justify-content-center align-items-center">
<canvas id="candidate_funnel_chart"></canvas> <canvas id="application_funnel_chart"></canvas>
</div> </div>
</div> </div>
</div> </div>
@ -289,11 +289,11 @@
<script> <script>
// Pass context data safely to JavaScript // Pass context data safely to JavaScript
const totalCandidatesScoped = parseInt('{{ total_candidates|default:0 }}'); const totalCandidatesScoped = parseInt('{{ total_applications|default:0 }}');
const jobTitles = JSON.parse('{{ job_titles|escapejs }}').slice(0, 5); const jobTitles = JSON.parse('{{ job_titles|escapejs }}').slice(0, 5);
const jobAppCounts = JSON.parse('{{ job_app_counts|escapejs }}').slice(0, 5); const jobAppCounts = JSON.parse('{{ job_app_counts|escapejs }}').slice(0, 5);
const stages = JSON.parse('{{ candidate_stage|escapejs }}'); const stages = JSON.parse('{{ application_stage|escapejs }}');
const counts = JSON.parse('{{ candidates_count|safe }}'); const counts = JSON.parse('{{ application_count|safe }}');
// --- 1. BAR CHART configuration (Top 5 Applications) --- // --- 1. BAR CHART configuration (Top 5 Applications) ---
const ctxBar = document.getElementById('applicationsChart').getContext('2d'); const ctxBar = document.getElementById('applicationsChart').getContext('2d');
@ -358,7 +358,7 @@
// Slice and use the first N shades based on the number of stages // Slice and use the first N shades based on the number of stages
const stageColors = tealShades.slice(tealShades.length - stages.length); const stageColors = tealShades.slice(tealShades.length - stages.length);
const ctxFunnel = document.getElementById('candidate_funnel_chart').getContext('2d'); const ctxFunnel = document.getElementById('application_funnel_chart').getContext('2d');
new Chart(ctxFunnel, { new Chart(ctxFunnel, {
type: 'bar', type: 'bar',
@ -377,7 +377,7 @@
}, },
// 2. VISIBLE CANDIDATE COUNT DATASET // 2. VISIBLE CANDIDATE COUNT DATASET
{ {
label: '{% trans "Candidate Count" %}', label: '{% trans "Application Count" %}',
data: counts, data: counts,
backgroundColor: stageColors, backgroundColor: stageColors,
barThickness: 50 barThickness: 50
@ -490,7 +490,7 @@
}, },
y: { y: {
beginAtZero: true, beginAtZero: true,
title: { display: true, text: '{% trans "New Candidates" %}' }, title: { display: true, text: '{% trans "New Applications" %}' },
ticks: { precision: 0 }, ticks: { precision: 0 },
grid: { color: '#e0e0e0' } grid: { color: '#e0e0e0' }
} }

View File

@ -1,18 +1,18 @@
<td class="text-center" id="status-result-{{ candidate.pk}}"> <td class="text-center" id="status-result-{{ application.pk}}">
{% if candidate.exam_status %} {% if application.exam_status %}
<button type="button" class="btn btn-{% if candidate.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm" <button type="button" class="btn btn-{% if application.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'exam' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
{{ candidate.exam_status }} {{ application.exam_status }}
</button> </button>
{% else %} {% else %}
-- --
{% endif %} {% endif %}
</td> </td>
<td id="exam-score-{{ candidate.pk}}" hx-swap-oob="true"> <td id="exam-score-{{ application.pk}}" hx-swap-oob="true">
{{candidate.exam_score|default:"--"}} {{application.exam_score|default:"--"}}
</td> </td>

View File

@ -1,22 +1,22 @@
<td class="text-center" id="interview-result-{{ candidate.pk}}"> <td class="text-center" id="interview-result-{{ application.pk}}">
{% if not candidate.interview_status %} {% if not application.interview_status %}
<button type="button" class="btn btn-warning btn-sm" <button type="button" class="btn btn-warning btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'interview' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
</button> </button>
{% else %} {% else %}
{% if candidate.interview_status %} {% if application.interview_status %}
<button type="button" class="btn btn-{% if candidate.interview_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm" <button type="button" class="btn btn-{% if application.interview_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'interview' 'passed' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
{{ candidate.interview_status }} {{ application.interview_status }}
</button> </button>
{% else %} {% else %}
-- --

View File

@ -1,22 +1,22 @@
<td class="text-center" id="status-result-{{ candidate.pk}}"> <td class="text-center" id="status-result-{{ application.pk}}">
{% if not candidate.offer_status %} {% if not application.offer_status %}
<button type="button" class="btn btn-warning btn-sm" <button type="button" class="btn btn-warning btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'offer' 'Accepted' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
</button> </button>
{% else %} {% else %}
{% if candidate.offer_status %} {% if application.offer_status %}
<button type="button" class="btn btn-{% if candidate.offer_status == 'Accepted' %}success{% else %}danger{% endif %} btn-sm" <button type="button" class="btn btn-{% if application.offer_status == 'Accepted' %}success{% else %}danger{% endif %} btn-sm"
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#candidateviewModal" data-bs-target="#candidateviewModal"
hx-get="{% url 'update_application_status' job.slug candidate.slug 'offer' 'Rejected' %}" hx-get="{% url 'update_application_status' job.slug application.slug 'offer' 'Rejected' %}"
hx-target="#candidateviewModalBody" hx-target="#candidateviewModalBody"
title="Pass Exam"> title="Pass Exam">
{{ candidate.offer_status }} {{ application.offer_status }}
</button> </button>
{% else %} {% else %}
-- --

View File

@ -1,10 +1,11 @@
<!-- Stage Update Modal --> <!-- Stage Update Modal -->
{% load i18n%}
<div class="modal fade" id="stageUpdateModal" tabindex="-1" aria-labelledby="stageUpdateModalLabel" aria-hidden="true"> <div class="modal fade" id="stageUpdateModal" tabindex="-1" aria-labelledby="stageUpdateModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-top"> <div class="modal-dialog modal-dialog-top">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="stageUpdateModalLabel"> <h5 class="modal-title" id="stageUpdateModalLabel">
<i class="fas fa-exchange-alt me-2"></i>Update Application Stage <i class="fas fa-exchange-alt me-2"></i>{% trans "Update Application Stage" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
@ -16,14 +17,14 @@
<div class="alert alert-info mb-4"> <div class="alert alert-info mb-4">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<i class="fas fa-info-circle me-2"></i> <i class="fas fa-info-circle me-2"></i>
<strong>Current Stage:</strong> <strong>{% trans "Current Stage:" %}</strong>
<span class="badge bg-primary ms-2" id="currentStageBadge">{{candidate.stage}}</span> <span class="badge bg-primary ms-2" id="currentStageBadge">{{application.stage}}</span>
</div> </div>
</div> </div>
<!-- Form Container --> <!-- Form Container -->
<div id="stageUpdateFormContainer"> <div id="stageUpdateFormContainer">
<form id="stageUpdateForm" action="{% url 'application_update_stage' candidate.slug %}" method="post"> <form id="stageUpdateForm" action="{% url 'application_update_stage' application.slug %}" method="post">
{% csrf_token %} {% csrf_token %}
<!-- Stage Selection --> <!-- Stage Selection -->
<div class="mb-3"> <div class="mb-3">
@ -73,26 +74,26 @@ function updateStageInfo(selectedValue, currentStage) {
if (selectedValue && selectedValue !== currentStage) { if (selectedValue && selectedValue !== currentStage) {
const stageLabels = { const stageLabels = {
'Applied': 'Applied', 'Applied': '{% trans "Applied" %}',
'Exam': 'Exam', 'Exam': '{% trans "Exam" %}',
'Interview': 'Interview', 'Interview': '{% trans "Interview" %}',
'Offer': 'Offer' 'Offer': '{% trans "Offer" %}'
}; };
if (availableStagesText) { if (availableStagesText) {
availableStagesText.textContent = `Moving from "${currentStage}" to "${stageLabels[selectedValue]}". This transition is allowed.`; availableStagesText.textContent = `{% trans "Moving from" %} "${currentStage}" {% trans "to" %} "${stageLabels[selectedValue]}". {% trans "This transition is allowed." %}`;
availableStagesText.parentElement.parentElement.classList.remove('alert-warning', 'alert-light'); availableStagesText.parentElement.parentElement.classList.remove('alert-warning', 'alert-light');
availableStagesText.parentElement.parentElement.classList.add('alert-success'); availableStagesText.parentElement.parentElement.classList.add('alert-success');
} }
} else if (selectedValue === currentStage) { } else if (selectedValue === currentStage) {
if (availableStagesText) { if (availableStagesText) {
availableStagesText.textContent = 'No stage change selected. The candidate is already in this stage.'; availableStagesText.textContent = '{% trans "No stage change selected. The candidate is already in this stage." %}';
availableStagesText.parentElement.parentElement.classList.remove('alert-success', 'alert-warning'); availableStagesText.parentElement.parentElement.classList.remove('alert-success', 'alert-warning');
availableStagesText.parentElement.parentElement.classList.add('alert-info'); availableStagesText.parentElement.parentElement.classList.add('alert-info');
} }
} else { } else {
if (availableStagesText) { if (availableStagesText) {
availableStagesText.textContent = 'Select a stage above to see transition information.'; availableStagesText.textContent = '{% trans "Select a stage above to see transition information." %}';
availableStagesText.parentElement.parentElement.classList.remove('alert-success', 'alert-warning'); availableStagesText.parentElement.parentElement.classList.remove('alert-success', 'alert-warning');
availableStagesText.parentElement.parentElement.classList.add('alert-light'); availableStagesText.parentElement.parentElement.classList.add('alert-light');
} }

View File

@ -25,9 +25,9 @@
{# SCOPED - 3. Total Candidates #} {# SCOPED - 3. Total Candidates #}
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-users stat-icon"></i> {% trans "Total Candidates" %}</h3> <h3><i class="fas fa-users stat-icon"></i> {% trans "Total Applications" %}</h3>
</div> </div>
<div class="stat-value">{{ total_candidates }}</div> <div class="stat-value">{{ total_applications }}</div>
<div class="stat-caption">{% trans "Total applications" %}</div> <div class="stat-caption">{% trans "Total applications" %}</div>
</div> </div>

View File

@ -10,16 +10,16 @@
<h1 class="h3 mb-1"> <h1 class="h3 mb-1">
<i class="fas fa-calendar-plus me-2"></i> <i class="fas fa-calendar-plus me-2"></i>
{% if has_future_meeting %} {% if has_future_meeting %}
{% trans "Update Interview" %} for {{ candidate.name }} {% trans "Update Interview" %} for {{ application.name }}
{% else %} {% else %}
{% trans "Schedule Interview" %} for {{ candidate.name }} {% trans "Schedule Interview" %} for {{ application.name }}
{% endif %} {% endif %}
</h1> </h1>
<p class="text-muted mb-0">{% trans "Job" %}: {{ job.title }}</p> <p class="text-muted mb-0">{% trans "Job" %}: {{ job.title }}</p>
{% if has_future_meeting %} {% if has_future_meeting %}
<div class="alert alert-info mt-2 mb-0" role="alert"> <div class="alert alert-info mt-2 mb-0" role="alert">
<i class="fas fa-info-circle me-1"></i> <i class="fas fa-info-circle me-1"></i>
{% trans "This candidate has upcoming interviews. You are updating an existing schedule." %} {% trans "This application has upcoming interviews. You are updating an existing schedule." %}
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static i18n %}
{% block title %}Assign Staff to {{ job.title }}{% endblock %} {% block title %}{% trans "Assign Staff to" %} {{ job.title }}{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
@ -142,34 +142,34 @@
<!-- Page Header --> <!-- Page Header -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<div> <div>
<h1 class="page-header h3 mb-1">Assign Staff to Job</h1> <h1 class="page-header h3 mb-1">{% trans "Assign Staff to Job" %}</h1>
<p class="text-secondary mb-0">Job: {{ job.title }} ({{ job.internal_job_id }})</p> <p class="text-secondary mb-0">{% trans "Job:" %} {{ job.title }} ({{ job.internal_job_id }})</p>
</div> </div>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-2"></i>Back to Job Details <i class="fas fa-arrow-left me-2"></i>{% trans "Back to Job Details" %}
</a> </a>
</div> </div>
<!-- Job Information Card --> <!-- Job Information Card -->
<div class="kaauh-card mb-4"> <div class="kaauh-card mb-4">
<div class="job-header-card"> <div class="job-header-card">
<h5 class="mb-0"><i class="fas fa-briefcase me-2"></i>Job Information</h5> <h5 class="mb-0"><i class="fas fa-briefcase me-2"></i>{% trans "Job Information" %}</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row mx-2 my-2"> <div class="row mx-2 my-2">
<div class="col-md-6"> <div class="col-md-6">
<p><strong>Department:</strong> {{ job.get_department_display|default:job.department }}</p> <p><strong>{% trans "Department:" %}</strong> {{ job.get_department_display|default:job.department }}</p>
<p><strong>Job Type:</strong> {{ job.get_job_type_display }}</p> <p><strong>{% trans "Job Type:" %}</strong> {{ job.get_job_type_display }}</p>
<p><strong>Workplace Type:</strong> {{ job.get_workplace_type_display }}</p> <p><strong>{% trans "Workplace Type:" %}</strong> {{ job.get_workplace_type_display }}</p>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<p><strong>Status:</strong> <p><strong>{% trans "Status:" %}</strong>
<span class="badge {% if job.status == 'ACTIVE' %}bg-success{% elif job.status == 'CLOSED' %}bg-danger{% else %}bg-secondary{% endif %}"> <span class="badge {% if job.status == 'ACTIVE' %}bg-success{% elif job.status == 'CLOSED' %}bg-danger{% else %}bg-secondary{% endif %}">
{{ job.get_status_display }} {{ job.get_status_display }}
</span> </span>
</p> </p>
<p><strong>Applications:</strong> {{ applications.count }}</p> <p><strong>{% trans "Applications:" %}</strong> {{ applications.count }}</p>
<p><strong>Created:</strong> {{ job.created_at|date:"M d, Y" }}</p> <p><strong>{% trans "Created:" %}</strong> {{ job.created_at|date:"M d, Y" }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -178,7 +178,7 @@
<!-- Staff Assignment Form --> <!-- Staff Assignment Form -->
<div class="kaauh-card"> <div class="kaauh-card">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0 text-primary"><i class="fas fa-user-tie me-2"></i>Staff Assignment</h5> <h5 class="mb-0 text-primary"><i class="fas fa-user-tie me-2"></i>{% trans "Staff Assignment" %}</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
{% if messages %} {% if messages %}
@ -248,10 +248,10 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<button type="submit" class="btn btn-main-action"> <button type="submit" class="btn btn-main-action">
<i class="fas fa-user-plus me-2"></i>Assign Staff to Job <i class="fas fa-user-plus me-2"></i>{% trans "Assign Staff to Job" %}
</button> </button>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary ms-2"> <a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary ms-2">
<i class="fas fa-times me-2"></i>Cancel <i class="fas fa-times me-2"></i>{% trans "Cancel" %}
</a> </a>
</div> </div>
</div> </div>
@ -263,18 +263,18 @@
{% if job.staff_assignments.exists %} {% if job.staff_assignments.exists %}
<div class="kaauh-card mt-4"> <div class="kaauh-card mt-4">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0 text-primary"><i class="fas fa-users me-2"></i>Current Staff Assignments</h5> <h5 class="mb-0 text-primary"><i class="fas fa-users me-2"></i>{% trans "Current Staff Assignments" %}</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Staff Member</th> <th>{% trans "Staff Member" %}</th>
<th>Email</th> <th>{% trans "Email" %}</th>
<th>Assignment Date</th> <th>{% trans "Assignment Date" %}</th>
<th>Notes</th> <th>{% trans "Notes" %}</th>
<th>Status</th> <th>{% trans "Status" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -283,16 +283,16 @@
<td> <td>
<strong>{{ assignment.staff.get_full_name|default:assignment.staff.username }}</strong> <strong>{{ assignment.staff.get_full_name|default:assignment.staff.username }}</strong>
{% if assignment.staff.is_active %} {% if assignment.staff.is_active %}
<span class="badge bg-success ms-2">Active</span> <span class="badge bg-success ms-2">{% trans "Active" %}</span>
{% else %} {% else %}
<span class="badge bg-danger ms-2">Inactive</span> <span class="badge bg-danger ms-2">{% trans "Inactive" %}</span>
{% endif %} {% endif %}
</td> </td>
<td>{{ assignment.staff.email }}</td> <td>{{ assignment.staff.email }}</td>
<td>{{ assignment.assignment_date|date:"M d, Y" }}</td> <td>{{ assignment.assignment_date|date:"M d, Y" }}</td>
<td>{{ assignment.notes|default:"-" }}</td> <td>{{ assignment.notes|default:"-" }}</td>
<td> <td>
<span class="badge bg-primary">Assigned</span> <span class="badge bg-primary">{% trans "Assigned" %}</span>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -306,7 +306,7 @@
<!-- Available Staff Members --> <!-- Available Staff Members -->
<div class="kaauh-card mt-4"> <div class="kaauh-card mt-4">
<div class="card-header"> <div class="card-header">
<h5 class="mb-0 text-primary"><i class="fas fa-users me-2"></i>Available Staff Members</h5> <h5 class="mb-0 text-primary"><i class="fas fa-users me-2"></i>{% trans "Available Staff Members" %}</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
@ -329,11 +329,11 @@
</div> </div>
<div class="mt-2"> <div class="mt-2">
{% if staff_user.is_active %} {% if staff_user.is_active %}
<span class="badge bg-success">Active</span> <span class="badge bg-success">{% trans "Active" %}</span>
{% else %} {% else %}
<span class="badge bg-danger">Inactive</span> <span class="badge bg-danger">{% trans "Inactive" %}</span>
{% endif %} {% endif %}
<span class="badge bg-primary ms-1">Staff</span> <span class="badge bg-primary ms-1">{% trans "Staff" %}</span>
</div> </div>
</div> </div>
</div> </div>
@ -342,7 +342,7 @@
<div class="col-12"> <div class="col-12">
<div class="alert alert-warning"> <div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i> <i class="fas fa-exclamation-triangle me-2"></i>
No staff members available. Please create staff accounts first. {% trans "No staff members available. Please create staff accounts first." %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
@ -359,18 +359,18 @@
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="staffAssignmentModalLabel"> <h5 class="modal-title" id="staffAssignmentModalLabel">
<i class="fas fa-user-plus me-2"></i>Confirm Staff Assignment <i class="fas fa-user-plus me-2"></i>{% trans "Confirm Staff Assignment" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% trans 'Close' %}"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Are you sure you want to assign this staff member to the job <strong>{{ job.title }}</strong>?</p> <p>{% trans "Are you sure you want to assign this staff member to the job" %} <strong>{{ job.title }}</strong>?</p>
<div id="selectedStaffInfo"></div> <div id="selectedStaffInfo"></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button> <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="button" class="btn btn-main-action" id="confirmAssignmentBtn"> <button type="button" class="btn btn-main-action" id="confirmAssignmentBtn">
<i class="fas fa-check me-2"></i>Confirm Assignment <i class="fas fa-check me-2"></i>{% trans "Confirm Assignment" %}
</button> </button>
</div> </div>
</div> </div>
@ -398,16 +398,16 @@ document.addEventListener('DOMContentLoaded', function() {
// Show selected staff info in modal // Show selected staff info in modal
selectedStaffInfo.innerHTML = ` selectedStaffInfo.innerHTML = `
<div class="alert alert-info"> <div class="alert alert-info">
<strong>Selected Staff Member:</strong><br> <strong>{% trans "Selected Staff Member:" %}</strong><br>
Name: ${selectedOption.text}<br> {% trans "Name:" %} ${selectedOption.text}<br>
This assignment will take effect immediately. {% trans "This assignment will take effect immediately." %}
</div> </div>
`; `;
// Show modal // Show modal
modal.show(); modal.show();
} else { } else {
alert('Please select a staff member.'); alert('{% trans "Please select a staff member." %}');
} }
}); });
} }