623 lines
17 KiB
HTML
623 lines
17 KiB
HTML
{% extends "base.html" %}
|
||
{% load static %}
|
||
|
||
{% block title %}{{ job.title }} - Applicants{% endblock %}
|
||
|
||
{% block customCSS %}
|
||
<style>
|
||
.job-applicants-container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
|
||
.job-header {
|
||
background: linear-gradient(135deg, #00636e 0%, #004a53 100%);
|
||
color: white;
|
||
padding: 2rem;
|
||
border-radius: 15px;
|
||
margin-bottom: 2rem;
|
||
position: relative;
|
||
}
|
||
|
||
.job-header h1 {
|
||
margin: 0 0 0.5rem 0;
|
||
font-size: 2rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.job-header p {
|
||
margin: 0;
|
||
font-size: 1.1rem;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.job-meta {
|
||
display: flex;
|
||
gap: 2rem;
|
||
margin-top: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.job-meta-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.filters-section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.filters-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.filter-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.filter-group label {
|
||
font-weight: 600;
|
||
margin-bottom: 0.5rem;
|
||
color: #333;
|
||
}
|
||
|
||
.filter-group select,
|
||
.filter-group input {
|
||
padding: 0.75rem;
|
||
border: 2px solid #e1e5e9;
|
||
border-radius: 8px;
|
||
font-size: 0.95rem;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.filter-group select:focus,
|
||
.filter-group input:focus {
|
||
outline: none;
|
||
border-color: #00636e;
|
||
box-shadow: 0 0 0 3px rgba(0, 99, 110, 0.1);
|
||
}
|
||
|
||
.filters-actions {
|
||
display: flex;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn-filter {
|
||
padding: 0.75rem 1.5rem;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: #00636e;
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: #004a53;
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: #5a6268;
|
||
}
|
||
|
||
.stats-section {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: 1.5rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.stat-card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
text-align: center;
|
||
border-left: 4px solid #00636e;
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 2rem;
|
||
font-weight: 700;
|
||
color: #00636e;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.stat-label {
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.applicants-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||
gap: 1.5rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
|
||
.applicant-card {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
||
transition: all 0.3s ease;
|
||
border: 2px solid transparent;
|
||
position: relative;
|
||
}
|
||
|
||
.applicant-card:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 8px 25px rgba(0,0,0,0.15);
|
||
border-color: #00636e;
|
||
}
|
||
|
||
.applicant-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.applicant-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.applicant-name {
|
||
font-size: 1.2rem;
|
||
font-weight: 700;
|
||
color: #333;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.applicant-email {
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.applicant-phone {
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.applicant-stage {
|
||
padding: 0.25rem 0.75rem;
|
||
border-radius: 20px;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.stage-applied { background: #6c757d; color: white; }
|
||
.stage-screening { background: #17a2b8; color: white; }
|
||
.stage-exam { background: #ffc107; color: #212529; }
|
||
.stage-interview { background: #007bff; color: white; }
|
||
.stage-document_review { background: #6f42c1; color: white; }
|
||
.stage-offer { background: #28a745; color: white; }
|
||
.stage-hired { background: #20c997; color: white; }
|
||
.stage-rejected { background: #dc3545; color: white; }
|
||
|
||
.applicant-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 1rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.meta-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
font-size: 0.85rem;
|
||
color: #666;
|
||
}
|
||
|
||
.ai-score {
|
||
background: linear-gradient(135deg, #28a745, #20c997);
|
||
color: white;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 12px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.ai-score.low { background: linear-gradient(135deg, #dc3545, #c82333); }
|
||
.ai-score.medium { background: linear-gradient(135deg, #ffc107, #e0a800); }
|
||
.ai-score.high { background: linear-gradient(135deg, #28a745, #20c997); }
|
||
|
||
.applicant-actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn-action {
|
||
padding: 0.5rem 1rem;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
text-decoration: none;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.btn-view-profile {
|
||
background: #007bff;
|
||
color: white;
|
||
}
|
||
|
||
.btn-view-profile:hover {
|
||
background: #0056b3;
|
||
}
|
||
|
||
.btn-schedule-interview {
|
||
background: #28a745;
|
||
color: white;
|
||
}
|
||
|
||
.btn-schedule-interview:hover {
|
||
background: #1e7e34;
|
||
}
|
||
|
||
.btn-send-email {
|
||
background: #6c757d;
|
||
color: white;
|
||
}
|
||
|
||
.btn-send-email:hover {
|
||
background: #5a6268;
|
||
}
|
||
|
||
.pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
margin-top: 2rem;
|
||
}
|
||
|
||
.pagination a,
|
||
.pagination span {
|
||
padding: 0.75rem 1rem;
|
||
border: 2px solid #e1e5e9;
|
||
border-radius: 8px;
|
||
text-decoration: none;
|
||
color: #333;
|
||
font-weight: 500;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.pagination a:hover {
|
||
background: #00636e;
|
||
color: white;
|
||
border-color: #00636e;
|
||
}
|
||
|
||
.pagination .current {
|
||
background: #00636e;
|
||
color: white;
|
||
border-color: #00636e;
|
||
}
|
||
|
||
.no-results {
|
||
text-align: center;
|
||
padding: 3rem;
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.no-results h3 {
|
||
color: #333;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.no-results p {
|
||
color: #666;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.job-applicants-container {
|
||
padding: 1rem;
|
||
}
|
||
|
||
.filters-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.applicants-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.stats-section {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.job-meta {
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="job-applicants-container">
|
||
<!-- Job Header -->
|
||
<div class="job-header">
|
||
<h1>{{ job.title }}</h1>
|
||
<p>{{ job.department|default:"General" }} • {{ job.get_job_type_display }} • {{ job.get_workplace_type_display }}</p>
|
||
<div class="job-meta">
|
||
<div class="job-meta-item">
|
||
<span></span>
|
||
<span>{{ total_applications }} Total Applicants</span>
|
||
</div>
|
||
{% if job.max_applications %}
|
||
<div class="job-meta-item">
|
||
<span></span>
|
||
<span>{{ job.max_applications }} Positions Available</span>
|
||
</div>
|
||
{% endif %}
|
||
<div class="job-meta-item">
|
||
<span></span>
|
||
<span>Posted {{ job.created_at|date:"M d, Y" }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Statistics Section -->
|
||
<div class="stats-section">
|
||
<div class="stat-card">
|
||
<div class="stat-number">{{ total_applications }}</div>
|
||
<div class="stat-label">Total Applications</div>
|
||
</div>
|
||
|
||
{% for stage_key, stage_data in stage_stats.items %}
|
||
<div class="stat-card">
|
||
<div class="stat-number">{{ stage_data.count }}</div>
|
||
<div class="stat-label">{{ stage_data.label }}</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
{% if ai_score_stats.average %}
|
||
<div class="stat-card">
|
||
<div class="stat-number">{{ ai_score_stats.average|floatformat:1 }}</div>
|
||
<div class="stat-label">Average AI Score</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Filters Section -->
|
||
<div class="filters-section">
|
||
<form method="GET" class="filters-form">
|
||
<div class="filters-grid">
|
||
<!-- Search Box -->
|
||
<div class="filter-group">
|
||
<label for="search"> Search Applicants</label>
|
||
<input
|
||
type="text"
|
||
id="search"
|
||
name="q"
|
||
value="{{ search_query }}"
|
||
placeholder="Search by name, email, or phone..."
|
||
>
|
||
</div>
|
||
|
||
<!-- Stage Filter -->
|
||
<div class="filter-group">
|
||
<label for="stage"> Application Stage</label>
|
||
<select id="stage" name="stage">
|
||
<option value="">All Stages</option>
|
||
{% for key, value in stage_choices %}
|
||
<option value="{{ key }}" {% if stage_filter == key %}selected{% endif %}>
|
||
{{ value }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
|
||
<!-- AI Score Range -->
|
||
<div class="filter-group">
|
||
<label for="min_ai_score"> Min AI Score</label>
|
||
<input
|
||
type="number"
|
||
id="min_ai_score"
|
||
name="min_ai_score"
|
||
value="{{ min_ai_score }}"
|
||
placeholder="0"
|
||
min="0"
|
||
max="100"
|
||
>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label for="max_ai_score"> Max AI Score</label>
|
||
<input
|
||
type="number"
|
||
id="max_ai_score"
|
||
name="max_ai_score"
|
||
value="{{ max_ai_score }}"
|
||
placeholder="100"
|
||
min="0"
|
||
max="100"
|
||
>
|
||
</div>
|
||
|
||
<!-- Date Range -->
|
||
<div class="filter-group">
|
||
<label for="date_from"> From Date</label>
|
||
<input
|
||
type="date"
|
||
id="date_from"
|
||
name="date_from"
|
||
value="{{ date_from }}"
|
||
>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<label for="date_to"> To Date</label>
|
||
<input
|
||
type="date"
|
||
id="date_to"
|
||
name="date_to"
|
||
value="{{ date_to }}"
|
||
>
|
||
</div>
|
||
|
||
<!-- Sort By -->
|
||
<div class="filter-group">
|
||
<label for="sort"> Sort By</label>
|
||
<select id="sort" name="sort">
|
||
<option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>Newest First</option>
|
||
<option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>Oldest First</option>
|
||
<option value="person__first_name" {% if sort_by == 'person__first_name' %}selected{% endif %}>Name (A-Z)</option>
|
||
<option value="-person__first_name" {% if sort_by == '-person__first_name' %}selected{% endif %}>Name (Z-A)</option>
|
||
<option value="stage" {% if sort_by == 'stage' %}selected{% endif %}>Stage</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filter Actions -->
|
||
<div class="filters-actions">
|
||
<button type="submit" class="btn-filter btn-primary">
|
||
Apply Filters
|
||
</button>
|
||
<a href="{% url 'job_applicants' job.slug %}" class="btn-filter btn-secondary">
|
||
Clear All
|
||
</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- Applicants Grid -->
|
||
{% if page_obj.object_list %}
|
||
<div class="applicants-grid">
|
||
{% for application in page_obj.object_list %}
|
||
<div class="applicant-card">
|
||
<!-- Applicant Header -->
|
||
<div class="applicant-header">
|
||
<div class="applicant-info">
|
||
<div class="applicant-name">
|
||
{{ application.person.first_name }} {{ application.person.last_name }}
|
||
</div>
|
||
<div class="applicant-email">{{ application.person.email }}</div>
|
||
{% if application.person.phone %}
|
||
<div class="applicant-phone">{{ application.person.phone }}</div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="applicant-stage stage-{{ application.stage|lower }}">
|
||
{{ application.get_stage_display }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Applicant Meta -->
|
||
<div class="applicant-meta">
|
||
<div class="meta-item">
|
||
<span></span>
|
||
<span>Applied {{ application.created_at|date:"M d, Y" }}</span>
|
||
</div>
|
||
{% if application.ai_analysis_data.analysis_data_en.match_score %}
|
||
<div class="meta-item">
|
||
<span></span>
|
||
<span class="ai-score {% if application.ai_analysis_data.analysis_data_en.match_score >= 75 %}high{% elif application.ai_analysis_data.analysis_data_en.match_score >= 50 %}medium{% else %}low{% endif %}">
|
||
{{ application.ai_analysis_data.analysis_data_en.match_score }}%
|
||
</span>
|
||
</div>
|
||
{% endif %}
|
||
{% if application.person.gpa %}
|
||
<div class="meta-item">
|
||
<span></span>
|
||
<span>GPA: {{ application.person.gpa|floatformat:2 }}</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Applicant Actions -->
|
||
<div class="applicant-actions">
|
||
{% comment %} <a href="{% url 'person_detail' application.person.pk %}" class="btn-action btn-view-profile">
|
||
👁️ Profile
|
||
</a> {% endcomment %}
|
||
<a href="{% url 'application_detail' application.slug %}" class="btn-filter btn-primary btn-sm">
|
||
Application
|
||
</a>
|
||
{% if application.stage == 'Interview' %}
|
||
<a href="{% url 'interview_list' %}" class="btn-filter btn-primary btn-sm">
|
||
Schedule
|
||
</a>
|
||
{% endif %}
|
||
<a href="{% url 'message_create' %}?job={{ job.pk }}&candidate={{ application.pk }}" class="btn-filter btn-primary btn-sm">
|
||
✉️ Email
|
||
</a>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<!-- Pagination -->
|
||
{% if page_obj.has_other_pages %}
|
||
<div class="pagination">
|
||
{% if page_obj.has_previous %}
|
||
<a href="?page=1{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">« First</a>
|
||
<a href="?page={{ page_obj.previous_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">‹ Previous</a>
|
||
{% endif %}
|
||
|
||
<span class="current">
|
||
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||
</span>
|
||
|
||
{% if page_obj.has_next %}
|
||
<a href="?page={{ page_obj.next_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Next ›</a>
|
||
<a href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Last »</a>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
{% else %}
|
||
<!-- No Results -->
|
||
<div class="no-results">
|
||
<h3>😔 No Applicants Found</h3>
|
||
<p>
|
||
{% if search_query or stage_filter or min_ai_score or max_ai_score or date_from or date_to %}
|
||
We couldn't find any applicants matching your current filters. Try adjusting your search criteria or clearing some filters.
|
||
{% else %}
|
||
There are currently no applicants for this job.
|
||
{% endif %}
|
||
</p>
|
||
<a href="{% url 'job_applicants' job.slug %}" class="btn-filter btn-primary">
|
||
🔄 Clear Filters
|
||
</a>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|