Merge branch 'main' of http://10.10.1.136:3000/marwan/kaauh_ats into frontend
This commit is contained in:
commit
667b4c45f2
6
.env
6
.env
@ -1,3 +1,3 @@
|
||||
DB_NAME=haikal_db
|
||||
DB_USER=faheed
|
||||
DB_PASSWORD=Faheed@215
|
||||
DB_NAME=norahuniversity
|
||||
DB_USER=norahuniversity
|
||||
DB_PASSWORD=norahuniversity
|
||||
@ -214,16 +214,16 @@ ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"}
|
||||
# EMAIL_PORT = 2225
|
||||
# EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
# EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD", "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI")
|
||||
# EMAIL_HOST = "smtp.mailersend.net"
|
||||
# EMAIL_PORT = 2525
|
||||
# EMAIL_HOST_USER = "MS_lhygCJ@test-65qngkd8nx3lwr12.mlsender.net"
|
||||
# EMAIL_HOST_PASSWORD = "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI"
|
||||
# EMAIL_USE_TLS = True
|
||||
# EMAIL_HOST = 'sandbox.smtp.mailtrap.io'
|
||||
# EMAIL_HOST_USER = '38e5179debe69a'
|
||||
# EMAIL_HOST_PASSWORD = 'ffa75647d01ecb'
|
||||
# EMAIL_PORT = '2525'
|
||||
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD", "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI")
|
||||
EMAIL_HOST = "smtp.mailersend.net"
|
||||
EMAIL_PORT = 2525
|
||||
EMAIL_HOST_USER = "MS_lhygCJ@test-65qngkd8nx3lwr12.mlsender.net"
|
||||
EMAIL_HOST_PASSWORD = "mssp.0Q0rSwb.zr6ke4n2k3e4on12.aHwJqnI"
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_HOST = 'sandbox.smtp.mailtrap.io'
|
||||
EMAIL_HOST_USER = '38e5179debe69a'
|
||||
EMAIL_HOST_PASSWORD = 'ffa75647d01ecb'
|
||||
EMAIL_PORT = '2525'
|
||||
|
||||
|
||||
|
||||
|
||||
@ -377,6 +377,13 @@ class ApplicationForm(forms.ModelForm):
|
||||
Submit("submit", _("Submit"), css_class="btn btn-primary"),
|
||||
)
|
||||
|
||||
def clean_job(self):
|
||||
job = self.cleaned_data.get("job")
|
||||
if job.max_applications <= Application.objects.filter(job=job).count():
|
||||
raise forms.ValidationError(
|
||||
"The maximum number of applicants for this job has been reached."
|
||||
)
|
||||
return job
|
||||
# def clean(self):
|
||||
# cleaned_data = super().clean()
|
||||
# job = cleaned_data.get("job")
|
||||
@ -1805,6 +1812,27 @@ class PasswordResetForm(forms.Form):
|
||||
raise forms.ValidationError(_('New passwords do not match.'))
|
||||
|
||||
return cleaned_data
|
||||
class PersonPasswordResetForm(forms.Form):
|
||||
new_password1 = forms.CharField(
|
||||
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
|
||||
label=_('New Password')
|
||||
)
|
||||
new_password2 = forms.CharField(
|
||||
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
|
||||
label=_('Confirm New Password')
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
"""Custom validation for password reset"""
|
||||
cleaned_data = super().clean()
|
||||
new_password1 = cleaned_data.get('new_password1')
|
||||
new_password2 = cleaned_data.get('new_password2')
|
||||
|
||||
if new_password1 and new_password2:
|
||||
if new_password1 != new_password2:
|
||||
raise forms.ValidationError(_('New passwords do not match.'))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class StaffAssignmentForm(forms.ModelForm):
|
||||
|
||||
@ -28,8 +28,9 @@ class EmailService:
|
||||
|
||||
try:
|
||||
# Using EmailMessage for more control (e.g., HTML content)
|
||||
|
||||
from time import sleep
|
||||
for recipient in recipient_list:
|
||||
sleep(2)
|
||||
email = EmailMessage(
|
||||
subject=subject,
|
||||
body=body,
|
||||
@ -46,8 +47,6 @@ class EmailService:
|
||||
recipient_user=User.objects.filter(email=recipient).first()
|
||||
if result and recipient_user and not context["message_created"]:
|
||||
Message.objects.create(sender=context['sender_user'],recipient=recipient_user,job=context['job'],subject=subject,content=context['email_message'],message_type='DIRECT',is_read=False)
|
||||
|
||||
|
||||
return len(recipient_list)
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@ -17,6 +17,7 @@ urlpatterns = [
|
||||
# Job CRUD Operations
|
||||
path("jobs/", views.JobListView.as_view(), name="job_list"),
|
||||
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>/update/", views.edit_job, name="job_update"),
|
||||
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>/applications/", views.JobApplicationListView.as_view(), name="job_applications_list"),
|
||||
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
|
||||
path("jobs/<slug:slug>/post-to-linkedin/", views.post_to_linkedin, name="post_to_linkedin"),
|
||||
@ -103,6 +103,7 @@ urlpatterns = [
|
||||
path("persons/<slug:slug>/", views.PersonDetailView.as_view(), name="person_detail"),
|
||||
path("persons/<slug:slug>/update/", views.PersonUpdateView.as_view(), name="person_update"),
|
||||
path("persons/<slug:slug>/delete/", views.PersonDeleteView.as_view(), name="person_delete"),
|
||||
path("persons/<slug:slug>/password_reset/", views.password_reset, name="password_reset"),
|
||||
|
||||
# ========================================================================
|
||||
# FORM & TEMPLATE MANAGEMENT
|
||||
|
||||
@ -280,6 +280,11 @@ class PersonDetailView(DetailView, LoginRequiredMixin, StaffRequiredMixin):
|
||||
template_name = "people/person_detail.html"
|
||||
context_object_name = "person"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
from .forms import PersonPasswordResetForm
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['password_form'] = PersonPasswordResetForm()
|
||||
return context
|
||||
|
||||
class PersonUpdateView(UpdateView, LoginRequiredMixin, StaffOrAgencyRequiredMixin):
|
||||
model = Person
|
||||
@ -1302,11 +1307,10 @@ def delete_form_template(request, template_id):
|
||||
# @staff_or_candidate_required
|
||||
def application_submit_form(request, slug):
|
||||
"""Display the form as a step-by-step wizard"""
|
||||
form_template = get_object_or_404(FormTemplate, slug=slug, is_active=True)
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
if not request.user.is_authenticated:
|
||||
return redirect("application_signup", slug=slug)
|
||||
print(form_template.job.slug)
|
||||
job = get_object_or_404(JobPosting, slug=form_template.job.slug)
|
||||
|
||||
if request.user.user_type == "candidate":
|
||||
person = request.user.person_profile
|
||||
if job.has_already_applied_to_this_job(person):
|
||||
@ -1316,23 +1320,16 @@ def application_submit_form(request, slug):
|
||||
"You have already applied to this job: Multiple applications are not allowed."
|
||||
),
|
||||
)
|
||||
return redirect("job_application_detail", slug=job.slug)
|
||||
return redirect("job_application_detail", slug=slug)
|
||||
|
||||
# template = get_object_or_404(FormTemplate, slug=slug, is_active=True)
|
||||
template = job.form_template
|
||||
stage = template.stages.filter(name="Contact Information")
|
||||
|
||||
job_id = template.job.internal_job_id
|
||||
job = template.job
|
||||
is_limit_exceeded = job.is_application_limit_reached
|
||||
if is_limit_exceeded:
|
||||
if job.is_application_limit_reached:
|
||||
messages.error(
|
||||
request,
|
||||
_(
|
||||
"Application limit reached: This job is no longer accepting new applications."
|
||||
),
|
||||
)
|
||||
return redirect("application_detail", slug=job.slug)
|
||||
return redirect("job_application_detail", slug=slug)
|
||||
if job.is_expired:
|
||||
messages.error(
|
||||
request,
|
||||
@ -1340,12 +1337,12 @@ def application_submit_form(request, slug):
|
||||
"Application deadline passed: This job is no longer accepting new applications."
|
||||
),
|
||||
)
|
||||
return redirect("application_detail", slug=job.slug)
|
||||
return redirect("job_application_detail", slug=slug)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"applicant/application_submit_form.html",
|
||||
{"template_slug": template.slug, "job_id": job_id},
|
||||
{"template_slug": job.form_template.slug, "job_id": job.internal_job_id},
|
||||
)
|
||||
|
||||
|
||||
@ -1357,8 +1354,9 @@ def application_submit(request, template_slug):
|
||||
import re
|
||||
|
||||
"""Handle form submission"""
|
||||
if not request.user.is_authenticated: # or request.user.user_type != "candidate":
|
||||
if not request.user.is_authenticated or request.user.user_type != "candidate":
|
||||
return JsonResponse({"success": False, "message": "Unauthorized access."})
|
||||
|
||||
template = get_object_or_404(FormTemplate, slug=template_slug)
|
||||
job = template.job
|
||||
if request.method == "POST":
|
||||
@ -1414,38 +1412,38 @@ def application_submit(request, template_slug):
|
||||
except FormField.DoesNotExist:
|
||||
continue
|
||||
try:
|
||||
gpa = submission.responses.get(field__label="GPA")
|
||||
if gpa and gpa.value:
|
||||
gpa_str = gpa.value.replace("/", "").strip()
|
||||
# gpa = submission.responses.get(field__label="GPA")
|
||||
# if gpa and gpa.value:
|
||||
# gpa_str = gpa.value.replace("/", "").strip()
|
||||
|
||||
if not re.match(r"^\d+(\.\d+)?$", gpa_str):
|
||||
# --- FIX APPLIED HERE ---
|
||||
return JsonResponse(
|
||||
{
|
||||
"success": False,
|
||||
"message": _("GPA must be a numeric value."),
|
||||
}
|
||||
)
|
||||
# if not re.match(r"^\d+(\.\d+)?$", gpa_str):
|
||||
# # --- FIX APPLIED HERE ---
|
||||
# return JsonResponse(
|
||||
# {
|
||||
# "success": False,
|
||||
# "message": _("GPA must be a numeric value."),
|
||||
# }
|
||||
# )
|
||||
|
||||
try:
|
||||
gpa_float = float(gpa_str)
|
||||
except ValueError:
|
||||
# --- FIX APPLIED HERE ---
|
||||
return JsonResponse(
|
||||
{
|
||||
"success": False,
|
||||
"message": _("GPA must be a numeric value."),
|
||||
}
|
||||
)
|
||||
# try:
|
||||
# gpa_float = float(gpa_str)
|
||||
# except ValueError:
|
||||
# # --- FIX APPLIED HERE ---
|
||||
# return JsonResponse(
|
||||
# {
|
||||
# "success": False,
|
||||
# "message": _("GPA must be a numeric value."),
|
||||
# }
|
||||
# )
|
||||
|
||||
if not (0.0 <= gpa_float <= 4.0):
|
||||
# --- FIX APPLIED HERE ---
|
||||
return JsonResponse(
|
||||
{
|
||||
"success": False,
|
||||
"message": _("GPA must be between 0.0 and 4.0."),
|
||||
}
|
||||
)
|
||||
# if not (0.0 <= gpa_float <= 4.0):
|
||||
# # --- FIX APPLIED HERE ---
|
||||
# return JsonResponse(
|
||||
# {
|
||||
# "success": False,
|
||||
# "message": _("GPA must be between 0.0 and 4.0."),
|
||||
# }
|
||||
# )
|
||||
|
||||
resume = submission.responses.get(field__label="Resume Upload")
|
||||
|
||||
@ -1456,7 +1454,7 @@ def application_submit(request, template_slug):
|
||||
submission.save()
|
||||
# time=timezone.now()
|
||||
person = request.user.person_profile
|
||||
person.gpa = gpa.value if gpa else None
|
||||
# person.gpa = gpa.value if gpa else None
|
||||
person.save()
|
||||
Application.objects.create(
|
||||
person=person,
|
||||
@ -2257,60 +2255,60 @@ def reschedule_meeting_for_application(request, slug):
|
||||
def interview_calendar_view(request, slug):
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
|
||||
# # Get all scheduled interviews for this job
|
||||
# scheduled_interviews = ScheduledInterview.objects.filter(job=job).select_related(
|
||||
# "applicaton", "zoom_meeting"
|
||||
# )
|
||||
# Get all scheduled interviews for this job
|
||||
scheduled_interviews = ScheduledInterview.objects.filter(job=job).select_related(
|
||||
"interview","application"
|
||||
)
|
||||
|
||||
# # Convert interviews to calendar events
|
||||
# events = []
|
||||
# for interview in scheduled_interviews:
|
||||
# # Create start datetime
|
||||
# start_datetime = datetime.combine(
|
||||
# interview.interview_date, interview.interview_time
|
||||
# )
|
||||
# Convert interviews to calendar events
|
||||
events = []
|
||||
for interview in scheduled_interviews:
|
||||
# Create start datetime
|
||||
start_datetime = datetime.combine(
|
||||
interview.interview_date, interview.interview_time
|
||||
)
|
||||
|
||||
# # Calculate end datetime based on interview duration
|
||||
# duration = interview.zoom_meeting.duration if interview.zoom_meeting else 60
|
||||
# end_datetime = start_datetime + timedelta(minutes=duration)
|
||||
# Calculate end datetime based on interview duration
|
||||
duration = interview.interview.duration if interview.interview else 60
|
||||
end_datetime = start_datetime + timedelta(minutes=duration)
|
||||
|
||||
# # Determine event color based on status
|
||||
# color = "#00636e" # Default color
|
||||
# if interview.status == "confirmed":
|
||||
# color = "#00a86b" # Green for confirmed
|
||||
# elif interview.status == "cancelled":
|
||||
# color = "#e74c3c" # Red for cancelled
|
||||
# elif interview.status == "completed":
|
||||
# color = "#95a5a6" # Gray for completed
|
||||
# Determine event color based on status
|
||||
color = "#00636e" # Default color
|
||||
if interview.status == "confirmed":
|
||||
color = "#00a86b" # Green for confirmed
|
||||
elif interview.status == "cancelled":
|
||||
color = "#e74c3c" # Red for cancelled
|
||||
elif interview.status == "completed":
|
||||
color = "#95a5a6" # Gray for completed
|
||||
|
||||
# events.append(
|
||||
# {
|
||||
# "title": f"Interview: {interview.candidate.name}",
|
||||
# "start": start_datetime.isoformat(),
|
||||
# "end": end_datetime.isoformat(),
|
||||
# "url": f"{request.path}interview/{interview.id}/",
|
||||
# "color": color,
|
||||
# "extendedProps": {
|
||||
# "candidate": interview.candidate.name,
|
||||
# "email": interview.candidate.email,
|
||||
# "status": interview.status,
|
||||
# "meeting_id": interview.zoom_meeting.meeting_id
|
||||
# if interview.zoom_meeting
|
||||
# else None,
|
||||
# "join_url": interview.zoom_meeting.join_url
|
||||
# if interview.zoom_meeting
|
||||
# else None,
|
||||
# },
|
||||
# }
|
||||
# )
|
||||
events.append(
|
||||
{
|
||||
"title": f"Interview: {interview.application.person.full_name}",
|
||||
"start": start_datetime.isoformat(),
|
||||
"end": end_datetime.isoformat(),
|
||||
"url": f"{request.path}interview/{interview.id}/",
|
||||
"color": color,
|
||||
"extendedProps": {
|
||||
"candidate": interview.application.person.full_name,
|
||||
"email": interview.application.person.email,
|
||||
"status": interview.interview.status,
|
||||
"meeting_id": interview.interview.meeting_id
|
||||
if interview.interview
|
||||
else None,
|
||||
"join_url": interview.interview.join_url
|
||||
if interview.interview
|
||||
else None,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# context = {
|
||||
# "job": job,
|
||||
# "events": events,
|
||||
# "calendar_color": "#00636e",
|
||||
# }
|
||||
context = {
|
||||
"job": job,
|
||||
"events": events,
|
||||
"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):
|
||||
@ -2990,6 +2988,22 @@ def portal_password_reset(request, pk):
|
||||
for error in errors:
|
||||
messages.error(request, f"{field}: {error}")
|
||||
|
||||
@require_POST
|
||||
def password_reset(request, slug):
|
||||
from .forms import PersonPasswordResetForm
|
||||
person = get_object_or_404(Person, slug=slug)
|
||||
if request.method == "POST":
|
||||
form = PersonPasswordResetForm(request.POST)
|
||||
if form.is_valid():
|
||||
person.user.set_password(form.cleaned_data["new_password1"])
|
||||
person.user.save()
|
||||
messages.success(request, "Password reset successfully.")
|
||||
return redirect("person_detail", slug=person.slug)
|
||||
else:
|
||||
for field, errors in form.errors.items():
|
||||
for error in errors:
|
||||
messages.error(request, f"{field}: {error}")
|
||||
|
||||
|
||||
def portal_login(request):
|
||||
"""Unified portal login for agency and applicant"""
|
||||
|
||||
@ -877,6 +877,10 @@
|
||||
}
|
||||
|
||||
function renderCurrentStage() {
|
||||
// Always show stage container and hide preview container initially
|
||||
elements.stageContainer.style.display = 'block';
|
||||
elements.previewContainer.style.display = 'none';
|
||||
|
||||
if (state.isPreview) {
|
||||
renderPreview();
|
||||
return;
|
||||
@ -884,7 +888,6 @@
|
||||
|
||||
const currentStage = state.stages[state.currentStage];
|
||||
elements.stageContainer.innerHTML = '';
|
||||
elements.previewContainer.style.display = 'none';
|
||||
|
||||
const stageTitle = document.createElement('h2');
|
||||
stageTitle.className = 'stage-title';
|
||||
@ -900,11 +903,14 @@
|
||||
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'
|
||||
|
||||
// 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>';
|
||||
}
|
||||
|
||||
|
||||
function createFieldElement(field) {
|
||||
const fieldDiv = document.createElement('div');
|
||||
fieldDiv.className = 'field-container';
|
||||
@ -1245,11 +1251,14 @@
|
||||
|
||||
function prevStage() {
|
||||
if (state.isPreview) {
|
||||
// Go back to last stage
|
||||
// Go back to last stage from preview
|
||||
state.isPreview = false;
|
||||
// Set to the last form stage
|
||||
state.currentStage = state.stages.length - 1;
|
||||
renderCurrentStage();
|
||||
updateProgress();
|
||||
} else if (state.currentStage > 0) {
|
||||
// Go to previous stage
|
||||
state.currentStage--;
|
||||
renderCurrentStage();
|
||||
updateProgress();
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
|
||||
<a href="{% url 'application_submit_form' job.slug %}" class="btn btn-main-action btn-lg w-100">
|
||||
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -220,12 +220,10 @@
|
||||
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
|
||||
<a href="{% url 'application_submit_form' job.slug %}" class="btn btn-main-action btn-lg w-100">
|
||||
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</footer>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endblock content%}
|
||||
@ -234,11 +234,7 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<li><hr class="dropdown-divider my-1"></li>
|
||||
|
||||
|
||||
<li>
|
||||
<form method="post" action="{% url 'account_logout'%}" class="d-inline">
|
||||
{% csrf_token %}
|
||||
|
||||
@ -231,7 +231,7 @@
|
||||
<div class="mt-auto pt-2 border-top">
|
||||
<div class="d-flex gap-2 justify-content-end">
|
||||
|
||||
<a href="{% url 'application_submit_form' template.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Preview' %}">
|
||||
<a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Preview' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}">
|
||||
@ -286,7 +286,7 @@
|
||||
<td>{{ template.updated_at|date:"M d, Y" }}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{% url 'application_submit_form' template.slug %}" class="btn btn-outline-primary" title="{% trans 'Preview' %}">
|
||||
<a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary" title="{% trans 'Preview' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
||||
|
||||
@ -147,6 +147,30 @@
|
||||
|
||||
{% block content %}
|
||||
<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">
|
||||
<ol class="breadcrumb">
|
||||
@ -192,10 +216,11 @@
|
||||
{% else %}bg-secondary{% endif %}'>
|
||||
{{ job.get_status_display }}
|
||||
</span>
|
||||
|
||||
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
|
||||
<button type="button" class="btn btn-outline-light btn-sm ms-2" data-bs-toggle="modal" data-bs-target="#editStatusModal">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Share Public Link Button #}
|
||||
@ -223,7 +248,14 @@
|
||||
{% endif %}
|
||||
<div class="float-end">
|
||||
<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>
|
||||
</h5>
|
||||
@ -333,6 +365,9 @@
|
||||
<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" %}
|
||||
</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 jobzip_created %}
|
||||
@ -369,10 +404,11 @@
|
||||
<p class="text-muted small mb-3">
|
||||
{% trans "Manage the custom application forms associated with this job posting." %}
|
||||
</p>
|
||||
|
||||
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
|
||||
<a href="{% url 'form_builder' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
|
||||
<i class="fas fa-list-alt me-1"></i> {% trans "Manage Job Form" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% comment %} {% if not job.form_template %}
|
||||
<a href="{% url 'create_form_template' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}
|
||||
@ -412,31 +448,6 @@
|
||||
</button>
|
||||
{% 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 %}
|
||||
<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." %}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}{{ person.get_full_name }} - {{ block.super }}{% endblock %}
|
||||
|
||||
@ -536,6 +536,33 @@
|
||||
</a>
|
||||
{% if user.is_staff %}
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#setStaffPasswordModal">
|
||||
<i class="fas fa-lock me-1"></i> {% trans "Set Staff Password" %}
|
||||
</button>
|
||||
|
||||
<!-- Set Staff Password Modal -->
|
||||
<div class="modal fade" id="setStaffPasswordModal" tabindex="-1" aria-labelledby="setStaffPasswordModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="setStaffPasswordModalLabel">
|
||||
<i class="fas fa-lock me-2"></i> {% trans "Set Staff Password" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="{% url 'password_reset' person.slug %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{password_form|crispy}}
|
||||
<div class="d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<a href="{% url 'person_update' person.slug %}" class="btn btn-main-action">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Applicant" %}
|
||||
</a>
|
||||
@ -543,8 +570,6 @@
|
||||
|
||||
<i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %}
|
||||
</a>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -231,7 +231,7 @@
|
||||
<div class="card-footer text-center">
|
||||
<small class="text-muted">
|
||||
{% trans "Already have an account?" %}
|
||||
<a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.form_template.slug %}" class="text-decoration-none text-kaauh-teal">
|
||||
<a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.slug %}" class="text-decoration-none text-kaauh-teal">
|
||||
{% trans "Login here" %}
|
||||
</a>
|
||||
</small>
|
||||
|
||||
@ -298,7 +298,7 @@
|
||||
</small>
|
||||
</div>
|
||||
{# Change Stage button #}
|
||||
{% if user.is_staff %}
|
||||
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
|
||||
<button type="button" class="btn btn-outline-light btn-sm mt-1" data-bs-toggle="modal" data-bs-target="#stageUpdateModal">
|
||||
<i class="fas fa-exchange-alt"></i> {% trans "Change Stage" %}
|
||||
</button>
|
||||
|
||||
@ -402,7 +402,7 @@
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#noteModal"
|
||||
hx-get="{% url 'application_add_note' application.slug %}"
|
||||
hx-swap="outerHTML"
|
||||
hx-swap="innerHTML"
|
||||
hx-target=".notemodal">
|
||||
<i class="fas fa-calendar-plus me-1"></i>
|
||||
Add note
|
||||
|
||||
@ -329,7 +329,7 @@
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#noteModal"
|
||||
hx-get="{% url 'application_add_note' application.slug %}"
|
||||
hx-swap="outerHTML"
|
||||
hx-swap="innerHTML"
|
||||
hx-target=".notemodal">
|
||||
<i class="fas fa-calendar-plus me-1"></i>
|
||||
Add note
|
||||
|
||||
@ -358,7 +358,7 @@
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#noteModal"
|
||||
hx-get="{% url 'application_add_note' application.slug %}"
|
||||
hx-swap="outerHTML"
|
||||
hx-swap="innerHTML"
|
||||
hx-target=".notemodal">
|
||||
<i class="fas fa-calendar-plus me-1"></i>
|
||||
Add note
|
||||
|
||||
@ -383,6 +383,9 @@
|
||||
<th scope="col" style="width: 5%;">
|
||||
<i class="fas fa-graduation-cap me-1"></i> {% trans "GPA" %}
|
||||
</th>
|
||||
<th scope="col" style="width: 5%;">
|
||||
<i class="fas fa-graduation-cap me-1"></i> {% trans "Years of Experience" %}
|
||||
</th>
|
||||
<th scope="col" style="width: 5%;" class="text-center">
|
||||
<i class="fas fa-robot me-1"></i> {% trans "AI Score" %}
|
||||
</th>
|
||||
@ -426,6 +429,11 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">{{application.person.gpa|default:"0"}}</td>
|
||||
<td class="text-center">
|
||||
<span class="badge ai-score-badge">
|
||||
{{ application.years_of_experience }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if application.is_resume_parsed %}
|
||||
{% if application.match_score %}
|
||||
@ -473,7 +481,7 @@
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#noteModal"
|
||||
hx-get="{% url 'application_add_note' application.slug %}"
|
||||
hx-swap="outerHTML"
|
||||
hx-swap="innerHTML"
|
||||
hx-target=".notemodal">
|
||||
<i class="fas fa-calendar-plus me-1"></i>
|
||||
Add note
|
||||
|
||||
@ -161,7 +161,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<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="d-flex align-items-center">
|
||||
<i class="fas fa-file-alt fa-3x text-primary-theme me-4"></i>
|
||||
@ -199,25 +199,44 @@
|
||||
</div>
|
||||
<div class="text-end mt-3">
|
||||
{% if not request.session.linkedin_authenticated %}
|
||||
|
||||
<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>
|
||||
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<p class="text-primary">
|
||||
<i class="fab fa-linkedin me-2"></i>
|
||||
{% trans "LinkedIn Connected" %}
|
||||
</p>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</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>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user