fix the resume prev stage not rendring

This commit is contained in:
ismail 2025-12-17 17:17:58 +03:00
parent 99ec8b0171
commit ade0f1506c
7 changed files with 239 additions and 206 deletions

6
.env
View File

@ -1,3 +1,3 @@
DB_NAME=haikal_db DB_NAME=norahuniversity
DB_USER=faheed DB_USER=norahuniversity
DB_PASSWORD=Faheed@215 DB_PASSWORD=norahuniversity

View File

@ -17,6 +17,7 @@ urlpatterns = [
# Job CRUD Operations # Job CRUD Operations
path("jobs/", views.JobListView.as_view(), name="job_list"), path("jobs/", views.JobListView.as_view(), name="job_list"),
path("jobs/create/", views.create_job, name="job_create"), path("jobs/create/", views.create_job, name="job_create"),
path("jobs/bank/", views.job_bank_view, name="job_bank"),
path("jobs/<slug:slug>/", views.job_detail, name="job_detail"), path("jobs/<slug:slug>/", views.job_detail, name="job_detail"),
path("jobs/<slug:slug>/update/", views.edit_job, name="job_update"), path("jobs/<slug:slug>/update/", views.edit_job, name="job_update"),
path("jobs/<slug:slug>/upload-image/", views.job_image_upload, name="job_image_upload"), path("jobs/<slug:slug>/upload-image/", views.job_image_upload, name="job_image_upload"),
@ -25,7 +26,6 @@ urlpatterns = [
path("jobs/<slug:slug>/applicants/", views.job_applicants_view, name="job_applicants"), path("jobs/<slug:slug>/applicants/", views.job_applicants_view, name="job_applicants"),
path("jobs/<slug:slug>/applications/", views.JobApplicationListView.as_view(), name="job_applications_list"), path("jobs/<slug:slug>/applications/", views.JobApplicationListView.as_view(), name="job_applications_list"),
path("jobs/<slug:slug>/calendar/", views.interview_calendar_view, name="interview_calendar"), path("jobs/<slug:slug>/calendar/", views.interview_calendar_view, name="interview_calendar"),
path("jobs/bank/", views.job_bank_view, name="job_bank"),
# Job Actions & Integrations # Job Actions & Integrations
path("jobs/<slug:slug>/post-to-linkedin/", views.post_to_linkedin, name="post_to_linkedin"), path("jobs/<slug:slug>/post-to-linkedin/", views.post_to_linkedin, name="post_to_linkedin"),

View File

@ -2230,60 +2230,60 @@ def reschedule_meeting_for_application(request, slug):
def interview_calendar_view(request, slug): def interview_calendar_view(request, slug):
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
# # Get all scheduled interviews for this job # Get all scheduled interviews for this job
# scheduled_interviews = ScheduledInterview.objects.filter(job=job).select_related( scheduled_interviews = ScheduledInterview.objects.filter(job=job).select_related(
# "applicaton", "zoom_meeting" "interview","application"
# ) )
# # Convert interviews to calendar events # Convert interviews to calendar events
# events = [] events = []
# for interview in scheduled_interviews: for interview in scheduled_interviews:
# # Create start datetime # Create start datetime
# start_datetime = datetime.combine( start_datetime = datetime.combine(
# interview.interview_date, interview.interview_time interview.interview_date, interview.interview_time
# ) )
# # Calculate end datetime based on interview duration # Calculate end datetime based on interview duration
# duration = interview.zoom_meeting.duration if interview.zoom_meeting else 60 duration = interview.interview.duration if interview.interview else 60
# end_datetime = start_datetime + timedelta(minutes=duration) end_datetime = start_datetime + timedelta(minutes=duration)
# # Determine event color based on status # Determine event color based on status
# color = "#00636e" # Default color color = "#00636e" # Default color
# if interview.status == "confirmed": if interview.status == "confirmed":
# color = "#00a86b" # Green for confirmed color = "#00a86b" # Green for confirmed
# elif interview.status == "cancelled": elif interview.status == "cancelled":
# color = "#e74c3c" # Red for cancelled color = "#e74c3c" # Red for cancelled
# elif interview.status == "completed": elif interview.status == "completed":
# color = "#95a5a6" # Gray for completed color = "#95a5a6" # Gray for completed
# events.append( events.append(
# { {
# "title": f"Interview: {interview.candidate.name}", "title": f"Interview: {interview.application.person.full_name}",
# "start": start_datetime.isoformat(), "start": start_datetime.isoformat(),
# "end": end_datetime.isoformat(), "end": end_datetime.isoformat(),
# "url": f"{request.path}interview/{interview.id}/", "url": f"{request.path}interview/{interview.id}/",
# "color": color, "color": color,
# "extendedProps": { "extendedProps": {
# "candidate": interview.candidate.name, "candidate": interview.application.person.full_name,
# "email": interview.candidate.email, "email": interview.application.person.email,
# "status": interview.status, "status": interview.interview.status,
# "meeting_id": interview.zoom_meeting.meeting_id "meeting_id": interview.interview.meeting_id
# if interview.zoom_meeting if interview.interview
# else None, else None,
# "join_url": interview.zoom_meeting.join_url "join_url": interview.interview.join_url
# if interview.zoom_meeting if interview.interview
# else None, else None,
# }, },
# } }
# ) )
# context = { context = {
# "job": job, "job": job,
# "events": events, "events": events,
# "calendar_color": "#00636e", "calendar_color": "#00636e",
# } }
# return render(request, "recruitment/interview_calendar.html", context) return render(request, "recruitment/interview_calendar.html", context)
def user_profile_image_update(request, pk): def user_profile_image_update(request, pk):
@ -4476,7 +4476,7 @@ def source_list(request):
"""List all sources with search and pagination""" """List all sources with search and pagination"""
search_query = request.GET.get("q", "") search_query = request.GET.get("q", "")
sources = Source.objects.all() sources = Source.objects.all()
if search_query: if search_query:
sources = sources.filter( sources = sources.filter(
Q(name__icontains=search_query) Q(name__icontains=search_query)

View File

@ -877,33 +877,39 @@
} }
function renderCurrentStage() { function renderCurrentStage() {
if (state.isPreview) { // Always show stage container and hide preview container initially
renderPreview(); elements.stageContainer.style.display = 'block';
return; elements.previewContainer.style.display = 'none';
}
const currentStage = state.stages[state.currentStage]; if (state.isPreview) {
elements.stageContainer.innerHTML = ''; renderPreview();
elements.previewContainer.style.display = 'none'; return;
}
const stageTitle = document.createElement('h2'); const currentStage = state.stages[state.currentStage];
stageTitle.className = 'stage-title'; elements.stageContainer.innerHTML = '';
stageTitle.textContent = currentStage.name;
elements.stageContainer.appendChild(stageTitle);
currentStage.fields.forEach(field => { const stageTitle = document.createElement('h2');
const fieldElement = createFieldElement(field); stageTitle.className = 'stage-title';
elements.stageContainer.appendChild(fieldElement); stageTitle.textContent = currentStage.name;
}); elements.stageContainer.appendChild(stageTitle);
currentStage.fields.forEach(field => {
const fieldElement = createFieldElement(field);
elements.stageContainer.appendChild(fieldElement);
});
// Update navigation buttons
elements.backBtn.style.display = state.currentStage > 0 ? 'flex' : 'none';
elements.submitBtn.style.display = 'none';
elements.nextBtn.style.display = 'flex';
// Fix: Update the Next button text correctly
elements.nextBtn.innerHTML = state.currentStage === state.stages.length - 1 ?
'Preview <i class="fas fa-arrow-right"></i>' :
'Next <i class="fas fa-arrow-right"></i>';
}
// Update navigation buttons
elements.backBtn.style.display = state.currentStage > 0 ? 'flex' : 'none';
elements.submitBtn.style.display = 'none';
elements.nextBtn.style.display = 'flex';
elements.nextBtn.textContent = state.currentStage === state.stages.length - 1 ?
'Preview' :
'Next'
}
function createFieldElement(field) { function createFieldElement(field) {
const fieldDiv = document.createElement('div'); const fieldDiv = document.createElement('div');
@ -1158,103 +1164,106 @@
} }
function renderPreview() { function renderPreview() {
elements.stageContainer.style.display = 'none'; elements.stageContainer.style.display = 'none';
elements.previewContainer.style.display = 'block'; elements.previewContainer.style.display = 'block';
elements.previewContent.innerHTML = ''; elements.previewContent.innerHTML = '';
// Add applicant info if available // Add applicant info if available
if (state.formData.applicant_name || state.formData.applicant_email) { if (state.formData.applicant_name || state.formData.applicant_email) {
const applicantDiv = document.createElement('div'); const applicantDiv = document.createElement('div');
applicantDiv.className = 'preview-item'; applicantDiv.className = 'preview-item';
applicantDiv.innerHTML = ` applicantDiv.innerHTML = `
<div class="preview-label">Applicant Information</div> <div class="preview-label">Applicant Information</div>
<div class="preview-value"> <div class="preview-value">
${state.formData.applicant_name ? `<strong>Name:</strong> ${state.formData.applicant_name}<br>` : ''} ${state.formData.applicant_name ? `<strong>Name:</strong> ${state.formData.applicant_name}<br>` : ''}
${state.formData.applicant_email ? `<strong>Email:</strong> ${state.formData.applicant_email}` : ''} ${state.formData.applicant_email ? `<strong>Email:</strong> ${state.formData.applicant_email}` : ''}
</div> </div>
`; `;
elements.previewContent.appendChild(applicantDiv); elements.previewContent.appendChild(applicantDiv);
} }
// Add stage data // Add stage data
state.stages.forEach(stage => { state.stages.forEach(stage => {
const stageDiv = document.createElement('div'); const stageDiv = document.createElement('div');
stageDiv.className = 'preview-item'; stageDiv.className = 'preview-item';
const stageTitle = document.createElement('div'); const stageTitle = document.createElement('div');
stageTitle.className = 'preview-label'; stageTitle.className = 'preview-label';
stageTitle.textContent = stage.name; stageTitle.textContent = stage.name;
stageDiv.appendChild(stageTitle); stageDiv.appendChild(stageTitle);
const stageContent = document.createElement('div'); const stageContent = document.createElement('div');
stageContent.className = 'preview-value'; stageContent.className = 'preview-value';
stage.fields.forEach(field => { stage.fields.forEach(field => {
let value = state.formData[field.id]; let value = state.formData[field.id];
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
value = '<em>Not provided</em>'; value = '<em>Not provided</em>';
} else if (field.type === 'file' && value instanceof File) { } else if (field.type === 'file' && value instanceof File) {
value = value.name; value = value.name;
} else if (field.type === 'checkbox' && Array.isArray(value)) { } else if (field.type === 'checkbox' && Array.isArray(value)) {
value = value.join(', '); value = value.join(', ');
} }
const fieldDiv = document.createElement('div'); const fieldDiv = document.createElement('div');
fieldDiv.innerHTML = `<strong>${field.label}:</strong> ${value}`; fieldDiv.innerHTML = `<strong>${field.label}:</strong> ${value}`;
stageContent.appendChild(fieldDiv); stageContent.appendChild(fieldDiv);
}); });
stageDiv.appendChild(stageContent); stageDiv.appendChild(stageContent);
elements.previewContent.appendChild(stageDiv); elements.previewContent.appendChild(stageDiv);
}); });
// Update navigation buttons // Update navigation buttons
elements.backBtn.style.display = 'flex'; elements.backBtn.style.display = 'flex';
elements.nextBtn.style.display = 'none'; elements.nextBtn.style.display = 'none';
elements.submitBtn.style.display = 'flex'; elements.submitBtn.style.display = 'flex';
} }
// Navigation Functions // Navigation Functions
function nextStage() { function nextStage() {
if (state.isPreview) { if (state.isPreview) {
submitForm(); submitForm();
return; return;
} }
if (!validateCurrentStage()) { if (!validateCurrentStage()) {
// Scroll to first error // Scroll to first error
const firstError = document.querySelector('.error-message.show'); const firstError = document.querySelector('.error-message.show');
if (firstError) { if (firstError) {
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' }); firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
} }
return; return;
} }
if (state.currentStage === state.stages.length - 1) { if (state.currentStage === state.stages.length - 1) {
// Go to preview // Go to preview
state.isPreview = true; state.isPreview = true;
renderCurrentStage(); renderCurrentStage();
updateProgress(); updateProgress();
} else { } else {
// Go to next stage // Go to next stage
state.currentStage++; state.currentStage++;
renderCurrentStage(); renderCurrentStage();
updateProgress(); updateProgress();
} }
} }
function prevStage() { function prevStage() {
if (state.isPreview) { if (state.isPreview) {
// Go back to last stage // Go back to last stage from preview
state.isPreview = false; state.isPreview = false;
renderCurrentStage(); // Set to the last form stage
updateProgress(); state.currentStage = state.stages.length - 1;
} else if (state.currentStage > 0) { renderCurrentStage();
state.currentStage--; updateProgress();
renderCurrentStage(); } else if (state.currentStage > 0) {
updateProgress(); // Go to previous stage
} state.currentStage--;
} renderCurrentStage();
updateProgress();
}
}
// Initialize Application // Initialize Application
function init() { function init() {

View File

@ -234,11 +234,7 @@
</a> </a>
</li> </li>
{% endif %} {% endif %}
<li><hr class="dropdown-divider my-1"></li> <li><hr class="dropdown-divider my-1"></li>
<li> <li>
<form method="post" action="{% url 'account_logout'%}" class="d-inline"> <form method="post" action="{% url 'account_logout'%}" class="d-inline">
{% csrf_token %} {% csrf_token %}

View File

@ -147,6 +147,30 @@
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container-fluid py-4">
<!-- Modal for Staff Assignment -->
<div class="modal fade" id="staffAssignmentModal" tabindex="-1" aria-labelledby="staffAssignmentModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staffAssignmentModalLabel">
<i class="fas fa-user-plus me-2"></i> {% trans "Assign Staff Member" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'staff_assignment_view' job.slug %}">
{% csrf_token %}
{{staff_form|crispy}}
<div class="d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-main-action">
<i class="fas fa-save me-1"></i> {% trans "Save Assignment" %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
@ -223,7 +247,14 @@
{% endif %} {% endif %}
<div class="float-end"> <div class="float-end">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<i class="fas fa-user-tie me-2 text-primary"></i> <strong>{% trans "Assigned to :" %} </strong> {{ job.assigned_to|default:"" }} {% if job.assigned_to %}
<i class="fas fa-user-tie me-2 text-primary"></i>
<strong> {% trans "Assigned to :" %} </strong> {{ job.assigned_to|default:"" }}
{% else %}
<button type="button" class="btn btn-main-action btn-sm" data-bs-toggle="modal" data-bs-target="#staffAssignmentModal">
<i class="fas fa-user-plus me-1"></i> {% trans "Click To Assign" %}
</button>
{% endif %}
</div> </div>
</div> </div>
</h5> </h5>
@ -333,7 +364,10 @@
<a href="{% url 'applications_screening_view' job.slug %}" class="btn btn-main-action"> <a href="{% url 'applications_screening_view' job.slug %}" class="btn btn-main-action">
<i class="fas fa-layer-group me-1"></i> {% trans "Manage Applications" %} <i class="fas fa-layer-group me-1"></i> {% trans "Manage Applications" %}
</a> </a>
<a href="{% url 'interview_calendar' job.slug %}" class="btn btn-main-action">
<i class="fas fa-calendar me-1"></i> {% trans "View Calendar" %}
</a>
{% if not job.form_template.is_active %} {% if not job.form_template.is_active %}
{% if not jobzip_created %} {% if not jobzip_created %}
<a href="{% url 'request_cvs_download' job.slug %}" class="btn btn-main-action"> <a href="{% url 'request_cvs_download' job.slug %}" class="btn btn-main-action">
@ -412,31 +446,6 @@
</button> </button>
{% endif %} {% endif %}
<!-- Modal for Staff Assignment -->
<div class="modal fade" id="staffAssignmentModal" tabindex="-1" aria-labelledby="staffAssignmentModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staffAssignmentModalLabel">
<i class="fas fa-user-plus me-2"></i> {% trans "Assign Staff Member" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'staff_assignment_view' job.slug %}">
{% csrf_token %}
{{staff_form|crispy}}
<div class="d-flex justify-content-end mt-3">
<button type="submit" class="btn btn-main-action">
<i class="fas fa-save me-1"></i> {% trans "Save Assignment" %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% if not job.assigned_to %} {% if not job.assigned_to %}
<div class="alert alert-info p-2 small mb-0"> <div class="alert alert-info p-2 small mb-0">
<i class="fas fa-info-circle me-1"></i> {% trans "No staff members assigned to this job yet." %} <i class="fas fa-info-circle me-1"></i> {% trans "No staff members assigned to this job yet." %}

View File

@ -161,7 +161,7 @@
</a> </a>
</div> </div>
<div class="col-md-4 mb-4"> <div class="col-md-4 mb-4">
<a href="{% url "easy_logs" %}" class="text-decoration-none"> <a href="{% url 'easy_logs' %}" class="text-decoration-none">
<div class="kaauh-card shadow-sm p-4 h-100" style="border-left: 5px solid var(--kaauh-teal);"> <div class="kaauh-card shadow-sm p-4 h-100" style="border-left: 5px solid var(--kaauh-teal);">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<i class="fas fa-file-alt fa-3x text-primary-theme me-4"></i> <i class="fas fa-file-alt fa-3x text-primary-theme me-4"></i>
@ -199,25 +199,44 @@
</div> </div>
<div class="text-end mt-3"> <div class="text-end mt-3">
{% if not request.session.linkedin_authenticated %} {% if not request.session.linkedin_authenticated %}
<a class="text-decoration-none text-teal" href="{% url 'linkedin_login' %}">
<a class="text-decoration-none text-teal" href="{% url 'linkedin_login' %}"> <button class="btn btn-sm btn-outline-secondary">
{% trans "Sign to linkedin" %}<i class="fas fa-arrow-right ms-1"></i>
<button class="btn btn-sm btn-outline-secondary"> </button>
{% trans "Sign to linkedin" %}<i class="fas fa-arrow-right ms-1"></i> </a>
</button> {% else %}
<p class="text-primary">
</a> <i class="fab fa-linkedin me-2"></i>
{% trans "LinkedIn Connected" %}
{% else %} </p>
<p class="text-primary">
<i class="fab fa-linkedin me-2"></i>
{% trans "LinkedIn Connected" %}
</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</a> </a>
</div> </div>
<div class="col-md-4 mb-4">
<a href="{% url 'job_bank' %}" class="text-decoration-none">
<div class="kaauh-card shadow-sm p-4 h-100" style="border-left: 5px solid var(--kaauh-teal);">
<div class="d-flex align-items-center">
<i class="fas fa-database fa-3x text-primary-theme me-4"></i>
<div>
<h5 class="fw-bold mb-1" style="color: var(--kaauh-teal-dark);">
{% trans "Job Bank" %}
</h5>
<p class="text-muted small mb-0">
{% trans "Store your job postings in our Job Bank to reuse them later." %}
</p>
</div>
</div>
<div class="text-end mt-3">
<button class="btn btn-sm btn-outline-secondary">
{% trans "Go to Job Bank" %}<i class="fas fa-arrow-right ms-1"></i>
</button>
</div>
</div>
</a>
</div>
</div> </div>
{% endblock %} {% endblock %}