355 lines
15 KiB
HTML
355 lines
15 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static i18n %}
|
|
{% load widget_tweaks %}
|
|
|
|
{% block title %}Bulk Interview Scheduling - {{ job.title }} - ATS{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid py-4">
|
|
<style>
|
|
/* KAAT-S UI Variables */
|
|
:root {
|
|
--kaauh-teal: #00636e;
|
|
--kaauh-teal-dark: #004a53;
|
|
--kaauh-border: #eaeff3;
|
|
--kaauh-primary-text: #343a40;
|
|
--kaauh-success: #28a745;
|
|
--kaauh-info: #17a2b8;
|
|
--kaauh-danger: #dc3545;
|
|
--kaauh-warning: #ffc107;
|
|
}
|
|
|
|
/* 1. Card & Container Styling */
|
|
.kaauh-card {
|
|
border: 1px solid var(--kaauh-border);
|
|
border-radius: 0.75rem;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
background-color: white;
|
|
padding: 1.5rem;
|
|
}
|
|
.container-fluid.py-4 { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; }
|
|
|
|
/* 2. Typography & Headers */
|
|
.page-header {
|
|
color: var(--kaauh-teal-dark);
|
|
font-weight: 700;
|
|
}
|
|
.section-header {
|
|
color: var(--kaauh-primary-text);
|
|
font-weight: 600;
|
|
border-bottom: 2px solid var(--kaauh-border);
|
|
padding-bottom: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
label {
|
|
font-weight: 500;
|
|
color: var(--kaauh-primary-text);
|
|
font-size: 0.9rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
.form-control, .form-select {
|
|
border-radius: 0.5rem;
|
|
border: 1px solid #ced4da;
|
|
padding: 0.5rem 0.75rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
.form-group > .form-check {
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
/* FIX: SCROLLABLE CANDIDATE LIST */
|
|
.form-group select[multiple] {
|
|
max-height: 450px;
|
|
overflow-y: auto;
|
|
min-height: 250px;
|
|
padding: 0;
|
|
}
|
|
|
|
/* 3. Button Styling */
|
|
.btn-main-action {
|
|
background-color: var(--kaauh-teal);
|
|
border-color: var(--kaauh-teal);
|
|
color: white;
|
|
font-weight: 600;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.btn-main-action:hover {
|
|
background-color: var(--kaauh-teal-dark);
|
|
border-color: var(--kaauh-teal-dark);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
}
|
|
.btn-secondary {
|
|
color: var(--kaauh-teal-dark);
|
|
border-color: var(--kaauh-border);
|
|
background-color: #f1f3f4;
|
|
}
|
|
|
|
/* 4. Break Times Section Styling */
|
|
.break-time-form {
|
|
background-color: #f8f9fa;
|
|
padding: 0.75rem;
|
|
border-radius: 0.5rem;
|
|
border: 1px solid var(--kaauh-border);
|
|
align-items: flex-end;
|
|
}
|
|
.remove-break {
|
|
width: 100%;
|
|
}
|
|
.note-box {
|
|
background-color: #fff3cd;
|
|
border-left: 5px solid var(--kaauh-warning);
|
|
padding: 1rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.9rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
</style>
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<div>
|
|
<h1 class="h3 page-header">
|
|
<i class="fas fa-calendar-alt me-2"></i>
|
|
{% trans "Bulk Interview Scheduling" %}
|
|
</h1>
|
|
<h2 class="h5 text-muted mb-0">
|
|
{% trans "Configure time slots for:" %} **{{ job.title }}**
|
|
</h2>
|
|
</div>
|
|
<a href="{% url 'job_detail' slug=job.slug %}" class="btn btn-outline-secondary">
|
|
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Job" %}
|
|
</a>
|
|
</div>
|
|
|
|
<div class="kaauh-card shadow-sm">
|
|
<form method="post" id="schedule-form">
|
|
{% csrf_token %}
|
|
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<h5 class="section-header">{% trans "Select Candidates" %}</h5>
|
|
<div class="form-group">
|
|
<label for="{{ form.candidates.id_for_label }}">
|
|
{% trans "Candidates to Schedule (Hold Ctrl/Cmd to select multiple)" %}
|
|
</label>
|
|
{{ form.applications }}
|
|
{% if form.applications.errors %}
|
|
<div class="text-danger small mt-1">{{ form.applications.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-8">
|
|
<h5 class="section-header">{% trans "Schedule Details" %}</h5>
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.topic.id_for_label }}">{% trans "Topic" %}</label>
|
|
{{ form.topic }}
|
|
{% if form.topic.errors %}
|
|
<div class="text-danger small mt-1">{{ form.topic.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.schedule_interview_type.id_for_label }}">{% trans "Interview Type" %}</label>
|
|
{{ form.schedule_interview_type }}
|
|
{% if form.schedule_interview_type.errors %}
|
|
<div class="text-danger small mt-1">{{ form.schedule_interview_type.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.start_date.id_for_label }}">{% trans "Start Date" %}</label>
|
|
{{ form.start_date }}
|
|
{% if form.start_date.errors %}
|
|
<div class="text-danger small mt-1">{{ form.start_date.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.end_date.id_for_label }}">{% trans "End Date" %}</label>
|
|
{{ form.end_date }}
|
|
{% if form.end_date.errors %}
|
|
<div class="text-danger small mt-1">{{ form.end_date.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group mb-3">
|
|
<label>{% trans "Working Days" %}</label>
|
|
<div class="d-flex flex-wrap gap-3 p-2 border rounded" style="background-color: #f8f9fa;">
|
|
{{ form.working_days }}
|
|
{% if form.working_days.errors %}
|
|
<div class="text-danger small mt-1">{{ form.working_days.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.start_time.id_for_label }}">{% trans "Start Time" %}</label>
|
|
{{ form.start_time }}
|
|
{% if form.start_time.errors %}
|
|
<div class="text-danger small mt-1">{{ form.start_time.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.end_time.id_for_label }}">{% trans "End Time" %}</label>
|
|
{{ form.end_time }}
|
|
{% if form.end_time.errors %}
|
|
<div class="text-danger small mt-1">{{ form.end_time.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.interview_duration.id_for_label }}">{% trans "Duration (min)" %}</label>
|
|
{{ form.interview_duration }}
|
|
{% if form.interview_duration.errors %}
|
|
<div class="text-danger small mt-1">{{ form.interview_duration.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-3">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.buffer_time.id_for_label }}">{% trans "Buffer (min)" %}</label>
|
|
{{ form.buffer_time }}
|
|
{% if form.buffer_time.errors %}
|
|
<div class="text-danger small mt-1">{{ form.buffer_time.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 pt-4 border-top">
|
|
<h5 class="section-header">{% trans "Daily Break Times" %}</h5>
|
|
<div id="break-times-container">
|
|
<div class="break-time-form row mb-2 g-2">
|
|
<div class="col-5">
|
|
<label for="{{ form.break_start_time.id_for_label }}">{% trans "Start Time" %}</label>
|
|
{{ form.break_start_time }}
|
|
{% if form.break_start_time.errors %}
|
|
<div class="text-danger small mt-1">{{ form.break_start_time.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
<div class="col-5">
|
|
<label for="{{ form.break_end_time.id_for_label }}">{% trans "End Time" %}</label>
|
|
{{ form.break_end_time }}
|
|
{% if form.break_end_time.errors %}
|
|
<div class="text-danger small mt-1">{{ form.break_end_time.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="form-group mb-3">
|
|
<label for="{{ form.physical_address.id_for_label }}">{% trans "Physical Address" %}</label>
|
|
{{ form.physical_address }}
|
|
{% if form.physical_address.errors %}
|
|
<div class="text-danger small mt-1">{{ form.physical_address.errors }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 pt-4 border-top d-flex justify-content-end gap-2">
|
|
<button type="submit" class="btn btn-main-action">
|
|
<i class="fas fa-calendar-check me-1"></i> {% trans "Preview Schedule" %}
|
|
</button>
|
|
<a href="{% url 'job_detail' slug=job.slug %}" class="btn btn-secondary">
|
|
{% trans "Cancel" %}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const addBreakBtn = document.getElementById('add-break');
|
|
const breakTimesContainer = document.getElementById('break-times-container');
|
|
|
|
// *** FIX: Hardcode formset prefix for reliability (requires matching Python change) ***
|
|
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 the Python view and management_form is rendered.`);
|
|
return;
|
|
}
|
|
|
|
addBreakBtn.addEventListener('click', function() {
|
|
const formCount = parseInt(totalFormsInput.value);
|
|
|
|
// Template for a new form, ensuring the correct classes are applied
|
|
// Use the hardcoded prefix in all new form fields
|
|
const newFormHtml = `
|
|
<div class="break-time-form row mb-2 g-2">
|
|
<div class="col-5">
|
|
<label for="id_${FORMSET_PREFIX}-${formCount}-start_time">Start Time</label>
|
|
<input type="time" name="${FORMSET_PREFIX}-${formCount}-start_time" class="form-control" id="id_${FORMSET_PREFIX}-${formCount}-start_time">
|
|
</div>
|
|
<div class="col-5">
|
|
<label for="id_${FORMSET_PREFIX}-${formCount}-end_time">End Time</label>
|
|
<input type="time" name="${FORMSET_PREFIX}-${formCount}-end_time" class="form-control" id="id_${FORMSET_PREFIX}-${formCount}-end_time">
|
|
</div>
|
|
<div class="col-2 d-flex align-items-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="btn btn-danger btn-sm remove-break w-100">
|
|
<i class="fas fa-trash-alt"></i> Remove
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = newFormHtml.trim();
|
|
const newForm = tempDiv.firstChild;
|
|
|
|
breakTimesContainer.appendChild(newForm);
|
|
totalFormsInput.value = formCount + 1;
|
|
});
|
|
|
|
// Handle remove button clicks (both existing and dynamically added)
|
|
breakTimesContainer.addEventListener('click', function(e) {
|
|
if (e.target.closest('.remove-break')) {
|
|
const form = e.target.closest('.break-time-form');
|
|
if (form) {
|
|
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
|
if (deleteCheckbox) {
|
|
// Check the DELETE box and hide the form
|
|
deleteCheckbox.checked = true;
|
|
form.style.display = 'none';
|
|
} else {
|
|
// If it's a new form, remove it entirely
|
|
form.remove();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
</script>
|
|
{% endblock %}
|