402 lines
21 KiB
HTML
402 lines
21 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static i18n %}
|
|
|
|
{% block title %}{% blocktrans %}Hired Stage - {{ job.title }} - University ATS{% endblocktrans %}{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<div class="p-3 sm:p-4 lg:p-8">
|
|
<!-- Page Header -->
|
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
|
|
<div>
|
|
<h1 class="text-2xl sm:text-3xl font-bold text-temple-dark mb-2 flex items-center gap-2">
|
|
<i data-lucide="trophy" class="w-6 h-6 sm:w-7 sm:h-7"></i>
|
|
{% trans "Hired Applications" %} - {{ job.title }}
|
|
</h1>
|
|
<h2 class="text-sm sm:text-base text-gray-500 mb-0">
|
|
{% trans "Successfully Hired:" %} <span class="font-bold">{{ applications|length }}</span>
|
|
</h2>
|
|
</div>
|
|
<div class="flex gap-2 w-full sm:w-auto">
|
|
<a href="{% url 'export_applications_csv' job.slug 'hired' %}"
|
|
class="flex items-center gap-2 px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium"
|
|
title="{% trans 'Export hired applications to CSV' %}">
|
|
<i data-lucide="download" class="w-4 h-4"></i>
|
|
<span class="hidden sm:inline">{% trans "Export CSV" %}</span>
|
|
</a>
|
|
<a href="{% url 'job_detail' job.slug %}"
|
|
class="flex items-center gap-2 px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium">
|
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
|
<span class="hidden sm:inline">{% trans "Back to Job" %}</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Header -->
|
|
<div class="bg-gradient-to-br from-green-500 to-teal-500 text-white p-6 rounded-xl mb-6 text-center shadow-lg">
|
|
<i data-lucide="check-circle" class="w-12 h-12 mb-3 mx-auto"></i>
|
|
<h3 class="text-xl font-bold mb-2">{% trans "Congratulations!" %}</h3>
|
|
<p class="mb-0 opacity-90">{% trans "These applications have successfully completed the hiring process and joined your team." %}</p>
|
|
</div>
|
|
|
|
<!-- ERP Sync Status -->
|
|
{% if job.source %}
|
|
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-4 sm:p-6 mb-6">
|
|
<div class="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
|
|
<div>
|
|
<h5 class="text-lg font-bold text-temple-dark mb-4 flex items-center gap-2">
|
|
<i data-lucide="database" class="w-5 h-5"></i> {% trans "ERP Sync Status" %}
|
|
</h5>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
|
<div>
|
|
<small class="text-gray-500 block text-xs">{% trans "Source:" %}</small>
|
|
<strong class="text-sm">{{ job.source.name }}</strong>
|
|
</div>
|
|
<div>
|
|
<small class="text-gray-500 block text-xs">{% trans "Sync Status:" %}</small>
|
|
<span class="px-2 py-1 rounded-full text-xs font-bold
|
|
{% if job.source.sync_status == 'SUCCESS' %}bg-green-500 text-white
|
|
{% elif job.source.sync_status == 'SYNCING' %}bg-yellow-400 text-yellow-900
|
|
{% elif job.source.sync_status == 'ERROR' %}bg-red-500 text-white
|
|
{% else %}bg-gray-400 text-white{% endif %}">
|
|
{{ job.source.get_sync_status_display }}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<small class="text-gray-500 block text-xs">{% trans "Last Sync:" %}</small>
|
|
<strong class="text-sm">
|
|
{% if job.source.last_sync_at %}
|
|
{{ job.source.last_sync_at|date:"M d, Y H:i" }}
|
|
{% else %}
|
|
<span class="text-gray-400">{% trans "Never" %}</span>
|
|
{% endif %}
|
|
</strong>
|
|
</div>
|
|
<div>
|
|
<small class="text-gray-500 block text-xs">{% trans "Hired Candidates:" %}</small>
|
|
<strong class="text-sm">{{ applications|length }}</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg mt-4 text-sm" role="alert">
|
|
<i data-lucide="info" class="w-4 h-4 inline mr-2"></i>
|
|
{% trans "ERP sync is automatically triggered when candidates are moved to 'Hired' stage." %}
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="bg-yellow-50 border border-yellow-200 text-yellow-800 px-4 py-3 rounded-lg mb-6 text-sm" role="alert">
|
|
<i data-lucide="alert-triangle" class="w-4 h-4 inline mr-2"></i>
|
|
{% trans "No ERP source configured for this job. Automatic sync is disabled." %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Applicant Tracking Timeline -->
|
|
<div class="mb-6">
|
|
{% include 'jobs/partials/applicant_tracking.html' %}
|
|
</div>
|
|
|
|
<!-- Application List Header -->
|
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4 gap-3">
|
|
<h2 class="text-xl font-bold text-temple-dark flex items-center gap-2">
|
|
<i data-lucide="users" class="w-5 h-5"></i>
|
|
{% trans "Hired Applications" %}
|
|
<span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full">
|
|
{{ applications|length }}
|
|
</span>
|
|
</h2>
|
|
</div>
|
|
|
|
<!-- Main Card -->
|
|
<div class="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
|
|
<!-- Table -->
|
|
<div class="overflow-x-auto">
|
|
<form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get">
|
|
{% csrf_token %}
|
|
<table class="w-full border-collapse">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
|
<i data-lucide="user" class="w-3 h-3 inline mr-1"></i> {% trans "Name" %}
|
|
</th>
|
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
|
<i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {% trans "Contact Info" %}
|
|
</th>
|
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
|
<i data-lucide="briefcase" class="w-3 h-3 inline mr-1"></i> {% trans "Applied Position" %}
|
|
</th>
|
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
|
<i data-lucide="calendar-check" class="w-3 h-3 inline mr-1"></i> {% trans "Hired Date" %}
|
|
</th>
|
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
|
<i data-lucide="check-circle" class="w-3 h-3 inline mr-1"></i> {% trans "Status" %}
|
|
</th>
|
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
|
|
<i data-lucide="settings" class="w-3 h-3 inline mr-1"></i> {% trans "Actions" %}
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for application in applications %}
|
|
<tr class="hover:bg-gray-50 transition-colors">
|
|
<td class="px-4 py-3 border-b border-gray-200">
|
|
<div class="font-semibold text-temple-dark">
|
|
{{ application.name }}
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3 border-b border-gray-200">
|
|
<div class="text-xs text-gray-500">
|
|
<i data-lucide="mail" class="w-3 h-3 inline mr-1"></i> {{ application.email }}<br>
|
|
<i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {{ application.phone }}
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3 border-b border-gray-200">
|
|
<div class="text-xs text-gray-500">
|
|
<strong>{{ job.title }}</strong><br>
|
|
<small>{{ job.department }}</small>
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3 text-center border-b border-gray-200">
|
|
<div class="text-xs text-gray-500">
|
|
{% if application.offer_date %}
|
|
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
|
{{ application.offer_date|date:"M d, Y" }}
|
|
{% else %}
|
|
<span class="text-gray-400">--</span>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3 text-center border-b border-gray-200">
|
|
<div class="px-3 py-1.5 bg-green-500 text-white rounded-lg text-xs font-bold inline-flex items-center gap-1">
|
|
<i data-lucide="check-circle" class="w-3 h-3"></i>
|
|
{% trans "Hired" %}
|
|
</div>
|
|
</td>
|
|
<td class="px-4 py-3 border-b border-gray-200 text-center">
|
|
<div class="flex items-center justify-center gap-1">
|
|
<button type="button"
|
|
class="px-3 py-1.5 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-xs font-medium"
|
|
onclick="openCandidateModal('{% url 'application_criteria_view_htmx' application.pk %}')"
|
|
title="{% trans 'View Profile' %}">
|
|
<i data-lucide="eye" class="w-3 h-3"></i>
|
|
</button>
|
|
<a href="{% url 'application_resume_template' application.slug %}"
|
|
class="px-3 py-1.5 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-xs font-medium"
|
|
title="{% trans 'View Resume Template' %}">
|
|
<i data-lucide="file-alt" class="w-3 h-3"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
{% if not applications %}
|
|
<div class="p-8 text-center bg-blue-50 border border-blue-200 rounded-lg m-4">
|
|
<i data-lucide="info" class="w-8 h-8 text-blue-500 mx-auto mb-2"></i>
|
|
<p class="text-blue-700 text-sm">{% trans "No applications have been hired for this position yet." %}</p>
|
|
</div>
|
|
{% endif %}
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Candidate View Modal -->
|
|
<div id="candidateviewModal" class="fixed inset-0 z-50 hidden" aria-labelledby="candidateviewModalLabel" role="dialog" aria-modal="true">
|
|
<!-- Backdrop -->
|
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeCandidateModal()"></div>
|
|
|
|
<!-- Modal Content -->
|
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
|
<h5 class="text-lg font-bold text-temple-dark" id="candidateviewModalLabel">
|
|
{% trans "Hired Application Details" %}
|
|
</h5>
|
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeCandidateModal()" aria-label="Close">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
</button>
|
|
</div>
|
|
<div id="candidateviewModalBody" class="flex-1 overflow-y-auto p-6">
|
|
<div class="text-center py-10 text-gray-500">
|
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
|
<p>{% trans "Loading application data..." %}</p>
|
|
</div>
|
|
</div>
|
|
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
|
|
<button type="button" class="w-full px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeCandidateModal()">
|
|
{% trans "Close" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Email Modal -->
|
|
<div id="emailModal" class="fixed inset-0 z-50 hidden" aria-labelledby="emailModalLabel" role="dialog" aria-modal="true">
|
|
<!-- Backdrop -->
|
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeEmailModal()"></div>
|
|
|
|
<!-- Modal Content -->
|
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
|
<h5 class="text-lg font-bold text-temple-dark" id="emailModalLabel">
|
|
<i data-lucide="mail" class="w-5 h-5 inline mr-2"></i>{% trans "Compose Email" %}
|
|
</h5>
|
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeEmailModal()" aria-label="Close">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
</button>
|
|
</div>
|
|
<div id="emailModalBody" class="flex-1 overflow-y-auto p-6">
|
|
<div class="text-center py-10 text-gray-500">
|
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
|
<p>{% trans "Loading email form..." %}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sync Results Modal -->
|
|
<div id="syncResultsModal" class="fixed inset-0 z-50 hidden" aria-labelledby="syncResultsModalLabel" role="dialog" aria-modal="true">
|
|
<!-- Backdrop -->
|
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeSyncResultsModal()"></div>
|
|
|
|
<!-- Modal Content -->
|
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
|
<h5 class="text-lg font-bold text-temple-dark" id="syncResultsModalLabel">
|
|
<i data-lucide="refresh-ccw" class="w-5 h-5 inline mr-2"></i>{% trans "Sync Results" %}
|
|
</h5>
|
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeSyncResultsModal()" aria-label="Close">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
</button>
|
|
</div>
|
|
<div id="syncResultsModalBody" class="flex-1 overflow-y-auto p-6">
|
|
<div class="text-center py-10 text-gray-500">
|
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
|
<p>{% trans "Syncing applications..." %}</p>
|
|
</div>
|
|
</div>
|
|
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
|
|
<button type="button" class="w-full px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeSyncResultsModal()">
|
|
{% trans "Close" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block customJS %}
|
|
<script>
|
|
// Reinitialize Lucide icons after content loads
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
if (typeof lucide !== 'undefined') {
|
|
lucide.createIcons();
|
|
}
|
|
});
|
|
|
|
// ========================================
|
|
// Modal Functions
|
|
// ========================================
|
|
|
|
function openCandidateModal(url) {
|
|
const modal = document.getElementById('candidateviewModal');
|
|
const modalBody = document.getElementById('candidateviewModalBody');
|
|
|
|
// Reset content
|
|
modalBody.innerHTML = `
|
|
<div class="text-center py-10 text-gray-500">
|
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
|
<p>{% trans "Loading application data..." %}</p>
|
|
</div>
|
|
`;
|
|
|
|
// Show modal
|
|
modal.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// Load content via HTMX
|
|
if (url && typeof htmx !== 'undefined') {
|
|
htmx.ajax('GET', url, {target: '#candidateviewModalBody', swap: 'innerHTML'});
|
|
}
|
|
|
|
// Reinitialize icons
|
|
setTimeout(() => {
|
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
}, 100);
|
|
}
|
|
|
|
function closeCandidateModal() {
|
|
const modal = document.getElementById('candidateviewModal');
|
|
modal.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
|
|
function openEmailModal() {
|
|
const modal = document.getElementById('emailModal');
|
|
const modalBody = document.getElementById('emailModalBody');
|
|
const applicationForm = document.getElementById('application-form');
|
|
const url = '{% url "compose_application_email" job.slug %}';
|
|
|
|
// Reset content
|
|
modalBody.innerHTML = `
|
|
<div class="text-center py-10 text-gray-500">
|
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
|
<p>{% trans "Loading email form..." %}</p>
|
|
</div>
|
|
`;
|
|
|
|
// Show modal
|
|
modal.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// Load content via HTMX with form data
|
|
if (url && typeof htmx !== 'undefined' && applicationForm) {
|
|
htmx.ajax('GET', url, {
|
|
target: '#emailModalBody',
|
|
swap: 'innerHTML',
|
|
values: htmx.values(applicationForm)
|
|
});
|
|
}
|
|
|
|
// Reinitialize icons
|
|
setTimeout(() => {
|
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
}, 100);
|
|
}
|
|
|
|
function closeEmailModal() {
|
|
const modal = document.getElementById('emailModal');
|
|
modal.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
|
|
function openSyncResultsModal() {
|
|
const modal = document.getElementById('syncResultsModal');
|
|
modal.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
function closeSyncResultsModal() {
|
|
const modal = document.getElementById('syncResultsModal');
|
|
modal.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
}
|
|
|
|
// Close modals on escape key
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') {
|
|
closeCandidateModal();
|
|
closeEmailModal();
|
|
closeSyncResultsModal();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |