fix table and card view care interview list

This commit is contained in:
Faheed 2025-12-16 16:05:03 +03:00
parent 04bfdc09be
commit 596905fc71
10 changed files with 194 additions and 303 deletions

View File

@ -1369,43 +1369,70 @@ class CandidateEmailForm(forms.Form):
if candidate and candidate.stage == 'Applied': if candidate and candidate.stage == 'Applied':
message_parts = [ message_parts = [
f"Than you, for your interest in the {self.job.title} role.", f"Thank you for your interest in the {self.job.title} position at KAAUH and for taking the time to submit your application.",
f"We regret to inform you that you were not selected to move forward to the exam round at this time.", f"We have carefully reviewed your qualifications; however, we regret to inform you that your application was not selected to proceed to the examination round at this time.",
f"We encourage you to check our career page for further updates and future opportunities:", f"The selection process was highly competitive, and we had a large number of highly qualified applicants.",
f"https://kaauh/careers", f"We encourage you to review other opportunities and apply for roles that align with your skills on our career portal:",
f"Wishing you the best in your job search,", f"[settings.CAREER_PAGE_URL]", # Use a Django setting for the URL for flexibility
f"The KAAUH Hiring team" f"We wish you the best of luck in your current job search and future career endeavors.",
f"Sincerely,",
f"The KAAUH Recruitment Team",
] ]
elif candidate and candidate.stage == 'Exam': elif candidate and candidate.stage == 'Exam':
message_parts = [ message_parts = [
f"Than you,for your interest in the {self.job.title} role.", f"Dear Candidate,",
f"We're pleased to inform you that your initial screening was successful!", f"Thank you once again for your continued interest in the **{self.job.title}** position.",
f"The next step is the mandatory online assessment exam.", f"We are pleased to inform you that, following a careful review of your application, you have been **selected to proceed to the next phase** of our recruitment process.",
f"Please complete the assessment by using the following link:", f"The next mandatory step is the **Online Assessment Examination** designed to evaluate essential skills for this role.",
f"https://kaauh/hire/exam", f"\n**Action Required:**",
f"We look forward to reviewing your results.", f"Please click on the link below to access and complete the assessment:",
f"Best regards, The KAAUH Hiring team" f"[settings.EXAM_LINK_URL]", # Using a settings variable is a professional best practice
f"\n**Important Details:**",
f"* **Deadline:** The exam must be completed within **72 hours** of receiving this notification.",
f"* **Duration:** The assessment is timed and will take approximately [Insert Time e.g., 60 minutes] to complete.",
f"* **Technical Note:** Please ensure you have a stable internet connection before beginning.",
f"We appreciate your dedication to this process and look forward to reviewing your results.",
f"Sincerely,",
f"The KAAUH Recruitment Team",
] ]
elif candidate and candidate.stage == 'Interview': elif candidate and candidate.stage == 'Interview':
message_parts = [ message_parts = [
f"Than you, for your interest in the {self.job.title} role.", f"Dear Candidate,",
f"We're pleased to inform you that you have cleared your exam!", f"Thank you for your performance in the recent assessment for the **{self.job.title}** role.",
f"The next step is the mandatory interview.", f"We are pleased to inform you that you have **successfully cleared the examination phase** and have been selected to proceed to an interview.",
f"Please complete the assessment by using the following link:", f"The interview is a mandatory step that allows us to learn more about your experience and fit for the role.",
f"https://kaauh/hire/exam", f"\n**Next Steps:**",
f"We look forward to reviewing your results.", f"Our recruitment coordinator will contact you directly within the next 1-2 business days to schedule your interview time and provide the necessary details (such as the interview panel, format, and location/virtual meeting link).",
f"Best regards, The KAAUH Hiring team" f"\n**Please ensure your phone number and email address are current.**",
f"We look forward to speaking with you and discussing this exciting opportunity further.",
f"Sincerely,",
f"The KAAUH Recruitment Team",
] ]
elif candidate and candidate.stage == 'Offer': elif candidate and candidate.stage == 'Offer':
message_parts = [
f"Dear Candidate,",
f"We are delighted to extend to you a **formal offer of employment** for the position of **{self.job.title}** at KAAUH.",
f"Congratulations! This is an exciting moment, and we are very enthusiastic about the prospect of you joining our team.",
f"\n**Next Steps & Documentation:**",
f"A comprehensive offer package, detailing your compensation, benefits, and the full terms of employment, will be transmitted to your email address within the next **24 hours**.",
f"Please review this document carefully.",
f"\n**Questions and Support:**",
f"Should you have any immediate questions regarding the offer or the next steps, please do not hesitate to contact our Human Resources department directly at [HR Contact Email/Phone].",
f"\nWe eagerly anticipate your favorable response and officially welcoming you to the KAAUH team!",
f"Sincerely,",
f"The KAAUH Recruitment Team",
]
elif candidate and candidate.stage == 'Document Review':
message_parts = [ message_parts = [
f"Congratulations, ! We are delighted to inform you that we are extending a formal offer of employment for the {self.job.title} role.", f"Congratulations on progressing to the final stage for the {self.job.title} role!",
f"This is an exciting moment, and we look forward to having you join the KAAUH team.", f"The next critical step is to complete your application by uploading the required employment verification documents.",
f"A detailed offer letter and compensation package will be sent to you via email within 24 hours.", f"**Please log into the Candidate Portal immediately** to access the 'Document Upload' section.",
f"In the meantime, please contact our HR department at [HR Contact] if you have immediate questions.", f"Required documents typically include: National ID/Iqama, Academic Transcripts, and Professional Certifications.",
f"Welcome to the team!", f"You have **7 days** to upload all documents. Failure to do so may delay or invalidate your candidacy.",
f"Best regards, The KAAUH Hiring team" f"If you encounter any technical issues, please contact our support team at [Support Email/Phone] immediately.",
f"We appreciate your cooperation as we finalize your employment process.",
] ]
elif candidate and candidate.stage == 'Hired': elif candidate and candidate.stage == 'Hired':
message_parts = [ message_parts = [
@ -1414,7 +1441,7 @@ class CandidateEmailForm(forms.Form):
f"You will receive a separate email shortly with details regarding your start date, first-day instructions, and onboarding documents.", f"You will receive a separate email shortly with details regarding your start date, first-day instructions, and onboarding documents.",
f"We look forward to seeing you at KAAUH.", f"We look forward to seeing you at KAAUH.",
f"If you have any questions before your start date, please contact [Onboarding Contact].", f"If you have any questions before your start date, please contact [Onboarding Contact].",
f"Best regards, The KAAUH Hiring team"
] ]
elif candidate: elif candidate:
message_parts="" message_parts=""
@ -2155,10 +2182,7 @@ Job: {job.title}
else: else:
initial_message += "This is an onsite schedule. Please arrive 10 minutes early.\n\n" initial_message += "This is an onsite schedule. Please arrive 10 minutes early.\n\n"
initial_message += """
Best regards,
KAAUH Hiring Team
"""
self.fields['message'].initial = initial_message self.fields['message'].initial = initial_message

View File

@ -0,0 +1,19 @@
# Generated by Django 5.2.7 on 2025-12-16 09:13
import secured_fields.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0004_settings_name'),
]
operations = [
migrations.AlterField(
model_name='settings',
name='value',
field=secured_fields.fields.EncryptedTextField(help_text='Value for the setting', verbose_name='Setting Value'),
),
]

View File

@ -17,7 +17,7 @@ from django_countries.fields import CountryField
from django_ckeditor_5.fields import CKEditor5Field from django_ckeditor_5.fields import CKEditor5Field
from django_extensions.db.fields import RandomCharField from django_extensions.db.fields import RandomCharField
from django.contrib.postgres.validators import MinValueValidator, MaxValueValidator from django.contrib.postgres.validators import MinValueValidator, MaxValueValidator
from secured_fields import EncryptedCharField from secured_fields import EncryptedCharField,EncryptedTextField
from typing import List, Dict, Any from typing import List, Dict, Any
@ -2606,7 +2606,7 @@ class Settings(Base):
verbose_name=_("Setting Key"), verbose_name=_("Setting Key"),
help_text=_("Unique key for the setting"), help_text=_("Unique key for the setting"),
) )
value = models.TextField( value = EncryptedTextField(
verbose_name=_("Setting Value"), verbose_name=_("Setting Value"),
help_text=_("Value for the setting"), help_text=_("Value for the setting"),
) )

View File

@ -27,6 +27,8 @@ from django.template.loader import render_to_string
from .models import BulkInterviewTemplate, Interview, Message, ScheduledInterview from .models import BulkInterviewTemplate, Interview, Message, ScheduledInterview
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from .utils import get_setting from .utils import get_setting
from pypdf import PdfReader
User = get_user_model() User = get_user_model()
# Add python-docx import for Word document processing # Add python-docx import for Word document processing
@ -227,6 +229,10 @@ def format_job_description(pk):
def ai_handler(prompt): def ai_handler(prompt):
print("model call") print("model call")
OPENROUTER_API_URL = get_setting("OPENROUTER_API_URL")
OPENROUTER_API_KEY = get_setting("OPENROUTER_API_KEY")
OPENROUTER_MODEL = get_setting("OPENROUTER_MODEL")
print(OPENROUTER_MODEL)
response = requests.post( response = requests.post(
url=OPENROUTER_API_URL, url=OPENROUTER_API_URL,
headers={ headers={

View File

@ -184,6 +184,7 @@ class PersonListView(StaffRequiredMixin, ListView, LoginRequiredMixin):
model = Person model = Person
template_name = "people/person_list.html" template_name = "people/person_list.html"
context_object_name = "people_list" context_object_name = "people_list"
paginate_by=100
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset().select_related("user") queryset = super().get_queryset().select_related("user")
@ -2571,7 +2572,7 @@ def agency_list(request):
agencies = agencies.order_by("-created_at") agencies = agencies.order_by("-created_at")
# Pagination # Pagination
paginator = Paginator(agencies, 10) # Show 10 agencies per page paginator = Paginator(agencies,20) # Show 10 agencies per page
page_number = request.GET.get("page") page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
@ -4520,7 +4521,7 @@ def source_list(request):
sources = sources.order_by("-created_at") sources = sources.order_by("-created_at")
# Pagination # Pagination
paginator = Paginator(sources, 15) # Show 15 sources per page paginator = Paginator(sources, 1) # Show 15 sources per page
page_number = request.GET.get("page") page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
@ -4795,7 +4796,7 @@ def interview_list(request):
"status_filter": status_filter, "status_filter": status_filter,
"job_filter": job_filter, "job_filter": job_filter,
"search_query": search_query, "search_query": search_query,
"interviews": interviews, "interviews": page_obj,
"jobs": jobs, "jobs": jobs,
} }
return render(request, "interviews/interview_list.html", context) return render(request, "interviews/interview_list.html", context)

View File

@ -15,6 +15,7 @@
--kaauh-info: #17a2b8; --kaauh-info: #17a2b8;
--kaauh-danger: #dc3545; --kaauh-danger: #dc3545;
--kaauh-warning: #ffc107; --kaauh-warning: #ffc107;
--kaauh-gray-light: #f8f9fa; /* Added for consistency */
} }
/* Primary Color Overrides */ /* Primary Color Overrides */
@ -93,14 +94,12 @@
} }
/* Column Widths */ /* Column Widths */
.interview-table thead th:nth-child(1) { width: 40px; } .interview-table thead th:nth-child(1) { width: 18%; }
.interview-table thead th:nth-child(2) { width: 15%; } .interview-table thead th:nth-child(2) { width: 15%; }
.interview-table thead th:nth-child(3) { width: 12%; } .interview-table thead th:nth-child(3) { width: 15%; }
.interview-table thead th:nth-child(4) { width: 12%; } .interview-table thead th:nth-child(4) { width: 10%; }
.interview-table thead th:nth-child(5) { width: 10%; } .interview-table thead th:nth-child(5) { width: 10%; }
.interview-table thead th:nth-child(6) { width: 8%; } .interview-table thead th:nth-child(6) { width: 10%; }
.interview-table thead th:nth-child(7) { width: 8%; }
.interview-table thead th:nth-child(8) { width: 15%; }
/* Candidate and Job Info */ /* Candidate and Job Info */
.candidate-name { .candidate-name {
@ -130,14 +129,6 @@
font-weight: 600; font-weight: 600;
} }
/* Status Colors */
.bg-scheduled { background-color: #6c757d !important; color: white; }
.bg-confirmed { background-color: var(--kaauh-info) !important; color: white; }
.bg-cancelled { background-color: var(--kaauh-danger) !important; color: white; }
.bg-completed { background-color: var(--kaauh-success) !important; color: white; }
.bg-remote { background-color: #007bff !important; color: white; }
.bg-onsite { background-color: #6f42c1 !important; color: white; }
/* Custom Height Optimization */ /* Custom Height Optimization */
.form-control-sm, .form-control-sm,
.btn-sm { .btn-sm {
@ -165,7 +156,6 @@
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container-fluid py-4">
<!-- Header Section -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<div> <div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
@ -173,7 +163,8 @@
{% trans "Interview Management" %} {% trans "Interview Management" %}
</h1> </h1>
<h2 class="h5 text-muted mb-0"> <h2 class="h5 text-muted mb-0">
{% trans "Total Interviews:" %} <span class="fw-bold">{{ interviews|length }}</span> {# Using count() instead of length filter if interviews is the Paginator Page Object #}
{% trans "Total Interviews:" %} <span class="fw-bold">{{ interviews.paginator.count }}</span>
</h2> </h2>
</div> </div>
<div class="d-flex gap-2"> <div class="d-flex gap-2">
@ -183,19 +174,9 @@
</div> </div>
</div> </div>
<!-- Filter Controls -->
<div class="filter-controls"> <div class="filter-controls">
<form method="get" class="row g-3"> <form method="get" class="row g-3">
<div class="col-md-3"> <div class="col-md-3">
{% comment %} <label for="job_filter" class="form-label form-label-sm">{% trans "Job" %}</label>
<select name="job" id="job_filter" class="form-select form-select-sm">
<option value="">{% trans "All Jobs" %}</option>
{% for job in jobs %}
<option value="{{ job.id }}" {% if request.GET.job == job.id|stringformat:"s" %}selected{% endif %}>
{{ job.title }}
</option>
{% endfor %}
</select> {% endcomment %}
<label for="job_filter" class="form-label small text-muted">{% trans "Filter by Job" %}</label> <label for="job_filter" class="form-label small text-muted">{% trans "Filter by Job" %}</label>
<select name="job" id="job_filter" class="form-select form-select-sm"> <select name="job" id="job_filter" class="form-select form-select-sm">
@ -238,91 +219,24 @@
</div> </div>
</form> </form>
</div> </div>
{# Using 'meetings' based on the context_object_name provided #}
{% if interviews %} {% if interviews %}
<div id="meetings-list"> <div id="interview-list">
{# View Switcher (kept the name for simplicity) #} {# View Switcher (kept the name for simplicity) #}
{% include "includes/_list_view_switcher.html" with list_id="meetings-list" %} {% include "includes/_list_view_switcher.html" with list_id="interview-list" %}
{# Card View #} {# Table View #}
<div class="card-view active row"> <div class="table-view active d-none d-lg-block">
{% for interview in meetings %}
<div class="col-md-6 col-lg-4 mb-4">
<div class="card meeting-card h-100 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title flex-grow-1 me-3">
<a href="{% url 'application_detail' interview.candidate.slug %}" class="text-decoration-none text-primary-theme">{{ interview.candidate.name }}</a>
</h5>
<span class="status-badge bg-{{ interview.status }}">
{{ interview.status|title }}
</span>
</div>
<p class="card-text text-muted small mb-3">
<i class="fas fa-briefcase"></i> {% trans "Job" %}:
<a class="text-secondary text-decoration-none" href="{% url 'job_detail' interview.job.slug %}">{{ interview.job.title }}</a><br>
{# --- Remote/Onsite Logic - Handles both cases safely --- #}
<i class="fas {% if interview.schedule.interview_type == 'Remote' %}fa-globe{% else %}fa-map-marker-alt{% endif %}"></i>
{% trans "Type" %}: {{ interview.schedule.get_interview_type_display }}
{% if interview.schedule.interview_type == 'Remote' %}<br>
{# CRITICAL FIX: Safe access to zoom_meeting details #}
<i class="fas fa-hashtag"></i> {% trans "Zoom ID" %}: {{ interview.zoom_meeting.meeting_id|default:"N/A" }}
{% else %}<br>
<i class="fas fa-building"></i> {% trans "Location" %}: {{ interview.schedule.location }}
{% endif %}<br>
<i class="fas fa-clock"></i> {% trans "Date" %}: {{ interview.interview_date|date:"M d, Y" }}<br>
<i class="fas fa-clock"></i> {% trans "Time" %}: {{ interview.interview_time|time:"H:i" }}<br>
<i class="fas fa-stopwatch"></i> {% trans "Duration" %}: {{ interview.schedule.interview_duration }} minutes
</p>
<div class="mt-auto pt-2 border-top">
<div class="d-flex gap-2">
<a href="{% url 'scheduled_interview_detail' interview.slug %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye"></i> {% trans "View" %}
</a>
{# CRITICAL FIX: Safe access to join URL #}
{% if interview.schedule.interview_type == 'Remote' and interview.zoom_meeting and interview.zoom_meeting.join_url %}
<a href="{{ interview.zoom_meeting.join_url }}" target="_blank" class="btn btn-sm btn-main-action">
<i class="fas fa-link"></i> {% trans "Join" %}
</a>
{% endif %}
<a href="{% url 'update_scheduled_interview' interview.slug %}" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-edit"></i>
</a>
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}"
data-bs-toggle="modal" data-bs-target="#deleteModal"
hx-post="{% url 'delete_scheduled_interview' interview.slug %}"
hx-target="#deleteModalBody"
hx-swap="outerHTML"
data-item-name="{{ interview.candidate.name }} Interview">
<i class="fas fa-trash-alt"></i>
</button>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<form id="interview-form">
{% csrf_token %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table interview-table align-middle"> <table class="table interview-table table-hover align-middle mb-0">
<thead> <thead>
<tr> <tr>
<th><i class="fas fa-user me-1"></i> {% trans "Candidate" %}</th> <th>{% trans "Candidate" %}</th>
<th><i class="fas fa-briefcase me-1"></i> {% trans "Job" %}</th> <th>{% trans "Job" %}</th>
<th><i class="fas fa-calendar me-1"></i> {% trans "Date & Time" %}</th> <th>{% trans "Date & Time" %}</th>
<th><i class="fas fa-tag me-1"></i> {% trans "Type" %}</th> <th>{% trans "Type" %}</th>
<th><i class="fas fa-info-circle me-1"></i> {% trans "Status" %}</th> <th>{% trans "Status" %}</th>
{% comment %} <th><i class="fas fa-users me-1"></i> {% trans "Participants" %}</th> {% endcomment %} <th class="text-end">{% trans "Actions" %}</th>
<th><i class="fas fa-cog me-1"></i> {% trans "Actions" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -346,12 +260,13 @@
</div> </div>
</td> </td>
<td> <td>
{% if interview.interview.location_type == 'Remote' %} {# Assuming interview.interview.location_type is meant to be interview.location_type #}
<span class="badge interview-type-badge bg-remote"> {% if interview.location_type == 'Remote' %}
<span class="badge interview-type-badge bg-primary-theme">
<i class="fas fa-video me-1"></i> {% trans "Remote" %} <i class="fas fa-video me-1"></i> {% trans "Remote" %}
</span> </span>
{% else %} {% else %}
<span class="badge interview-type-badge bg-onsite"> <span class="badge interview-type-badge bg-primary-theme">
<i class="fas fa-building me-1"></i> {% trans "Onsite" %} <i class="fas fa-building me-1"></i> {% trans "Onsite" %}
</span> </span>
{% endif %} {% endif %}
@ -362,103 +277,103 @@
</span> </span>
</td> </td>
<td> <td class="text-end">
<div class="btn-group" role="group">
<a href="{% url 'interview_detail' interview.slug %}" <a href="{% url 'interview_detail' interview.slug %}"
class="btn btn-outline-primary btn-sm" class="btn btn-outline-secondary btn-sm"
title="{% trans 'View Details' %}"> title="{% trans 'View Details' %}">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</a> </a>
{% comment %} {% if interview.status != 'CANCELLED' and interview.status != 'COMPLETED' %} {# Actions for Reschedule/Cancel (Commented out in original, kept commented) #}
<button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal"
data-bs-target="#actionModal"
hx-get="#"
hx-target="#actionModalBody"
title="{% trans 'Reschedule' %}">
<i class="fas fa-redo-alt"></i>
</button>
<button type="button" class="btn btn-outline-danger btn-sm"
data-bs-toggle="modal"
data-bs-target="#actionModal"
hx-get="#"
hx-target="#actionModalBody"
title="{% trans 'Cancel' %}">
<i class="fas fa-times"></i>
</button>
{% endif %} {% endcomment %}
</div>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</form> </div>
{# Card View (Mobile/Tablet) #}
<div class="card-view row g-4 d-lg-none">
{% for interview in interviews %}
<div class="col-md-6 col-sm-12">
<div class="card kaauh-card h-100 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title flex-grow-1 me-3">
<a href="{% url 'interview_detail' interview.slug %}" class="text-decoration-none text-primary-theme">{{ interview.application.name }}</a>
</h5>
<span class="status-badge bg-primary-theme text-white">
{{ interview.status|title }}
</span>
</div>
<!-- Pagination --> <p class="card-text text-muted small mb-3">
{% if is_paginated %} <i class="fas fa-briefcase"></i> {% trans "Job" %}:
<nav aria-label="Interview pagination" class="mt-4"> <a class="text-secondary text-decoration-none" href="{% url 'job_detail' interview.job.slug %}">{{ interview.job.title }}</a><br>
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">{% trans "First" %}</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">{% trans "Previous" %}</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %} <i class="fas fa-calendar-day"></i> {% trans "Date" %}: {{ interview.interview_date|date:"M d, Y" }}<br>
{% if page_obj.number == num %} <i class="fas fa-clock"></i> {% trans "Time" %}: {{ interview.interview_time|time:"H:i A" }}<br>
<li class="page-item active">
<span class="page-link">{{ num }}</span> {# --- Type/Location --- #}
</li> <i class="fas {% if interview.location_type == 'Remote' %}fa-globe{% else %}fa-map-marker-alt{% endif %}"></i>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %} {% trans "Type" %}: {{ interview.location_type }}
<li class="page-item"> {% if interview.location_type == 'Remote' %}<br>
<a class="page-link" href="?page={{ num }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">{{ num }}</a> {# Using interview.join_url directly if available, assuming interview is the full object #}
</li> <i class="fas fa-link"></i> {% trans "Link" %}: {% if interview.join_url %}<a href="{{ interview.join_url }}" target="_blank">Join Meeting</a>{% else %}N/A{% endif %}
{% endif %} {% else %}<br>
{% endfor %} <i class="fas fa-building"></i> {% trans "Location" %}: {{ interview.location_details|default:"Onsite" }}
{% endif %}
</p>
{% if page_obj.has_next %} <div class="mt-auto pt-2 border-top">
<li class="page-item"> <div class="d-flex gap-2">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">{% trans "Next" %}</a> <a href="{% url 'interview_detail' interview.slug %}" class="btn btn-sm btn-main-action">
</li> <i class="fas fa-eye"></i> {% trans "View" %}
<li class="page-item"> </a>
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">{% trans "Last" %}</a> {# Join button logic simplified #}
</li> {% if interview.location_type == 'Remote' and interview.join_url %}
{% endif %} <a href="{{ interview.join_url }}" target="_blank" class="btn btn-sm btn-outline-secondary">
</ul> <i class="fas fa-link"></i> {% trans "Join" %}
</nav> </a>
{% endif %} {% endif %}
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{# FIX: Added the missing pagination include here #}
{% include "includes/paginator.html" %}
{% else %} {% else %}
<!-- Empty State --> <div class="text-center py-5 kaauh-card">
<div class="text-center py-5"> <div class="card-body">
<div class="mb-4"> <i class="fas fa-calendar fa-4x text-muted" style="color: var(--kaauh-teal-dark) !important;"></i>
<i class="fas fa-calendar fa-4x text-muted"></i>
<h4 class="text-muted mt-3 mb-3">
{% if search_query or job_filter or request.GET.status or request.GET.type %}
{% trans "There are no interviews matching your filters." %}
{% else %}
{% trans "There are no interviews scheduled." %}
{% endif %}
</h4>
<p class="text-muted mb-4">
{% trans "Start by setting the interview stage for an application." %}
</p>
{# Add button if needed, otherwise rely on the header button #}
</div> </div>
<h4 class="text-muted mb-3">
{% if search_query %}
{% trans "There are no interviews matching your search filters." %}
{% else %}
{% trans "There are no interviews." %}
{% endif %}
</h4>
<p class="text-muted mb-4">
{% trans "Start by adding your first interview." %}
</p>
{% comment %} <a href="{% url 'interview_list' %}" class="btn btn-main-action">
<i class="fas fa-plus me-2"></i> {% trans "Add Your First Agency" %}
</a> {% endcomment %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
<!-- Action Modal -->
<div class="modal fade" id="actionModal" tabindex="-1" aria-labelledby="actionModalLabel" aria-hidden="true"> <div class="modal fade" id="actionModal" tabindex="-1" aria-labelledby="actionModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content kaauh-card"> <div class="modal-content kaauh-card">
@ -539,4 +454,4 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -108,7 +108,7 @@
<div class="stage-icon"> <div class="stage-icon">
<i class="fas fa-file-signature cd_screening"></i> <i class="fas fa-file-signature cd_screening"></i>
</div> </div>
<div class="stage-label cd_screening">{% trans "Screened" %}</div> <div class="stage-label cd_screening">{% trans "Screening" %}</div>
<div class="stage-count">{{ job.screening_applications.count|default:"0" }}</div> <div class="stage-count">{{ job.screening_applications.count|default:"0" }}</div>
</a> </a>

View File

@ -333,41 +333,8 @@
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<nav aria-label="{% trans 'Agency pagination' %}" class="mt-4"> <!-- Pagination -->
<ul class="pagination justify-content-center"> {% include "includes/paginator.html" %}
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-chevron-left"></i>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}">{{ num }}</a>
</li>
{% elif num == 1 or num == page_obj.paginator.num_pages %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-chevron-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %} {% endif %}
{% else %} {% else %}
<!-- Empty State --> <!-- Empty State -->

View File

@ -403,7 +403,7 @@
</div> </div>
{# Pagination (Standardized to Reference) #} {# Pagination (Standardized to Reference) #}
{% include "includes/paginator.html" %} {% include "includes/paginator.html" %}
{% else %} {% else %}
<div class="text-center py-5 card shadow-sm"> <div class="text-center py-5 card shadow-sm">
<div class="card-body"> <div class="card-body">

View File

@ -128,49 +128,8 @@
</div> </div>
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %}
<nav aria-label="Sources pagination"> {% include "includes/paginator.html" %}
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-angle-double-left"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-angle-left"></i>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-angle-right"></i>
</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}">
<i class="fas fa-angle-double-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-5">
<i class="fas fa-database fa-3x text-muted mb-3"></i> <i class="fas fa-database fa-3x text-muted mb-3"></i>