Compare commits
No commits in common. "c532ca97b6459f6fcb77b808e993b354607ba4d1" and "4caf57a3e0610a9e0d89e28c952d8d9b522c1319" have entirely different histories.
c532ca97b6
...
4caf57a3e0
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -234,12 +234,12 @@ class JobPostingForm(forms.ModelForm):
|
||||
'value': 'United States'
|
||||
}),
|
||||
|
||||
|
||||
|
||||
'salary_range': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '$60,000 - $80,000'
|
||||
}),
|
||||
|
||||
|
||||
|
||||
# Application Information
|
||||
# 'application_url': forms.URLInput(attrs={
|
||||
@ -255,7 +255,7 @@ class JobPostingForm(forms.ModelForm):
|
||||
'class': 'form-control',
|
||||
'type': 'date'
|
||||
}),
|
||||
|
||||
|
||||
'open_positions': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'min': 1,
|
||||
@ -483,12 +483,4 @@ class JobPostingStatusForm(forms.ModelForm):
|
||||
class FormTemplateIsActiveForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = FormTemplate
|
||||
fields = ['is_active']
|
||||
|
||||
class CandidateExamDateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Candidate
|
||||
fields = ['exam_date']
|
||||
widgets = {
|
||||
'exam_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}),
|
||||
}
|
||||
fields = ['is_active']
|
||||
@ -1,23 +0,0 @@
|
||||
# Generated by Django 5.2.6 on 2025-10-12 12:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='candidate',
|
||||
name='is_potential_candidate',
|
||||
field=models.BooleanField(default=False, verbose_name='Potential Candidate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='jobposting',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], default='DRAFT', max_length=20),
|
||||
),
|
||||
]
|
||||
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.2.6 on 2025-10-12 15:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0002_candidate_is_potential_candidate_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='candidate',
|
||||
name='exam_date',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Exam Date'),
|
||||
),
|
||||
]
|
||||
@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.2.6 on 2025-10-12 15:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0003_alter_candidate_exam_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='candidate',
|
||||
name='interview_date',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Interview Date'),
|
||||
),
|
||||
]
|
||||
@ -229,7 +229,7 @@ class JobPosting(Base):
|
||||
parts.append(self.location_city)
|
||||
if self.location_state:
|
||||
parts.append(self.location_state)
|
||||
if self.location_country:
|
||||
if self.location_country and self.location_country != "United States":
|
||||
parts.append(self.location_country)
|
||||
return ", ".join(parts) if parts else "Not specified"
|
||||
|
||||
@ -295,9 +295,6 @@ class Candidate(Base):
|
||||
is_resume_parsed = models.BooleanField(
|
||||
default=False, verbose_name=_("Resume Parsed")
|
||||
)
|
||||
is_potential_candidate = models.BooleanField(
|
||||
default=False, verbose_name=_("Potential Candidate")
|
||||
)
|
||||
parsed_summary = models.TextField(blank=True, verbose_name=_("Parsed Summary"))
|
||||
applied = models.BooleanField(default=False, verbose_name=_("Applied"))
|
||||
stage = models.CharField(
|
||||
@ -314,7 +311,7 @@ class Candidate(Base):
|
||||
blank=True,
|
||||
verbose_name=_("Applicant Status"),
|
||||
)
|
||||
exam_date = models.DateTimeField(null=True, blank=True, verbose_name=_("Exam Date"))
|
||||
exam_date = models.DateField(null=True, blank=True, verbose_name=_("Exam Date"))
|
||||
exam_status = models.CharField(
|
||||
choices=ExamStatus.choices,
|
||||
max_length=100,
|
||||
@ -322,7 +319,7 @@ class Candidate(Base):
|
||||
blank=True,
|
||||
verbose_name=_("Exam Status"),
|
||||
)
|
||||
interview_date = models.DateTimeField(
|
||||
interview_date = models.DateField(
|
||||
null=True, blank=True, verbose_name=_("Interview Date")
|
||||
)
|
||||
interview_status = models.CharField(
|
||||
|
||||
@ -65,15 +65,8 @@ urlpatterns = [
|
||||
path('forms/builder/<int:template_id>/', views.form_builder, name='form_builder'),
|
||||
path('forms/', views.form_templates_list, name='form_templates_list'),
|
||||
path('forms/create-template/', views.create_form_template, name='create_form_template'),
|
||||
|
||||
path('jobs/<slug:slug>/candidate-tiers/', views.candidate_tier_management_view, name='candidate_tier_management'),
|
||||
path('jobs/<slug:slug>/candidate_exam_view/', views.candidate_exam_view, name='candidate_exam_view'),
|
||||
path('jobs/<slug:slug>/update_candidate_exam_status/', views.update_candidate_exam_status, name='update_candidate_exam_status'),
|
||||
path('jobs/<slug:slug>/bulk_update_candidate_exam_status/', views.bulk_update_candidate_exam_status, name='bulk_update_candidate_exam_status'),
|
||||
|
||||
path('htmx/<int:pk>/candidate_criteria_view/', views.candidate_criteria_view_htmx, name='candidate_criteria_view_htmx'),
|
||||
path('htmx/<slug:slug>/candidate_set_exam_date/', views.candidate_set_exam_date, name='candidate_set_exam_date'),
|
||||
path('htmx/bulk_candidate_move_to_exam/', views.bulk_candidate_move_to_exam, name='bulk_candidate_move_to_exam'),
|
||||
|
||||
# path('forms/form/<int:template_id>/submit/', views.submit_form, name='submit_form'),
|
||||
# path('forms/form/<int:template_id>/', views.form_wizard_view, name='form_wizard'),
|
||||
|
||||
@ -12,7 +12,6 @@ from django.urls import reverse
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from .forms import (
|
||||
CandidateExamDateForm,
|
||||
ZoomMeetingForm,
|
||||
JobPostingForm,
|
||||
FormTemplateForm,
|
||||
@ -217,7 +216,7 @@ def create_job(request):
|
||||
job.created_by = request.POST.get("created_by", "").strip()
|
||||
if not job.created_by:
|
||||
job.created_by = "University Administrator"
|
||||
|
||||
|
||||
job.save()
|
||||
job_apply_url_relative=reverse('job_detail_candidate',kwargs={'slug':job.slug})
|
||||
job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative)
|
||||
@ -274,7 +273,7 @@ def edit_job(request, slug):
|
||||
def job_detail(request, slug):
|
||||
"""View details of a specific job"""
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
|
||||
|
||||
|
||||
# Get all candidates for this job, ordered by most recent
|
||||
applicants = job.candidates.all().order_by("-created_at")
|
||||
@ -292,22 +291,22 @@ def job_detail(request, slug):
|
||||
|
||||
# 2. Check for POST request (Status Update Submission)
|
||||
if request.method == 'POST':
|
||||
|
||||
|
||||
status_form = JobPostingStatusForm(request.POST, instance=job)
|
||||
|
||||
|
||||
if status_form.is_valid():
|
||||
status_form.save()
|
||||
|
||||
|
||||
# Add a success message
|
||||
messages.success(request, f"Status for '{job.title}' updated to '{job.get_status_display()}' successfully!")
|
||||
|
||||
|
||||
|
||||
|
||||
return redirect('job_detail', slug=slug)
|
||||
else:
|
||||
|
||||
|
||||
messages.error(request, "Failed to update status due to validation errors.")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
context = {
|
||||
"job": job,
|
||||
@ -328,15 +327,15 @@ def job_image_upload(request, slug):
|
||||
image_upload_form=JobPostingImageForm(request.POST,request.FILES)
|
||||
if image_upload_form.is_valid():
|
||||
image_upload_form = image_upload_form.save(commit=False)
|
||||
|
||||
|
||||
image_upload_form.job = job
|
||||
image_upload_form.save()
|
||||
messages.success(request, f"Image uploaded successfully for {job.title}.")
|
||||
return redirect('job_detail', slug=job.slug)
|
||||
else:
|
||||
|
||||
|
||||
messages.error(request, "Image upload failed: Please ensure a valid image file was selected.")
|
||||
|
||||
|
||||
return redirect('job_detail', slug=job.slug)
|
||||
return redirect('job_detail', slug=job.slug)
|
||||
|
||||
@ -964,8 +963,6 @@ def submit_form(request, template_id):
|
||||
resume=resume.get_file if resume.is_file else None,
|
||||
job=submission.template.job,
|
||||
)
|
||||
return redirect('application_success')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Candidate creation failed,{e}")
|
||||
pass
|
||||
@ -1241,102 +1238,102 @@ def candidate_tier_management_view(request, slug):
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
|
||||
# Get all candidates for this job, ordered by match score (descending)
|
||||
candidates = job.candidates.filter(stage="Applied").order_by("-match_score")
|
||||
candidates = job.candidates.all().order_by("-match_score")
|
||||
|
||||
# Get tier categorization parameters
|
||||
# tier1_count = int(request.GET.get("tier1_count", 100))
|
||||
tier1_count = int(request.GET.get("tier1_count", 100))
|
||||
|
||||
# # Categorize candidates into tiers
|
||||
# tier1_candidates = candidates[:tier1_count] if tier1_count > 0 else []
|
||||
# remaining_candidates = candidates[tier1_count:] if tier1_count > 0 else []
|
||||
# Categorize candidates into tiers
|
||||
tier1_candidates = candidates[:tier1_count] if tier1_count > 0 else []
|
||||
remaining_candidates = candidates[tier1_count:] if tier1_count > 0 else []
|
||||
|
||||
# if len(remaining_candidates) > 0:
|
||||
# # Tier 2: Next 50% of remaining candidates
|
||||
# tier2_count = max(1, len(remaining_candidates) // 2)
|
||||
# tier2_candidates = remaining_candidates[:tier2_count]
|
||||
# tier3_candidates = remaining_candidates[tier2_count:]
|
||||
# else:
|
||||
# tier2_candidates = []
|
||||
# tier3_candidates = []
|
||||
if len(remaining_candidates) > 0:
|
||||
# Tier 2: Next 50% of remaining candidates
|
||||
tier2_count = max(1, len(remaining_candidates) // 2)
|
||||
tier2_candidates = remaining_candidates[:tier2_count]
|
||||
tier3_candidates = remaining_candidates[tier2_count:]
|
||||
else:
|
||||
tier2_candidates = []
|
||||
tier3_candidates = []
|
||||
|
||||
# # Handle form submissions
|
||||
# if request.method == "POST":
|
||||
# # Update tier categorization
|
||||
# if "update_tiers" in request.POST:
|
||||
# tier1_count = int(request.POST.get("tier1_count", 100))
|
||||
# messages.success(request, f"Tier categorization updated. Tier 1: {tier1_count} candidates")
|
||||
# return redirect("candidate_tier_management", slug=slug)
|
||||
# Handle form submissions
|
||||
if request.method == "POST":
|
||||
# Update tier categorization
|
||||
if "update_tiers" in request.POST:
|
||||
tier1_count = int(request.POST.get("tier1_count", 100))
|
||||
messages.success(request, f"Tier categorization updated. Tier 1: {tier1_count} candidates")
|
||||
return redirect("candidate_tier_management", slug=slug)
|
||||
|
||||
# # Update individual candidate stages
|
||||
# elif "update_stage" in request.POST:
|
||||
# candidate_id = request.POST.get("candidate_id")
|
||||
# new_stage = request.POST.get("new_stage")
|
||||
# candidate = get_object_or_404(Candidate, id=candidate_id, job=job)
|
||||
# Update individual candidate stages
|
||||
elif "update_stage" in request.POST:
|
||||
candidate_id = request.POST.get("candidate_id")
|
||||
new_stage = request.POST.get("new_stage")
|
||||
candidate = get_object_or_404(Candidate, id=candidate_id, job=job)
|
||||
|
||||
# if candidate.can_transition_to(new_stage):
|
||||
# old_stage = candidate.stage
|
||||
# candidate.stage = new_stage
|
||||
# candidate.save()
|
||||
# messages.success(request, f"Updated {candidate.name} from {old_stage} to {new_stage}")
|
||||
# else:
|
||||
# messages.error(request, f"Cannot transition {candidate.name} from {candidate.stage} to {new_stage}")
|
||||
if candidate.can_transition_to(new_stage):
|
||||
old_stage = candidate.stage
|
||||
candidate.stage = new_stage
|
||||
candidate.save()
|
||||
messages.success(request, f"Updated {candidate.name} from {old_stage} to {new_stage}")
|
||||
else:
|
||||
messages.error(request, f"Cannot transition {candidate.name} from {candidate.stage} to {new_stage}")
|
||||
|
||||
# # Update exam status
|
||||
# elif "update_exam_status" in request.POST:
|
||||
# candidate_id = request.POST.get("candidate_id")
|
||||
# exam_status = request.POST.get("exam_status")
|
||||
# exam_date = request.POST.get("exam_date")
|
||||
# candidate = get_object_or_404(Candidate, id=candidate_id, job=job)
|
||||
# Update exam status
|
||||
elif "update_exam_status" in request.POST:
|
||||
candidate_id = request.POST.get("candidate_id")
|
||||
exam_status = request.POST.get("exam_status")
|
||||
exam_date = request.POST.get("exam_date")
|
||||
candidate = get_object_or_404(Candidate, id=candidate_id, job=job)
|
||||
|
||||
# if candidate.stage == "Exam":
|
||||
# candidate.exam_status = exam_status
|
||||
# if exam_date:
|
||||
# candidate.exam_date = exam_date
|
||||
# candidate.save()
|
||||
# messages.success(request, f"Updated exam status for {candidate.name}")
|
||||
# else:
|
||||
# messages.error(request, f"Can only update exam status for candidates in Exam stage")
|
||||
if candidate.stage == "Exam":
|
||||
candidate.exam_status = exam_status
|
||||
if exam_date:
|
||||
candidate.exam_date = exam_date
|
||||
candidate.save()
|
||||
messages.success(request, f"Updated exam status for {candidate.name}")
|
||||
else:
|
||||
messages.error(request, f"Can only update exam status for candidates in Exam stage")
|
||||
|
||||
# # Bulk stage update
|
||||
# elif "bulk_update_stage" in request.POST:
|
||||
# selected_candidates = request.POST.getlist("selected_candidates")
|
||||
# new_stage = request.POST.get("bulk_new_stage")
|
||||
# updated_count = 0
|
||||
# Bulk stage update
|
||||
elif "bulk_update_stage" in request.POST:
|
||||
selected_candidates = request.POST.getlist("selected_candidates")
|
||||
new_stage = request.POST.get("bulk_new_stage")
|
||||
updated_count = 0
|
||||
|
||||
# for candidate_id in selected_candidates:
|
||||
# candidate = get_object_or_404(Candidate, id=candidate_id, job=job)
|
||||
# if candidate.can_transition_to(new_stage):
|
||||
# candidate.stage = new_stage
|
||||
# candidate.save()
|
||||
# updated_count += 1
|
||||
for candidate_id in selected_candidates:
|
||||
candidate = get_object_or_404(Candidate, id=candidate_id, job=job)
|
||||
if candidate.can_transition_to(new_stage):
|
||||
candidate.stage = new_stage
|
||||
candidate.save()
|
||||
updated_count += 1
|
||||
|
||||
# messages.success(request, f"Updated {updated_count} candidates to {new_stage} stage")
|
||||
messages.success(request, f"Updated {updated_count} candidates to {new_stage} stage")
|
||||
|
||||
# # Mark individual candidate as Candidate
|
||||
# elif "mark_as_candidate" in request.POST:
|
||||
# candidate_id = request.POST.get("candidate_id")
|
||||
# candidate = get_object_or_404(Candidate, id=candidate_id, job=job)
|
||||
# Mark individual candidate as Candidate
|
||||
elif "mark_as_candidate" in request.POST:
|
||||
candidate_id = request.POST.get("candidate_id")
|
||||
candidate = get_object_or_404(Candidate, id=candidate_id, job=job)
|
||||
|
||||
# if candidate.applicant_status == "Applicant":
|
||||
# candidate.applicant_status = "Candidate"
|
||||
# candidate.save()
|
||||
# messages.success(request, f"Marked {candidate.name} as Candidate")
|
||||
# else:
|
||||
# messages.info(request, f"{candidate.name} is already marked as Candidate")
|
||||
if candidate.applicant_status == "Applicant":
|
||||
candidate.applicant_status = "Candidate"
|
||||
candidate.save()
|
||||
messages.success(request, f"Marked {candidate.name} as Candidate")
|
||||
else:
|
||||
messages.info(request, f"{candidate.name} is already marked as Candidate")
|
||||
|
||||
# # Mark all Tier 1 candidates as Candidates
|
||||
# elif "mark_as_candidates" in request.POST:
|
||||
# updated_count = 0
|
||||
# for candidate in tier1_candidates:
|
||||
# if candidate.applicant_status == "Applicant":
|
||||
# candidate.applicant_status = "Candidate"
|
||||
# candidate.save()
|
||||
# updated_count += 1
|
||||
# Mark all Tier 1 candidates as Candidates
|
||||
elif "mark_as_candidates" in request.POST:
|
||||
updated_count = 0
|
||||
for candidate in tier1_candidates:
|
||||
if candidate.applicant_status == "Applicant":
|
||||
candidate.applicant_status = "Candidate"
|
||||
candidate.save()
|
||||
updated_count += 1
|
||||
|
||||
# if updated_count > 0:
|
||||
# messages.success(request, f"Marked {updated_count} Tier 1 candidates as Candidates")
|
||||
# else:
|
||||
# messages.info(request, "All Tier 1 candidates are already marked as Candidates")
|
||||
if updated_count > 0:
|
||||
messages.success(request, f"Marked {updated_count} Tier 1 candidates as Candidates")
|
||||
else:
|
||||
messages.info(request, "All Tier 1 candidates are already marked as Candidates")
|
||||
|
||||
# Group candidates by current stage for display
|
||||
stage_groups = {
|
||||
@ -1348,75 +1345,17 @@ def candidate_tier_management_view(request, slug):
|
||||
|
||||
context = {
|
||||
"job": job,
|
||||
"candidates": candidates,
|
||||
# "stage_groups": stage_groups,
|
||||
# "tier1_count": tier1_count,
|
||||
# "total_candidates": candidates.count(),
|
||||
"tier1_candidates": tier1_candidates,
|
||||
"tier2_candidates": tier2_candidates,
|
||||
"tier3_candidates": tier3_candidates,
|
||||
"stage_groups": stage_groups,
|
||||
"tier1_count": tier1_count,
|
||||
"total_candidates": candidates.count(),
|
||||
}
|
||||
|
||||
return render(request, "recruitment/candidate_tier_management.html", context)
|
||||
|
||||
|
||||
def candidate_exam_view(request, slug):
|
||||
"""
|
||||
Manage candidate tiers and stage transitions
|
||||
"""
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
candidates = job.candidates.filter(stage="Exam").order_by("-match_score")
|
||||
|
||||
return render(request, "recruitment/candidate_exam_view.html", {"job": job, "candidates": candidates})
|
||||
|
||||
def update_candidate_exam_status(request, slug):
|
||||
candidate = get_object_or_404(Candidate, slug=slug)
|
||||
if request.method == "POST":
|
||||
form = CandidateExamDateForm(request.POST, instance=candidate)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect("candidate_exam_view", slug=candidate.job.slug)
|
||||
else:
|
||||
form = CandidateExamDateForm(request.POST, instance=candidate)
|
||||
return render(request, "includes/candidate_exam_status_form.html", {"candidate": candidate,"form": form})
|
||||
def bulk_update_candidate_exam_status(request,slug):
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
print(request.headers)
|
||||
status = request.headers.get('status')
|
||||
print(status)
|
||||
if status:
|
||||
for c in request.POST.items():
|
||||
try:
|
||||
candidate = Candidate.objects.get(pk=c[0])
|
||||
candidate.exam_status = "Passed" if status == "pass" else "Failed"
|
||||
candidate.stage = "Interview"
|
||||
candidate.save()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
messages.success(request, f"Updated exam status selected candidates")
|
||||
return redirect("candidate_exam_view", slug=job.slug)
|
||||
|
||||
def candidate_criteria_view_htmx(request, pk):
|
||||
candidate = get_object_or_404(Candidate, pk=pk)
|
||||
return render(request, "includes/candidate_modal_body.html", {"candidate": candidate})
|
||||
|
||||
|
||||
def candidate_set_exam_date(request, slug):
|
||||
candidate = get_object_or_404(Candidate, slug=slug)
|
||||
candidate.exam_date = timezone.now()
|
||||
candidate.save()
|
||||
messages.success(request, f"Set exam date for {candidate.name} to {candidate.exam_date}")
|
||||
return redirect("candidate_tier_management", slug=candidate.job.slug)
|
||||
def bulk_candidate_move_to_exam(request):
|
||||
for c in request.POST.items():
|
||||
try:
|
||||
candidate = Candidate.objects.get(pk=c[0])
|
||||
candidate.stage = "Exam"
|
||||
candidate.applicant_status = "Candidate"
|
||||
candidate.exam_date = timezone.now()
|
||||
candidate.save()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
messages.success(request, f"Moved {candidate.name} to Exam stage")
|
||||
return redirect("candidate_tier_management", slug=candidate.job.slug)
|
||||
# def response():
|
||||
# yield SSE.patch_elements("","")
|
||||
# yield SSE.execute_script("console.log('hello world');")
|
||||
# return DatastarResponse(response())
|
||||
print(candidate)
|
||||
return render(request, "includes/candidate_modal_body.html", {"candidate": candidate})
|
||||
@ -245,7 +245,7 @@
|
||||
</style>
|
||||
{% block customCSS %}{% endblock %}
|
||||
</head>
|
||||
<body class="d-flex flex-column min-vh-100" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||
<body class="d-flex flex-column min-vh-100">
|
||||
|
||||
<div class="top-bar d-none d-md-block">
|
||||
{# Changed container to container-fluid and added max-width-1600 to inner div #}
|
||||
@ -587,8 +587,7 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js"></script>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js"></script>
|
||||
|
||||
{% block customJS %}{% endblock %}
|
||||
|
||||
|
||||
@ -5,9 +5,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ATS Form Builder - Vanilla JS</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
|
||||
<style>
|
||||
/* Updated CSS styles with a new Teal/Aqua theme */
|
||||
:root {
|
||||
@ -773,22 +771,8 @@
|
||||
</div>
|
||||
<!-- Main Content Area -->
|
||||
<div class="main-content">
|
||||
<nav>
|
||||
<div class="bread" style="color: #6c757d; font-size: 0.9rem;">
|
||||
<span class="me-2">
|
||||
<a href="{% url 'dashboard' %}" style="color: #6c757d !important; text-decoration: none !important;">Home</a>
|
||||
/
|
||||
</span>
|
||||
<span class="me-2">
|
||||
<a href="{% url 'job_list' %}" style="color: #6c757d !important; text-decoration: none !important;">Jobs</a>
|
||||
/
|
||||
</span>
|
||||
<span style="color: #6c757d; font-weight: 600;">Form Builder</span>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="toolbar">
|
||||
<h1 id="formTitle">Resume Application Form</h1>
|
||||
|
||||
<div>
|
||||
<button class="btn btn-outline" id="formSettingsBtn">
|
||||
<i class="fas fa-cog"></i> Settings
|
||||
@ -977,9 +961,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
// Application State
|
||||
const state = {
|
||||
draggedStageIndex: null,
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% url 'update_candidate_exam_status' slug=candidate.slug as url %}
|
||||
<form data-on-submit="@post('{{url}}', {contentType: 'form', headers: {'X-CSRFToken': '{{ csrf_token }}'}})">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn btn-primary">{% trans "Update" %}</button>
|
||||
</form>
|
||||
@ -14,11 +14,7 @@
|
||||
{% for key, value in candidate.criteria_checklist.items %}
|
||||
<li class="list-group-item d-flex justify-content-between">
|
||||
<span>{{ key }}</span>
|
||||
{% if value == 'Met' %}
|
||||
<span class="badge bg-success">Yes</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">Not Mentioned</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-{{ value|yesno:"success,danger" }}">{{ value|yesno:"Yes,No" }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
@ -393,7 +393,7 @@
|
||||
id="copyJobLinkButton"
|
||||
data-url="{{ job.application_url }}">
|
||||
<i class="fas fa-link me-1"></i>
|
||||
{% trans "Copy and Share Public Link" %}
|
||||
{% trans "Copy Public Link" %}
|
||||
</button>
|
||||
|
||||
<span id="copyFeedback" class="text-success ms-2 small" style="display:none;">
|
||||
|
||||
@ -13,10 +13,6 @@
|
||||
--kaauh-primary-text: #343a40;
|
||||
}
|
||||
|
||||
/* Primary Color Overrides */
|
||||
.text-primary-theme { color: var(--kaauh-teal) !important; }
|
||||
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
|
||||
|
||||
/* Enhanced Card Styling */
|
||||
.card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
@ -24,7 +20,7 @@
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.card:not(.no-hover):hover {
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
|
||||
}
|
||||
@ -36,9 +32,6 @@
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.btn-main-action:hover {
|
||||
@ -58,6 +51,17 @@
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
}
|
||||
|
||||
/* Job Card Specifics */
|
||||
.job-card .card-title {
|
||||
color: var(--kaauh-teal-dark);
|
||||
font-weight: 600;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
.job-card .card-text i {
|
||||
color: var(--kaauh-teal);
|
||||
width: 1.25rem; /* Align icons vertically */
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
.status-badge {
|
||||
font-size: 0.8rem;
|
||||
@ -67,100 +71,41 @@
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.7px;
|
||||
}
|
||||
.bg-DRAFT { background-color: #6c757d !important; }
|
||||
.bg-ACTIVE { background-color: var(--kaauh-teal) !important; }
|
||||
.bg-CLOSED { background-color: #dc3545 !important; }
|
||||
.bg-ARCHIVED { background-color: #343a40 !important; }
|
||||
.bg-info { background-color: #17a2b8 !important; }
|
||||
/* Mapped color classes to theme */
|
||||
.bg-draft { background-color: #6c757d !important; } /* secondary */
|
||||
.bg-active { background-color: var(--kaauh-teal) !important; } /* primary teal */
|
||||
.bg-closed { background-color: #dc3545 !important; } /* danger */
|
||||
.bg-archived { background-color: #343a40 !important; } /* dark */
|
||||
|
||||
/* --- TABLE ALIGNMENT AND SIZING FIXES --- */
|
||||
.table {
|
||||
table-layout: fixed; /* Ensures column widths are respected */
|
||||
width: 100%;
|
||||
}
|
||||
.table thead th {
|
||||
color: var(--kaauh-primary-text);
|
||||
font-weight: 500; /* Lighter weight for smaller font */
|
||||
font-size: 0.85rem; /* Smaller font size for header text */
|
||||
vertical-align: middle;
|
||||
border-bottom: 2px solid var(--kaauh-border);
|
||||
padding: 0.5rem 0.25rem; /* Reduced vertical and horizontal padding */
|
||||
}
|
||||
.table-hover tbody tr:hover {
|
||||
background-color: #f3f7f9;
|
||||
}
|
||||
|
||||
/* Optimized Main Table Column Widths (Total must be 100%) */
|
||||
.table th:nth-child(1) { width: 22%; } /* Job ID (Tight) */
|
||||
|
||||
.table th:nth-child(2) { width: 12%; } /* Source (Tight) */
|
||||
.table th:nth-child(3) { width: 8%; } /* Actions (Tight, icon buttons) */
|
||||
.table th:nth-child(4) { width: 8%; } /* Form (Tight, icon buttons) */
|
||||
.table th:nth-child(5) { width: 50%; } /* Candidate Metrics (colspan=7) */
|
||||
.bg-info { background-color: #17a2b8 !important; } /* LinkedIn badge */
|
||||
|
||||
/* NESTED TABLE STYLING FOR CANDIDATE MANAGEMENT HEADER */
|
||||
.nested-header-table {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed; /* CRITICAL for 1:1 data alignment */
|
||||
}
|
||||
.nested-header-table thead th {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 0.3rem 0 0.1rem 0; /* Reduced padding here too */
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-size: 0.75rem; /* Even smaller font for nested headers */
|
||||
width: calc(100% / 7);
|
||||
}
|
||||
/* Explicit widths are technically defined by the 1/7 rule, but keeping them for clarity/safety */
|
||||
.nested-header-table thead th:nth-child(1),
|
||||
.nested-header-table thead th:nth-child(2),
|
||||
.nested-header-table thead th:nth-child(5) {
|
||||
width: calc(100% / 7);
|
||||
}
|
||||
.nested-header-table thead th:nth-child(3),
|
||||
.nested-header-table thead th:nth-child(4) {
|
||||
width: calc(100% / 7 * 2);
|
||||
}
|
||||
|
||||
/* Inner Nested Table (P/F) */
|
||||
.nested-stage-metrics {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
table-layout: fixed;
|
||||
}
|
||||
.nested-stage-metrics thead th {
|
||||
padding: 0.1rem 0; /* Very minimal padding */
|
||||
font-weight: 600;
|
||||
/* Pagination Link Styling */
|
||||
.pagination .page-item .page-link {
|
||||
color: var(--kaauh-teal-dark);
|
||||
font-size: 0.7rem; /* Smallest font size */
|
||||
width: 50%;
|
||||
border-color: var(--kaauh-border);
|
||||
}
|
||||
.pagination .page-item.active .page-link {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
}
|
||||
.pagination .page-item:hover .page-link:not(.active) {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
/* Main TH for Candidate Management Header */
|
||||
.candidate-management-header {
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
border-left: 2px solid var(--kaauh-teal);
|
||||
border-right: 1px solid var(--kaauh-border) !important;
|
||||
/* Filter & Search Layout Adjustments */
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem; /* Space between filter and clear buttons */
|
||||
}
|
||||
|
||||
/* Candidate Management Data Cells (7 columns total now) */
|
||||
.candidate-data-cell {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem; /* Keep data readable */
|
||||
border-left: 1px solid var(--kaauh-border);
|
||||
.form-control-search {
|
||||
/* Optional: Add slight shadow/focus effect to search bar */
|
||||
box-shadow: none;
|
||||
border-color: var(--kaauh-border);
|
||||
}
|
||||
.candidate-data-cell a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
padding: 0.4rem 0; /* Minimized vertical padding */
|
||||
.form-control-search:focus {
|
||||
border-color: var(--kaauh-teal);
|
||||
box-shadow: 0 0 0 0.1rem rgba(0, 99, 110, 0.25);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -169,10 +114,10 @@
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-briefcase me-2"></i> {% trans "Job Postings" %}
|
||||
<i class="fas fa-briefcase me-2"></i> Job Postings
|
||||
</h1>
|
||||
<a href="{% url 'job_create' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Create New Job" %}
|
||||
<i class="fas fa-plus me-1"></i> Create New Job
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -180,7 +125,7 @@
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="search" class="form-label small text-muted">{% trans "Search by Title or Department" %}</label>
|
||||
<label for="search" class="form-label small text-muted">Search by Title or Department</label>
|
||||
<div class="input-group input-group-lg mb-3">
|
||||
<form method="get" action="" class="w-100">
|
||||
{% include 'includes/search_form.html' %}
|
||||
@ -188,123 +133,117 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<form method="GET" class="row g-3 align-items-end" >
|
||||
<div class="col-md-3">
|
||||
<label for="status" class="form-label small text-muted">{% trans "Filter by Status" %}</label>
|
||||
<select name="status" id="status" class="form-select form-select-sm">
|
||||
<option value="">{% trans "All Statuses" %}</option>
|
||||
<option value="DRAFT" {% if status_filter == 'DRAFT' %}selected{% endif %}>{% trans "Draft" %}</option>
|
||||
<option value="ACTIVE" {% if status_filter == 'ACTIVE' %}selected{% endif %}>{% trans "Active" %}</option>
|
||||
<option value="CLOSED" {% if status_filter == 'CLOSED' %}selected{% endif %}>{% trans "Closed" %}</option>
|
||||
<option value="ARCHIVED" {% if status_filter == 'ARCHIVED' %}selected{% endif %}>{% trans "Archived" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<div class="filter-buttons">
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Apply Filters" %}
|
||||
</button>
|
||||
<a href="{% url 'job_list' %}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% url 'job_list' as job_list_url %}
|
||||
|
||||
<form method="GET" class="row g-3 align-items-end" >
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="status" class="form-label small text-muted">Filter by Status</label>
|
||||
<select name="status" id="status" class="form-select form-select-sm">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="DRAFT" {% if status_filter == 'DRAFT' %}selected{% endif %}>Draft</option>
|
||||
<option value="PUBLISHED" {% if status_filter == 'PUBLISHED' %}selected{% endif %}>Published</option>
|
||||
<option value="CLOSED" {% if status_filter == 'CLOSED' %}selected{% endif %}>Closed</option>
|
||||
<option value="ARCHIVED" {% if status_filter == 'ARCHIVED' %}selected{% endif %}>Archived</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<div class="filter-buttons">
|
||||
<button type="submit" class="btn btn-main-action btn-lg">
|
||||
<i class="fas fa-filter me-1"></i> Apply Filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% comment %} --- START OF TABLE VIEW (Data relied upon context variable 'jobs') --- {% endcomment %}
|
||||
<div id="job-list">
|
||||
{% comment %} Placeholder for View Switcher {% endcomment %}
|
||||
{% include "includes/_list_view_switcher.html" with list_id="job-list" %}
|
||||
{% if page_obj %}
|
||||
<div id="job-list">
|
||||
{# View Switcher #}
|
||||
{% include "includes/_list_view_switcher.html" with list_id="job-list" %}
|
||||
|
||||
<div class="table-view active">
|
||||
<div class="card shadow-sm">
|
||||
<div class="table-responsive ">
|
||||
<table class="table table-hover align-middle mb-0 table-sm">
|
||||
{# Card View (Default) #}
|
||||
<div class="card-view active row">
|
||||
{% for job in page_obj %}
|
||||
<div class="col-md-6 col-lg-4 mb-4 ">
|
||||
<div class="card job-card h-100">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title flex-grow-1 me-3">{{ job.title }}</h5>
|
||||
<span class="badge bg-{{ job.status|lower|striptags|yesno:'active,draft,closed,archived' }} status-badge">
|
||||
{{ job.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="card-text text-muted small">
|
||||
<i class="fas fa-building"></i> {{ job.department|default:"No Department" }}<br>
|
||||
<i class="fas fa-map-marker-alt"></i> {{ job.get_location_display }}<br>
|
||||
<i class="fas fa-clock"></i> {{ job.get_job_type_display }}<br>
|
||||
<i class="fas fa-briefcase"></i> {{ job.get_source }}
|
||||
</p>
|
||||
|
||||
<div class="mt-auto pt-2 border-top">
|
||||
{% if job.posted_to_linkedin %}
|
||||
<span class="badge bg-info mb-2">
|
||||
<i class="fab fa-linkedin me-1"></i> Posted to LinkedIn
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'job_detail' job.slug %}" class="btn btn-sm btn-main-action">
|
||||
<i class="fas fa-eye"></i> View
|
||||
</a>
|
||||
<a href="{% url 'job_update' job.slug %}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Table View #}
|
||||
<div class="table-view">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Job ID" %}</th>
|
||||
{% comment %} <th scope="col">{% trans "Job Title" %}</th>
|
||||
<th scope="col">{% trans "Status" %}</th> {% endcomment %}
|
||||
<th scope="col">{% trans "Job Title" %}</th>
|
||||
<th scope="col">{% trans "Department" %}</th>
|
||||
<th scope="col">{% trans "Location" %}</th>
|
||||
<th scope="col">{% trans "Job Type" %}</th>
|
||||
<th scope="col">{% trans "Status" %}</th>
|
||||
<th scope="col">{% trans "Source" %}</th>
|
||||
<th scope="col">{% trans "Actions" %}</th>
|
||||
<th scop="col" class="text-center">{% trans "Manage Forms" %}</th>
|
||||
|
||||
<th scope="col" colspan="7" class="candidate-management-header">
|
||||
{% trans "Applicants Metrics" %}
|
||||
<table class="nested-header-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 14.28%;">{% trans "Applied" %}</th>
|
||||
<th style="width: 14.28%;">{% trans "Screened" %}</th>
|
||||
|
||||
<th colspan="2">{% trans "Exam" %}
|
||||
<table class="nested-stage-metrics">
|
||||
<thead>
|
||||
<th>P</th>
|
||||
<th>F</th>
|
||||
</thead>
|
||||
</table>
|
||||
</th>
|
||||
|
||||
<th colspan="2">{% trans "Interview" %}
|
||||
<table class="nested-stage-metrics">
|
||||
<thead>
|
||||
<th>P</th>
|
||||
<th>F</th>
|
||||
</thead>
|
||||
</table>
|
||||
</th>
|
||||
<th style="width: 14.28%;">{% trans "Offer" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</th>
|
||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% comment %} This loop relies on the 'jobs' variable passed from the Django view {% endcomment %}
|
||||
{% for job in jobs %}
|
||||
{% for job in page_obj %}
|
||||
<tr>
|
||||
<td class="fw-medium text-primary-theme">{{ job }}</td>
|
||||
{% comment %} <td class="fw-medium text-primary-theme">{{ job.title }}</td>
|
||||
<td><span class="badge bg-{{ job.status }} status-badge">{{ job.status }}</span></td> {% endcomment %}
|
||||
<td class="fw-medium">{{ job.title }}</td>
|
||||
<td>{{ job.department|default:"N/A" }}</td>
|
||||
<td>{{ job.get_location_display }}</td>
|
||||
<td>{{ job.get_job_type_display }}</td>
|
||||
<td><span class="badge bg-{{ job.status|lower|striptags|yesno:'active,draft,closed,archived' }} status-badge">{{ job.get_status_display }}</span></td>
|
||||
<td>{{ job.get_source }}</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary" title="{% trans 'View' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{% url 'job_update' job.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{% url 'form_wizard' job.form_template.id %}" class="btn btn-outline-primary" title="{% trans 'Preview' %}">
|
||||
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-primary" title="View">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{% url 'form_builder' job.form_template.id %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
||||
<a href="{% url 'job_update' job.slug %}" class="btn btn-outline-secondary" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Submissions' %}">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{# CANDIDATE MANAGEMENT DATA - 7 SEPARATE COLUMNS CORRESPONDING TO THE HEADER #}
|
||||
<td class="candidate-data-cell text-primary-theme"><a href="#" class="text-primary-theme">{% if job.metrics.applied %}{{ job.metrics.applied }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-info"><a href="#" class="text-info">{% if job.metrics.screening %}{{ job.metrics.screening }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-success"><a href="#" class="text-success">{% if job.metrics.exam_p %}{{ job.metrics.exam_p }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-danger"><a href="#" class="text-danger">{% if job.metrics.exam_f %}{{ job.metrics.exam_f }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-success"><a href="#" class="text-success">{% if job.metrics.interview_p %}{{ job.metrics.interview_p }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-danger"><a href="#" class="text-danger">{% if job.metrics.interview_f %}{{ job.metrics.interview_f }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-success"><a href="#" class="text-success">{% if job.metrics.offer %}{{ job.metrics.offer }}{% else %}-{% endif %}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -312,20 +251,45 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% comment %} Fallback/Empty State {% endcomment %}
|
||||
{% if not jobs and not job_list_data and not page_obj %}
|
||||
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="Job pagination" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1{% if status_filter %}&status={{ status_filter }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">First</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">Previous</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
||||
</li>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">Next</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">Last</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-5 card shadow-sm">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-briefcase fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
|
||||
<h3>{% trans "No job postings found" %}</h3>
|
||||
<p class="text-muted">{% trans "Create your first job posting to get started or adjust your filters." %}</p>
|
||||
<h3>No job postings found</h3>
|
||||
<p class="text-muted">Create your first job posting to get started or adjust your filters.</p>
|
||||
<a href="{% url 'job_create' %}" class="btn btn-main-action mt-3">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Create Job" %}
|
||||
<i class="fas fa-plus me-1"></i> Create Job
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -1,363 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}Candidate Tier Management - {{ job.title }} - ATS{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* Minimal Tier Management Styles */
|
||||
.tier-controls {
|
||||
background-color: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.tier-controls .form-row {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.tier-controls .form-group {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.bulk-update-controls {
|
||||
background-color: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border-radius: 0.375rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.stage-groups {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.stage-group {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.375rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.stage-group .stage-header {
|
||||
background-color: #495057;
|
||||
color: white;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-weight: 500;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.stage-group .stage-body {
|
||||
padding: 0.75rem;
|
||||
min-height: 80px;
|
||||
}
|
||||
.stage-candidate {
|
||||
padding: 0.375rem;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
}
|
||||
.stage-candidate:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.match-score {
|
||||
font-weight: 600;
|
||||
color: #0056b3;
|
||||
}
|
||||
.btn-sm {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.2rem 0.4rem;
|
||||
}
|
||||
|
||||
/* Tab Styles for Tiers */
|
||||
.nav-tabs {
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.nav-tabs .nav-link {
|
||||
border: none;
|
||||
color: #495057;
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.nav-tabs .nav-link:hover {
|
||||
border: none;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.nav-tabs .nav-link.active {
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
border: none;
|
||||
border-bottom: 2px solid #007bff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tier-1 .nav-link {
|
||||
color: #155724;
|
||||
}
|
||||
.tier-1 .nav-link.active {
|
||||
border-bottom-color: #28a745;
|
||||
}
|
||||
.tier-2 .nav-link {
|
||||
color: #856404;
|
||||
}
|
||||
.tier-2 .nav-link.active {
|
||||
border-bottom-color: #ffc107;
|
||||
}
|
||||
.tier-3 .nav-link {
|
||||
color: #721c24;
|
||||
}
|
||||
.tier-3 .nav-link.active {
|
||||
border-bottom-color: #dc3545;
|
||||
}
|
||||
|
||||
/* Candidate Table Styles */
|
||||
.candidate-table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
background-color: white;
|
||||
border-radius: 0.375rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
}
|
||||
.candidate-table thead {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.candidate-table th {
|
||||
padding: 0.75rem;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
color: #495057;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
.candidate-table td {
|
||||
padding: 0.75rem;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.candidate-table tbody tr:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.candidate-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.candidate-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.candidate-details {
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
.candidate-table-responsive {
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.stage-badge {
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
margin-left: 0.375rem;
|
||||
}
|
||||
.stage-Applied {
|
||||
background-color: #e9ecef;
|
||||
color: #495057;
|
||||
}
|
||||
.stage-Exam {
|
||||
background-color: #cce5ff;
|
||||
color: #004085;
|
||||
}
|
||||
.stage-Interview {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
}
|
||||
.stage-Offer {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
.exam-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
margin-top: 0.375rem;
|
||||
}
|
||||
.exam-controls select,
|
||||
.exam-controls input {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.125rem 0.25rem;
|
||||
}
|
||||
.tier-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
color: #495057;
|
||||
margin-left: 0.375rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">
|
||||
<i class="fas fa-layer-group me-2"></i>
|
||||
{% trans "Exam" %} - {{ job.title }}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
Total Candidates: {{ total_candidates }}
|
||||
</p>
|
||||
</div>
|
||||
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Job" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tier Controls -->
|
||||
<div class="tier-controls">
|
||||
<form method="post" class="mb-0">
|
||||
{% csrf_token %}
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="tier1_count">{% trans "Number of candidates in Tier 1 (Top N)" %}</label>
|
||||
<input type="number" name="tier1_count" id="tier1_count" class="form-control"
|
||||
value="{{ tier1_count }}" min="1" max="{{ total_candidates }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" name="update_tiers" class="btn btn-primary">
|
||||
<i class="fas fa-sync-alt me-1"></i> {% trans "Update Tiers" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tier Display -->
|
||||
<h2 class="h4 mb-3 mt-5">{% trans "Candidate Tiers" %}</h2>
|
||||
<div class="candidate-table-responsive" data-signals__ifmissing="{_fetching: false, selections: Array({{ candidates|length }}).fill(false)}">
|
||||
{% url "bulk_update_candidate_exam_status" job.slug as bulk_update_candidate_exam_status_url %}
|
||||
{% if candidates %}
|
||||
<button class="btn btn-primary"
|
||||
data-attr="{disabled: !$selections.filter(Boolean).length}"
|
||||
data-on-click="@post('{{bulk_update_candidate_exam_status_url}}',{
|
||||
contentType: 'form',
|
||||
selector: '#myform',
|
||||
headers: {'X-CSRFToken': '{{ csrf_token }}','status': 'pass'}
|
||||
})"
|
||||
>Mark as Pass and move to Interview</button>
|
||||
<button class="btn btn-danger"
|
||||
data-attr="{disabled: !$selections.filter(Boolean).length}"
|
||||
data-on-click="@post('{{bulk_update_candidate_exam_status_url}}',{
|
||||
contentType: 'form',
|
||||
selector: '#myform',
|
||||
headers: {'X-CSRFToken': '{{ csrf_token }}','status': 'fail'}
|
||||
})"
|
||||
>Mark as Failed</button>
|
||||
{% endif %}
|
||||
<form id="myform" action="{{move_to_exam_url}}" method="post">
|
||||
<table class="candidate-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% if candidates %}
|
||||
<div class="form-check">
|
||||
<input
|
||||
data-bind-_all
|
||||
data-on-change="$selections = Array({{ candidates|length }}).fill($_all)"
|
||||
data-effect="$selections; $_all = $selections.every(Boolean)"
|
||||
data-attr-disabled="$_fetching"
|
||||
type="checkbox" class="form-check-input" id="candidate-{{ candidate.id }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Contact" %}</th>
|
||||
<th>{% trans "AI Score" %}</th>
|
||||
<th>{% trans "Exam Status" %}</th>
|
||||
<th>{% trans "Exam Date" %}</th>
|
||||
<th>{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for candidate in candidates %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input
|
||||
data-bind-selections
|
||||
data-attr-disabled="$_fetching"
|
||||
name="{{ candidate.id }}"
|
||||
|
||||
type="checkbox" class="form-check-input" id="candidate-{{ candidate.id }}">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="candidate-name">{{ candidate.name }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="candidate-details">
|
||||
Email: {{ candidate.email }}<br>
|
||||
Phone: {{ candidate.phone }}<br>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-success">{{ candidate.match_score|default:"0" }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if candidate.exam_status == "Passed" %}
|
||||
<span class="badge bg-success">{{ candidate.exam_status }}</span>
|
||||
{% elif candidate.exam_status == "Failed" %}
|
||||
<span class="badge bg-danger">{{ candidate.exam_status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{candidate.exam_date|date:"M d, Y h:i A"}}</td>
|
||||
<td>
|
||||
<button class="btn btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'candidate_criteria_view_htmx' candidate.pk %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
>
|
||||
{% include "icons/view.html" %}
|
||||
{% trans "View" %}</button>
|
||||
<button class="btn btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'update_candidate_exam_status' candidate.slug %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
>
|
||||
{% include "icons/view.html" %}
|
||||
{% trans "Set Exam Date" %}</button>
|
||||
{% if candidate.stage != "Exam" %}
|
||||
<button hx-post="{% url 'candidate_set_exam_date' candidate.slug %}"
|
||||
hx-target=".candidate-table"
|
||||
hx-select=".candidate-table"
|
||||
hx-swap="outerHTML"
|
||||
class="btn btn-primary"> {% trans "Move to Exam" %} {% include "icons/right.html" %}</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Tab Content -->
|
||||
|
||||
<div class="modal fade modal-lg" id="candidateviewModal" tabindex="-1" aria-labelledby="candidateviewModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="candidateviewModalLabel">Form Settings</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div id="candidateviewModalBody" class="modal-body">
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -174,132 +174,59 @@
|
||||
{% trans "Number of Potential Candidates (Top N)" %}
|
||||
</label>
|
||||
<input type="number" name="tier1_count" id="tier1_count" class="form-control"
|
||||
value="{{ tier1_count }}" min="1" max="{{ total_candidates }}">
|
||||
value="{{ tier1_count }}" min="1" max="{{ total_candidates }}"
|
||||
placeholder="Enter N for top candidates">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" name="update_tiers" class="btn btn-primary">
|
||||
<i class="fas fa-sync-alt me-1"></i> {% trans "Update Tiers" %}
|
||||
|
||||
<div class="col-md-auto">
|
||||
<button type="submit" name="update_tiers" class="btn btn-main-action">
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Apply Filter" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="small text-muted mt-2 mb-0">
|
||||
{% trans "The candidates list is filtered by either the minimum AI Score OR the Top N candidates, whichever criteria results in a smaller set." %}
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tier Display -->
|
||||
<h2 class="h4 mb-3 mt-5">{% trans "Candidate Tiers" %}</h2>
|
||||
<div class="candidate-table-responsive" data-signals__ifmissing="{_fetching: false, selections: Array({{ candidates|length }}).fill(false)}">
|
||||
{% url "bulk_candidate_move_to_exam" as move_to_exam_url %}
|
||||
{% if candidates %}
|
||||
<button class="btn btn-primary"
|
||||
data-attr="{disabled: !$selections.filter(Boolean).length}"
|
||||
data-on-click="@post('{{move_to_exam_url}}',{
|
||||
contentType: 'form',
|
||||
selector: '#myform',
|
||||
headers: {'X-CSRFToken': '{{ csrf_token }}'}})"
|
||||
>Bulk Move To Exam</button>
|
||||
{% endif %}
|
||||
<form id="myform" action="{{move_to_exam_url}}" method="post">
|
||||
<table class="candidate-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{% if candidates %}
|
||||
<div class="form-check">
|
||||
<input
|
||||
data-bind-_all
|
||||
data-on-change="$selections = Array({{ candidates|length }}).fill($_all)"
|
||||
data-effect="$selections; $_all = $selections.every(Boolean)"
|
||||
data-attr-disabled="$_fetching"
|
||||
type="checkbox" class="form-check-input" id="candidate-{{ candidate.id }}">
|
||||
</div>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Contact" %}</th>
|
||||
<th>{% trans "AI Score" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "Stage" %}</th>
|
||||
<th>{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for candidate in candidates %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input
|
||||
data-bind-selections
|
||||
data-attr-disabled="$_fetching"
|
||||
name="{{ candidate.id }}"
|
||||
<hr class="my-4">
|
||||
|
||||
type="checkbox" class="form-check-input" id="candidate-{{ candidate.id }}">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="candidate-name">{{ candidate.name }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="candidate-details">
|
||||
Email: {{ candidate.email }}<br>
|
||||
Phone: {{ candidate.phone }}<br>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-success">{{ candidate.match_score|default:"0" }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge {% if candidate.applicant_status == 'Candidate' %}bg-success{% else %}bg-secondary{% endif %}">
|
||||
{{ candidate.get_applicant_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="stage-badge stage-{{ candidate.stage }}">
|
||||
{{ candidate.get_stage_display }}
|
||||
</span>
|
||||
{% if candidate.stage == "Exam" and candidate.exam_status %}
|
||||
<br>
|
||||
<span class="badge bg-info">{{ candidate.get_exam_status_display }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'candidate_criteria_view_htmx' candidate.pk %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
>
|
||||
{% include "icons/view.html" %}
|
||||
{% trans "View" %}</button>
|
||||
{% if candidate.stage != "Exam" %}
|
||||
<button hx-post="{% url 'candidate_move_to_exam' candidate.pk %}"
|
||||
hx-target=".candidate-table"
|
||||
hx-select=".candidate-table"
|
||||
hx-swap="outerHTML"
|
||||
class="btn btn-primary"> {% trans "Move to Exam" %} {% include "icons/right.html" %}</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Tab Content -->
|
||||
<ul class="nav nav-pills mb-3 justify-content-center" id="candidateViewTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="all-applicants-tab" data-bs-toggle="tab" data-bs-target="#all-applicants" type="button"
|
||||
role="tab" aria-controls="all-applicants" aria-selected="true">
|
||||
<i class="fas fa-users me-1"></i> {% trans "All Applicants" %}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="potential-candidates-tab" data-bs-toggle="tab" data-bs-target="#potential-candidates" type="button"
|
||||
role="tab" aria-controls="potential-candidates" aria-selected="false">
|
||||
<i class="fas fa-trophy me-1"></i> {% trans "Potential Candidates" %}
|
||||
<span class="candidate-indicator-badge">{{ tier1_candidates|length }}</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="modal fade modal-lg" id="candidateviewModal" tabindex="-1" aria-labelledby="candidateviewModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="candidateviewModalLabel">Form Settings</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div id="candidateviewModalBody" class="modal-body">
|
||||
<div class="tab-content kaauh-card p-3 shadow-sm" id="candidateViewTabContent">
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
</div>
|
||||
<div class="tab-pane fade show active" id="all-applicants" role="tabpanel" aria-labelledby="all-applicants-tab">
|
||||
<h2 class="h5 mb-3" style="color: var(--kaauh-teal-dark);">{% trans "Complete List of All Submissions" %}</h2>
|
||||
{% include "recruitment/partials/_candidate_table.html" with candidates=all_applicants_list is_potential_view=False only %}
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="potential-candidates" role="tabpanel" aria-labelledby="potential-candidates-tab">
|
||||
<h2 class="h5 mb-3" style="color: var(--kaauh-teal-dark);">
|
||||
{% trans "Top" %} {{ tier1_candidates|length }} {% trans "Candidates by AI Score" %}
|
||||
</h2>
|
||||
{% include "recruitment/partials/_candidate_table.html" with candidates=tier1_candidates is_potential_view=True only %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% comment %}
|
||||
Modals for candidate view and exam status update should be included here,
|
||||
e.g., {% include "includes/_modals_for_candidate_management.html" %}
|
||||
{% endcomment %}
|
||||
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user