Compare commits

...

7 Commits

33 changed files with 693 additions and 709 deletions

View File

@ -226,6 +226,15 @@ EMAIL_PORT = 2225
# EMAIL_PORT = '2525' # EMAIL_PORT = '2525'
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_HOST = 'smtp.gmail.com'
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True
# EMAIL_HOST_USER = 'faheedk215@gmail.com' # Use your actual Gmail email address
# EMAIL_HOST_PASSWORD = 'nfxf xpzo bpsb lqje' #
# DEFAULT_FROM_EMAIL='faheedlearn@gmail.com'
# Crispy Forms Configuration # Crispy Forms Configuration
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5" CRISPY_TEMPLATE_PACK = "bootstrap5"

BIN
ats-ERD.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 KiB

BIN
ats.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@ -377,6 +377,13 @@ class ApplicationForm(forms.ModelForm):
Submit("submit", _("Submit"), css_class="btn btn-primary"), 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): # def clean(self):
# cleaned_data = super().clean() # cleaned_data = super().clean()
# job = cleaned_data.get("job") # job = cleaned_data.get("job")
@ -1805,6 +1812,27 @@ class PasswordResetForm(forms.Form):
raise forms.ValidationError(_('New passwords do not match.')) raise forms.ValidationError(_('New passwords do not match.'))
return cleaned_data 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): class StaffAssignmentForm(forms.ModelForm):

View File

@ -28,8 +28,9 @@ class EmailService:
try: try:
# Using EmailMessage for more control (e.g., HTML content) # Using EmailMessage for more control (e.g., HTML content)
from time import sleep
for recipient in recipient_list: for recipient in recipient_list:
sleep(2)
email = EmailMessage( email = EmailMessage(
subject=subject, subject=subject,
body=body, body=body,
@ -46,8 +47,6 @@ class EmailService:
recipient_user=User.objects.filter(email=recipient).first() recipient_user=User.objects.filter(email=recipient).first()
if result and recipient_user and not context["message_created"]: 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) 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) return len(recipient_list)
except Exception as e: except Exception as e:

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"),
@ -103,6 +103,7 @@ urlpatterns = [
path("persons/<slug:slug>/", views.PersonDetailView.as_view(), name="person_detail"), 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>/update/", views.PersonUpdateView.as_view(), name="person_update"),
path("persons/<slug:slug>/delete/", views.PersonDeleteView.as_view(), name="person_delete"), 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 # FORM & TEMPLATE MANAGEMENT

View File

@ -280,6 +280,11 @@ class PersonDetailView(DetailView, LoginRequiredMixin, StaffRequiredMixin):
template_name = "people/person_detail.html" template_name = "people/person_detail.html"
context_object_name = "person" 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): class PersonUpdateView(UpdateView, LoginRequiredMixin, StaffOrAgencyRequiredMixin):
model = Person model = Person
@ -1302,11 +1307,11 @@ def delete_form_template(request, template_id):
# @staff_or_candidate_required # @staff_or_candidate_required
def application_submit_form(request, slug): def application_submit_form(request, slug):
"""Display the form as a step-by-step wizard""" """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)
template_slug=job.form_template.slug
if not request.user.is_authenticated: if not request.user.is_authenticated:
return redirect("application_signup", slug=slug) return redirect("application_signup", slug=template_slug)
print(form_template.job.slug)
job = get_object_or_404(JobPosting, slug=form_template.job.slug)
if request.user.user_type == "candidate": if request.user.user_type == "candidate":
person = request.user.person_profile person = request.user.person_profile
if job.has_already_applied_to_this_job(person): if job.has_already_applied_to_this_job(person):
@ -1316,23 +1321,16 @@ def application_submit_form(request, slug):
"You have already applied to this job: Multiple applications are not allowed." "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) if job.is_application_limit_reached:
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:
messages.error( messages.error(
request, request,
_( _(
"Application limit reached: This job is no longer accepting new applications." "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: if job.is_expired:
messages.error( messages.error(
request, request,
@ -1340,12 +1338,12 @@ def application_submit_form(request, slug):
"Application deadline passed: This job is no longer accepting new applications." "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( return render(
request, request,
"applicant/application_submit_form.html", "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 +1355,9 @@ def application_submit(request, template_slug):
import re import re
"""Handle form submission""" """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."}) return JsonResponse({"success": False, "message": "Unauthorized access."})
template = get_object_or_404(FormTemplate, slug=template_slug) template = get_object_or_404(FormTemplate, slug=template_slug)
job = template.job job = template.job
if request.method == "POST": if request.method == "POST":
@ -1414,38 +1413,38 @@ def application_submit(request, template_slug):
except FormField.DoesNotExist: except FormField.DoesNotExist:
continue continue
try: try:
gpa = submission.responses.get(field__label="GPA") # gpa = submission.responses.get(field__label="GPA")
if gpa and gpa.value: # if gpa and gpa.value:
gpa_str = gpa.value.replace("/", "").strip() # gpa_str = gpa.value.replace("/", "").strip()
if not re.match(r"^\d+(\.\d+)?$", gpa_str): # if not re.match(r"^\d+(\.\d+)?$", gpa_str):
# --- FIX APPLIED HERE --- # # --- FIX APPLIED HERE ---
return JsonResponse( # return JsonResponse(
{ # {
"success": False, # "success": False,
"message": _("GPA must be a numeric value."), # "message": _("GPA must be a numeric value."),
} # }
) # )
try: # try:
gpa_float = float(gpa_str) # gpa_float = float(gpa_str)
except ValueError: # except ValueError:
# --- FIX APPLIED HERE --- # # --- FIX APPLIED HERE ---
return JsonResponse( # return JsonResponse(
{ # {
"success": False, # "success": False,
"message": _("GPA must be a numeric value."), # "message": _("GPA must be a numeric value."),
} # }
) # )
if not (0.0 <= gpa_float <= 4.0): # if not (0.0 <= gpa_float <= 4.0):
# --- FIX APPLIED HERE --- # # --- FIX APPLIED HERE ---
return JsonResponse( # return JsonResponse(
{ # {
"success": False, # "success": False,
"message": _("GPA must be between 0.0 and 4.0."), # "message": _("GPA must be between 0.0 and 4.0."),
} # }
) # )
resume = submission.responses.get(field__label="Resume Upload") resume = submission.responses.get(field__label="Resume Upload")
@ -1456,7 +1455,7 @@ def application_submit(request, template_slug):
submission.save() submission.save()
# time=timezone.now() # time=timezone.now()
person = request.user.person_profile person = request.user.person_profile
person.gpa = gpa.value if gpa else None # person.gpa = gpa.value if gpa else None
person.save() person.save()
Application.objects.create( Application.objects.create(
person=person, person=person,
@ -2257,60 +2256,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):
@ -2434,8 +2433,11 @@ def create_staff_user(request):
@superuser_required @superuser_required
def admin_settings(request): def admin_settings(request):
staffs = User.objects.filter(user_type="staff", is_superuser=False) staffs = User.objects.filter(user_type="staff", is_superuser=False)
paginator=Paginator(staffs,20)
page_number=request.GET.get('page')
page_obj=paginator.get_page(page_number)
form = ToggleAccountForm() form = ToggleAccountForm()
context = {"staffs": staffs, "form": form} context = {"staffs": page_obj, "form": form,"page_obj":page_obj}
return render(request, "user/admin_settings.html", context) return render(request, "user/admin_settings.html", context)
@ -2771,7 +2773,7 @@ def agency_assignment_list(request):
assignments = assignments.filter(status=status_filter) assignments = assignments.filter(status=status_filter)
# Pagination # Pagination
paginator = Paginator(assignments, 15) # Show 15 assignments per page paginator = Paginator(assignments, 20) # Show 15 assignments 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)
@ -2990,6 +2992,22 @@ def portal_password_reset(request, pk):
for error in errors: for error in errors:
messages.error(request, f"{field}: {error}") 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): def portal_login(request):
"""Unified portal login for agency and applicant""" """Unified portal login for agency and applicant"""
@ -4500,133 +4518,133 @@ def api_application_detail(request, candidate_id):
# Source CRUD Views # Source CRUD Views
@login_required # @login_required
@staff_user_required # @staff_user_required
def source_list(request): # 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)
| Q(source_type__icontains=search_query) # | Q(source_type__icontains=search_query)
| Q(description__icontains=search_query) # | Q(description__icontains=search_query)
) # )
# Order by most recently created # # Order by most recently created
sources = sources.order_by("-created_at") # sources = sources.order_by("-created_at")
# Pagination # # Pagination
paginator = Paginator(sources, 1) # 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)
context = { # context = {
"page_obj": page_obj, # "page_obj": page_obj,
"search_query": search_query, # "search_query": search_query,
"total_sources": sources.count(), # "total_sources": sources.count(),
} # }
return render(request, "recruitment/source_list.html", context) # return render(request, "recruitment/source_list.html", context)
@login_required # @login_required
@staff_user_required # @staff_user_required
def source_create(request): # def source_create(request):
"""Create a new source""" # """Create a new source"""
if request.method == "POST": # if request.method == "POST":
form = SourceForm(request.POST) # form = SourceForm(request.POST)
if form.is_valid(): # if form.is_valid():
source = form.save() # source = form.save()
messages.success(request, f'Source "{source.name}" created successfully!') # messages.success(request, f'Source "{source.name}" created successfully!')
return redirect("source_detail", slug=source.slug) # return redirect("source_detail", slug=source.slug)
else: # else:
messages.error(request, "Please correct the errors below.") # messages.error(request, "Please correct the errors below.")
else: # else:
form = SourceForm() # form = SourceForm()
context = { # context = {
"form": form, # "form": form,
"title": "Create New Source", # "title": "Create New Source",
"button_text": "Create Source", # "button_text": "Create Source",
} # }
return render(request, "recruitment/source_form.html", context) # return render(request, "recruitment/source_form.html", context)
@login_required # @login_required
@staff_user_required # @staff_user_required
def source_detail(request, slug): # def source_detail(request, slug):
"""View details of a specific source""" # """View details of a specific source"""
source = get_object_or_404(Source, slug=slug) # source = get_object_or_404(Source, slug=slug)
# Get integration logs for this source # # Get integration logs for this source
integration_logs = source.integration_logs.order_by("-created_at")[ # integration_logs = source.integration_logs.order_by("-created_at")[
:10 # :10
] # Show recent 10 logs # ] # Show recent 10 logs
# Statistics # # Statistics
total_logs = source.integration_logs.count() # total_logs = source.integration_logs.count()
successful_logs = source.integration_logs.filter(method="POST").count() # successful_logs = source.integration_logs.filter(method="POST").count()
failed_logs = source.integration_logs.filter( # failed_logs = source.integration_logs.filter(
method="POST", status_code__gte=400 # method="POST", status_code__gte=400
).count() # ).count()
context = { # context = {
"source": source, # "source": source,
"integration_logs": integration_logs, # "integration_logs": integration_logs,
"total_logs": total_logs, # "total_logs": total_logs,
"successful_logs": successful_logs, # "successful_logs": successful_logs,
"failed_logs": failed_logs, # "failed_logs": failed_logs,
} # }
return render(request, "recruitment/source_detail.html", context) # return render(request, "recruitment/source_detail.html", context)
@login_required # @login_required
@staff_user_required # @staff_user_required
def source_update(request, slug): # def source_update(request, slug):
"""Update an existing source""" # """Update an existing source"""
source = get_object_or_404(Source, slug=slug) # source = get_object_or_404(Source, slug=slug)
if request.method == "POST": # if request.method == "POST":
form = SourceForm(request.POST, instance=source) # form = SourceForm(request.POST, instance=source)
if form.is_valid(): # if form.is_valid():
source = form.save() # source = form.save()
messages.success(request, f'Source "{source.name}" updated successfully!') # messages.success(request, f'Source "{source.name}" updated successfully!')
return redirect("source_detail", slug=source.slug) # return redirect("source_detail", slug=source.slug)
else: # else:
messages.error(request, "Please correct the errors below.") # messages.error(request, "Please correct the errors below.")
else: # else:
form = SourceForm(instance=source) # form = SourceForm(instance=source)
context = { # context = {
"form": form, # "form": form,
"source": source, # "source": source,
"title": _("Edit Source: %(name)s") % {"name": source.name}, # "title": _("Edit Source: %(name)s") % {"name": source.name},
"button_text": _("Update Source"), # "button_text": _("Update Source"),
} # }
return render(request, "recruitment/source_form.html", context) # return render(request, "recruitment/source_form.html", context)
@login_required # @login_required
@staff_user_required # @staff_user_required
def source_delete(request, slug): # def source_delete(request, slug):
"""Delete a source""" # """Delete a source"""
source = get_object_or_404(Source, slug=slug) # source = get_object_or_404(Source, slug=slug)
if request.method == "POST": # if request.method == "POST":
source_name = source.name # source_name = source.name
source.delete() # source.delete()
messages.success(request, f'Source "{source_name}" deleted successfully!') # messages.success(request, f'Source "{source_name}" deleted successfully!')
return redirect("source_list") # return redirect("source_list")
context = { # context = {
"source": source, # "source": source,
"title": _("Delete Source: %(name)s") % {"name": source.name}, # "title": _("Delete Source: %(name)s") % {"name": source.name},
"message": _('Are you sure you want to delete the source "%(name)s"?') # "message": _('Are you sure you want to delete the source "%(name)s"?')
% {"name": source.name}, # % {"name": source.name},
"cancel_url": reverse("source_detail", kwargs={"slug": source.slug}), # "cancel_url": reverse("source_detail", kwargs={"slug": source.slug}),
} # }
return render(request, "recruitment/source_confirm_delete.html", context) # return render(request, "recruitment/source_confirm_delete.html", context)
@login_required @login_required
@ -5365,7 +5383,7 @@ class JobListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
model = JobPosting model = JobPosting
template_name = "jobs/job_list.html" template_name = "jobs/job_list.html"
context_object_name = "jobs" context_object_name = "jobs"
paginate_by = 10 paginate_by = 20
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset().order_by("-created_at") queryset = super().get_queryset().order_by("-created_at")
@ -5493,8 +5511,8 @@ class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
queryset = queryset.filter(job__slug=job) queryset = queryset.filter(job__slug=job)
if stage: if stage:
queryset = queryset.filter(stage=stage) queryset = queryset.filter(stage=stage)
queryset=queryset.order_by("-created_at")
return queryset.order_by("-created_at") return queryset
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)

View File

@ -16,6 +16,7 @@
--kaauh-info: #17a2b8; --kaauh-info: #17a2b8;
--kaauh-danger: #dc3545; --kaauh-danger: #dc3545;
--kaauh-warning: #ffc107; --kaauh-warning: #ffc107;
--kaauh-border: #eaeff3;
} }
/* Primary Color Overrides */ /* Primary Color Overrides */
@ -733,5 +734,47 @@ html[dir="rtl"] .me-auto { margin-right: 0 !important; margin-left: auto !import
color: var(--kaauh-teal); color: var(--kaauh-teal);
} }
/* --- Pagination Base Styles --- */
.pagination .page-link {
color: var(--kaauh-teal); /* Teal text for links */
border-color: var(--kaauh-border); /* Light border */
background-color: #fff;
font-weight: 500;
font-size: 0.9rem;
transition: all 0.2s ease-in-out;
}
/* --- Hover State --- */
.pagination .page-link:hover {
color: var(--kaauh-teal-dark); /* Darker teal text on hover */
background-color: var(--kaauh-gray-light);
border-color: var(--kaauh-border);
z-index: 2;
}
/* --- Active State (Current Page) --- */
/* This targets the <li class="active"> and overrides the inner span */
.pagination .page-item.active .page-link {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: #ffffff;
z-index: 3;
}
/* --- Focus State (Accessibility) --- */
/* Replaces the default blue glow with a teal glow */
.pagination .page-link:focus {
box-shadow: 0 0 0 0.25rem rgba(0, 99, 110, 0.25);
background-color: var(--kaauh-gray-light);
color: var(--kaauh-teal-dark);
}
/* --- First/Last Item Border Radius (Optional Rounding) --- */
.pagination .page-item:first-child .page-link {
border-top-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
.pagination .page-item:last-child .page-link {
border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;
}

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

@ -41,7 +41,7 @@
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %} <i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
</button> </button>
{% else %} {% 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" %} <i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
</a> </a>
{% endif %} {% endif %}
@ -220,12 +220,10 @@
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %} <i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
</button> </button>
{% else %} {% 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" %} <i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
</a> </a>
{% endif %} {% endif %}
</footer> </footer>
{% endif %} {% endif %}
{% endblock content%} {% endblock content%}

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

@ -231,7 +231,7 @@
<div class="mt-auto pt-2 border-top"> <div class="mt-auto pt-2 border-top">
<div class="d-flex gap-2 justify-content-end"> <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> <i class="fas fa-eye"></i>
</a> </a>
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}"> <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>{{ template.updated_at|date:"M d, Y" }}</td>
<td class="text-end"> <td class="text-end">
<div class="btn-group btn-group-sm" role="group"> <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> <i class="fas fa-eye"></i>
</a> </a>
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}"> <a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">

View File

@ -1,60 +1,58 @@
{% if page_obj.has_previous or page_obj.has_next %}
{% load url_extras i18n %}
{% if page_obj.has_other_pages %}
<nav aria-label="Page navigation" class="mt-4"> <nav aria-label="Page navigation" class="mt-4">
<ul class="pagination justify-content-center"> <ul class="pagination justify-content-center">
{# Helper to build the query string while excluding the 'page' parameter #} {# Get the filter parameters (e.g., "q=searchterm&job=dev") #}
{% load url_extras %}
{# Build a string of all current filters (e.g., &department=IT&type=FULL_TIME) #}
{% add_get_params request.GET as filter_params %} {% add_get_params request.GET as filter_params %}
{% with filter_params=filter_params %}
{% if page_obj.has_previous %} {# Define the & prefix logic inline to avoid custom tags #}
{% if filter_params %}{% if "?" in request.get_full_path or "page" in request.get_full_path %}{% endif %}{% endif %}
{# First Page Link #} {# --- First & Previous --- #}
<li class="page-item"> {% if page_obj.has_previous %}
<a class="page-link text-primary-theme" <li class="page-item">
href="?page=1{{ filter_params }}"> <a class="page-link text-primary-theme" href="?page=1{% if filter_params %}&{{ filter_params }}{% endif %}">
First <i class="fas fa-angle-double-left"></i>
</a> </a>
</li>
{# Previous Page Link #}
<li class="page-item">
<a class="page-link text-primary-theme"
href="?page={{ page_obj.previous_page_number }}{{ filter_params }}">
Previous
</a>
</li>
{% endif %}
{# Current Page Status - Use your teal/custom background class here #}
<li class="page-item active" aria-current="page">
<span class="page-link bg-kaauh-teal text-white">
{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
</li> </li>
<li class="page-item">
<a class="page-link text-primary-theme" href="?page={{ page_obj.previous_page_number }}{% if filter_params %}&{{ filter_params }}{% endif %}">
<i class="fas fa-angle-left"></i>
</a>
</li>
{% endif %}
{% if page_obj.has_next %} {# --- Page Numbers --- #}
{# --- Page Numbers --- #}
{# Next Page Link #} {% for num in page_obj.paginator.page_range %}
<li class="page-item"> {% if page_obj.number == num %}
<a class="page-link text-primary-theme" <li class="page-item active"><span class="page-link bg-kaauh-teal text-white border-kaauh-teal">{{ num }}</span></li>
href="?page={{ page_obj.next_page_number }}{{ filter_params }}"> {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
Next <li class="page-item"><a class="page-link text-primary-theme" href="?page={{ num }}{% if filter_params %}&{{ filter_params }}{% endif %}">{{ num }}</a></li>
</a> {% elif num == 1 or num == page_obj.paginator.num_pages %}
</li> {# Optional: Always show first and last page number #}
<li class="page-item"><a class="page-link text-primary-theme" href="?page={{ num }}{% if filter_params %}&{{ filter_params }}{% endif %}">{{ num }}</a></li>
{# Last Page Link #} {% elif num == page_obj.number|add:'-3' or num == page_obj.number|add:'3' %}
<li class="page-item"> {# Show the dots #}
<a class="page-link text-primary-theme" <li class="page-item disabled"><span class="page-link">...</span></li>
href="?page={{ page_obj.paginator.num_pages }}{{ filter_params }}">
Last
</a>
</li>
{% endif %} {% endif %}
{% endfor %}
{% endwith %} {# --- Next & Last --- #}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link text-primary-theme" href="?page={{ page_obj.next_page_number }}{% if filter_params %}&{{ filter_params }}{% endif %}">
<i class="fas fa-angle-right"></i>
</a>
</li>
<li class="page-item">
<a class="page-link text-primary-theme" href="?page={{ page_obj.paginator.num_pages }}{% if filter_params %}&{{ filter_params }}{% endif %}">
<i class="fas fa-angle-double-right"></i>
</a>
</li>
{% endif %}
</ul> </ul>
</nav> </nav>

View File

@ -138,19 +138,7 @@
font-size: 0.8rem !important; font-size: 0.8rem !important;
} }
/* Pagination Styling */
.pagination .page-link {
color: var(--kaauh-teal);
border-color: var(--kaauh-border);
}
.pagination .page-item.active .page-link {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
}
.pagination .page-link:hover {
color: var(--kaauh-teal-dark);
background-color: #f8f9fa;
}
</style> </style>
{% endblock %} {% endblock %}

View File

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load i18n %}
{% block title %}{{ job.title }} - Applicants{% endblock %} {% block title %}{{ job.title }} - Applicants{% endblock %}
{% block customCSS %} {% block customCSS %}
@ -374,33 +374,31 @@
{% block content %} {% block content %}
<div class="job-applicants-container"> <div class="job-applicants-container">
<!-- Job Header -->
<div class="job-header"> <div class="job-header">
<h1>{{ job.title }}</h1> <h1>{{ job.title }}</h1>
<p>{{ job.department|default:"General" }} • {{ job.get_job_type_display }} • {{ job.get_workplace_type_display }}</p> <p>{{ job.department|default:"General" }} • {{ job.get_job_type_display }} • {{ job.get_workplace_type_display }}</p>
<div class="job-meta"> <div class="job-meta">
<div class="job-meta-item"> <div class="job-meta-item">
<span></span> <i class="fas fa-users"></i>
<span>{{ total_applications }} Total Applicants</span> <span>{{ total_applications }} {% trans "Total Applicants" %}</span>
</div> </div>
{% if job.max_applications %} {% if job.max_applications %}
<div class="job-meta-item"> <div class="job-meta-item">
<span></span> <i class="fas fa-briefcase"></i>
<span>{{ job.max_applications }} Positions Available</span> <span>{{ job.max_applications }} {% trans "Positions Available" %}</span>
</div> </div>
{% endif %} {% endif %}
<div class="job-meta-item"> <div class="job-meta-item">
<span></span> <i class="fas fa-calendar-alt"></i>
<span>Posted {{ job.created_at|date:"M d, Y" }}</span> <span>{% trans "Posted" %} {{ job.created_at|date:"M d, Y" }}</span>
</div> </div>
</div> </div>
</div> </div>
<!-- Statistics Section -->
<div class="stats-section"> <div class="stats-section">
<div class="stat-card"> <div class="stat-card">
<div class="stat-number">{{ total_applications }}</div> <div class="stat-number">{{ total_applications }}</div>
<div class="stat-label">Total Applications</div> <div class="stat-label">{% trans "Total Applications" %}</div>
</div> </div>
{% for stage_key, stage_data in stage_stats.items %} {% for stage_key, stage_data in stage_stats.items %}
@ -413,32 +411,29 @@
{% if ai_score_stats.average %} {% if ai_score_stats.average %}
<div class="stat-card"> <div class="stat-card">
<div class="stat-number">{{ ai_score_stats.average|floatformat:1 }}</div> <div class="stat-number">{{ ai_score_stats.average|floatformat:1 }}</div>
<div class="stat-label">Average AI Score</div> <div class="stat-label">{% trans "Average AI Score" %}</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<!-- Filters Section -->
<div class="filters-section"> <div class="filters-section">
<form method="GET" class="filters-form"> <form method="GET" class="filters-form">
<div class="filters-grid"> <div class="filters-grid">
<!-- Search Box -->
<div class="filter-group"> <div class="filter-group">
<label for="search"> Search Applicants</label> <label for="search">{% trans "Search Applicants" %}</label>
<input <input
type="text" type="text"
id="search" id="search"
name="q" name="q"
value="{{ search_query }}" value="{{ search_query }}"
placeholder="Search by name, email, or phone..." placeholder="{% trans 'Search by name, email, or phone...' %}"
> >
</div> </div>
<!-- Stage Filter -->
<div class="filter-group"> <div class="filter-group">
<label for="stage"> Application Stage</label> <label for="stage">{% trans "Application Stage" %}</label>
<select id="stage" name="stage"> <select id="stage" name="stage">
<option value="">All Stages</option> <option value="">{% trans "All Stages" %}</option>
{% for key, value in stage_choices %} {% for key, value in stage_choices %}
<option value="{{ key }}" {% if stage_filter == key %}selected{% endif %}> <option value="{{ key }}" {% if stage_filter == key %}selected{% endif %}>
{{ value }} {{ value }}
@ -447,9 +442,8 @@
</select> </select>
</div> </div>
<!-- AI Score Range -->
<div class="filter-group"> <div class="filter-group">
<label for="min_ai_score"> Min AI Score</label> <label for="min_ai_score">{% trans "Min AI Score" %}</label>
<input <input
type="number" type="number"
id="min_ai_score" id="min_ai_score"
@ -462,7 +456,7 @@
</div> </div>
<div class="filter-group"> <div class="filter-group">
<label for="max_ai_score"> Max AI Score</label> <label for="max_ai_score">{% trans "Max AI Score" %}</label>
<input <input
type="number" type="number"
id="max_ai_score" id="max_ai_score"
@ -474,9 +468,8 @@
> >
</div> </div>
<!-- Date Range -->
<div class="filter-group"> <div class="filter-group">
<label for="date_from"> From Date</label> <label for="date_from">{% trans "From Date" %}</label>
<input <input
type="date" type="date"
id="date_from" id="date_from"
@ -486,7 +479,7 @@
</div> </div>
<div class="filter-group"> <div class="filter-group">
<label for="date_to"> To Date</label> <label for="date_to">{% trans "To Date" %}</label>
<input <input
type="date" type="date"
id="date_to" id="date_to"
@ -495,37 +488,33 @@
> >
</div> </div>
<!-- Sort By -->
<div class="filter-group"> <div class="filter-group">
<label for="sort"> Sort By</label> <label for="sort">{% trans "Sort By" %}</label>
<select id="sort" name="sort"> <select id="sort" name="sort">
<option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>Newest First</option> <option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>{% trans "Newest First" %}</option>
<option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>Oldest First</option> <option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>{% trans "Oldest First" %}</option>
<option value="person__first_name" {% if sort_by == 'person__first_name' %}selected{% endif %}>Name (A-Z)</option> <option value="person__first_name" {% if sort_by == 'person__first_name' %}selected{% endif %}>{% trans "Name (A-Z)" %}</option>
<option value="-person__first_name" {% if sort_by == '-person__first_name' %}selected{% endif %}>Name (Z-A)</option> <option value="-person__first_name" {% if sort_by == '-person__first_name' %}selected{% endif %}>{% trans "Name (Z-A)" %}</option>
<option value="stage" {% if sort_by == 'stage' %}selected{% endif %}>Stage</option> <option value="stage" {% if sort_by == 'stage' %}selected{% endif %}>{% trans "Stage" %}</option>
</select> </select>
</div> </div>
</div> </div>
<!-- Filter Actions -->
<div class="filters-actions"> <div class="filters-actions">
<button type="submit" class="btn-filter btn-primary"> <button type="submit" class="btn-filter btn-primary">
Apply Filters <i class="fas fa-filter"></i> {% trans "Apply Filters" %}
</button> </button>
<a href="{% url 'job_applicants' job.slug %}" class="btn-filter btn-secondary"> <a href="{% url 'job_applicants' job.slug %}" class="btn-filter btn-secondary">
Clear All <i class="fas fa-undo"></i> {% trans "Clear All" %}
</a> </a>
</div> </div>
</form> </form>
</div> </div>
<!-- Applicants Grid -->
{% if page_obj.object_list %} {% if page_obj.object_list %}
<div class="applicants-grid"> <div class="applicants-grid">
{% for application in page_obj.object_list %} {% for application in page_obj.object_list %}
<div class="applicant-card"> <div class="applicant-card">
<!-- Applicant Header -->
<div class="applicant-header"> <div class="applicant-header">
<div class="applicant-info"> <div class="applicant-info">
<div class="applicant-name"> <div class="applicant-name">
@ -541,15 +530,14 @@
</div> </div>
</div> </div>
<!-- Applicant Meta -->
<div class="applicant-meta"> <div class="applicant-meta">
<div class="meta-item"> <div class="meta-item">
<span></span> <i class="far fa-clock"></i>
<span>Applied {{ application.created_at|date:"M d, Y" }}</span> <span>{% trans "Applied" %} {{ application.created_at|date:"M d, Y" }}</span>
</div> </div>
{% if application.ai_analysis_data.analysis_data_en.match_score %} {% if application.ai_analysis_data.analysis_data_en.match_score %}
<div class="meta-item"> <div class="meta-item">
<span></span> <i class="fas fa-robot"></i>
<span class="ai-score {% if application.ai_analysis_data.analysis_data_en.match_score >= 75 %}high{% elif application.ai_analysis_data.analysis_data_en.match_score >= 50 %}medium{% else %}low{% endif %}"> <span class="ai-score {% if application.ai_analysis_data.analysis_data_en.match_score >= 75 %}high{% elif application.ai_analysis_data.analysis_data_en.match_score >= 50 %}medium{% else %}low{% endif %}">
{{ application.ai_analysis_data.analysis_data_en.match_score }}% {{ application.ai_analysis_data.analysis_data_en.match_score }}%
</span> </span>
@ -557,64 +545,44 @@
{% endif %} {% endif %}
{% if application.person.gpa %} {% if application.person.gpa %}
<div class="meta-item"> <div class="meta-item">
<span></span> <i class="fas fa-graduation-cap"></i>
<span>GPA: {{ application.person.gpa|floatformat:2 }}</span> <span>{% trans "GPA" %}: {{ application.person.gpa|floatformat:2 }}</span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<!-- Applicant Actions -->
<div class="applicant-actions"> <div class="applicant-actions">
{% comment %} <a href="{% url 'person_detail' application.person.pk %}" class="btn-action btn-view-profile">
👁️ Profile
</a> {% endcomment %}
<a href="{% url 'application_detail' application.slug %}" class="btn-filter btn-primary btn-sm"> <a href="{% url 'application_detail' application.slug %}" class="btn-filter btn-primary btn-sm">
Application <i class="fas fa-file-alt"></i> {% trans "Application" %}
</a> </a>
{% if application.stage == 'Interview' %} {% if application.stage == 'Interview' %}
<a href="{% url 'interview_list' %}" class="btn-filter btn-primary btn-sm"> <a href="{% url 'interview_list' %}" class="btn-filter btn-primary btn-sm">
Schedule <i class="fas fa-calendar-check"></i> {% trans "Schedule" %}
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'message_create' %}?job={{ job.pk }}&candidate={{ application.pk }}" class="btn-filter btn-primary btn-sm"> <a href="{% url 'message_create' %}?job={{ job.pk }}&candidate={{ application.pk }}" class="btn-filter btn-primary btn-sm">
✉️ Email <i class="fas fa-envelope"></i> {% trans "Email" %}
</a> </a>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<div class="pagination"> {% include "includes/paginator.html" %}
{% if page_obj.has_previous %}
<a href="?page=1{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">« First</a>
<a href="?page={{ page_obj.previous_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}"> Previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Next </a>
<a href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Last »</a>
{% endif %}
</div>
{% endif %} {% endif %}
{% else %} {% else %}
<!-- No Results -->
<div class="no-results"> <div class="no-results">
<h3>😔 No Applicants Found</h3> <h3>😔 {% trans "No Applicants Found" %}</h3>
<p> <p>
{% if search_query or stage_filter or min_ai_score or max_ai_score or date_from or date_to %} {% if search_query or stage_filter or min_ai_score or max_ai_score or date_from or date_to %}
We couldn't find any applicants matching your current filters. Try adjusting your search criteria or clearing some filters. {% trans "We couldn't find any applicants matching your current filters. Try adjusting your search criteria or clearing some filters." %}
{% else %} {% else %}
There are currently no applicants for this job. {% trans "There are currently no applicants for this job." %}
{% endif %} {% endif %}
</p> </p>
<a href="{% url 'job_applicants' job.slug %}" class="btn-filter btn-primary"> <a href="{% url 'job_applicants' job.slug %}" class="btn-filter btn-primary">
🔄 Clear Filters <i class="fas fa-sync-alt"></i> {% trans "Clear Filters" %}
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@ -1,5 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load i18n %}
{% block title %}Job Bank - All Opportunities{% endblock %} {% block title %}Job Bank - All Opportunities{% endblock %}
@ -303,36 +304,7 @@
color: white; color: white;
} }
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
margin-top: 2rem;
}
.pagination a,
.pagination span {
padding: 0.75rem 1rem;
border: 2px solid #e1e5e9;
border-radius: 8px;
text-decoration: none;
color: #333;
font-weight: 500;
transition: all 0.3s ease;
}
.pagination a:hover {
background: #667eea;
color: white;
border-color: #667eea;
}
.pagination .current {
background: #667eea;
color: white;
border-color: #667eea;
}
.no-results { .no-results {
text-align: center; text-align: center;
@ -387,8 +359,8 @@
<div class="job-bank-container"> <div class="job-bank-container">
<!-- Header Section --> <!-- Header Section -->
<div class="job-bank-header"> <div class="job-bank-header">
<h1>🏦 Job Bank</h1> <h1>🏦 {% trans "Job Bank" %}</h1>
<p>Explore all available opportunities across departments and find your perfect role</p> <p>{% trans "Explore all available opportunities across departments and find your perfect role" %}</p>
</div> </div>
<!-- Filters Section --> <!-- Filters Section -->
@ -397,21 +369,21 @@
<div class="filters-grid"> <div class="filters-grid">
<!-- Search Box --> <!-- Search Box -->
<div class="filter-group search-box"> <div class="filter-group search-box">
<label for="search">🔍 Search Jobs</label> <label for="search">🔍 {% trans "Search Jobs" %}</label>
<input <input
type="text" type="text"
id="search" id="search"
name="q" name="q"
value="{{ search_query }}" value="{{ search_query }}"
placeholder="Search by title, department, or keywords..." placeholder="{% trans "Search by title, department, or keywords..." %}"
> >
</div> </div>
<!-- Department Filter --> <!-- Department Filter -->
<div class="filter-group"> <div class="filter-group">
<label for="department">📁 Department</label> <label for="department">📁 {% trans "Department" %}</label>
<select id="department" name="department"> <select id="department" name="department">
<option value="">All Departments</option> <option value="">{% trans "All Departments" %}</option>
{% for dept in departments %} {% for dept in departments %}
<option value="{{ dept }}" {% if department_filter == dept %}selected{% endif %}> <option value="{{ dept }}" {% if department_filter == dept %}selected{% endif %}>
{{ dept }} {{ dept }}
@ -422,9 +394,9 @@
<!-- Job Type Filter --> <!-- Job Type Filter -->
<div class="filter-group"> <div class="filter-group">
<label for="job_type">💼 Job Type</label> <label for="job_type">💼 {% trans "Job Type" %}</label>
<select id="job_type" name="job_type"> <select id="job_type" name="job_type">
<option value="">All Types</option> <option value="">{% trans "All Types" %}</option>
{% for key, value in job_types.items %} {% for key, value in job_types.items %}
<option value="{{ key }}" {% if job_type_filter == key %}selected{% endif %}> <option value="{{ key }}" {% if job_type_filter == key %}selected{% endif %}>
{{ value }} {{ value }}
@ -435,9 +407,9 @@
<!-- Workplace Type Filter --> <!-- Workplace Type Filter -->
<div class="filter-group"> <div class="filter-group">
<label for="workplace_type">🏢 Workplace Type</label> <label for="workplace_type">🏢 {% trans "Workplace Type" %}</label>
<select id="workplace_type" name="workplace_type"> <select id="workplace_type" name="workplace_type">
<option value="">All Types</option> <option value="">{% trans "All Types" %}</option>
{% for key, value in workplace_types.items %} {% for key, value in workplace_types.items %}
<option value="{{ key }}" {% if workplace_type_filter == key %}selected{% endif %}> <option value="{{ key }}" {% if workplace_type_filter == key %}selected{% endif %}>
{{ value }} {{ value }}
@ -448,9 +420,9 @@
<!-- Status Filter --> <!-- Status Filter -->
<div class="filter-group"> <div class="filter-group">
<label for="status">📊 Status</label> <label for="status">📊 {% trans "Status" %}</label>
<select id="status" name="status"> <select id="status" name="status">
<option value="">All Statuses</option> <option value="">{% trans "All Statuses" %}</option>
{% for key, value in status_choices.items %} {% for key, value in status_choices.items %}
<option value="{{ key }}" {% if status_filter == key %}selected{% endif %}> <option value="{{ key }}" {% if status_filter == key %}selected{% endif %}>
{{ value }} {{ value }}
@ -461,25 +433,25 @@
<!-- Date Filter --> <!-- Date Filter -->
<div class="filter-group"> <div class="filter-group">
<label for="date_filter">📅 Posted Within</label> <label for="date_filter">📅 {% trans "Posted Within" %}</label>
<select id="date_filter" name="date_filter"> <select id="date_filter" name="date_filter">
<option value="">Any Time</option> <option value="">{% trans "Any Time" %}</option>
<option value="week" {% if date_filter == 'week' %}selected{% endif %}>Last Week</option> <option value="week" {% if date_filter == 'week' %}selected{% endif %}>{% trans "Last Week" %}</option>
<option value="month" {% if date_filter == 'month' %}selected{% endif %}>Last Month</option> <option value="month" {% if date_filter == 'month' %}selected{% endif %}>{% trans "Last Month" %}</option>
<option value="quarter" {% if date_filter == 'quarter' %}selected{% endif %}>Last 3 Months</option> <option value="quarter" {% if date_filter == 'quarter' %}selected{% endif %}>{% trans "Last 3 Months" %}</option>
</select> </select>
</div> </div>
<!-- Sort By --> <!-- Sort By -->
<div class="filter-group"> <div class="filter-group">
<label for="sort">🔄 Sort By</label> <label for="sort">🔄 {% trans "Sort By" %}</label>
<select id="sort" name="sort"> <select id="sort" name="sort">
<option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>Newest First</option> <option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>{% trans "Newest First" %}</option>
<option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>Oldest First</option> <option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>{% trans "Oldest First" %}</option>
<option value="title" {% if sort_by == 'title' %}selected{% endif %}>Title (A-Z)</option> <option value="title" {% if sort_by == 'title' %}selected{% endif %}>{% trans Title %} (A-Z)</option>
<option value="-title" {% if sort_by == '-title' %}selected{% endif %}>Title (Z-A)</option> <option value="-title" {% if sort_by == '-title' %}selected{% endif %}>{% trans "Title" %} (Z-A)</option>
<option value="department" {% if sort_by == 'department' %}selected{% endif %}>Department (A-Z)</option> <option value="department" {% if sort_by == 'department' %}selected{% endif %}>{% trans "Department" %} (A-Z)</option>
<option value="-department" {% if sort_by == '-department' %}selected{% endif %}>Department (Z-A)</option> <option value="-department" {% if sort_by == '-department' %}selected{% endif %}>{% trans "Department" %} (Z-A)</option>
</select> </select>
</div> </div>
</div> </div>
@ -487,10 +459,10 @@
<!-- Filter Actions --> <!-- Filter Actions -->
<div class="filters-actions"> <div class="filters-actions">
<button type="submit" class="btn-filter btn-primary"> <button type="submit" class="btn-filter btn-primary">
🔍 Apply Filters 🔍 {% trans "Apply Filters" %}
</button> </button>
<a href="{% url 'job_bank' %}" class="btn-filter btn-secondary"> <a href="{% url 'job_bank' %}" class="btn-filter btn-secondary">
🔄 Clear All 🔄 {% trans "Clear All" %}
</a> </a>
</div> </div>
</form> </form>
@ -499,9 +471,9 @@
<!-- Results Header --> <!-- Results Header -->
<div class="results-header"> <div class="results-header">
<div class="results-count"> <div class="results-count">
📊 Found <strong>{{ total_jobs }}</strong> job{{ total_jobs|pluralize }} 📊 {% trans "Found" %} <strong>{{ total_jobs }}</strong> {%trans "job" %}{{ total_jobs|pluralize }}
{% if search_query or department_filter or job_type_filter or workplace_type_filter or status_filter or date_filter %} {% if search_query or department_filter or job_type_filter or workplace_type_filter or status_filter or date_filter %}
with filters applied {% trans "with filters applied" %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -535,7 +507,7 @@
{% if job.max_applications %} {% if job.max_applications %}
<div class="meta-item"> <div class="meta-item">
<span class="meta-icon">👥</span> <span class="meta-icon">👥</span>
<span>{{ job.max_applications }} positions</span> <span>{{ job.max_applications }} {% trans "positions" %}</span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -549,11 +521,11 @@
<div class="job-actions"> <div class="job-actions">
{% if job.status == 'ACTIVE' %} {% if job.status == 'ACTIVE' %}
<a href="{% url 'job_applicants' job.slug %}" class="btn-apply"> <a href="{% url 'job_applicants' job.slug %}" class="btn-apply">
<EFBFBD> View Applicants {% trans "View Applicants" %}
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'job_detail' job.slug %}" class="btn-view"> <a href="{% url 'job_detail' job.slug %}" class="btn-view">
👁️ View Details {% trans "View Details" %}
</a> </a>
</div> </div>
</div> </div>
@ -562,35 +534,21 @@
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<div class="pagination"> {% include "includes/paginator.html" %}
{% if page_obj.has_previous %}
<a href="?page=1{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">« First</a>
<a href="?page={{ page_obj.previous_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}"> Previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Next </a>
<a href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.urlencode %}&{{ request.GET.urlencode }}{% endif %}">Last »</a>
{% endif %}
</div>
{% endif %} {% endif %}
{% else %} {% else %}
<!-- No Results --> <!-- No Results -->
<div class="no-results"> <div class="no-results">
<h3>😔 No Jobs Found</h3> <h3> {% trans "No Jobs Found" %}</h3>
<p> <p>
{% if search_query or department_filter or job_type_filter or workplace_type_filter or status_filter or date_filter %} {% if search_query or department_filter or job_type_filter or workplace_type_filter or status_filter or date_filter %}
We couldn't find any jobs matching your current filters. Try adjusting your search criteria or clearing some filters. {% trans "We couldn't find any jobs matching your current filters. Try adjusting your search criteria or clearing some filters." %}
{% else %} {% else %}
There are currently no job postings in the system. Check back later for new opportunities! {% trans "There are currently no job postings in the system. Check back later for new opportunities!" %}
{% endif %} {% endif %}
</p> </p>
<a href="{% url 'job_bank' %}" class="btn-filter btn-primary"> <a href="{% url 'job_bank' %}" class="btn-filter btn-primary">
🔄 Clear Filters {% trans "Clear Filters" %}
</a> </a>
</div> </div>
{% endif %} {% endif %}

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">
@ -192,10 +216,11 @@
{% else %}bg-secondary{% endif %}'> {% else %}bg-secondary{% endif %}'>
{{ job.get_status_display }} {{ job.get_status_display }}
</span> </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"> <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> <i class="fas fa-edit"></i>
</button> </button>
{% endif %}
</div> </div>
{# Share Public Link Button #} {# Share Public Link Button #}
@ -223,7 +248,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,6 +365,9 @@
<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 %}
@ -369,10 +404,11 @@
<p class="text-muted small mb-3"> <p class="text-muted small mb-3">
{% trans "Manage the custom application forms associated with this job posting." %} {% trans "Manage the custom application forms associated with this job posting." %}
</p> </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"> <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" %} <i class="fas fa-list-alt me-1"></i> {% trans "Manage Job Form" %}
</a> </a>
{% endif %}
{% comment %} {% if not job.form_template %} {% comment %} {% if not job.form_template %}
<a href="{% url 'create_form_template' %}" class="btn btn-main-action"> <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" %} <i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}
@ -412,31 +448,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

@ -182,39 +182,8 @@
</table> </table>
</div> </div>
{% if page_obj.has_other_pages %}
<nav aria-label="Message pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link text-primary-theme" href="?page={{ page_obj.previous_page_number }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">
<i class="fas fa-chevron-left"></i>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %} {% include "includes/paginator.html" %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link bg-primary-theme border-primary-theme">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link text-primary-theme" href="?page={{ num }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link text-primary-theme" href="?page={{ page_obj.next_page_number }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">
<i class="fas fa-chevron-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %} {% else %}
<div class="text-center text-muted py-5"> <div class="text-center text-muted py-5">
<i class="fas fa-inbox fa-3x mb-3 text-primary-theme"></i> <i class="fas fa-inbox fa-3x mb-3 text-primary-theme"></i>

View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n crispy_forms_tags %}
{% block title %}{{ person.get_full_name }} - {{ block.super }}{% endblock %} {% block title %}{{ person.get_full_name }} - {{ block.super }}{% endblock %}
@ -536,6 +536,33 @@
</a> </a>
{% if user.is_staff %} {% if user.is_staff %}
<div class="d-flex gap-2"> <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"> <a href="{% url 'person_update' person.slug %}" class="btn btn-main-action">
<i class="fas fa-edit me-1"></i> {% trans "Edit Applicant" %} <i class="fas fa-edit me-1"></i> {% trans "Edit Applicant" %}
</a> </a>
@ -543,8 +570,6 @@
<i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %} <i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %}
</a> </a>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View File

@ -108,19 +108,7 @@
background-color: var(--kaauh-gray-light); background-color: var(--kaauh-gray-light);
} }
/* Pagination Link Styling */
.pagination .page-item .page-link {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-border);
}
.pagination .page-item.active .page-link {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.pagination .page-item:hover .page-link:not(.active) {
background-color: #e9ecef;
}
/* Profile Image Styling */ /* Profile Image Styling */
.profile-image-small { .profile-image-small {

View File

@ -192,47 +192,7 @@
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<nav aria-label="{% trans 'Assignments pagination' %}"> {% include "includes/paginator.html" %}
<ul class="pagination justify-content-center mt-4">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}{% if status_filter %}&status={{ status_filter }}{% 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 %}{% if status_filter %}&status={{ status_filter }}{% 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 %}{% if status_filter %}&status={{ status_filter }}{% 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 %}{% if status_filter %}&status={{ status_filter }}{% 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 %}{% if status_filter %}&status={{ status_filter }}{% endif %}">
<i class="fas fa-angle-double-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-5">

View File

@ -219,6 +219,7 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% include "includes/paginator.html" %}
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-5">
<i class="fas fa-briefcase fa-3x text-muted mb-3"></i> <i class="fas fa-briefcase fa-3x text-muted mb-3"></i>

View File

@ -231,7 +231,7 @@
<div class="card-footer text-center"> <div class="card-footer text-center">
<small class="text-muted"> <small class="text-muted">
{% trans "Already have an account?" %} {% 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" %} {% trans "Login here" %}
</a> </a>
</small> </small>

View File

@ -298,7 +298,7 @@
</small> </small>
</div> </div>
{# Change Stage button #} {# 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"> <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" %} <i class="fas fa-exchange-alt"></i> {% trans "Change Stage" %}
</button> </button>

View File

@ -402,7 +402,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#noteModal" data-bs-target="#noteModal"
hx-get="{% url 'application_add_note' application.slug %}" hx-get="{% url 'application_add_note' application.slug %}"
hx-swap="outerHTML" hx-swap="innerHTML"
hx-target=".notemodal"> hx-target=".notemodal">
<i class="fas fa-calendar-plus me-1"></i> <i class="fas fa-calendar-plus me-1"></i>
Add note Add note

View File

@ -329,7 +329,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#noteModal" data-bs-target="#noteModal"
hx-get="{% url 'application_add_note' application.slug %}" hx-get="{% url 'application_add_note' application.slug %}"
hx-swap="outerHTML" hx-swap="innerHTML"
hx-target=".notemodal"> hx-target=".notemodal">
<i class="fas fa-calendar-plus me-1"></i> <i class="fas fa-calendar-plus me-1"></i>
Add note Add note

View File

@ -120,19 +120,6 @@
background-color: var(--kaauh-gray-light); background-color: var(--kaauh-gray-light);
} }
/* Pagination Link Styling (Consistent) */
.pagination .page-item .page-link {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-border);
}
.pagination .page-item.active .page-link {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.pagination .page-item:hover .page-link:not(.active) {
background-color: #e9ecef;
}
/* Filter & Search Layout Adjustments */ /* Filter & Search Layout Adjustments */
.filter-buttons { .filter-buttons {

View File

@ -358,7 +358,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#noteModal" data-bs-target="#noteModal"
hx-get="{% url 'application_add_note' application.slug %}" hx-get="{% url 'application_add_note' application.slug %}"
hx-swap="outerHTML" hx-swap="innerHTML"
hx-target=".notemodal"> hx-target=".notemodal">
<i class="fas fa-calendar-plus me-1"></i> <i class="fas fa-calendar-plus me-1"></i>
Add note Add note

View File

@ -383,6 +383,9 @@
<th scope="col" style="width: 5%;"> <th scope="col" style="width: 5%;">
<i class="fas fa-graduation-cap me-1"></i> {% trans "GPA" %} <i class="fas fa-graduation-cap me-1"></i> {% trans "GPA" %}
</th> </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"> <th scope="col" style="width: 5%;" class="text-center">
<i class="fas fa-robot me-1"></i> {% trans "AI Score" %} <i class="fas fa-robot me-1"></i> {% trans "AI Score" %}
</th> </th>
@ -426,6 +429,11 @@
</div> </div>
</td> </td>
<td class="text-center">{{application.person.gpa|default:"0"}}</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"> <td class="text-center">
{% if application.is_resume_parsed %} {% if application.is_resume_parsed %}
{% if application.match_score %} {% if application.match_score %}
@ -473,7 +481,7 @@
data-bs-toggle="modal" data-bs-toggle="modal"
data-bs-target="#noteModal" data-bs-target="#noteModal"
hx-get="{% url 'application_add_note' application.slug %}" hx-get="{% url 'application_add_note' application.slug %}"
hx-swap="outerHTML" hx-swap="innerHTML"
hx-target=".notemodal"> hx-target=".notemodal">
<i class="fas fa-calendar-plus me-1"></i> <i class="fas fa-calendar-plus me-1"></i>
Add note Add note

View File

@ -40,14 +40,14 @@
<div class="stat-caption">{% trans "Total Slots to be Filled " %}</div> <div class="stat-caption">{% trans "Total Slots to be Filled " %}</div>
</div> </div>
{# GLOBAL - 5. Total Participants #} {% comment %} {# GLOBAL - 5. Total Participants #}
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-address-book stat-icon"></i> {% trans "Total Participants" %}</h3> <h3><i class="fas fa-address-book stat-icon"></i> {% trans "Total Participants" %}</h3>
</div> </div>
<div class="stat-value">{{ total_participants }}</div> <div class="stat-value">{{ total_participants }}</div>
<div class="stat-caption">{% trans "Total Recruiters/Interviewers" %}</div> <div class="stat-caption">{% trans "Total Recruiters/Interviewers" %}</div>
</div> </div> {% endcomment %}
{# GLOBAL - 6. Total LinkedIn Posts #} {# GLOBAL - 6. Total LinkedIn Posts #}
{% comment %} <div class="card"> {% comment %} <div class="card">

View File

@ -4,6 +4,7 @@
{% block title %}{% trans "Sources" %}{% endblock %} {% block title %}{% trans "Sources" %}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
@ -16,7 +17,7 @@
</nav> </nav>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">{% trans "Integration Sources" %}</h1> <h1 class="h3 mb-0">{% trans "Integration Sources" %}</h1>
<a href="{% url 'source_create' %}" class="btn btn-main-action"> <a href="{% url 'source_create' %}" class="btn btn-main-action">
{% trans "Create Source for Integration" %} <i class="fas fa-plus"></i> {% trans "Create Source for Integration" %} <i class="fas fa-plus"></i>
@ -74,6 +75,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for source in page_obj %} {% for source in page_obj %}
<tr> <tr>
<td> <td>
@ -129,7 +131,7 @@
<!-- Pagination --> <!-- Pagination -->
{% include "includes/paginator.html" %} {% include "includes/paginator.html" %}
{% 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>
@ -153,7 +155,7 @@
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block customJS %}
<script> <script>
// Auto-refresh after status toggle // Auto-refresh after status toggle
document.body.addEventListener('htmx:afterRequest', function(evt) { document.body.addEventListener('htmx:afterRequest', function(evt) {

View File

@ -262,6 +262,7 @@
</tbody> </tbody>
</table> </table>
</div> </div>
{% include "includes/paginator.html" %}
{# NOTE: Add Pagination links here if the `staffs` object is a Paginator object #} {# NOTE: Add Pagination links here if the `staffs` object is a Paginator object #}

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 %}