Merge pull request 'job list table' (#11) from frontend into main

Reviewed-on: #11
This commit is contained in:
ismail 2025-10-13 17:12:32 +03:00
commit ce15603802
34 changed files with 2617 additions and 1166 deletions

Binary file not shown.

View File

@ -195,9 +195,9 @@ class JobPostingForm(forms.ModelForm):
fields = [
'title', 'department', 'job_type', 'workplace_type',
'location_city', 'location_state', 'location_country',
'description', 'qualifications', 'salary_range', 'benefits'
'description', 'qualifications', 'salary_range', 'benefits','application_start_date'
,'application_deadline', 'application_instructions',
'position_number', 'reporting_to', 'start_date', 'status',
'position_number', 'reporting_to', 'joining_date', 'status',
'created_by','open_positions','hash_tags'
]
widgets = {
@ -247,6 +247,10 @@ class JobPostingForm(forms.ModelForm):
# 'placeholder': 'https://university.edu/careers/job123',
# 'required': True
# }),
'application_start_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'application_deadline': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
@ -272,7 +276,7 @@ class JobPostingForm(forms.ModelForm):
'class': 'form-control',
'placeholder': 'Department Chair, Director, etc.'
}),
'start_date': forms.DateInput(attrs={
'joining_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
@ -333,31 +337,31 @@ class JobPostingForm(forms.ModelForm):
raise forms.ValidationError('Please enter a valid URL (e.g., https://example.com)')
return url
def clean(self):
"""Cross-field validation"""
cleaned_data = super().clean()
# def clean(self):
# """Cross-field validation"""
# cleaned_data = super().clean()
# Validate dates
start_date = cleaned_data.get('start_date')
application_deadline = cleaned_data.get('application_deadline')
# # Validate dates
# start_date = cleaned_data.get('start_date')
# application_deadline = cleaned_data.get('application_deadline')
# Perform cross-field validation only if both fields have values
if start_date and application_deadline:
if application_deadline > start_date:
self.add_error('application_deadline',
'The application deadline must be set BEFORE the job start date.')
# # Perform cross-field validation only if both fields have values
# if start_date and application_deadline:
# if application_deadline > start_date:
# self.add_error('application_deadline',
# 'The application deadline must be set BEFORE the job start date.')
# # Validate that if status is ACTIVE, we have required fields
# status = cleaned_data.get('status')
# if status == 'ACTIVE':
# if not cleaned_data.get('application_url'):
# self.add_error('application_url',
# 'Application URL is required for active jobs.')
# if not cleaned_data.get('description'):
# self.add_error('description',
# 'Job description is required for active jobs.')
# # # Validate that if status is ACTIVE, we have required fields
# # status = cleaned_data.get('status')
# # if status == 'ACTIVE':
# # if not cleaned_data.get('application_url'):
# # self.add_error('application_url',
# # 'Application URL is required for active jobs.')
# # if not cleaned_data.get('description'):
# # self.add_error('description',
# # 'Job description is required for active jobs.')
return cleaned_data
# return cleaned_data
class JobPostingImageForm(forms.ModelForm):
class Meta:

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2025-10-12 10:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0001_initial'),
]
operations = [
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),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.2.7 on 2025-10-12 13:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0002_alter_jobposting_status'),
]
operations = [
migrations.RenameField(
model_name='jobposting',
old_name='start_date',
new_name='joining_date',
),
migrations.AddField(
model_name='jobposting',
name='application_start_date',
field=models.DateField(blank=True, null=True),
),
]

View File

@ -98,6 +98,7 @@ class JobPosting(Base):
null=True,
blank=True,
)
application_start_date=models.DateField(null=True, blank=True)
application_deadline = models.DateField(null=True, blank=True)
application_instructions =CKEditor5Field(
blank=True, null=True,config_name='extends'
@ -150,7 +151,7 @@ class JobPosting(Base):
reporting_to = models.CharField(
max_length=100, blank=True, help_text="Who this position reports to"
)
start_date = models.DateField(null=True, blank=True, help_text="Desired start date")
joining_date = models.DateField(null=True, blank=True, help_text="Desired start date")
open_positions = models.PositiveIntegerField(
default=1, help_text="Number of open positions for this job"
)
@ -228,7 +229,7 @@ class JobPosting(Base):
parts.append(self.location_city)
if self.location_state:
parts.append(self.location_state)
if self.location_country and self.location_country != "United States":
if self.location_country:
parts.append(self.location_country)
return ", ".join(parts) if parts else "Not specified"

View File

@ -15,6 +15,7 @@ urlpatterns = [
path('jobs/<slug:slug>/', views.job_detail, name='job_detail'),
path('jobs/<slug:slug>/candidate/', views.job_detail_candidate, name='job_detail_candidate'),
path('jobs/<slug:slug>/candidate/application/success', views.application_success, name='application_success'),
path('careers/',views.kaauh_career,name='kaauh_career'),
# LinkedIn Integration URLs

View File

@ -277,17 +277,19 @@ def job_detail(request, slug):
# Get all candidates for this job, ordered by most recent
candidates = job.candidates.all().order_by("-created_at")
applicants = job.candidates.all().order_by("-created_at")
# Count candidates by stage for summary statistics
total_candidates = candidates.count()
applied_count = candidates.filter(stage="Applied").count()
interview_count = candidates.filter(stage="Interview").count()
offer_count = candidates.filter(stage="Offer").count()
total_applicant = applicants.count()
applied_count = applicants.filter(stage="Applied").count()
interview_count = applicants.filter(stage="Interview").count()
offer_count = applicants.filter(stage="Offer").count()
status_form = JobPostingStatusForm(instance=job)
image_upload_form=JobPostingImageForm(instance=job)
# 2. Check for POST request (Status Update Submission)
if request.method == 'POST':
@ -309,8 +311,8 @@ def job_detail(request, slug):
context = {
"job": job,
"candidates": candidates,
"total_candidates": total_candidates,
"applicants": applicants,
"total_applicants": total_applicant,
"applied_count": applied_count,
"interview_count": interview_count,
"offer_count": offer_count,
@ -339,6 +341,16 @@ def job_image_upload(request, slug):
return redirect('job_detail', slug=job.slug)
def kaauh_career(request):
active_jobs = JobPosting.objects.select_related(
'form_template'
).filter(
status='ACTIVE',
form_template__is_active=True
)
return render(request,'jobs/career.html',{'active_jobs':active_jobs})
# job detail facing the candidate:
@ -952,6 +964,8 @@ 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

View File

@ -92,6 +92,8 @@ class JobCandidatesListView(LoginRequiredMixin, ListView):
context_object_name = 'candidates'
paginate_by = 10
def get_queryset(self):
# Get the job by slug
self.job = get_object_or_404(models.JobPosting, slug=self.kwargs['slug'])
@ -99,6 +101,11 @@ class JobCandidatesListView(LoginRequiredMixin, ListView):
# Filter candidates for this specific job
queryset = models.Candidate.objects.filter(job=self.job)
if self.request.GET.get('stage'):
stage=self.request.GET.get('stage')
queryset=queryset.filter(stage=stage)
# Handle search
search_query = self.request.GET.get('search', '')
if search_query:

View File

@ -22,7 +22,15 @@
--kaauh-border: #eaeff3;
}
/* NEW CLASS FOR WIDER CONTENT */
.max-width-1600 {
max-width: 1600px;
margin-right: auto;
margin-left: auto;
padding-right: var(--bs-gutter-x, 0.75rem); /* Add Bootstrap padding for responsiveness */
padding-left: var(--bs-gutter-x, 0.75rem);
}
/* === Top Bar === */
.top-bar {
background-color: white;
@ -45,7 +53,7 @@
padding: 0.25rem 0.5rem;
}
.top-bar .logo-container img {
height: 40px;
height: 60px;
object-fit: contain;
}
@ -68,6 +76,10 @@
background-color: var(--kaauh-teal) !important;
box-shadow: 0 2px 6px rgba(0,0,0,0.12);
}
/* Change the outer navbar container to fluid, rely on inner max-width */
.navbar-dark > .container {
max-width: 100%; /* Override default container width */
}
.nav-link {
font-weight: 500;
transition: all 0.2s ease;
@ -205,7 +217,8 @@
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
main.container {
/* The main content width is already handled by the inline style, but making it explicit here */
main.container-fluid {
min-height: calc(100vh - 200px);
padding: 1.5rem 0;
}
@ -235,244 +248,265 @@
<body class="d-flex flex-column min-vh-100" hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
<div class="top-bar d-none d-md-block">
<div class="container d-flex justify-content-between align-items-center gap-2">
<div class="d-flex align-items-center gap-3 social-icons">
{% comment %} <span class="text-muted">{% trans "Follow Us:" %}</span>
<a href="#" aria-label="Facebook"><i class="fab fa-facebook-f"></i></a>
<a href="#" aria-label="Twitter"><i class="fab fa-twitter"></i></a>
<a href="#" aria-label="Instagram"><i class="fab fa-instagram"></i></a> {% endcomment %}
</div>
<div class="contact-info d-flex gap-3">
{% comment %} <div class="contact-item">
<i class="fas fa-envelope text-primary"></i>
<span>info@kaauh.edu.sa</span>
{# Changed container to container-fluid and added max-width-1600 to inner div #}
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center gap-2 max-width-1600">
<div class="d-flex align-items-center gap-3 social-icons">
{% comment %} <span class="text-muted">{% trans "Follow Us:" %}</span>
<a href="#" aria-label="Facebook"><i class="fab fa-facebook-f"></i></a>
<a href="#" aria-label="Twitter"><i class="fab fa-twitter"></i></a>
<a href="#" aria-label="Instagram"><i class="fab fa-instagram"></i></a> {% endcomment %}
</div>
<div class="contact-info d-flex gap-3">
{% comment %} <div class="contact-item">
<i class="fas fa-envelope text-primary"></i>
<span>info@kaauh.edu.sa</span>
</div>
<div class="contact-item">
<i class="fas fa-phone text-primary"></i>
<span>+966 11 820 0000</span>
</div> {% endcomment %}
</div>
<div class="logo-container d-flex gap-2">
<img src="{% static 'image/vision.svg' %}" alt="{% trans 'Saudi Vision 2030' %}" loading="lazy">
<div class="kaauh-logo-container d-flex flex-column flex-md-row align-items-center gap-2 ms-4">
<div class="hospital-text text-center text-md-start me-3">
<div class="ar small">جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية</div>
<div class="ar small">ومستشفى الملك عبدالله بن عبدالعزيز التخصصي</div>
<div class="en small">Princess Nourah bint Abdulrahman University</div>
<div class="en small">King Abdullah bin Abdulaziz University Hospital</div>
</div>
</div>
<img src="{% static 'image/kaauh.png' %}" alt="KAAUH Logo" style="max-height: 100px;max-width:100px;">
</div>
<div class="contact-item">
<i class="fas fa-phone text-primary"></i>
<span>+966 11 820 0000</span>
</div> {% endcomment %}
</div>
<div class="logo-container d-flex gap-2">
<img src="{% static 'image/vision.svg' %}" alt="{% trans 'Saudi Vision 2030' %}" loading="lazy">
<img src="{% static 'image/hospital_logo_3.png' %}" alt="{% trans 'King Abdullah Academic University Hospital' %}" loading="lazy">
</div>
</div>
</div>
<nav class="navbar navbar-expand-lg navbar-dark sticky-top">
<div class="container">
<a class="navbar-brand text-white" href="{% url 'dashboard' %}">
<img src="{% static 'image/kaauh.jpeg' %}" alt="{% trans 'Saudi Vision 2030' %}" style="width: 60px; height: 60px;">
</a>
{# Changed container to container-fluid and added max-width-1600 to inner div #}
<div class="container-fluid">
<div class="navbar-content-wrapper max-width-1600 d-flex justify-content-between align-items-center" style="width: 100%;">
<a class="navbar-brand text-white d-none d-md-block" href="{% url 'dashboard' %}">
<img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" style="width: 60px; height: 60px;">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="{% trans 'Toggle navigation' %}">
<span class="navbar-toggler-icon"></span>
</button>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="{% trans 'Toggle navigation' %}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% comment %} <li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}" href="{% url 'dashboard' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/dashboard.html" %}
{% trans "Dashboard" %}
</span>
</a>
</li> {% endcomment %}
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'job_list' %}active{% endif %}" href="{% url 'job_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/jobs.html" %}
{% trans "Jobs" %}
</span>
</a>
</li>
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'candidate_list' %}active{% endif %}" href="{% url 'candidate_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/users.html" %}
{% trans "Candidates" %}
</span>
</a>
</li>
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'training_list' %}active{% endif %}" href="{% url 'training_list' %}">
<span class="d-flex align-items-center gap-2">
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% comment %} <li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}" href="{% url 'dashboard' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/dashboard.html" %}
{% trans "Dashboard" %}
</span>
</a>
</li> {% endcomment %}
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'job_list' %}active{% endif %}" href="{% url 'job_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/jobs.html" %}
{% trans "Jobs" %}
</span>
</a>
</li>
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'form_templates_list' %}active{% endif %}" href="{% url 'form_templates_list' %}">
<span class="d-flex align-items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>
{% trans "Form Templates" %}
</span>
</a>
</li>
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'candidate_list' %}active{% endif %}" href="{% url 'candidate_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/users.html" %}
{% trans "Candidates" %}
</span>
</a>
</li>
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'list_meetings' %}active{% endif %}" href="{% url 'list_meetings' %}">
<span class="d-flex align-items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.26 10.147a60.438 60.438 0 0 0-.491 6.347A48.62 48.62 0 0 1 12 20.904a48.62 48.62 0 0 1 8.232-4.41 60.46 60.46 0 0 0-.491-6.347m-15.482 0a50.636 50.636 0 0 0-2.658-.813A59.906 59.906 0 0 1 12 3.493a59.903 59.903 0 0 1 10.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.717 50.717 0 0 1 12 13.489a50.702 50.702 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm0 0v-3.675A55.378 55.378 0 0 1 12 8.443m-7.007 11.55A5.981 5.981 0 0 0 6.75 15.75v-1.5" />
</svg>
{% trans "Training" %}
</span>
</a>
</li>
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'list_meetings' %}active{% endif %}" href="{% url 'list_meetings' %}">
<span class="d-flex align-items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>
{% trans "Meetings" %}
</span>
</a>
</li>
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'form_templates_list' %}active{% endif %}" href="{% url 'form_templates_list' %}">
<span class="d-flex align-items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg>
{% trans "Form Templates" %}
</span>
</a>
</li>
<li class="nav-item dropdown ms-2">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
data-bs-offset="0, 8" data-bs-auto-close="outside">
{% trans "More" %}
</a>
<ul class="dropdown-menu" data-bs-popper="static">
<li><a class="dropdown-item" href="#"><i class="fas fa-calendar me-2"></i> {% trans "Meetings" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-clock me-2"></i> {% trans "Schedule" %}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-briefcase me-2"></i> {% trans "Active Jobs" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-file-alt me-2"></i> {% trans "Draft Jobs" %}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-users me-2"></i> {% trans "All Candidates" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-user-plus me-2"></i> {% trans "New Candidates" %}</a></li>
</ul>
</li>
</ul>
<ul class="navbar-nav me-2">
<li class="nav-item dropdown">
<a class="language-toggle-btn dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}">
<i class="fas fa-globe"></i>
<span class="d-none d-lg-inline">{{ LANGUAGE_CODE|upper }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" data-bs-popper="static">
{% get_current_language as LANGUAGE_CODE %}
<li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit">
<span class="me-2">🇺🇸</span> English
</button>
</form>
</li>
<li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit">
<span class="me-2">🇸🇦</span> العربية (Arabic)
</button>
</form>
</li>
</ul>
</li>
</ul>
<ul class="navbar-nav ms-4">
<li class="nav-item dropdown">
<button
class="nav-link p-0 border-0 bg-transparent dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
aria-label="{% trans 'Toggle user menu' %}"
data-bs-auto-close="outside"
data-bs-offset="0, 8"
>
{% if user.profile.profile_image %}
<img src="{{ user.profile.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar"
style="width: 36px; height: 36px; object-fit: cover; background-color: var(--kaauh-teal); display: inline-block; vertical-align: middle;"
title="{% trans 'Your account' %}">
{% else %}
<div class="profile-avatar" title="{% trans 'Your account' %}">
{{ user.username|first|upper }}
</div>
{% endif %}
{% comment %} <span class="ms-2 d-none d-lg-inline fw-semibold">{{ user.username }}</span> {% endcomment %}
</button>
<ul
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
data-bs-popper="static"
style="min-width: 240px;"
>
<li class="px-4 py-3 ">
<div class="d-flex align-items-center">
<div class="me-3 d-flex align-items-center justify-content-center" style="min-width: 48px;">
{% if user.profile.profile_image %}
<img src="{{ user.profile.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar shadow-sm border"
style="width: 44px; height: 44px; object-fit: cover; background-color: var(--kaauh-teal); display: block;"
title="{% trans 'Your account' %}">
{% else %}
<div class="profile-avatar shadow-sm border d-flex align-items-center justify-content-center"
style="width: 44px; height: 44px; background-color: var(--kaauh-teal); font-size: 1.2rem;">
{{ user.username|first|upper }}
</div>
{% endif %}
</div>
<div>
<div class="fw-semibold text-dark">{{ user.get_full_name|default:user.username }}</div>
<div class="text-muted small">{{ user.email|truncatechars:24 }}</div>
</div>
</div>
</li>
<li><hr class="dropdown-divider my-1"></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-user-circle me-3 text-primary fs-5"></i> <span>{% trans "My Profile" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-cog me-3 text-primary fs-5"></i> <span>{% trans "Settings" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-history me-3 text-primary fs-5"></i> <span>{% trans "Activity Log" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-question-circle me-3 text-primary fs-5"></i> <span>{% trans "Help & Support" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#">
{% if not request.session.linkedin_authenticated %}
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'linkedin_login' %}">
<i class="fab fa-linkedin me-1"></i> {% trans "Connect LinkedIn" %}
</a>
</li>
</ul>
{% else %}
<i class="fab fa-linkedin text-primary me-1"></i>
<span class="text-primary d-none d-lg-inline ms-auto me-3">
{% trans "LinkedIn Connected" %}
{% trans "Meetings" %}
</span>
{% endif %}
</a></li>
</a>
</li>
<li class="nav-item me-2">
<a class="nav-link {% if request.resolver_match.url_name == 'training_list' %}active{% endif %}" href="{% url 'training_list' %}">
<span class="d-flex align-items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.26 10.147a60.438 60.438 0 0 0-.491 6.347A48.62 48.62 0 0 1 12 20.904a48.62 48.62 0 0 1 8.232-4.41 60.46 60.46 0 0 0-.491-6.347m-15.482 0a50.636 50.636 0 0 0-2.658-.813A59.906 59.906 0 0 1 12 3.493a59.903 59.903 0 0 1 10.399 5.84c-.896.248-1.783.52-2.658.814m-15.482 0A50.717 50.717 0 0 1 12 13.489a50.702 50.702 0 0 1 7.74-3.342M6.75 15a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm0 0v-3.675A55.378 55.378 0 0 1 12 8.443m-7.007 11.55A5.981 5.981 0 0 0 6.75 15.75v-1.5" />
</svg>
<li><hr class="dropdown-divider my-1"></li>
<li>
<form method="post" action="" class="d-inline">
{% csrf_token %}
<button
type="submit"
class="dropdown-item py-2 px-4 text-danger d-flex align-items-center border-0 bg-transparent text-start"
aria-label="{% trans 'Sign out' %}"
>
<i class="fas fa-sign-out-alt me-3 fs-5"></i>
<span>{% trans "Sign Out" %}</span>
</button>
</form>
</li>
</ul>
</li>
{% trans "Training" %}
</span>
</a>
</li>
<li class="nav-item dropdown ms-2">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
data-bs-offset="0, 8" data-bs-auto-close="outside">
{% trans "More" %}
</a>
<ul class="dropdown-menu" data-bs-popper="static">
<li><a class="dropdown-item" href="#"><i class="fas fa-calendar me-2"></i> {% trans "Meetings" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-clock me-2"></i> {% trans "Schedule" %}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-briefcase me-2"></i> {% trans "Active Jobs" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-file-alt me-2"></i> {% trans "Draft Jobs" %}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-users me-2"></i> {% trans "All Candidates" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-user-plus me-2"></i> {% trans "New Candidates" %}</a></li>
</ul>
</li>
</ul>
<ul class="navbar-nav me-2">
<li class="nav-item dropdown">
<a class="language-toggle-btn dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}">
<i class="fas fa-globe"></i>
<span class="d-none d-lg-inline">{{ LANGUAGE_CODE|upper }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" data-bs-popper="static">
{% get_current_language as LANGUAGE_CODE %}
<li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit">
<span class="me-2">🇺🇸</span> English
</button>
</form>
</li>
<li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit">
<span class="me-2">🇸🇦</span> العربية (Arabic)
</button>
</form>
</li>
</ul>
</li>
</ul>
<ul class="navbar-nav ms-4">
<li class="nav-item dropdown">
<button
class="nav-link p-0 border-0 bg-transparent dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
aria-label="{% trans 'Toggle user menu' %}"
data-bs-auto-close="outside"
data-bs-offset="0, 8"
>
{% if user.profile.profile_image %}
<img src="{{ user.profile.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar"
style="width: 36px; height: 36px; object-fit: cover; background-color: var(--kaauh-teal); display: inline-block; vertical-align: middle;"
title="{% trans 'Your account' %}">
{% else %}
<div class="profile-avatar" title="{% trans 'Your account' %}">
{{ user.username|first|upper }}
</div>
{% endif %}
{% comment %} <span class="ms-2 d-none d-lg-inline fw-semibold">{{ user.username }}</span> {% endcomment %}
</button>
<ul
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
data-bs-popper="static"
style="min-width: 240px;"
>
<li class="px-4 py-3 ">
<div class="d-flex align-items-center">
<div class="me-3 d-flex align-items-center justify-content-center" style="min-width: 48px;">
{% if user.profile.profile_image %}
<img src="{{ user.profile.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar shadow-sm border"
style="width: 44px; height: 44px; object-fit: cover; background-color: var(--kaauh-teal); display: block;"
title="{% trans 'Your account' %}">
{% else %}
<div class="profile-avatar shadow-sm border d-flex align-items-center justify-content-center"
style="width: 44px; height: 44px; background-color: var(--kaauh-teal); font-size: 1.2rem;">
{{ user.username|first|upper }}
</div>
{% endif %}
</div>
<div>
<div class="fw-semibold text-dark">{{ user.get_full_name|default:user.username }}</div>
<div class="text-muted small">{{ user.email|truncatechars:24 }}</div>
</div>
</div>
</li>
<li><hr class="dropdown-divider my-1"></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-user-circle me-3 text-primary fs-5"></i> <span>{% trans "My Profile" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-cog me-3 text-primary fs-5"></i> <span>{% trans "Settings" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-history me-3 text-primary fs-5"></i> <span>{% trans "Activity Log" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-question-circle me-3 text-primary fs-5"></i> <span>{% trans "Help & Support" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#">
{% if not request.session.linkedin_authenticated %}
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'linkedin_login' %}">
<i class="fab fa-linkedin me-1"></i> {% trans "Connect LinkedIn" %}
</a>
</li>
</ul>
{% else %}
<i class="fab fa-linkedin text-primary me-1"></i>
<span class="text-primary d-none d-lg-inline ms-auto me-3">
{% trans "LinkedIn Connected" %}
</span>
{% endif %}
</a></li>
<li><hr class="dropdown-divider my-1"></li>
<li>
<form method="post" action="" class="d-inline">
{% csrf_token %}
<button
type="submit"
class="dropdown-item py-2 px-4 text-danger d-flex align-items-center border-0 bg-transparent text-start"
aria-label="{% trans 'Sign out' %}"
>
<i class="fas fa-sign-out-alt me-3 fs-5"></i>
<span>{% trans "Sign Out" %}</span>
</button>
</form>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</nav>
<main class="container flex-grow-1">
<main class="container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
@ -484,14 +518,24 @@
{% block content %}
{% endblock %}
</main>
<footer class="footer mt-auto">
<div class="container text-center">
<p class="mb-0">
&copy; {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %}<br>
<small>{% trans "All rights reserved." %}</small>
</p>
<footer class="mt-auto">
<div class="footer-bottom py-3 small text-muted" style="background-color: #00363a;">
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center flex-wrap max-width-1600">
<p class="mb-0 text-white-50">
&copy; {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %}
{% trans "All rights reserved." %}
</p>
<a class="text-decoration-none" href="https://tenhal.sa/" target='_blank'>
<p class="mb-0 text-white-50">
{% trans "Powered by" %} <strong class="text-white">Tenhal</strong>
</p>
</a>
</div>
</div>
</footer>
</div>
</footer>
{% include 'includes/delete_modal.html' %}

View File

@ -5,7 +5,9 @@
<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 {
@ -771,8 +773,22 @@
</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
@ -961,7 +977,9 @@
</div>
</div>
</div>
<script>
// Application State
const state = {
draggedStageIndex: null,

View File

@ -1,45 +1,198 @@
{% extends "base.html" %}
{% load i18n static %}
{% load form_filters %}
{% block title %}{{ form.name }} - Submission Details{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e; /* Primary */
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
.text-info { color: #17a2b8 !important; }
.text-success { color: #28a745 !important; }
.text-secondary { color: #6c757d !important; }
.bg-info { background-color: #17a2b8 !important; }
.bg-secondary { background-color: #6c757d !important; }
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
.card-header {
font-weight: 600;
padding: 1rem 1.25rem;
background-color: #f8f9fa; /* Light background */
border-bottom: 1px solid var(--kaauh-border);
}
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.5rem 1rem;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary outline button */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
font-weight: 500;
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* ================================================= */
/* RESPONSES TABLE SPECIFIC STYLES (Horizontal Layout) */
/* ================================================= */
/* Main table container */
.table-submission {
margin-bottom: 0;
border: none;
table-layout: auto; /* Allow columns to resize based on content */
}
/* Fixed first column (the row headers) */
.table-submission th:first-child,
.table-submission td:first-child {
background-color: #f0f4f7; /* Slightly darker than header */
font-weight: 600;
color: var(--kaauh-primary-text);
width: 150px;
min-width: 150px;
position: sticky; /* Keep it visible when scrolling right */
left: 0;
z-index: 2;
border-right: 1px solid var(--kaauh-border);
}
.table-submission th:first-child {
top: 0; /* Important for sticky header/row-header intersection */
}
/* Field Label Header Row (Top Row) */
.table-submission thead th {
font-weight: 600;
background-color: #e9ecef; /* Light gray for headers */
color: var(--kaauh-primary-text);
vertical-align: middle;
text-align: center;
border: 1px solid var(--kaauh-border);
border-bottom: 2px solid var(--kaauh-teal); /* Highlight the bottom of the header */
padding: 0.75rem 0.5rem;
}
.table-submission thead th:not(:first-child) {
min-width: 200px; /* Give response columns space */
max-width: 300px;
white-space: normal;
word-wrap: break-word;
}
/* Data Cells */
.table-submission tbody td {
vertical-align: top;
padding: 0.75rem 0.75rem;
border: 1px solid var(--kaauh-border);
border-top: none;
}
/* Styling for multi-value responses */
.table-submission .badge {
font-weight: 500;
margin-top: 0.25rem;
}
/* File display */
.table-submission .fa-file {
color: var(--kaauh-teal);
}
.table-submission .btn-outline-primary {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="container-fluid py-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'job_detail' submission.template.job.slug %}">Job Detail</a></li>
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}">Form Template</a></li>
<li class="breadcrumb-item active" aria-current="page">Submission Details</li>
</ol>
</nav>
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2>Submission Details</h2>
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="text-primary fw-bold">{% trans "Submission Details" %}</h2>
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Back to Submissions
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
</a>
</div>
</div>
</div>
<!-- Basic Information -->
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="card shadow-sm mb-4">
<div class="card-header">
<h5 class="mb-0">{% trans "Submission Metadata" %}</h5>
</div>
<div class="card-body small">
<div class="row g-3">
<div class="col-md-4">
<strong>Submission ID:</strong> {{ submission.id }}
<i class="fas fa-fingerprint me-2 text-primary"></i>
<strong>{% trans "Submission ID:" %}</strong> <span class="text-secondary">{{ submission.id }}</span>
</div>
<div class="col-md-4">
<strong>Submitted:</strong> {{ submission.submitted_at|date:"M d, Y H:i" }}
<i class="fas fa-calendar-check me-2 text-primary"></i>
<strong>{% trans "Submitted:" %}</strong> <span class="text-secondary">{{ submission.submitted_at|date:"M d, Y H:i" }}</span>
</div>
<div class="col-md-4">
<strong>Form:</strong> {{ form.name }}
<i class="fas fa-file-alt me-2 text-primary"></i>
<strong>{% trans "Form:" %}</strong> <span class="text-secondary">{{ submission.template.name }}</span>
</div>
</div>
{% if submission.applicant_name or submission.applicant_email %}
<div class="row mt-2">
<div class="row g-3 mt-1">
{% if submission.applicant_name %}
<div class="col-md-6">
<strong>Applicant Name:</strong> {{ submission.applicant_name }}
<div class="col-md-4">
<i class="fas fa-user me-2 text-primary"></i>
<strong>{% trans "Applicant Name:" %}</strong> <span class="text-secondary">{{ submission.applicant_name }}</span>
</div>
{% endif %}
{% if submission.applicant_email %}
<div class="col-md-6">
<strong>Email:</strong> {{ submission.applicant_email }}
<div class="col-md-4">
<i class="fas fa-envelope me-2 text-primary"></i>
<strong>{% trans "Email:" %}</strong> <span class="text-secondary">{{ submission.applicant_email }}</span>
</div>
{% endif %}
</div>
@ -47,19 +200,20 @@
</div>
</div>
<!-- Responses Table -->
<div class="card">
<div class="card-body">
<h5 class="mb-3">Responses</h5>
<div class="card shadow-sm">
<div class="card-header">
<h5 class="mb-0">{% trans "Form Responses" %}</h5>
</div>
<div class="card-body p-0">
{% with submission=submission %}
{% get_all_responses_flat submission as flat_responses %}
{% if flat_responses %}
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead class="table-light">
<table class="table table-submission table-hover">
<thead>
<tr>
<th scope="col" style="width: 150px;">Field Label</th>
<th scope="col">{% trans "Field Property" %}</th>
{% for response in flat_responses %}
<th scope="col">{{ response.field_label }}</th>
{% endfor %}
@ -67,50 +221,50 @@
</thead>
<tbody>
<tr>
<td><strong>Response Value</strong></td>
<td><strong>{% trans "Response Value" %}</strong></td>
{% for response in flat_responses %}
<td>
{% if response.uploaded_file %}
<div>
<span class="text-primary"><i class="fas fa-file"></i> {{ response.uploaded_file.name }}</span>
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-primary ms-2" target="_blank" title="Download File">
<i class="fas fa-download"></i>
<span class="d-block text-truncate" style="max-width: 180px;"><i class="fas fa-file me-1"></i> {{ response.uploaded_file.name }}</span>
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-secondary mt-1" target="_blank" title="{% trans 'Download File' %}">
<i class="fas fa-download"></i> {% trans "Download" %}
</a>
</div>
{% elif response.value %}
{% if response.field_type == 'checkbox' and response.value|length > 0 %}
<div>
<div class="d-flex flex-wrap gap-1">
{% for val in response.value %}
<span class="badge bg-secondary me-1">{{ val }}</span>
<span class="badge bg-secondary">{{ val }}</span>
{% endfor %}
</div>
{% elif response.field_type == 'radio' or response.field_type == 'select' %}
<span class="badge bg-info">{{ response.value }}</span>
{% else %}
<p class="mb-0">{{ response.value|linebreaksbr }}</p>
<p class="mb-0 small text-wrap">{{ response.value|linebreaksbr }}</p>
{% endif %}
{% else %}
<span class="text-muted">Not provided</span>
<span class="text-muted small">{% trans "Not provided" %}</span>
{% endif %}
</td>
{% endfor %}
</tr>
<tr>
<td><strong>Stage</strong></td>
<td><strong>{% trans "Associated Stage" %}</strong></td>
{% for response in flat_responses %}
<td>
{{ response.stage_name|default:"N/A" }}
<span class="small text-secondary">{{ response.stage_name|default:"N/A" }}</span>
</td>
{% endfor %}
</tr>
<tr>
<td><strong>Required</strong></td>
<td><strong>{% trans "Field Required" %}</strong></td>
{% for response in flat_responses %}
<td>
{% if response.required %}
<span class="text-danger"><i class="fas fa-asterisk"></i> Yes</span>
<span class="text-danger small"><i class="fas fa-asterisk"></i> {% trans "Yes" %}</span>
{% else %}
<span>No</span>
<span class="small text-success">{% trans "No" %}</span>
{% endif %}
</td>
{% endfor %}
@ -119,43 +273,14 @@
</table>
</div>
{% else %}
<div class="text-center text-muted py-4">
<p>No responses found for this submission.</p>
<div class="text-center text-muted py-5 px-3">
<i class="fas fa-exclamation-circle fa-2x mb-3"></i>
<p class="lead">{% trans "No response fields were found for this submission." %}</p>
<p class="small">{% trans "This may occur if the form template was modified or responses were cleared." %}</p>
</div>
{% endif %}
{% endwith %}
</div>
</div>
</div>
{% endblock %}
{% block extra_css %}
<style>
/* Minimal styling */
.table th {
border-top: none;
font-weight: 600;
color: #495057;
vertical-align: top;
white-space: nowrap;
}
.table td {
vertical-align: top;
}
.response-value {
max-width: 300px;
}
.table th:first-child,
.table td:first-child {
background-color: #f8f9fa;
font-weight: 600;
}
.table-striped > tbody > tr:nth-of-type(odd) > td {
background-color: rgba(0, 0, 0, 0.02);
}
.table-bordered th,
.table-bordered td {
border: 1px solid #dee2e6;
}
</style>
{% endblock %}
{% endblock %}

View File

@ -25,7 +25,7 @@
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
font-weight: 900 ;
padding: 0.375rem 0.75rem;
border-radius: 0.5rem;
transition: all 0.2s ease;
@ -251,7 +251,7 @@
<div class="card-view">
<div class="row g-4">
{% for submission in page_obj %}
<div class="col-lg-4 col-md-6">
<div class="col-12">
<div class="card h-100">
<div class="card-header">
<h3 class="h5 mb-2">{% trans "Submission" %} #{{ submission.id }}</h3>
@ -328,7 +328,7 @@
<p class="text-muted mb-4">
{% trans "There are no submissions for this form template yet." %}
</p>
<a href="{% url 'form_templates_list' %}" class="btn btn-main-action">
<a href="{% url 'form_templates_list' %}" class="btn btn-main-action btn-sm">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %}
</a>
</div>

View File

@ -148,7 +148,7 @@
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0" style="color: var(--kaauh-teal-dark); font-weight: 700;">
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-file-alt me-2"></i>{% trans "Form Templates" %}
</h1>
<button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#createTemplateModal">

View File

@ -591,7 +591,7 @@
</div>
</div>
<div class="wizard-footer">
<div class="wizard-footer mt-2">
<button
id="backBtn"
class="nav-btn btn-back"

View File

@ -0,0 +1,9 @@
{{stage}}
{% for applicant in applicants_of_stage %}
{{applicant}}
{% endfor %}

View File

@ -0,0 +1,628 @@
{% load i18n static %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% trans 'Careers' %} - KAAUH{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-light-bg: #f9fbfd;
--kaauh-border: #eaeff3;
--kaauh-green-dark: #00363a;
--kaauh-nav-bg: #004a53;
--kaauh-dark-nav-active: #00363a;
}
body {
background-color: white;
}
.max-width-container {
max-width: 1600px;
margin-left: auto;
margin-right: auto;
padding-left: 15px;
padding-right: 15px;
}
/* === Header Top Info === */
.header-top-info {
background-color: white;
padding: 15px 0;
font-size: 0.95rem;
color: #333;
border-bottom: 1px solid #eee;
}
.header-top-info .section-divider {
border-inline-start: 1px solid #ddd;
height: 60px;
margin: 0 15px;
}
.header-top-info .follow-us-section {
display: flex;
align-items: center;
gap: 15px;
font-weight: 500;
}
.header-top-info .social-icon-circle {
width: 30px;
height: 30px;
border-radius: 50%;
background-color: var(--kaauh-teal);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
text-decoration: none;
transition: background-color 0.2s;
}
.header-top-info .social-icon-circle:hover {
background-color: var(--kaauh-teal-dark);
}
.header-top-info .contact-block {
display: flex;
align-items: center;
min-width: 200px;
}
.header-top-info .contact-icon-block {
display: flex;
align-items: center;
text-align: center;
padding: 0 15px;
min-height: 60px;
}
.header-top-info .contact-icon {
font-size: 2rem;
color: var(--kaauh-teal);
margin-inline-end: 15px;
}
.header-top-info .contact-details {
display: flex;
flex-direction: column;
text-align: start;
}
.header-top-info .contact-details span:first-child {
font-weight: bold;
color: var(--kaauh-teal);
}
.header-top-info .contact-details span:last-child {
font-size: 0.85rem;
color: #555;
}
.header-top-info .logo-section {
display: flex;
align-items: center;
gap: 15px;
}
.header-top-info .vision-logo-container {
border-inline-start: 1px solid #ddd;
padding-inline-start: 20px;
min-height: 60px;
display: flex;
align-items: center;
}
.header-top-info .vision-logo {
max-height: 50px;
width: auto;
}
.header-top-info .kaauh-logo-container {
display: flex;
align-items: center;
border-inline-start: 1px solid #ddd;
padding-inline-start: 20px;
}
.header-top-info .kaauh-logo-container img {
max-height: 80px;
width: auto;
margin-inline-end: 10px;
}
.header-top-info .hospital-text {
line-height: 1.2;
text-align: initial;
}
.header-top-info .hospital-text .ar {
font-size: 1.1rem;
font-weight: bold;
color: var(--kaauh-teal-dark);
}
.header-top-info .hospital-text .en {
font-size: 0.8rem;
color: #555;
}
/* === Main Navbar === */
.navbar-main {
background-color: var(--kaauh-nav-bg);
padding: 0;
min-height: 60px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.navbar-main .nav-link {
color: white !important;
font-weight: 500;
font-size: 0.95rem;
padding: 1.1rem 1.2rem;
transition: background-color 0.2s;
border-inline-end: 1px solid rgba(255, 255, 255, 0.1);
line-height: 1;
}
.navbar-main .nav-link:hover,
.navbar-main .nav-link.active {
background-color: var(--kaauh-dark-nav-active);
border-inline-end: 1px solid var(--kaauh-dark-nav-active);
}
.navbar-main .nav-icons-group {
display: flex;
align-items: center;
background-color: var(--kaauh-dark-nav-active);
height: 100%;
min-height: 60px;
}
.navbar-main .nav-icon {
color: white;
padding: 0 15px;
font-size: 1.5rem;
cursor: pointer;
text-decoration: none;
transition: opacity 0.2s;
}
.navbar-main .nav-icon:hover {
opacity: 0.8;
}
.navbar-main .profile-icon {
font-size: 1.8rem;
}
.navbar-main .lang-switch {
font-size: 1.2rem;
font-weight: bold;
text-transform: uppercase;
line-height: 1;
}
.navbar-main .dropdown-menu {
border-radius: 0;
margin-top: 0;
border: none;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.navbar-main .dropdown-item:hover {
background-color: var(--kaauh-light-bg);
color: var(--kaauh-teal-dark);
}
.navbar-toggler {
border: none;
padding: 0;
}
.navbar-toggler:focus {
box-shadow: none;
}
.navbar-toggler-icon {
filter: invert(1);
}
.navbar-main .collapse {
justify-content: flex-start !important;
}
/* === Footer === */
.footer-main {
background-color: var(--kaauh-green-dark);
color: white;
padding-top: 3rem;
}
.footer-main a {
color: white;
text-decoration: none;
transition: color 0.2s;
}
.footer-main a:hover {
color: var(--kaauh-teal);
}
.footer-main h5 {
color: var(--kaauh-teal);
font-weight: 700;
margin-bottom: 1.5rem;
}
.footer-main .social-icons a {
font-size: 1.5rem;
margin-inline-end: 15px;
}
.footer-main .contact-info,
.footer-main .app-download {
font-size: 0.95rem;
}
.footer-bottom {
background-color: #00282b;
padding: 1rem 0;
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.7);
}
.footer-bottom strong {
color: white;
}
.footer-main .col-lg-3, .footer-main .col-lg-5 {
padding-bottom: 2rem;
}
.footer-main .vision-logo {
max-width: 100px;
}
.footer-main .app-store-badge,
.footer-main .play-store-badge {
max-width: 150px;
height: auto;
margin-top: 10px;
}
/* === Hero Section === */
.hero-section {
height: 50vh;
background: url('{% static "image/hospital-bg.jpg" %}') no-repeat center center;
background-size: cover;
position: relative;
display: flex;
align-items: center;
justify-content: center;
color: white;
text-align: center;
}
.hero-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.45);
}
.hero-content {
z-index: 10;
}
.hero-content h1 {
font-size: 3rem;
font-weight: 700;
}
.breadcrumb-section {
padding: 10px 0;
background-color: #f1f1f1;
font-size: 0.9rem;
}
.breadcrumb-item a {
color: #6c757d;
}
.breadcrumb-item.active {
font-weight: 600;
color: var(--kaauh-teal-dark);
}
/* === RTL Adjustments === */
html[dir="rtl"] {
text-align: right;
direction: rtl;
}
html[dir="rtl"] .max-width-container {
padding-left: 15px;
padding-right: 15px;
}
html[dir="rtl"] .header-top-info .section-divider {
border-inline-start: none;
border-inline-end: 1px solid #ddd;
}
html[dir="rtl"] .header-top-info .contact-icon {
margin-inline-end: 0;
margin-inline-start: 15px;
}
html[dir="rtl"] .header-top-info .contact-details {
text-align: right;
}
html[dir="rtl"] .header-top-info .vision-logo-container {
border-inline-start: none;
border-inline-end: 1px solid #ddd;
padding-inline-start: 0;
padding-inline-end: 20px;
}
html[dir="rtl"] .header-top-info .kaauh-logo-container {
border-inline-start: none;
border-inline-end: 1px solid #ddd;
padding-inline-start: 0;
padding-inline-end: 20px;
}
html[dir="rtl"] .header-top-info .kaauh-logo-container img {
margin-inline-end: 0;
margin-inline-start: 10px;
}
html[dir="rtl"] .header-top-info .hospital-text {
text-align: right;
}
html[dir="rtl"] .navbar-main .nav-link {
border-inline-end: none;
border-inline-start: 1px solid rgba(255, 255, 255, 0.1);
}
html[dir="rtl"] .navbar-main .nav-link:hover,
html[dir="rtl"] .navbar-main .nav-link.active {
border-inline-start: 1px solid var(--kaauh-dark-nav-active);
}
html[dir="rtl"] .nav-icons i {
margin-inline-start: 10px;
margin-inline-end: 0;
}
html[dir="rtl"] .top-bar .social-icons a {
margin-inline-start: 0;
margin-inline-end: 10px;
}
html[dir="rtl"] .footer-main .social-icons a {
margin-inline-start: 15px;
margin-inline-end: 0;
}
/* === Responsive Adjustments for Small Screens === */
@media (max-width: 991.98px) {
.header-top-info .max-width-container {
flex-direction: column !important;
align-items: flex-start !important;
gap: 15px;
}
.header-top-info .follow-us-section,
.header-top-info .d-flex.align-items-center.flex-grow-1.justify-content-end {
width: 100%;
justify-content: space-between !important;
}
.header-top-info .contact-block,
.header-top-info .logo-section {
flex-wrap: wrap;
gap: 10px;
}
.header-top-info .section-divider {
display: none;
}
.header-top-info .kaauh-logo-container img {
max-height: 60px;
}
.header-top-info .hospital-text .ar,
.header-top-info .hospital-text .en {
font-size: 0.9rem;
}
.header-top-info .contact-icon {
font-size: 1.5rem;
}
.header-top-info .contact-icon-block {
min-height: auto;
padding: 8px 0;
}
}
@media (max-width: 575.98px) {
.header-top-info .kaauh-logo-container,
.header-top-info .vision-logo-container {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.header-top-info .hospital-text {
text-align: center !important;
}
.header-top-info .kaauh-logo-container img {
max-height: 50px;
}
.header-top-info .follow-us-section span,
.header-top-info .contact-details span {
font-size: 0.85rem;
}
.header-top-info .social-icon-circle {
width: 26px;
height: 26px;
font-size: 0.85rem;
}
.hero-content h1 {
font-size: 2rem;
}
}
</style>
{% block customCSS %}{% endblock %}
</head>
<body class="d-flex flex-column min-vh-100">
<!-- Responsive Header Top Info -->
<div class="header-top-info">
<div class="max-width-container d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center gap-3">
<div class="follow-us-section d-flex flex-wrap align-items-center gap-2">
<span>{% trans "Follow Us On:" %}</span>
<div class="d-flex gap-2">
<a href="#" class="social-icon-circle"><i class="fab fa-facebook-f"></i></a>
<a href="#" class="social-icon-circle"><i class="fab fa-twitter"></i></a>
<a href="#" class="social-icon-circle"><i class="fab fa-instagram"></i></a>
</div>
</div>
<div class="d-flex flex-column flex-md-row align-items-start align-items-md-center gap-3 w-100 w-md-auto">
<div class="d-flex flex-wrap justify-content-between gap-3">
<div class="contact-icon-block d-flex align-items-center">
<i class="fas fa-headset contact-icon"></i>
<div class="contact-details ms-2">
<span>24/7 {% trans "Online Support" %}</span>
<span class="d-block d-md-inline">info@kaauh.edu.sa</span>
</div>
</div>
<div class="contact-icon-block d-flex align-items-center">
<i class="fas fa-phone-alt contact-icon"></i>
<div class="contact-details ms-2">
<span>{% trans "Contact Us Free" %}</span>
<span class="d-block d-md-inline">+966118200000</span>
</div>
</div>
</div>
<div class="logo-section d-flex flex-wrap align-items-center gap-3 mt-2 mt-md-0">
<div class="kaauh-logo-container d-flex flex-column flex-md-row align-items-center gap-2">
<img src="{% static 'image/vision.svg' %}" alt="Vision 2030" class="vision-logo" style="min-height: 70px; min-width:300px;">
</div>
</div>
<div class="logo-section d-flex flex-wrap align-items-center gap-3 mt-2 mt-md-0 ms-6">
<div class="kaauh-logo-container d-flex flex-column flex-md-row align-items-center gap-2 ms-4">
<div class="hospital-text text-center text-md-start me-3">
<div class="ar">جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية</div>
<div class="ar">ومستشفى الملك عبدالله بن عبدالعزيز التخصصي</div>
<div class="en">Princess Nourah bint Abdulrahman University</div>
<div class="en">King Abdullah bin Abdulaziz University Hospital</div>
</div>
<img src="{% static 'image/kaauh.png' %}" alt="KAAUH Logo" style="max-height: 80px;min-width:80px;">
</div>
</div>
</div>
</div>
</div>
<!-- Main Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark navbar-main">
<div class="max-width-container d-flex justify-content-between align-items-stretch w-100">
<button class="navbar-toggler ms-auto" type="button" data-bs-toggle="collapse" data-bs-target="#publicNavCollapse"
aria-controls="publicNavCollapse" aria-expanded="false" aria-label="{% trans 'Toggle navigation' %}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="publicNavCollapse">
<ul class="navbar-nav mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="#">{% trans "About KAAUH" %}</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{% trans "Patients & Visitor" %}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">{% trans "Find a Doctor" %}</a></li>
<li><a class="dropdown-item" href="#">{% trans "Visiting Hours" %}</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{% trans "Training & Education" %}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">{% trans "Courses" %}</a></li>
<li><a class="dropdown-item" href="#">{% trans "Residency" %}</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link active" href="{% url 'kaauh_career' %}">{% trans "Careers" %}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">{% trans "Gallery" %}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">{% trans "Connect" %}</a>
</li>
</ul>
</div>
<div class="nav-icons-group d-none d-lg-flex">
<a href="#" class="nav-icon profile-icon"><i class="fas fa-user-circle"></i></a>
<a href="#" class="nav-icon lang-switch">ع</a>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="flex-grow-1">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="footer-main mt-auto">
<div class="max-width-container">
<div class="row">
<div class="col-lg-3 col-md-6 app-download">
<h5 class="text-white-50">{% trans "Download our mobile app" %}</h5>
<p>{% trans "Get the latest updates and services on the go." %}</p>
<div class="d-flex flex-column gap-2 mt-3">
<a href="#"><img src="{% static 'image/google-play-badge.png' %}" alt="Google Play" class="play-store-badge"></a>
<a href="#"><img src="{% static 'image/app-store-badge.png' %}" alt="App Store" class="app-store-badge"></a>
</div>
</div>
<div class="col-lg-2 col-md-6">
<h5>{% trans "Information" %}</h5>
<ul class="list-unstyled">
<li><a href="#">{% trans "About the Hospital" %}</a></li>
<li><a href="{% url 'kaauh_career' %}">{% trans "Careers" %}</a></li>
<li><a href="#">{% trans "Today's Clinic Hours" %}</a></li>
<li><a href="#">{% trans "Support Us" %}</a></li>
</ul>
</div>
<div class="col-lg-3 col-md-6">
<h5>{% trans "Need Help" %}</h5>
<ul class="list-unstyled">
<li><a href="#">{% trans "Support and Services" %}</a></li>
<li><a href="#">{% trans "Contact Us" %}</a></li>
<li><a href="#">{% trans "FAQ" %}</a></li>
<li><a href="#">{% trans "Sitemap" %}</a></li>
</ul>
<div class="contact-info mt-3">
<i class="fas fa-phone-alt me-2 text-white-50"></i> <strong class="text-white">966118200000</strong><br>
<i class="fas fa-envelope me-2 text-white-50"></i> <a href="mailto:info@kaauh.edu.sa">info@kaauh.edu.sa</a>
</div>
</div>
<div class="col-lg-4 col-md-6">
<h5>{% trans "Contact & Address" %}</h5>
<p>{% trans "KAAUH Campus, Riyadh, Saudi Arabia" %}</p>
<p class="small text-white-50">{% trans "Postal Code 11564, King Fahd Road, Al-Rabi District." %}</p>
<div class="d-flex align-items-center justify-content-start gap-3 mt-4">
<img src="{% static 'image/vision.svg' %}" alt="Vision 2030" class="vision-logo">
<div class="social-icons">
<a href="#"><i class="fab fa-facebook-f"></i></a>
<a href="#"><i class="fab fa-twitter"></i></a>
<a href="#"><i class="fab fa-instagram"></i></a>
<a href="#"><i class="fab fa-youtube"></i></a>
</div>
</div>
</div>
</div>
</div>
<div class="footer-bottom mt-5">
<div class="max-width-container d-flex justify-content-between align-items-center flex-wrap">
<p class="mb-0">
&copy; {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %}
{% trans "All rights reserved." %}
</p>
<p class="mb-0">
{% trans "Powered by" %} <strong class="text-white">Tenhal</strong>
</p>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
{% block customJS %}{% endblock %}
</body>
</html>

154
templates/jobs/career.html Normal file
View File

@ -0,0 +1,154 @@
{% extends "jobs/base_public.html" %}
{% load i18n static %}
{% block customCSS %}
<style>
/* Custom style for the job list table */
.job-listing-section {
padding: 3rem 0;
background-color: white;
}
.job-table {
border-collapse: separate;
border-spacing: 0;
overflow: hidden;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.08);
width: 100%;
}
.job-table thead th {
background-color: var(--kaauh-teal-dark);
color: white;
font-weight: 600;
padding: 1rem 1.5rem;
text-align: start;
border-bottom: 2px solid var(--kaauh-teal);
}
.job-table tbody td {
padding: 1rem 1.5rem;
vertical-align: middle;
border-bottom: 1px solid var(--kaauh-border);
font-size: 0.95rem;
font-weight: 500;
color: var(--kaauh-primary-text);
}
.job-table tbody tr:hover {
background-color: var(--kaauh-light-bg);
}
.job-link-cell {
font-size: 0.85rem;
text-align: center; /* Center the button in its cell */
}
.job-link-cell i {
color: var(--kaauh-teal);
}
/* Apply Button */
.btn-apply {
background-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.4rem 1.2rem;
border-radius: 5px;
transition: background-color 0.2s;
min-width: 80px;
display: inline-block;
text-align: center;
text-decoration: none;
border: 1px solid var(--kaauh-teal);
}
.btn-apply:hover {
background-color: var(--kaauh-teal-dark);
color: white;
}
.hero-section{
background-image: url("{% static 'image/kaauh_banner.png' %}");
background-size: cover;
background-position: center;
}
/* Hero Text Positioning */
.hero-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.arabic-title {
font-size: 4rem;
font-weight: 900;
line-height: 1;
margin-top: 0.5rem;
}
/* RTL specific table alignment */
html[dir="rtl"] .job-table thead th {
text-align: right;
}
html[dir="rtl"] .job-link-cell {
text-align: center; /* Ensure button remains centered in RTL */
}
</style>
{% endblock %}
{% block content %}
<div class="hero-section">
<div class="hero-overlay"></div>
<div class="hero-content">
<p class="text-uppercase small fw-bold mb-1">
{% trans "Home Page" %}
</p>
<h1 class="arabic-title">
{% if LANGUAGE_CODE == 'ar' %}التوظيف{% else %}Careers{% endif %}
</h1>
</div>
</div>
<div class="job-listing-section">
<div class="max-width-container">
<h2 class="h3 mb-4 text-center" style="color: var(--kaauh-teal-dark); font-weight: 700;">
{% trans "Open Positions" %}
</h2>
<div class="table-responsive">
<table class="job-table">
<thead>
<tr>
<th scope="col" style="width: 10%;">{% trans "Job ID" %}</th>
<th scope="col">{% trans "Job Title" %}</th>
<th scope="col" style="width: 10%;">{% trans "Hiring" %}</th>
<th scope="col" style="width: 15%;">{% trans "Posting Date" %}</th>
<th scope="col" style="width: 15%;">{% trans "Apply Before" %}</th>
<th scope="col" style="width: 10%;">{% trans "Apply" %}</th>
</tr>
</thead>
<tbody>
{% comment %} Django loop would typically go here: {% for job in jobs %} {% endcomment %}
{% for job in active_jobs %}
<tr>
<td class="text-nowrap">{{job.internal_job_id}}</td>
<td class="text-nowrap">{{job.title}}</td>
<td>{{job.open_positions}}</td>
<td>{{job.application_start_date}}</td>
<td>{{job.application_deadline}}</td>
<td class="job-link-cell">
<a href="{% url 'job_detail_candidate' job.slug %}" class="btn-apply">{% trans "Apply" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="text-center mt-5">
<a href="#" class="btn btn-lg btn-secondary">
<i class="fas fa-list-alt me-2"></i> {% trans "View All Openings" %}
</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -3,286 +3,387 @@
{% block title %}{{ job.title }} - Applicants{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1">
<a href="{% url 'job_detail' job.slug %}" class="text-decoration-none">
<i class="fas fa-arrow-left me-2"></i>
</a>
Applicants for "{{ job.title }}"
</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}">Dashboard</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_list' %}">Jobs</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_detail' job.slug %}">{{ job.title }}</a></li>
<li class="breadcrumb-item active" aria-current="page">Applicants</li>
</ol>
</nav>
</div>
<div>
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-success">
<i class="fas fa-user-plus"></i> Add New Applicant
</a>
</div>
</div>
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e; /* Primary */
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
<!-- Job Summary Card -->
<div class="card mb-4">
<div class="card-body">
<div class="row">
<div class="col-md-8">
<h5 class="card-title mb-3">{{ job.title }}</h5>
<div class="row">
<div class="col-md-6">
<small class="text-muted">Department:</small>
<div>{{ job.department|default:"Not specified" }}</div>
</div>
<div class="col-md-6">
<small class="text-muted">Location:</small>
<div>{{ job.get_location_display }}</div>
</div>
<div class="col-md-6">
<small class="text-muted">Job Type:</small>
<div>{{ job.get_job_type_display }}</div>
</div>
<div class="col-md-6">
<small class="text-muted">Workplace:</small>
<div>{{ job.get_workplace_type_display }}</div>
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
.text-info { color: #17a2b8 !important; }
.text-success { color: #28a745 !important; }
.text-secondary { color: #6c757d !important; }
.bg-primary { background-color: var(--kaauh-teal) !important; }
.bg-info { background-color: #17a2b8 !important; }
.bg-success { background-color: #28a745 !important; }
.bg-secondary { background-color: #6c757d !important; }
.bg-warning { background-color: #ffc107 !important; }
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
.card-header {
font-weight: 600;
padding: 1rem 1.25rem;
background-color: #f8f9fa; /* Light background */
border-bottom: 1px solid var(--kaauh-border);
}
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
justify-content: center;
text-align: center;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary outline button */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
font-weight: 500;
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Table styling */
.table-hover tbody tr:hover {
background-color: #f3f7f9;
cursor: pointer;
}
.table thead th {
color: #6c757d;
font-weight: 600;
border-bottom: 2px solid var(--kaauh-border);
}
/* Card view specific styling */
.card-view .card-header {
background-color: var(--kaauh-teal);
color: white;
}
.card-view .card-footer {
background-color: #f8f9fa;
border-top: 1px solid var(--kaauh-border);
}
.status-badge {
font-size: 0.9rem;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.7px;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1 fw-bold">
<a href="{% url 'job_detail' job.slug %}" class="text-primary text-decoration-none">
<i class="fas fa-arrow-left me-2"></i>
</a>
{% trans "Applicants for" %} "{{ job.title }}"
</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0 small">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">{% trans "Dashboard" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-secondary">{% trans "Jobs" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_detail' job.slug %}" class="text-secondary">{{ job.title }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Applicants" %}</li>
</ol>
</nav>
</div>
<div>
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action">
<i class="fas fa-user-plus"></i> {% trans "Add New Applicant" %}
</a>
</div>
</div>
<div class="card shadow-sm mb-4">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-8">
<h5 class="fw-bold mb-3 text-primary">{{ job.title }}</h5>
<div class="row small text-secondary g-2">
<div class="col-md-6">
<i class="fas fa-building me-2 text-info"></i> <strong>{% trans "Department:" %}</strong> {{ job.department|default:"N/A" }}
</div>
<div class="col-md-6">
<i class="fas fa-map-pin me-2 text-info"></i> <strong>{% trans "Location:" %}</strong> {{ job.get_location_display }}
</div>
<div class="col-md-6">
<i class="fas fa-briefcase me-2 text-info"></i> <strong>{% trans "Job Type:" %}</strong> {{ job.get_job_type_display }}
</div>
<div class="col-md-6">
<i class="fas fa-globe me-2 text-info"></i> <strong>{% trans "Workplace:" %}</strong> {{ job.get_workplace_type_display }}
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="text-end">
<span class="badge bg-{{ job.status|lower }} status-badge">
<div class="col-md-4 text-end border-start ps-4">
<span class="badge status-badge mb-2 bg-{% if job.status == 'ACTIVE' %}success{% elif job.status == 'DRAFT' or job.status == 'ARCHIVED' %}secondary{% elif job.status == 'CLOSED' %}warning{% else %}danger{% endif %}">
{{ job.get_status_display }}
</span>
{% if candidates %}
<div class="mt-2">
<small class="text-muted">Total Applicants:</small>
<h4 class="text-primary mb-0">{{ candidates.count }}</h4>
<small class="text-muted d-block">{% trans "Total Applicants" %}:</small>
<h3 class="text-primary fw-bold mb-0">{{ candidates.count }}</h3>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Search and Filters -->
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-6">
<label for="search" class="form-label">Search Applicants</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" id="search" name="search"
placeholder="Search by name, email, phone, or stage..."
value="{{ search_query }}">
</div>
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">
<i class="fas fa-filter"></i> Search
</button>
</div>
<div class="col-md-3 d-flex align-items-end">
<a href="{% url 'job_candidates_list' job.slug %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-times"></i> Clear
</a>
</div>
</form>
</div>
</div>
<!-- Candidates -->
{% if candidates %}
<div id="job-candidates-list">
{# View Switcher #}
{% include "includes/_list_view_switcher.html" with list_id="job-candidates-list" %}
{# Table View (Default) #}
<div class="table-view active">
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">Applicants ({{ candidates.count }})</h5>
<div class="d-flex gap-2">
<select class="form-select form-select-sm" style="width: auto;" onchange="window.location.href='?stage='+this.value+'&search={{ search_query }}'">
<option value="">All Stages</option>
<option value="Applied" {% if request.GET.stage == 'Applied' %}selected{% endif %}>Applied</option>
<option value="Interview" {% if request.GET.stage == 'Interview' %}selected{% endif %}>Interview</option>
<option value="Offer" {% if request.GET.stage == 'Offer' %}selected{% endif %}>Offer</option>
</select>
</div>
<div class="card shadow-sm mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-6">
<label for="search" class="form-label small text-muted">{% trans "Search Applicants" %}</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search text-primary"></i></span>
<input type="text" class="form-control" id="search" name="search"
placeholder="{% trans 'Search by name, email, phone, or stage...' %}"
value="{{ search_query }}">
</div>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">
<input type="checkbox" class="form-check-input" id="selectAll">
</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Phone</th>
<th scope="col">Stage</th>
<th scope="col">Applied Date</th>
<th scope="col" class="text-center">Actions</th>
</tr>
</thead>
<tbody>
{% for candidate in candidates %}
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-main-action w-100">
<i class="fas fa-filter"></i> {% trans "Filter Results" %}
</button>
</div>
<div class="col-md-3 d-flex align-items-end">
<a href="{% url 'job_candidates_list' job.slug %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-times"></i> {% trans "Clear Filters" %}
</a>
</div>
</form>
</div>
</div>
{% if candidates %}
<div id="job-candidates-list">
{# View Switcher (Assuming this template is customized to match the new UI style) #}
{% include "includes/_list_view_switcher.html" with list_id="job-candidates-list" %}
{# Table View (Default) #}
<div class="table-view active">
<div class="card shadow-sm">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">{% trans "Applicants" %} ({{ candidates.count }})</h5>
<div class="d-flex gap-2">
<select class="form-select form-select-sm" style="width: auto;" onchange="window.location.href='?stage='+this.value+'&search={{ search_query }}'">
<option value="">{% trans "All Stages" %}</option>
<option value="Applied" {% if request.GET.stage == 'Applied' %}selected{% endif %}>{% trans "Applied" %}</option>
<option value="Exam" {% if request.GET.stage == 'Exam' %}selected{% endif %}>{% trans "Exam" %}</option>
<option value="Interview" {% if request.GET.stage == 'Interview' %}selected{% endif %}>{% trans "Interview" %}</option>
<option value="Offer" {% if request.GET.stage == 'Offer' %}selected{% endif %}>{% trans "Offer" %}</option>
</select>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<td>
<input type="checkbox" class="form-check-input candidate-checkbox" value="{{ candidate.slug }}">
</td>
<td>
<div>
<strong>{{ candidate.first_name }} {{ candidate.last_name }}</strong>
</div>
</td>
<td>{{ candidate.email }}</td>
<td>{{ candidate.phone|default:"-" }}</td>
<td>
<span class="badge bg-{% if candidate.stage == 'Applied' %}primary{% elif candidate.stage == 'Interview' %}info{% elif candidate.stage == 'Offer' %}success{% else %}secondary{% endif %}">
{{ candidate.stage }}
</span>
</td>
<td>{{ candidate.created_at|date:"M d, Y" }}</td>
<td class="text-center">
<div class="btn-group" role="group">
<a href="{% url 'candidate_detail' candidate.slug %}" class="btn btn-outline-primary btn-sm" title="View">
<i class="fas fa-eye"></i>
<th scope="col" class="ps-3">
<input type="checkbox" class="form-check-input" id="selectAll">
</th>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Email" %}</th>
<th scope="col">{% trans "Phone" %}</th>
<th scope="col">{% trans "Stage" %}</th>
<th scope="col">{% trans "Applied Date" %}</th>
<th scope="col" class="text-center pe-3">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for candidate in candidates %}
<tr>
<td class="ps-3">
<input type="checkbox" class="form-check-input candidate-checkbox" value="{{ candidate.slug }}">
</td>
<td>
<strong class="text-primary">{{ candidate.first_name }} {{ candidate.last_name }}</strong>
</td>
<td>{{ candidate.email }}</td>
<td>{{ candidate.phone|default:"-" }}</td>
<td>
<span class="badge bg-{% if candidate.stage == 'Applied' %}primary{% elif candidate.stage == 'Exam' %}info{% elif candidate.stage == 'Interview' %}warning text-dark{% elif candidate.stage == 'Offer' %}success{% else %}secondary{% endif %}">
{{ candidate.stage }}
</span>
</td>
<td class="text-secondary small">{{ candidate.created_at|date:"M d, Y" }}</td>
<td class="text-center pe-3">
<div class="btn-group" role="group">
<a href="{% url 'candidate_detail' candidate.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'View' %}">
<i class="fas fa-eye"></i>
</a>
{% if user.is_staff %}
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}"
data-bs-toggle="modal" data-bs-target="#deleteModal"
data-delete-url="{% url 'candidate_delete' candidate.slug %}"
data-item-name="{{ candidate.first_name }} {{ candidate.last_name }}">
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="card-footer d-flex justify-content-between align-items-center bg-light">
<div class="d-flex align-items-center">
<span class="me-3 small text-muted">{% trans "Selected" %}: <strong id="selectedCount">0</strong></span>
<button class="btn btn-sm btn-outline-secondary me-2" onclick="bulkAction('Interview')"
{% if not user.is_staff %}disabled{% endif %}>
<i class="fas fa-comments me-1"></i> {% trans "Mark Interview" %}
</button>
<button class="btn btn-sm btn-main-action" onclick="bulkAction('Offer')"
{% if not user.is_staff %}disabled{% endif %}>
<i class="fas fa-handshake me-1"></i> {% trans "Mark Offer" %}
</button>
</div>
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
{% if user.is_staff %}
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-secondary btn-sm" title="Edit">
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active"><span class="page-link bg-primary border-primary">{{ num }}</span></li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&search={{ search_query }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
</div>
{# Card View #}
<div class="card-view">
<div class="row g-4">
{% for candidate in candidates %}
<div class="col-md-6 col-lg-4">
<div class="card h-100 shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<h5 class="h5 mb-1 text-white">{{ candidate.first_name }} {{ candidate.last_name }}</h5>
<small class="text-white-50">{{ candidate.email }}</small>
</div>
<span class="badge status-badge bg-{% if candidate.stage == 'Applied' %}bg-secondary{% elif candidate.stage == 'Exam' %}bg-info{% elif candidate.stage == 'Interview' %}bg-warning text-dark{% elif candidate.stage == 'Offer' %}bg-success{% else %}bg-secondary{% endif %}">
{{ candidate.stage }}
</span>
</div>
<div class="card-body small text-secondary">
<p class="mb-2">
<i class="fas fa-phone me-2 text-primary"></i> <strong>{% trans "Phone" %}:</strong> {{ candidate.phone|default:"N/A" }}
</p>
<p class="mb-0">
<i class="fas fa-calendar-alt me-2 text-primary"></i> <strong>{% trans "Applied Date" %}:</strong> {{ candidate.created_at|date:"M d, Y" }}
</p>
</div>
<div class="card-footer">
<div class="d-flex gap-2">
<a href="{% url 'candidate_detail' candidate.slug %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-eye"></i> {% trans "View Profile" %}
</a>
{% if user.is_staff %}
<div class="btn-group" role="group">
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-sm btn-outline-secondary" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<button type="button" class="btn btn-outline-danger btn-sm" title="Delete"
data-bs-toggle="deleteModal"
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}"
data-bs-toggle="modal" data-bs-target="#deleteModal"
data-delete-url="{% url 'candidate_delete' candidate.slug %}"
data-item-name="{{ candidate.first_name }} {{ candidate.last_name }}">
<i class="fas fa-trash"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Bulk Actions -->
<div class="card-footer d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<span class="me-3">Selected: <span id="selectedCount">0</span></span>
<button class="btn btn-sm btn-outline-primary me-2" onclick="bulkAction('interview')"
{% if not user.is_staff %}disabled{% endif %}>
<i class="fas fa-comments"></i> Mark as Interview
</button>
<button class="btn btn-sm btn-success" onclick="bulkAction('offer')"
{% if not user.is_staff %}disabled{% endif %}>
<i class="fas fa-handshake"></i> Mark as Offer
</button>
</div>
<!-- Pagination -->
{% if is_paginated %}
<nav aria-label="Page navigation">
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active"><span class="page-link">{{ num }}</span></li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&search={{ search_query }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&search={{ search_query }}{% endif %}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</div>
</div>
{# Card View #}
<div class="card-view">
<div class="row g-4">
{% for candidate in candidates %}
<div class="col-md-6 col-lg-4">
<div class="card h-100">
<div class="card-header">
<h5 class="h5 mb-1">{{ candidate.first_name }} {{ candidate.last_name }}</h5>
<small class="text-white-50">{{ candidate.email }}</small>
</div>
<div class="card-body">
<p class="card-text">
<strong>{% trans "Phone" %}:</strong> {{ candidate.phone|default:"N/A" }}<br>
<strong>{% trans "Stage" %}:</strong> <span class="badge bg-{% if candidate.stage == 'Applied' %}primary{% elif candidate.stage == 'Interview' %}info{% elif candidate.stage == 'Offer' %}success{% else %}secondary{% endif %}">{{ candidate.stage }}</span><br>
<strong>{% trans "Applied Date" %}:</strong> {{ candidate.created_at|date:"M d, Y" }}
</p>
</div>
<div class="card-footer">
<div class="d-flex gap-2">
<a href="{% url 'candidate_detail' candidate.slug %}" class="btn btn-sm btn-outline-primary w-100">
<i class="fas fa-eye"></i> {% trans "View" %}
</a>
{% if user.is_staff %}
<div class="btn-group w-100" role="group">
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-sm btn-outline-secondary" title="Edit">
<i class="fas fa-edit"></i>
</a>
<button type="button" class="btn btn-outline-danger btn-sm" title="Delete"
data-bs-toggle="deleteModal"
data-delete-url="{% url 'candidate_delete' candidate.slug %}"
data-item-name="{{ candidate.first_name }} {{ candidate.last_name }}">
<i class="fas fa-trash"></i>
</button>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-user-slash fa-3x text-muted mb-3"></i>
<h4 class="text-muted">No applicants found</h4>
<p class="text-muted">There are no candidates who have applied for this position yet.</p>
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-primary">
<i class="fas fa-user-plus"></i> Add First Applicant
</a>
</div>
{% endif %}
{% else %}
<div class="card shadow-sm">
<div class="text-center py-5">
<i class="fas fa-user-slash fa-3x text-muted mb-3"></i>
<h4 class="text-muted">{% trans "No applicants found" %}</h4>
<p class="text-secondary">{% trans "There are no candidates who have applied for this position yet." %}</p>
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action mt-3">
<i class="fas fa-user-plus"></i> {% trans "Add First Applicant" %}
</a>
</div>
</div>
{% endif %}
<!-- Delete Modal -->
{% include "includes/delete_modal.html" %}
{% include "includes/delete_modal.html" %}
</div>
{% endblock %}
{% block extra_js %}
@ -320,7 +421,28 @@ function bulkAction(stage) {
console.log('Updating candidates:', selectedCandidates, 'to stage:', stage);
// For now, just show a message
alert(`Bulk update functionality would mark ${selectedCandidates.length} candidates as ${stage}`);
// After a successful AJAX request, you would likely reload the page or update the rows dynamically.
}
}
// Bootstrap Delete Modal Fix for dynamically loaded data attributes
document.addEventListener('DOMContentLoaded', function () {
const deleteModal = document.getElementById('deleteModal');
if (deleteModal) {
deleteModal.addEventListener('show.bs.modal', function (event) {
const button = event.relatedTarget;
const deleteUrl = button.getAttribute('data-delete-url');
const itemName = button.getAttribute('data-item-name');
const modalTitle = deleteModal.querySelector('.modal-title');
const modalBody = deleteModal.querySelector('.modal-body p');
const deleteForm = deleteModal.querySelector('form');
modalTitle.textContent = `Delete ${itemName}`;
modalBody.innerHTML = `Are you sure you want to permanently delete the applicant <strong>${itemName}</strong>? This action cannot be undone.`;
deleteForm.action = deleteUrl;
});
}
});
</script>
{% endblock %}
{% endblock %}

View File

@ -1,18 +1,39 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ job.title }} - University ATS{% endblock %}
{% block customCSS %}
<style>
/* Custom styles for the Job Detail Page (using variables from base.html) */
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal: #00636e; /* Primary */
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
/* Custom Stage Colors for Tracker */
--stage-applied: var(--kaauh-teal); /* Teal */
--stage-exam: #17a2b8; /* Info Cyan */
--stage-interview: #ffc107; /* Warning Yellow */
--stage-offer: #28a745; /* Success Green */
--stage-inactive: #6c757d; /* Secondary Gray */
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
.text-info { color: var(--stage-exam) !important; }
.text-success { color: var(--stage-offer) !important; }
.text-secondary { color: var(--stage-inactive) !important; }
.bg-success { background-color: var(--kaauh-teal) !important; }
.bg-warning { background-color: #ffc107 !important; }
.bg-secondary { background-color: #6c757d !important; }
.bg-danger { background-color: #dc3545 !important; }
/* Header styling */
.job-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
@ -21,13 +42,11 @@
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.job-header-card h2 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
/* Status badge */
.status-badge {
font-size: 0.9rem;
@ -40,21 +59,6 @@
align-items: center;
}
/* Mapped color classes for status badges */
.bg-success { background-color: var(--kaauh-teal) !important; }
.bg-warning { background-color: #ffc107 !important; }
.bg-secondary { background-color: #6c757d !important; }
.bg-danger { background-color: #dc3545 !important; }
/* Fix for active tab text visibility */
.nav-tabs .nav-link.active,
.right-column-tabs .nav-link.active {
color: var(--kaauh-teal-dark) !important;
background-color: white !important;
border-bottom: 3px solid var(--kaauh-teal) !important;
font-weight: 600;
z-index: 2;
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
@ -69,25 +73,15 @@
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
}
/* Standard Card Header (used for single cards or fallback) */
/* Standard Card Header */
.card-header {
font-weight: 600;
padding: 1rem 1.25rem;
background-color: #f8f9fa; /* Light background */
background-color: #f8f9fa;
border-bottom: 1px solid var(--kaauh-border);
}
.card-header h5 {
font-weight: 600;
color: var(--kaauh-primary-text);
}
.card-footer {
padding: 1rem 1.25rem;
background-color: #f8f9fa;
border-top: 1px solid var(--kaauh-border);
}
/* Left Column Tabs Theming (main details) */
/* Left Column Tabs Theming */
.nav-tabs {
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa;
@ -102,51 +96,20 @@
margin-right: 0.5rem;
transition: all 0.2s;
}
.nav-tabs .nav-link:hover {
color: var(--kaauh-teal);
border-color: #e9ecef;
background-color: #fff;
}
.nav-tabs .nav-link.active {
color: var(--kaauh-teal-dark);
background-color: white;
border-bottom: 3px solid var(--kaauh-teal);
color: var(--kaauh-teal-dark) !important;
background-color: white !important;
border-bottom: 3px solid var(--kaauh-teal) !important;
font-weight: 600;
z-index: 2;
}
/* ==================================== */
/* RIGHT COLUMN TABS STYLING (IMPROVED) */
/* ==================================== */
/* Right Column Tabs */
.right-column-tabs {
padding: 0;
margin-bottom: 0;
border-bottom: 1px solid var(--kaauh-border);
}
.right-column-tabs .nav-tabs {
padding: 0;
margin-bottom: 0;
border-bottom: none;
background-color: transparent;
display: flex;
}
.right-column-tabs .nav-item {
flex-grow: 1;
text-align: center;
}
.right-column-tabs .nav-link {
padding: 0.9rem 1rem;
font-size: 0.95rem;
font-weight: 600;
color: var(--kaauh-primary-text);
border-radius: 0;
border-right: 1px solid var(--kaauh-border);
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa;
}
.right-column-tabs .nav-item:last-child .nav-link {
border-right: none;
}
.right-column-tabs .nav-link.active {
background-color: white;
color: var(--kaauh-teal-dark);
@ -154,23 +117,24 @@
border-right-color: transparent;
margin-bottom: -1px;
}
.right-column-tabs .nav-link:not(.active):hover {
background-color: #f0f4f7;
color: var(--kaauh-teal);
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
justify-content: center;
text-align: center;
}
.right-column-tabs .tab-content {
padding: 1.5rem 1.25rem;
background-color: white;
}
/* Section styling */
.job-section h5 {
color: var(--kaauh-teal);
font-weight: 700;
margin-bottom: 0.75rem;
font-size: 1.25rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--kaauh-border);
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
/* Applicant stats */
@ -189,45 +153,6 @@
font-size: 0.8rem;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
.text-info { color: #17a2b8 !important; }
.text-success { color: #28a745 !important; }
.text-secondary { color: #6c757d !important; }
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
justify-content: center;
text-align: center;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary outline button (for forms/back links) */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Specific styling for the deadline box */
.deadline-box {
padding: 0.75rem;
@ -235,18 +160,143 @@
border: 1px solid;
}
/* Table styling for the Applicant preview */
.table-applicants tbody tr:hover {
background-color: #f3f9f9;
/* ==================================== */
/* MULTI-COLORED CANDIDATE STAGE TRACKER */
/* ==================================== */
.progress-stages {
position: relative;
padding: 1.5rem 0;
}
.table-applicants td {
border-top: 1px solid var(--kaauh-border);
.progress-stages a {
text-decoration: none;
color: inherit;
}
.stage-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
min-width: 60px;
transition: all 0.3s ease;
color: var(--stage-inactive);
}
.stage-icon {
width: 36px;
height: 36px;
border-radius: 50%;
background-color: #e9ecef;
color: var(--stage-inactive);
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
z-index: 10;
border: 2px solid white;
box-shadow: 0 0 0 2px #e9ecef;
transition: all 0.3s ease;
}
/* ---------------- STAGE SPECIFIC COLORS ---------------- */
/* APPLIED STAGE (Teal) */
.stage-item[data-stage="Applied"].completed .stage-icon,
.stage-item[data-stage="Applied"].active .stage-icon {
background-color: var(--stage-applied);
color: white;
}
.stage-item[data-stage="Applied"].active { color: var(--stage-applied); }
/* EXAM STAGE (Cyan/Info) */
.stage-item[data-stage="Exam"].completed .stage-icon,
.stage-item[data-stage="Exam"].active .stage-icon {
background-color: var(--stage-exam);
color: white;
}
.stage-item[data-stage="Exam"].active { color: var(--stage-exam); }
/* INTERVIEW STAGE (Yellow/Warning) */
.stage-item[data-stage="Interview"].completed .stage-icon,
.stage-item[data-stage="Interview"].active .stage-icon {
background-color: var(--stage-interview);
color: var(--kaauh-primary-text); /* Dark text for light background */
}
.stage-item[data-stage="Interview"].active { color: var(--stage-interview); }
/* OFFER STAGE (Green/Success) */
.stage-item[data-stage="Offer"].completed .stage-icon,
.stage-item[data-stage="Offer"].active .stage-icon {
background-color: var(--stage-offer);
color: white;
}
.stage-item[data-stage="Offer"].active { color: var(--stage-offer); }
/* ---------------- GENERIC ACTIVE/COMPLETED STYLING ---------------- */
/* Active State (Applies glow/scale to current stage) */
.stage-item.active .stage-icon {
box-shadow: 0 0 0 4px rgba(0, 99, 110, 0.4);
transform: scale(1.1);
}
.stage-item.active .stage-count {
font-weight: 700;
}
/* Completed State (Applies dark text color to completed stages) */
.stage-item.completed {
color: var(--kaauh-primary-text);
}
/* Connector Line */
.stage-connector {
flex-grow: 1;
height: 3px;
background-color: #e9ecef;
margin: 0 0.5rem;
position: relative;
top: -18px;
z-index: 1;
transition: background-color 0.3s ease;
}
/* Line in completed state (Kept the line teal/primary for consistency) */
.stage-connector.completed {
background-color: var(--kaauh-teal);
}
/* Labels and counts */
.stage-label {
font-size: 0.75rem;
margin-top: 0.4rem;
font-weight: 500;
white-space: nowrap;
}
.stage-count {
font-size: 0.9rem;
font-weight: 600;
margin-top: 0.1rem;
color: #6c757d;
}
</style>
{% endblock %}
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-secondary">Jobs</a></li>
<li class="breadcrumb-item active" aria-current="page" class="text-secondary">Job Detail</li>
</ol>
</nav>
<div class="row g-4">
{# LEFT COLUMN: JOB DETAILS WITH TABS #}
@ -335,6 +385,23 @@
<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" }}
</div>
<div class="col-md-6">
<button
type="button"
class="btn btn-outline-secondary"
id="copyJobLinkButton"
data-url="{{ job.application_url }}">
<i class="fas fa-link me-1"></i>
{% trans "Copy and Share Public Link" %}
</button>
<span id="copyFeedback" class="text-success ms-2 small" style="display:none;">
{% trans "Copied!" %}
</span>
</div>
</div>
<h5 class="text-muted mb-3">{% trans "Financial & Timeline" %}</h5>
<div class="row g-3">
@ -419,8 +486,70 @@
</div>
{# RIGHT COLUMN: TABBED CARDS #}
<div class="col-lg-4">
<div class="card shadow-sm no-hover">
<div class="col-lg-4 ">
<div class="card shadow-sm no-hover mb-4">
<div class="card-body p-4">
<h6 class="text-muted mb-4">{% trans "Applicant Tracking" %}</h6>
<div class="progress-stages">
<div class="d-flex justify-content-between align-items-center">
{% comment %} STAGE 1: Applied {% endcomment %}
<a href="{% url 'job_candidates_list' job.slug %}?stage=Applied"
class="stage-item {% if current_stage == 'Applied' %}active{% endif %} {% if current_stage != 'Applied' and candidate.stage_history_has.Applied %}completed{% endif %}"
data-stage="Applied">
<div class="stage-icon">
<i class="fas fa-file-signature"></i>
</div>
<div class="stage-label">{% trans "Applied" %}</div>
<div class="stage-count">{{ applied_count|default:"0" }}</div>
</a>
{% comment %} CONNECTOR 1 -> 2 {% endcomment %}
<div class="stage-connector {% if current_stage != 'Applied' and candidate.stage_history_has.Exam %}completed{% endif %}"></div>
{% comment %} STAGE 2: Exam {% endcomment %}
<a href="{% url 'job_candidates_list' job.slug %}?stage=Exam"
class="stage-item {% if current_stage == 'Exam' %}active{% endif %} {% if current_stage != 'Exam' and candidate.stage_history_has.Exam %}completed{% endif %}"
data-stage="Exam">
<div class="stage-icon">
<i class="fas fa-clipboard-check"></i>
</div>
<div class="stage-label">{% trans "Exam" %}</div>
<div class="stage-count">{{ exam_count|default:"0" }}</div>
</a>
{% comment %} CONNECTOR 2 -> 3 {% endcomment %}
<div class="stage-connector {% if current_stage != 'Exam' and candidate.stage_history_has.Interview %}completed{% endif %}"></div>
{% comment %} STAGE 3: Interview {% endcomment %}
<a href="{% url 'job_candidates_list' job.slug %}?stage=Interview"
class="stage-item {% if current_stage == 'Interview' %}active{% endif %} {% if current_stage != 'Interview' and candidate.stage_history_has.Interview %}completed{% endif %}"
data-stage="Interview">
<div class="stage-icon">
<i class="fas fa-comments"></i>
</div>
<div class="stage-label">{% trans "Interview" %}</div>
<div class="stage-count">{{ interview_count|default:"0" }}</div>
</a>
{% comment %} CONNECTOR 3 -> 4 {% endcomment %}
<div class="stage-connector {% if current_stage != 'Interview' and candidate.stage_history_has.Offer %}completed{% endif %}"></div>
{% comment %} STAGE 4: Offer {% endcomment %}
<a href="{% url 'job_candidates_list' job.slug %}?stage=Offer"
class="stage-item {% if current_stage == 'Offer' %}active{% endif %} {% if current_stage != 'Offer' and candidate.stage_history_has.Offer %}completed{% endif %}"
data-stage="Offer">
<div class="stage-icon">
<i class="fas fa-handshake"></i>
</div>
<div class="stage-label">{% trans "Offer" %}</div>
<div class="stage-count">{{ offer_count|default:"0" }}</div>
</a>
</div>
</div>
</div>
</div>
<div class="card shadow-sm no-hover" style="height:400px;">
{# RIGHT TABS NAVIGATION #}
<ul class="nav nav-tabs right-column-tabs" id="rightJobTabs" role="tablist">
@ -431,7 +560,7 @@
</li>
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link" id="manage-tab" data-bs-toggle="tab" data-bs-target="#manage-pane" type="button" role="tab" aria-controls="manage-pane" aria-selected="false">
<i class="fas fa-cogs me-1 text-secondary"></i> {% trans "Manage" %}
<i class="fas fa-cogs me-1 text-secondary"></i> {% trans "Form Template" %}
</button>
</li>
<li class="nav-item flex-fill" role="presentation">
@ -445,8 +574,8 @@
{# TAB 1: APPLICANTS CONTENT #}
<div class="tab-pane fade show active" id="applicants-pane" role="tabpanel" aria-labelledby="applicants-tab">
<h5 class="mb-3">{% trans "Candidates" %} (<span id="total_candidates">{{ total_candidates }}</span>)</h5>
{% if total_candidates > 0 %}
<h5 class="mb-3">{% trans "Total Applicants" %} (<span id="total_candidates">{{ total_applicants }}</span>)</h5>
{% if total_applicants > 0 %}
<div class="row mb-4 applicant-stats">
<div class="col-4">
<div class="stat-item">
@ -469,7 +598,7 @@
</div>
<div class="col-12 mb-2">
<a href="{% url 'job_candidates_list' job.slug %}" class="btn btn-outline-secondary w-100">
{% trans "View All Applicants" %} ({{ total_candidates }})
{% trans "View All Applicants" %} ({{ total_applicants }})
</a>
</div>
@ -477,7 +606,10 @@
<div class="d-grid gap-2">
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action">
<i class="fas fa-user-plus"></i> {% trans "Create Candidate" %}
<i class="fas fa-user-plus"></i> {% trans "Create Applicant" %}
</a>
<a href="{% url 'candidate_tier_management' job.slug %}" class="btn btn-main-action">
<i class="fas fa-layer-group"></i> {% trans "Manage Applicants" %}
</a>
</div>
</div>
@ -552,27 +684,25 @@
{% endif %}
</div>
{# Applicant Form Management (Content from old card) #}
<h5 class="mb-3"><i class="fas fa-clipboard-list me-2 text-primary"></i>{% trans "Form Management" %}</h5>
<div class="d-grid gap-2">
{% comment %} {# Applicant Form Management (Content from old card) #}
<h5 class="mb-3"><i class="fas fa-clipboard-list me-2 text-primary"></i>{% trans "Form Management" %}</h5> {% endcomment %}
{% comment %} <div class="d-grid gap-2">
<p class="text-muted small mb-3">
{% trans "Manage the custom application forms associated with this job posting." %}
</p>
</p> {% endcomment %}
<a href="{% url 'create_form_template' %}" class="btn btn-main-action">
{% comment %} <a href="{% url 'create_form_template' %}" class="btn btn-main-action">
<i class="fas fa-plus-circle me-2"></i> {% trans "Create New Form" %}
</a>
<a href="" class="btn btn-outline-secondary">
<i class="fas fa-list-alt me-1"></i> {% trans "View All Existing Forms" %}
</a>
</a> {% endcomment %}
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action">
<i class="fas fa-user-plus"></i> {% trans "Create Candidate" %}
</a>
<a href="{% url 'candidate_tier_management' job.slug %}" class="btn btn-main-action">
<i class="fas fa-layer-group"></i> {% trans "Manage Tiers" %}
</a>
{% comment %} <a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action">
<i class="fas fa-user-plus"></i> {% trans "Create Applicant" %}
</a> {% endcomment %}
</div>
</div>
@ -636,3 +766,33 @@
</div>
{% endblock %}
{% block customJS%}
<script>
document.getElementById('copyJobLinkButton').addEventListener('click', function() {
// 1. Get the URL from the data attribute
const urlToCopy = this.getAttribute('data-url');
// 2. Use the modern Clipboard API
navigator.clipboard.writeText(urlToCopy).then(() => {
// 3. Show feedback message
const feedback = document.getElementById('copyFeedback');
feedback.style.display = 'inline';
// 4. Hide feedback after 2 seconds
setTimeout(() => {
feedback.style.display = 'none';
}, 2000);
}).catch(err => {
// Fallback for older browsers or security issues
console.error('Could not copy text: ', err);
alert("Copy failed. Please copy the URL manually: " + urlToCopy);
});
});
</script>
{% endblock %}

View File

@ -13,6 +13,10 @@
--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);
@ -20,7 +24,7 @@
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
.card:not(.no-hover):hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
}
@ -32,6 +36,9 @@
color: white;
font-weight: 600;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.btn-main-action:hover {
@ -51,17 +58,6 @@
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;
@ -71,41 +67,100 @@
text-transform: uppercase;
letter-spacing: 0.7px;
}
/* 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 */
.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; }
.bg-info { background-color: #17a2b8 !important; } /* LinkedIn badge */
/* --- 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) */
/* Pagination Link Styling */
.pagination .page-item .page-link {
/* 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;
color: var(--kaauh-teal-dark);
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;
font-size: 0.7rem; /* Smallest font size */
width: 50%;
}
/* Filter & Search Layout Adjustments */
.filter-buttons {
display: flex;
gap: 0.5rem; /* Space between filter and clear buttons */
/* 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;
}
.form-control-search {
/* Optional: Add slight shadow/focus effect to search bar */
box-shadow: none;
border-color: var(--kaauh-border);
/* 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:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.1rem rgba(0, 99, 110, 0.25);
.candidate-data-cell a {
display: block;
text-decoration: none;
padding: 0.4rem 0; /* Minimized vertical padding */
}
</style>
{% endblock %}
@ -114,10 +169,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> Job Postings
<i class="fas fa-briefcase me-2"></i> {% trans "Job Postings" %}
</h1>
<a href="{% url 'job_create' %}" class="btn btn-main-action">
<i class="fas fa-plus me-1"></i> Create New Job
<i class="fas fa-plus me-1"></i> {% trans "Create New Job" %}
</a>
</div>
@ -125,7 +180,7 @@
<div class="card-body">
<div class="row">
<div class="col-md-4">
<label for="search" class="form-label small text-muted">Search by Title or Department</label>
<label for="search" class="form-label small text-muted">{% trans "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' %}
@ -133,117 +188,123 @@
</div>
</div>
<div class="col-md-8">
{% 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>
<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>
</div>
</div>
</div>
</div>
{% if page_obj %}
<div id="job-list">
{# View Switcher #}
{% include "includes/_list_view_switcher.html" with list_id="job-list" %}
{% 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" %}
{# 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">
<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">
<thead>
<tr>
<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 "Job ID" %}</th>
{% comment %} <th scope="col">{% trans "Job Title" %}</th>
<th scope="col">{% trans "Status" %}</th> {% endcomment %}
<th scope="col">{% trans "Source" %}</th>
<th scope="col" class="text-end">{% trans "Actions" %}</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>
</tr>
</thead>
<tbody>
{% for job in page_obj %}
{% comment %} This loop relies on the 'jobs' variable passed from the Django view {% endcomment %}
{% for job in jobs %}
<tr>
<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 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>{{ job.get_source }}</td>
<td class="text-end">
<td>
<div class="btn-group btn-group-sm" role="group">
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-primary" title="View">
<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="Edit">
<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' %}">
<i class="fas fa-eye"></i>
</a>
<a href="{% url 'form_builder' job.form_template.id %}" class="btn btn-outline-secondary" title="{% trans '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>
@ -251,45 +312,20 @@
</div>
</div>
</div>
{% 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>
{% comment %} Fallback/Empty State {% endcomment %}
{% if not jobs and not job_list_data and not page_obj %}
<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>No job postings found</h3>
<p class="text-muted">Create your first job posting to get started or adjust your filters.</p>
<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>
<a href="{% url 'job_create' %}" class="btn btn-main-action mt-3">
<i class="fas fa-plus me-1"></i> Create Job
<i class="fas fa-plus me-1"></i> {% trans "Create Job" %}
</a>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% endblock %}

View File

@ -116,7 +116,7 @@
/* Primary Action Button (Create/Submit) */
.btn-main-action {
background-color: var(--kaauh-teal);
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
color: white;
}

View File

@ -6,7 +6,7 @@
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES (FROM JOB DETAIL) */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
@ -21,7 +21,7 @@
.text-success { color: #28a745 !important; }
.text-secondary { color: #6c757d !important; }
/* Main Action Button Style */
/* Main Action Button Style (Used for Download Resume) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
@ -40,7 +40,7 @@
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles */
/* Outlined Button Styles (Only used variants kept) */
.btn-outline-primary {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
@ -58,14 +58,6 @@
color: white;
border-color: var(--kaauh-teal-dark);
}
.btn-outline-info {
color: #17a2b8;
border-color: #17a2b8;
}
.btn-outline-info:hover {
background-color: #17a2b8;
color: white;
}
/* Card enhancements */
.card {
@ -121,70 +113,93 @@
font-weight: 600;
}
/* Right Column Tabs (Parsed Data/Activity) */
.right-column-card .nav-tabs {
padding: 0;
margin-bottom: 0;
border-bottom: none;
background-color: transparent;
}
.right-column-card .nav-link {
padding: 0.9rem 1rem;
font-size: 0.95rem;
font-weight: 600;
color: var(--kaauh-primary-text);
border-right: 1px solid var(--kaauh-border);
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa;
}
.right-column-card .nav-item:last-child .nav-link {
border-right: none;
}
.right-column-card .nav-link.active {
background-color: white !important;
color: var(--kaauh-teal-dark) !important;
border-bottom: 3px solid var(--kaauh-teal);
border-right-color: transparent;
margin-bottom: -1px;
}
/* Right Column Card (General styling) */
.right-column-card .tab-content {
padding: 1.5rem 1.25rem;
background-color: white;
}
/* ==================================== */
/* NEW: PARSED DATA GRID OPTIMIZATION */
/* NEW: Vertical Timeline Styling */
/* ==================================== */
.parsed-data-item {
/* Ensure the border/bg container takes full width of the grid column */
width: 100%;
/* Highlight box for the current stage */
.current-stage {
border: 1px solid var(--kaauh-border);
background-color: #f0f8ff; /* Light, subtle blue background */
}
.parsed-data-item .data-value {
/* Allow data values to wrap without breaking the layout */
word-wrap: break-word;
word-break: break-word;
.current-stage .text-primary {
color: var(--kaauh-teal) !important;
}
.timeline {
position: relative;
padding-left: 2rem;
}
.timeline::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 1.25rem;
width: 2px;
background-color: var(--kaauh-border);
}
.timeline-item {
position: relative;
margin-bottom: 2rem;
padding-left: 1.5rem;
}
.timeline-icon {
position: absolute;
left: 0;
top: 0;
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 0.8rem;
z-index: 10;
border: 4px solid white;
}
.timeline-item:last-child {
margin-bottom: 0;
}
/* Custom Timeline Background Classes for Stages (Using Bootstrap color palette) */
.timeline-bg-applied { background-color: var(--kaauh-teal) !important; }
.timeline-bg-exam { background-color: #17a2b8 !important; }
.timeline-bg-interview { background-color: #ffc107 !important; }
.timeline-bg-offer { background-color: #28a745 !important; }
.timeline-bg-rejected { background-color: #dc3545 !important; }
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="row g-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-secondary">Job:({{candidate.job.title}})</a></li>
<li class="breadcrumb-item active" aria-current="page" class="text-secondary">Applicant Detail</li>
</ol>
</nav>
{# LEFT COLUMN: MAIN CANDIDATE DETAILS AND TABS #}
<div class="col-lg-8">
<div class="card shadow-sm no-hover">
{# HEADER SECTION (The original Candidate Header Card content, redesigned) #}
{# HEADER SECTION #}
<div class="candidate-header-card">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<div>
<h1 class="h3 mb-2">{{ candidate.name }}</h1>
<div class="d-flex align-items-center gap-2 mb-2">
<span class="badge {% if candidate.applied %}bg-success{% else %}bg-warning{% endif %}">
{{ candidate.applied|yesno:"Applied,Pending" }}
</span>
<span id="stageDisplay" class="badge"
data-class="{'bg-primary': $stage == 'Applied', 'bg-info': $stage == 'Exam', 'bg-warning': $stage == 'Interview', 'bg-success': $stage == 'Offer'}"
data-signals-stage="'{{ candidate.stage }}'">
@ -196,7 +211,7 @@
{% trans "Applied for:" %} <strong>{{ candidate.job.title }}</strong>
</small>
</div>
{# Action buttons moved to the right column, keeping only the most visible here for quick access if preferred #}
{# Change Stage button #}
{% if user.is_staff %}
<button type="button" class="btn btn-outline-light btn-sm mt-1" data-bs-toggle="modal" data-bs-target="#stageUpdateModal">
<i class="fas fa-exchange-alt"></i> {% trans "Change Stage" %}
@ -222,7 +237,7 @@
{% if candidate.parsed_summary %}
<li class="nav-item" role="presentation">
<button class="nav-link" id="summary-tab" data-bs-toggle="tab" data-bs-target="#summary-pane" type="button" role="tab" aria-controls="summary-pane" aria-selected="false">
<i class="fas fa-chart-bar me-1"></i> {% trans "Summary" %}
<i class="fas fa-chart-bar me-1"></i> {% trans "Resume Summary" %}
</button>
</li>
{% endif %}
@ -231,7 +246,7 @@
<div class="card-body">
<div class="tab-content" id="candidateTabsContent">
{# TAB 1 CONTENT: CONTACT & DATES (Original Contact Card) #}
{# TAB 1 CONTENT: CONTACT & DATES #}
<div class="tab-pane fade show active" id="contact-pane" role="tabpanel" aria-labelledby="contact-tab">
<h5 class="text-primary mb-4">{% trans "Core Details" %}</h5>
<div class="row g-4">
@ -271,7 +286,7 @@
</div>
</div>
{# TAB 2 CONTENT: RESUME (Original Resume Card) #}
{# TAB 2 CONTENT: RESUME #}
{% if candidate.resume %}
<div class="tab-pane fade" id="resume-pane" role="tabpanel" aria-labelledby="resume-tab">
<h5 class="text-primary mb-4">{% trans "Resume Document" %}</h5>
@ -288,12 +303,12 @@
</div>
{% endif %}
{# TAB 3 CONTENT: PARSED SUMMARY (Original Parsed Summary Card) #}
{# TAB 3 CONTENT: PARSED SUMMARY #}
{% if candidate.parsed_summary %}
<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>
<div class="border-start border-primary ps-3 pt-1 pb-1">
<p class="mb-0 text-secondary">{{ candidate.parsed_summary|linebreaks }}</p>
{% include 'includes/candidate_modal_body.html' %}
</div>
</div>
{% endif %}
@ -303,18 +318,14 @@
</div>
</div>
{# RIGHT COLUMN: ACTIONS AND PARSED DATA TABS #}
{# RIGHT COLUMN: ACTIONS AND CANDIDATE TIMELINE #}
<div class="col-lg-4">
{# ACTIONS CARD (The new consolidated action card) #}
{# ACTIONS CARD #}
{% if user.is_staff %}
<div class="card shadow-sm mb-4 p-3">
<h5 class="text-muted mb-3"><i class="fas fa-cog me-2"></i>{% trans "Management Actions" %}</h5>
<div class="d-grid gap-2">
{# MODAL TRIGGER #}
{% comment %} <button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#stageUpdateModal">
<i class="fas fa-exchange-alt"></i> {% trans "Update Stage" %}
</button> {% endcomment %}
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-primary">
<i class="fas fa-edit"></i> {% trans "Edit Details" %}
</a>
@ -328,52 +339,50 @@
</div>
{% endif %}
{# PARSED DATA TABS (Original Parsed Data Card, now tabbed) #}
{% if parsed %}
<div class="card right-column-card shadow-sm">
<ul class="nav nav-tabs" id="parsedDataTabs" role="tablist">
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link active" id="data-tab" data-bs-toggle="tab" data-bs-target="#data-pane" type="button" role="tab" aria-controls="data-pane" aria-selected="true">
<i class="fas fa-database me-1"></i> {% trans "Parsed Data" %}
</button>
</li>
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link" id="activity-tab" data-bs-toggle="tab" data-bs-target="#activity-pane" type="button" role="tab" aria-controls="activity-pane" aria-selected="false">
<i class="fas fa-history me-1"></i> {% trans "Activity" %}
</button>
</li>
</ul>
{# ENHANCED: CANDIDATE JOURNEY TIMELINE CARD #}
<div class="card shadow-sm timeline-card">
<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>
</div>
<div class="card-body p-4">
<h6 class="text-uppercase text-secondary mb-3">{% trans "Current Stage" %}</h6>
<div class="p-3 mb-4 rounded current-stage">
<p class="mb-0 fw-bold fs-5 text-primary">{{ candidate.stage }}</p>
<small class="text-muted d-block mt-1">
{% trans "Latest status update:" %} {{ candidate.updated_at|date:"M d, Y" }}
</small>
</div>
<div class="tab-content" id="parsedDataTabsContent">
{# TAB 1: PARSED DATA - UPDATED TO 2-COLUMN GRID #}
<div class="tab-pane fade show active" id="data-pane" role="tabpanel" aria-labelledby="data-tab">
<h6 class="text-muted small text-uppercase mb-3">{% trans "Structured Resume Data" %}</h6>
<div class="row g-3">
{% for key, value in parsed.items %}
<div class="col-md-6 parsed-data-item">
<div class="p-3 border rounded h-100 bg-light">
<h6 class="text-muted small text-uppercase mb-1">
<i class="fas fa-tag me-1 text-primary"></i> {{ key|title }}
</h6>
<p class="mb-0 text-dark small data-value">
{{ value|default:"N/A"|linebreaksbr }}
</p>
</div>
</div>
{% endfor %}
<h6 class="text-uppercase text-secondary mb-3 pt-2 border-top">{% trans "Historical Timeline" %}</h6>
<div class="timeline">
{# Base Status: Application Submitted (Always required) #}
<div class="timeline-item">
<div class="timeline-icon timeline-bg-applied"><i class="fas fa-file-signature"></i></div>
<div class="timeline-content">
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Application Submitted" %}</p>
<small class="text-muted">
<i class="far fa-calendar-alt me-1"></i> {{ candidate.created_at|date:"M d, Y" }}
<span class="ms-2">|</span>
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.created_at|date:"h:i A" }}
</small>
</div>
</div>
</div>
{# TAB 2: ACTIVITY (Placeholder) #}
<div class="tab-pane fade" id="activity-pane" role="tabpanel" aria-labelledby="activity-tab">
<p class="text-muted">
{% trans "Activity feed (e.g., stage changes, notes, interview history) will appear here." %}
{# Fallback if no history is explicitly logged #}
{% if not candidate_stage_history %}
<p class="text-muted mt-3 mb-0 ps-3">
{% trans "Detailed stage history logs are currently unavailable." %}
</p>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>

View File

@ -1,197 +1,138 @@
{% extends 'base.html' %}
{% load static i18n %}
{% block title %}Candidate Tier Management - {{ job.title }} - ATS{% endblock %}
{% block title %}Candidate Management - {{ job.title }} - University ATS{% endblock %}
{% block customCSS %}
<style>
/* Minimal Tier Management Styles */
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745; /* Standard success for positive actions */
--kaauh-info: #17a2b8; /* Standard info/exam badge */
}
/* 1. Main Container & Card Styling */
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
.tier-controls {
background-color: #f8f9fa;
padding: 1rem;
border-radius: 0.375rem;
background-color: var(--kaauh-border); /* Light background for control sections */
border-radius: 0.5rem;
padding: 1.25rem;
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;
/* 2. Button Styling (from reference) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
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;
transition: all 0.2s ease;
}
.btn-sm {
font-size: 0.75rem;
padding: 0.2rem 0.4rem;
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Tab Styles for Tiers */
.nav-tabs {
border-bottom: 1px solid #dee2e6;
margin-bottom: 1rem;
}
.nav-tabs .nav-link {
border: none;
color: #495057;
/* 3. Tab Styles (View Switcher) */
.nav-pills .nav-link {
color: var(--kaauh-teal-dark);
font-weight: 500;
padding: 0.5rem 1rem;
transition: all 0.2s;
border-radius: 0.5rem;
transition: background-color 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;
.nav-pills .nav-link.active {
background-color: var(--kaauh-teal);
color: white;
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 */
/* 4. Candidate Table Styling (Aligned with KAAT-S) */
.candidate-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
background-color: white;
border-radius: 0.375rem;
border-radius: 0.5rem;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.candidate-table thead {
background-color: #f8f9fa;
background-color: var(--kaauh-border);
}
.candidate-table th {
padding: 0.75rem;
text-align: left;
font-weight: 600;
font-size: 0.875rem;
color: #495057;
border-bottom: 1px solid #dee2e6;
color: var(--kaauh-teal-dark);
border-bottom: 2px solid var(--kaauh-teal);
}
.candidate-table td {
padding: 0.75rem;
border-bottom: 1px solid #f1f3f4;
border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle;
}
.candidate-table tbody tr:hover {
background-color: #f8f9fa;
}
.candidate-table tbody tr:last-child td {
border-bottom: none;
background-color: #f1f3f4;
}
.candidate-name {
font-weight: 600;
font-size: 0.95rem;
color: var(--kaauh-primary-text);
}
.candidate-details {
font-size: 0.8rem;
color: #6c757d;
}
.candidate-table-responsive {
overflow-x: auto;
margin-bottom: 1rem;
/* 5. Badges (Status/Score) */
.status-badge {
font-size: 0.8rem;
padding: 0.3em 0.7em;
border-radius: 0.35rem;
font-weight: 700;
text-transform: uppercase;
}
.bg-applicant { background-color: #6c757d; color: white; } /* Secondary */
.bg-candidate { background-color: var(--kaauh-success); color: white; } /* Success */
.bg-score { background-color: var(--kaauh-teal-dark); color: white; }
.bg-exam-status { background-color: var(--kaauh-info); color: white; }
/* 6. Stage Badges (More distinct from KAAT-S reference) */
.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;
padding: 0.25rem 0.6rem;
border-radius: 0.3rem;
font-weight: 600;
}
.tier-badge {
.stage-Applied { background-color: #e9ecef; color: #495057; }
.stage-Exam { background-color: #d1ecf1; color: #0c5460; } /* Light cyan/info */
.stage-Interview { background-color: #ffc107; color: #856404; } /* Yellow/warning */
.stage-Offer { background-color: #d4edda; color: #155724; } /* Light green/success */
/* Candidate Indicator (used for the single Potential Candidates list) */
.candidate-indicator-badge {
font-size: 0.7rem;
padding: 0.125rem 0.375rem;
padding: 0.15rem 0.4rem;
border-radius: 0.25rem;
background-color: rgba(0,0,0,0.1);
color: #495057;
margin-left: 0.375rem;
font-weight: 700;
margin-left: 0.5rem;
background-color: var(--kaauh-teal);
color: white;
}
</style>
{% endblock %}
@ -200,12 +141,12 @@
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h1 class="h3 mb-1">
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-layer-group me-2"></i>
{% trans "Candidate Tier Management" %} - {{ job.title }}
{% trans "Applicant Screening for Job:" %} - {{ job.title }}&nbsp;&nbsp;<small class="text-muted fs-6">{{job.internal_job_id}}<small>
</h1>
<p class="text-muted mb-0">
Total Candidates: {{ total_candidates }}
Total Applicants: <span class="fw-bold">{{ total_candidates }}</span>
</p>
</div>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
@ -213,13 +154,25 @@
</a>
</div>
<!-- Tier Controls -->
<div class="tier-controls">
<div class="tier-controls kaauh-card shadow-sm">
<h4 class="h5 mb-3" style="color: var(--kaauh-teal-dark);">{% trans "Filter Potential Candidates" %}</h4>
<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>
<div class="row g-3 align-items-end">
<div class="col-md-3 col-sm-6">
<label for="min_ai_score" class="form-label small text-muted">
{% trans "Minimum AI Score" %}
</label>
<input type="number" name="min_ai_score" id="min_ai_score" class="form-control"
value="{{ min_ai_score|default:'0' }}" min="0" max="100" step="1"
placeholder="e.g., 75">
</div>
<div class="col-md-3 col-sm-6">
<label for="tier1_count" class="form-label small text-muted">
{% 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 }}">
</div>

View File

@ -0,0 +1,125 @@
{% load i18n %}
{% if candidates %}
<div class="candidate-table-responsive">
<form method="post" id="candidate-action-form">
{% csrf_token %}
<table class="candidate-table">
<thead>
<tr>
<th scope="col">{% trans "Name / Contact" %}</th>
<th scope="col">{% trans "AI Score" %}</th>
<th scope="col">{% trans "Status" %}</th>
<th scope="col">{% trans "Stage" %}</th>
<th scope="col" class="text-end">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for candidate in candidates %}
<tr>
<td>
<div class="candidate-name">{{ candidate.name }}</div>
<div class="candidate-details">
<i class="fas fa-envelope me-1"></i> {{ candidate.email|default:"N/A" }}<br>
<i class="fas fa-phone me-1"></i> {{ candidate.phone|default:"N/A" }}
</div>
{% if is_potential_view %}
{# Show candidate ID in this view to help with bulk update ID entry #}
<div class="candidate-details mt-1">
ID: **{{ candidate.id }}**
</div>
{% endif %}
</td>
<td>
<span class="status-badge bg-score">
{{ candidate.match_score|default:"0" }}
</span>
</td>
<td>
<span class="status-badge
{% if candidate.applicant_status == 'Candidate' %}bg-candidate{% else %}bg-applicant{% 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="status-badge bg-exam-status mt-1">
{{ candidate.get_exam_status_display }}
</span>
{% endif %}
</td>
<td class="text-end">
<div class="btn-group btn-group-sm" role="group">
{# View Candidate Criteria/Details (Uses HTMX target #}
<button type="button" class="btn btn-main-action"
data-bs-toggle="modal"
data-bs-target="#candidateviewModal"
hx-get="{% url 'candidate_criteria_view_htmx' candidate.pk %}"
hx-target="#candidateviewModalBody"
title="{% trans 'View Details and Score Breakdown' %}">
<i class="fas fa-eye"></i>
</button>
{# Individual actions (Shown if it's the filtered view OR if the applicant needs to be promoted) #}
{% if is_potential_view or candidate.applicant_status == 'Applicant' %}
{% if candidate.applicant_status == 'Applicant' %}
{# Mark as Candidate Button (Manual Selection/Promotion) #}
<button type="submit" name="mark_as_candidate"
class="btn btn-sm btn-success"
formaction="?candidate_id={{ candidate.id }}&action=mark_as_candidate"
title="{% trans 'Mark as Potential Candidate' %}">
<i class="fas fa-user-check"></i>
</button>
{% endif %}
{# Stage Progression Dropdown #}
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown" title="{% trans 'Move to Next Stage' %}">
<i class="fas fa-tasks"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
{% for next_stage in candidate.get_available_stages %}
<li>
<button type="submit" name="update_stage"
class="dropdown-item"
formaction="?candidate_id={{ candidate.id }}&new_stage={{ next_stage }}">
{% trans "Move to" %} {{ next_stage }}
</button>
</li>
{% endfor %}
{% if candidate.stage == "Exam" %}
<li><hr class="dropdown-divider"></li>
<li>
<button type="button" class="dropdown-item"
data-bs-toggle="modal"
data-bs-target="#examModal{{ candidate.id }}">
<i class="fas fa-clipboard-check me-1"></i> {% trans "Update Exam Status" %}
</button>
</li>
{% endif %}
</ul>
</div>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</form>
</div>
{% else %}
<div class="text-center py-4 text-muted kaauh-card p-4">
<i class="fas fa-inbox fa-2x mb-2" style="color: var(--kaauh-teal);"></i>
<p class="mb-0">{% trans "No candidates found in this list." %}</p>
{% if is_potential_view %}
<p class="small">{% trans "Adjust your 'Top N' filter in the controls above or check the All Applicants list." %}</p>
{% endif %}
</div>
{% endif %}

View File

@ -4,8 +4,7 @@
{% block title %}Create Training Material - {{ block.super }}{% endblock %}
{% block customCSS %}
{# 💡 Required for Summernote if you added it, otherwise remove this line #}
{{ form.media.css }}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
@ -21,9 +20,11 @@
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
.btn-main-action{
background-color: var(--kaauh-teal-dark); /* Changed to primary teal for main actions */
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
@ -34,7 +35,7 @@
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
background-color: var(--kaauh-teal-dark); /* Darker on hover */
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
@ -129,15 +130,14 @@
<form method="post" enctype="multipart/form-data" class="row">
{% csrf_token %}
{% crispy form %}
{# Add the main action button here for consistency #}
</form>
</div>
</div>
</div>
{# 💡 Required for Summernote if you added it, otherwise remove this line #}
{{ form.media.js }}
{% endblock %}