ATS/templates/recruitment/application_detail.html
2026-01-29 14:19:03 +03:00

586 lines
34 KiB
HTML

{% extends "base.html" %}
{% load static i18n humanize %}
{% block title %}{{ application.name }} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* Card Hover Effects */
.detail-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.detail-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
/* Tab Button Effects */
.tab-btn {
transition: all 0.2s ease;
}
.tab-btn:hover {
background-color: rgba(157, 34, 53, 0.05);
}
.tab-btn.active {
border-bottom-color: #9d2235;
color: #9d2235;
background-color: #ffffff;
}
/* Button Hover Effects */
.btn-action {
transition: all 0.2s ease;
}
.btn-action:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn-primary {
transition: all 0.2s ease;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(157, 34, 53, 0.4);
}
/* Timeline Animation */
.timeline-item {
transition: all 0.2s ease;
}
.timeline-item:hover {
transform: translateX(4px);
}
/* Info Card Animation */
.info-card {
transition: all 0.2s ease;
}
.info-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
/* Progress Bar Animation */
.progress-bar {
transition: width 0.5s ease;
}
</style>
{% endblock %}
{% block content %}
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{# Breadcrumb #}
<nav class="mb-6" aria-label="breadcrumb">
<ol class="flex items-center gap-2 text-sm">
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %}
</a></li>
<li class="text-gray-400">/</li>
<li><a href="{% url 'job_detail' application.job.slug %}" class="text-gray-500 hover:text-temple-red transition">
{% trans "Job:" %} ({{ application.job.title }})
</a></li>
<li class="text-gray-400">/</li>
<li class="text-temple-red font-semibold" aria-current="page">{% trans "Application Detail" %}</li>
</ol>
</nav>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
{# LEFT COLUMN: MAIN DETAILS AND TABS #}
<div class="lg:col-span-8">
<div class="detail-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
{# HEADER SECTION #}
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-6">
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
<div class="flex-1">
<h1 class="text-2xl sm:text-3xl font-bold mb-2">{{ application.name }}</h1>
<div class="flex items-center gap-2 mb-2">
<span class="inline-block text-xs font-bold uppercase tracking-wide px-3 py-1.5 rounded-full bg-white/20 backdrop-blur-sm">
{% trans "Stage:" %} <span class="font-normal">{{ application.stage }}</span>
</span>
</div>
<p class="text-sm text-white/80">
{% trans "Applied for:" %} <strong class="text-white">{{ application.job.title }}</strong>
</p>
</div>
{# Change Stage button #}
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
<button type="button"
class="btn-primary inline-flex items-center gap-2 bg-white/10 hover:bg-white/20 text-white font-medium px-4 py-2 rounded-xl text-sm transition"
onclick="document.getElementById('stageUpdateModal').classList.remove('hidden')">
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
{% trans "Change Stage" %}
</button>
{% endif %}
</div>
</div>
{# TABS NAVIGATION #}
<div class="border-b border-gray-200 bg-gray-50 overflow-x-auto">
<div class="flex min-w-max px-4 gap-1">
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
onclick="showTab('contact-pane')"
data-tab="contact-pane">
<i data-lucide="id-card" class="w-4 h-4 mr-2 inline"></i>
{% trans "Contact & Job" %}
</button>
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
onclick="showTab('timeline-pane')"
data-tab="timeline-pane">
<i data-lucide="route" class="w-4 h-4 mr-2 inline"></i>
{% trans "Journey Timeline" %}
</button>
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
onclick="showTab('documents-pane')"
data-tab="documents-pane">
<i data-lucide="file-text" class="w-4 h-4 mr-2 inline"></i>
{% trans "Documents" %}
</button>
{% if application.parsed_summary %}
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
onclick="showTab('summary-pane')"
data-tab="summary-pane">
<i data-lucide="sparkles" class="w-4 h-4 mr-2 inline"></i>
{% trans "AI Summary" %}
</button>
{% endif %}
{% if application.is_resume_parsed %}
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
onclick="showTab('analysis-pane')"
data-tab="analysis-pane">
<i data-lucide="bar-chart-3" class="w-4 h-4 mr-2 inline"></i>
{% trans "AI Analysis" %}
</button>
{% endif %}
</div>
</div>
<div class="p-6">
{# TAB 1: CONTACT & DATES #}
<div id="contact-pane" class="tab-content">
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
<i data-lucide="info" class="w-5 h-5"></i>
{% trans "Core Details" %}
</h5>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200">
<div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="mail" class="w-6 h-6 text-temple-red"></i>
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Email" %}</p>
<p class="font-semibold text-gray-900">{{ application.email }}</p>
</div>
</div>
</div>
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200">
<div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="briefcase" class="w-6 h-6 text-temple-red"></i>
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Position Applied" %}</p>
<p class="font-semibold text-gray-900">{{ application.job.title }}</p>
</div>
</div>
</div>
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200 md:col-span-2">
<div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="calendar-check" class="w-6 h-6 text-temple-red"></i>
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Applied Date" %}</p>
<div class="flex items-center gap-2 flex-wrap">
<p class="font-semibold text-gray-900">{{ application.created_at|date:"M d, Y H:i" }}</p>
<span class="inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full bg-temple-red/10 text-temple-red">
<i data-lucide="clock" class="w-3 h-3"></i>
{{ application.created_at|naturaltime }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
{# TAB 2: JOURNEY TIMELINE #}
<div id="timeline-pane" class="tab-content hidden">
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
<div class="p-4 border-b border-gray-200">
<h5 class="text-sm font-bold text-gray-600 flex items-center gap-2">
<i data-lucide="route" class="w-5 h-5"></i>
{% trans "Application Journey" %}
</h5>
</div>
<div class="p-6">
<p class="text-xs font-bold text-gray-500 uppercase tracking-wide mb-4">{% trans "Current Stage" %}</p>
<div class="bg-temple-red/5 border border-temple-red/30 rounded-xl p-4 mb-6">
<p class="text-xl font-bold text-temple-red mb-1">{{ application.stage }}</p>
<p class="text-xs text-gray-500">
{% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
</p>
</div>
<p class="text-xs font-bold text-gray-500 uppercase tracking-wide mb-4 pt-4 border-t border-gray-200">{% trans "Historical Timeline" %}</p>
<div class="relative pl-8 border-l-2 border-gray-200 space-y-6">
{# Application Submitted #}
<div class="timeline-item relative">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-temple-red flex items-center justify-center text-white border-4 border-white shadow-lg">
<i data-lucide="file-signature" class="w-3 h-3"></i>
</div>
<div class="ml-4">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Application Submitted" %}</p>
<p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
{{ application.created_at|date:"M d, Y" }}
<span class="mx-2">|</span>
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
{{ application.created_at|date:"h:i A" }}
</p>
</div>
</div>
{% if application.exam_date %}
<div class="timeline-item relative">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-temple-red flex items-center justify-center text-white border-4 border-white shadow-lg">
<i data-lucide="clipboard-check" class="w-3 h-3"></i>
</div>
<div class="ml-4">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Exam" %}</p>
<p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
{{ application.exam_date|date:"M d, Y" }}
<span class="mx-2">|</span>
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
{{ application.exam_date|date:"h:i A" }}
</p>
</div>
</div>
{% endif %}
{% if application.get_interview_date %}
<div class="timeline-item relative">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-amber-500 flex items-center justify-center text-white border-4 border-white shadow-lg">
<i data-lucide="message-circle" class="w-3 h-3"></i>
</div>
<div class="ml-4">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Interview" %}</p>
<p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
{{ application.get_interview_date }}
<span class="mx-2">|</span>
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
{{ application.get_interview_time }}
</p>
</div>
</div>
{% endif %}
{% if application.offer_date %}
<div class="timeline-item relative">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-emerald-500 flex items-center justify-center text-white border-4 border-white shadow-lg">
<i data-lucide="handshake" class="w-3 h-3"></i>
</div>
<div class="ml-4">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Offer" %}</p>
<p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
{{ application.offer_date|date:"M d, Y" }}
</p>
</div>
</div>
{% endif %}
{% if application.hired_date %}
<div class="timeline-item relative">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-emerald-600 flex items-center justify-center text-white border-4 border-white shadow-lg">
<i data-lucide="user-check" class="w-3 h-3"></i>
</div>
<div class="ml-4">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Hired" %}</p>
<p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
{{ application.hired_date|date:"M d, Y" }}
</p>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{# TAB 3: DOCUMENTS #}
<div id="documents-pane" class="tab-content hidden">
{% with documents=application.documents %}
{% include 'includes/document_list.html' %}
{% endwith %}
</div>
{# TAB 4: AI SUMMARY #}
{% if application.parsed_summary %}
<div id="summary-pane" class="tab-content hidden">
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
<i data-lucide="sparkles" class="w-5 h-5"></i>
{% trans "AI Generated Summary" %}
</h5>
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red">
{% include 'includes/application_modal_body.html' %}
</div>
</div>
{% endif %}
{# TAB 5: AI ANALYSIS #}
{% if application.is_resume_parsed %}
<div id="analysis-pane" class="tab-content hidden">
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
<i data-lucide="bar-chart-3" class="w-5 h-5"></i>
{% trans "AI Analysis Report" %}
</h5>
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red">
{% with analysis=application.ai_analysis_data %}
{# Match Score Card #}
<div class="bg-white rounded-xl p-4 mb-4 shadow-sm">
<div class="flex justify-between items-center mb-3">
<h6 class="font-bold text-gray-900">{% trans "Match Score" %}</h6>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold {% if analysis.match_score >= 70 %}bg-emerald-500 text-white{% elif analysis.match_score >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
{{ analysis.match_score }}%
</span>
</div>
<div class="h-2 bg-gray-200 rounded-full overflow-hidden">
<div class="progress-bar h-full {% if analysis.match_score >= 70 %}bg-emerald-500{% elif analysis.match_score >= 40 %}bg-amber-500{% else %}bg-red-500{% endif %}"
style="width: {{ analysis.match_score }}%"></div>
</div>
</div>
{# Category & Job Fit #}
<div class="mb-4">
<h6 class="font-bold text-temple-red mb-2">{% trans "Category" %}</h6>
<p class="text-gray-700 mb-3">{{ analysis.category }}</p>
<h6 class="font-bold text-temple-red mb-2">{% trans "Job Fit Narrative" %}</h6>
<p class="text-gray-700">{{ analysis.job_fit_narrative }}</p>
</div>
{# Strengths and Weaknesses #}
<div class="mb-4">
<h6 class="font-bold text-temple-red mb-2">{% trans "Strengths" %}</h6>
<p class="text-emerald-600 mb-3">{{ analysis.strengths }}</p>
<h6 class="font-bold text-temple-red mb-2">{% trans "Weaknesses" %}</h6>
<p class="text-red-600">{{ analysis.weaknesses }}</p>
</div>
{# Recommendation #}
<div class="bg-temple-red/5 rounded-xl p-4 mb-4 border border-temple-red/20">
<h6 class="font-bold text-temple-red mb-2">{% trans "Recommendation" %}</h6>
<p class="text-gray-700">{{ analysis.recommendation }}</p>
</div>
{# Top Keywords #}
<div class="mb-4">
<h6 class="font-bold text-temple-red mb-2">{% trans "Top Keywords" %}</h6>
<div class="flex flex-wrap gap-2">
{% for keyword in analysis.top_3_keywords %}
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-temple-red/10 text-temple-red">
{{ keyword }}
</span>
{% endfor %}
</div>
</div>
{# Professional Details #}
<div class="bg-white rounded-xl p-4 shadow-sm mb-4">
<h6 class="font-bold text-temple-red mb-3">{% trans "Professional Details" %}</h6>
<div class="space-y-2">
<p class="flex justify-between"><span class="text-gray-600">{% trans "Years of Experience:" %}</span> <strong>{{ analysis.years_of_experience }}</strong></p>
<p class="flex justify-between"><span class="text-gray-600">{% trans "Most Recent Job Title:" %}</span> <strong>{{ analysis.most_recent_job_title }}</strong></p>
<p class="flex justify-between">
<span class="text-gray-600">{% trans "Experience Industry Match:" %}</span>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-bold {% if analysis.experience_industry_match >= 70 %}bg-emerald-500 text-white{% elif analysis.experience_industry_match >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
{{ analysis.experience_industry_match }}%
</span>
</p>
<p class="flex justify-between"><span class="text-gray-600">{% trans "Soft Skills Score:" %}</span> <strong>{{ analysis.soft_skills_score }}%</strong></p>
</div>
</div>
{# Criteria Checklist #}
<div class="mb-4">
<h6 class="font-bold text-temple-red mb-2">{% trans "Criteria Assessment" %}</h6>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-2 px-3 font-semibold text-gray-700">{% trans "Criteria" %}</th>
<th class="text-left py-2 px-3 font-semibold text-gray-700">{% trans "Status" %}</th>
</tr>
</thead>
<tbody>
{% for criterion, status in analysis.criteria_checklist.items %}
<tr class="border-b border-gray-100">
<td class="py-2 px-3 text-gray-700">{{ criterion }}</td>
<td class="py-2 px-3">
{% if status == "Met" %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-700">{% trans "Met" %}</span>
{% elif status == "Not Met" %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-red-100 text-red-700">{% trans "Not Met" %}</span>
{% else %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-gray-100 text-gray-700">{{ status }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endwith %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
{# RIGHT COLUMN: ACTIONS #}
<div class="lg:col-span-4 space-y-4">
{# Management Actions #}
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
<h5 class="text-sm font-bold text-gray-600 mb-4 flex items-center gap-2">
<i data-lucide="settings" class="w-5 h-5"></i>
{% trans "Management Actions" %}
</h5>
<div class="space-y-2">
<a href="{% url 'application_list' %}"
class="btn-action inline-flex items-center gap-2 w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Back to List" %}
</a>
{% if application.resume %}
<a href="{{ application.resume.url }}" download
class="btn-primary inline-flex items-center gap-2 w-full bg-temple-red hover:bg-[#8a1e2f] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-lg hover:shadow-xl">
<i data-lucide="download" class="w-4 h-4"></i>
{% trans "Download Resume" %}
</a>
{% endif %}
</div>
</div>
{# Time to Hire #}
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
<h5 class="text-sm font-bold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="clock" class="w-5 h-5"></i>
{% trans "Time to Hire:" %}
</h5>
{% with days=application.time_to_hire_days %}
<p class="text-2xl font-bold text-temple-red">
{% if days > 0 %}{{ days }} day{{ days|pluralize }}{% else %}0{% endif %}
</p>
{% endwith %}
</div>
{# Resume Parsing Section #}
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
<h5 class="text-sm font-bold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="file-text" class="w-5 h-5"></i>
{% trans "Resume Analysis" %}
</h5>
<div class="resume-parsed-section">
{% if application.is_resume_parsed %}
{% include 'recruitment/application_resume_template.html' %}
{% else %}
{% if application.scoring_timeout %}
<div class="flex flex-col items-center justify-center py-6">
<div class="flex items-center gap-2 text-temple-red">
<i data-lucide="bot" class="w-6 h-6 animate-pulse"></i>
<span class="text-sm font-medium">{% trans "Resume Analysis In Progress..." %}</span>
</div>
</div>
{% else %}
<div class="flex flex-col items-center justify-center py-6">
<button type="submit"
class="btn-primary inline-flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-lg hover:shadow-xl"
hx-get="{% url 'application_retry_scoring' application.slug %}"
hx-select=".resume-parsed-section"
hx-target=".resume-parsed-section"
hx-swap="outerHTML"
hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume, Please Wait...`">
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
{% trans "Unable to Parse Resume, Click to Retry" %}
</button>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
</div>
{# Stage Update Modal for Staff Users #}
{% if user.is_staff %}
<div id="stageUpdateModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-xl max-w-lg w-full">
{% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %}
</div>
</div>
{% endif %}
<script>
lucide.createIcons();
// Tab switching functionality
function showTab(tabId) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.add('hidden');
});
// Remove active state from all tabs
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
btn.classList.remove('border-temple-red', 'text-temple-red', 'bg-white');
btn.classList.add('border-transparent', 'text-gray-600');
});
// Show selected tab content
document.getElementById(tabId).classList.remove('hidden');
// Add active state to selected tab
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`);
activeBtn.classList.add('active', 'border-temple-red', 'text-temple-red', 'bg-white');
activeBtn.classList.remove('border-transparent', 'text-gray-600');
}
// Initialize first tab as active
showTab('contact-pane');
// Modal close functionality
document.addEventListener('click', function(e) {
const modal = document.getElementById('stageUpdateModal');
if (e.target === modal) {
modal.classList.add('hidden');
}
});
</script>
{% endblock %}