ATS/templates/interviews/schedule_interviews.html
2026-01-29 14:19:03 +03:00

293 lines
18 KiB
HTML

{% extends 'base.html' %}
{% load static i18n widget_tweaks %}
{% block title %}{% trans "Bulk Interview Scheduling" %} - {{ job.title }} - ATS{% endblock %}
{% block content %}
<div class="max-w-7xl mx-auto space-y-6">
<!-- Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div>
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 flex items-center gap-3">
<div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="calendar-days" class="w-6 h-6 text-temple-red"></i>
</div>
{% trans "Bulk Interview Scheduling" %}
</h1>
<p class="text-gray-600 mt-1">
{% trans "Configure time slots for:" %} <strong class="text-temple-red">{{ job.title }}</strong>
</p>
</div>
<a href="{% url 'job_detail' job.slug %}"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 border-gray-300 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Job" %}
</a>
</div>
<!-- Form Card -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-6">
<form method="post" id="schedule-form" class="space-y-8">
{% csrf_token %}
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
<!-- Left Column: Candidates -->
<div class="lg:col-span-1">
<h2 class="text-lg font-bold text-gray-900 flex items-center gap-2 mb-4 pb-3 border-b-2 border-temple-red">
<i data-lucide="users" class="w-5 h-5 text-temple-red"></i> {% trans "Select Candidates" %}
</h2>
<div>
<label for="{{ form.applications.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Candidates to Schedule (Hold Ctrl/Cmd to select multiple)" %}
</label>
{{ form.applications|attr:"class: w-full px-3 py-2 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition max-h-[450px] overflow-y-auto min-h-[250px]"|attr:"multiple" }}
{% if form.applications.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.applications.errors }}</div>
{% endif %}
</div>
</div>
<!-- Right Column: Schedule Details -->
<div class="lg:col-span-3 space-y-6">
<h2 class="text-lg font-bold text-gray-900 flex items-center gap-2 mb-4 pb-3 border-b-2 border-temple-red">
<i data-lucide="settings" class="w-5 h-5 text-temple-red"></i> {% trans "Schedule Details" %}
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="md:col-span-2">
<label for="{{ form.topic.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="tag" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Topic" %}
</label>
{{ form.topic|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.topic.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.topic.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.schedule_interview_type.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="video" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Interview Type" %}
</label>
{{ form.schedule_interview_type|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.schedule_interview_type.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.schedule_interview_type.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.physical_address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="map-pin" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Physical Address" %}
</label>
{{ form.physical_address|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.physical_address.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.physical_address.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.start_date.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="calendar" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Start Date" %}
</label>
{{ form.start_date|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.start_date.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.start_date.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.end_date.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="calendar-check" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "End Date" %}
</label>
{{ form.end_date|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.end_date.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.end_date.errors }}</div>
{% endif %}
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-3">
<i data-lucide="calendar-days" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Working Days" %}
</label>
<div class="flex flex-wrap gap-3 p-3 border border-gray-200 rounded-xl bg-gray-50">
{{ form.working_days }}
{% if form.working_days.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.working_days.errors }}</div>
{% endif %}
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<div>
<label for="{{ form.start_time.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="clock" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Start Time" %}
</label>
{{ form.start_time|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.start_time.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.start_time.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.end_time.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="clock" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "End Time" %}
</label>
{{ form.end_time|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.end_time.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.end_time.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.interview_duration.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="timer" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Duration (min)" %}
</label>
{{ form.interview_duration|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.interview_duration.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.interview_duration.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.buffer_time.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="shield" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Buffer (min)" %}
</label>
{{ form.buffer_time|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.buffer_time.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.buffer_time.errors }}</div>
{% endif %}
</div>
</div>
<div class="pt-6 border-t border-gray-200">
<h2 class="text-lg font-bold text-gray-900 flex items-center gap-2 mb-4 pb-3 border-b-2 border-temple-red">
<i data-lucide="coffee" class="w-5 h-5 text-temple-red"></i> {% trans "Daily Break Times" %}
</h2>
<div id="break-times-container">
<div class="break-time-form bg-gray-50 border border-gray-200 rounded-xl p-4 mb-2">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label for="{{ form.break_start_time.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Start Time" %}
</label>
{{ form.break_start_time|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.break_start_time.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.break_start_time.errors }}</div>
{% endif %}
</div>
<div>
<label for="{{ form.break_end_time.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "End Time" %}
</label>
{{ form.break_end_time|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.break_end_time.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.break_end_time.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Actions -->
<div class="pt-6 border-t border-gray-200 flex justify-end gap-3">
<button type="submit"
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl font-medium text-white transition-all duration-200 bg-temple-red hover:bg-[#7a1a29] shadow-sm hover:shadow-md">
<i data-lucide="calendar-check" class="w-5 h-5"></i> {% trans "Preview Schedule" %}
</button>
<a href="{% url 'job_detail' job.slug %}"
class="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-medium border-2 border-gray-300 text-gray-700 hover:bg-gray-50 transition-all duration-200">
{% trans "Cancel" %}
</a>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
const addBreakBtn = document.getElementById('add-break');
const breakTimesContainer = document.getElementById('break-times-container');
// Formset prefix for reliability
const FORMSET_PREFIX = 'breaktime';
const totalFormsInput = document.querySelector(`input[name="${FORMSET_PREFIX}-TOTAL_FORMS"]`);
if (!totalFormsInput) {
console.error(`TOTAL_FORMS input with name ${FORMSET_PREFIX}-TOTAL_FORMS not found. Cannot add break dynamically. Please ensure formset prefix is set to '${FORMSET_PREFIX}' in Python view and management_form is rendered.`);
return;
}
addBreakBtn.addEventListener('click', function() {
const formCount = parseInt(totalFormsInput.value);
// Template for a new form with Tailwind classes
const newFormHtml = `
<div class="break-time-form bg-gray-50 border border-gray-200 rounded-xl p-4 mb-2">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label for="id_${FORMSET_PREFIX}-${formCount}-start_time" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Start Time" %}
</label>
<input type="time" name="${FORMSET_PREFIX}-${formCount}-start_time" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_${FORMSET_PREFIX}-${formCount}-start_time">
</div>
<div>
<label for="id_${FORMSET_PREFIX}-${formCount}-end_time" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "End Time" %}
</label>
<input type="time" name="${FORMSET_PREFIX}-${formCount}-end_time" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_${FORMSET_PREFIX}-${formCount}-end_time">
</div>
</div>
<div class="mt-4 flex justify-end">
<!-- Hidden management fields for new forms (ID and DELETE) -->
<input type="hidden" name="${FORMSET_PREFIX}-${formCount}-id" id="id_${FORMSET_PREFIX}-${formCount}-id" value="">
<input type="checkbox" name="${FORMSET_PREFIX}-${formCount}-DELETE" id="id_${FORMSET_PREFIX}-${formCount}-DELETE" style="display:none;">
<button type="button" class="inline-flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium bg-red-600 hover:bg-red-700 text-white transition-all duration-200">
<i data-lucide="trash-2" class="w-4 h-4"></i> {% trans "Remove" %}
</button>
</div>
</div>
`;
const tempDiv = document.createElement('div');
tempDiv.innerHTML = newFormHtml.trim();
const newForm = tempDiv.firstChild;
breakTimesContainer.appendChild(newForm);
totalFormsInput.value = formCount + 1;
// Initialize Lucide icons for new form
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
// Handle remove button clicks (both existing and dynamically added)
breakTimesContainer.addEventListener('click', function(e) {
if (e.target.closest('button')) {
const btn = e.target.closest('button');
if (btn.textContent.includes('Remove') || btn.querySelector('.lucide-trash-2')) {
const form = btn.closest('.break-time-form');
if (form) {
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
if (deleteCheckbox) {
// Check DELETE box and hide form
deleteCheckbox.checked = true;
form.style.display = 'none';
} else {
// If it's a new form, remove it entirely
form.remove();
}
}
}
}
});
});
</script>
{% endblock %}