kaauh_ats/templates/jobs/job_applicants.html
2025-12-10 13:56:51 +03:00

623 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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 %}