few fixes in ui and logic

This commit is contained in:
Faheed 2025-12-01 19:14:35 +03:00
parent 6dca145394
commit d4c8e634e7
13 changed files with 194 additions and 118 deletions

View File

@ -1023,6 +1023,7 @@ class HiringAgencyForm(forms.ModelForm):
def clean_email(self):
"""Validate email format and uniqueness"""
email = self.cleaned_data.get("email")
instance=self.instance
if email:
# Check email format
if not "@" in email or "." not in email.split("@")[1]:
@ -1031,8 +1032,16 @@ class HiringAgencyForm(forms.ModelForm):
# Check uniqueness (optional - remove if multiple agencies can have same email)
# instance = self.instance
email = email.lower().strip()
if CustomUser.objects.filter(email=email).exists():
raise ValidationError("This email is already associated with a user account.")
if not instance.pk: # Creating new instance
if HiringAgency.objects.filter(email=email).exists():
raise ValidationError("An agency with this email already exists.")
else: # Editing existing instance
if (
HiringAgency.objects.filter(email=email)
.exclude(pk=instance.pk)
.exists()
):
raise ValidationError("An agency with this email already exists.")
# if not instance.pk: # Creating new instance
# if HiringAgency.objects.filter(email=email).exists():
# raise ValidationError("An agency with this email already exists.")
@ -2293,6 +2302,11 @@ class ApplicantSignupForm(forms.ModelForm):
raise forms.ValidationError("Passwords do not match.")
return cleaned_data
def email_clean(self):
email = self.cleaned_data.get('email')
if CustomUser.objects.filter(email=email).exists():
raise forms.ValidationError("Email is already in use.")
return email
class DocumentUploadForm(forms.ModelForm):

View File

@ -160,6 +160,13 @@ class PersonListView(StaffRequiredMixin, ListView):
context_object_name = "people_list"
def get_queryset(self):
queryset=super().get_queryset()
search_query=self.request.GET.get('search','')
if search_query:
queryset=queryset.filter(
Q(first_name__icontains=search_query) |
Q(last_name__icontains=search_query) |
Q(email__icontains=search_query)
)
gender=self.request.GET.get('gender')
if gender:
queryset=queryset.filter(gender=gender)
@ -179,6 +186,7 @@ class PersonListView(StaffRequiredMixin, ListView):
nationality=self.request.GET.get('nationality')
context['nationality']=nationality
context['nationalities']=nationalities
context['search_query'] = self.request.GET.get('search', '')
return context
@ -630,7 +638,7 @@ def job_detail(request, slug):
# New statistics
"avg_match_score": avg_match_score,
"high_potential_count": high_potential_count,
"high_potential_ratio": high_potential_ratio,
# "high_potential_ratio": high_potential_ratio,
"avg_t2i_days": avg_t2i_days,
"avg_t_in_exam_days": avg_t_in_exam_days,
"linkedin_content_form": linkedin_content_form,
@ -700,6 +708,10 @@ def request_cvs_download(request, slug):
job.save(update_fields=["zip_created"])
# Use async_task to run the function in the background
# Pass only simple arguments (like the job ID)
if not job.applications.exists():
messages.warning(request, _("No applications found for this job. ZIP file generation skipped."))
return redirect('job_detail', slug=slug)
async_task('recruitment.tasks.generate_and_save_cv_zip', job.id)
# Provide user feedback and redirect
@ -711,6 +723,9 @@ def download_ready_cvs(request, slug):
View to serve the file once it is ready.
"""
job = get_object_or_404(JobPosting, slug=slug)
if not job.applications.exists():
messages.warning(request, _("No applications found for this job. ZIP file download unavailable."))
return redirect('job_detail', slug=slug)
if job.cv_zip_file and job.zip_created:
# Django FileField handles the HttpResponse and file serving easily
@ -3324,27 +3339,27 @@ def agency_detail(request, slug):
"""View details of a specific hiring agency"""
agency = get_object_or_404(HiringAgency, slug=slug)
# Get candidates associated with this agency
candidates = Application.objects.filter(hiring_agency=agency).order_by(
# Get applications associated with this agency
applications = Application.objects.filter(hiring_agency=agency).order_by(
"-created_at"
)
# Statistics
total_candidates = candidates.count()
active_candidates = candidates.filter(
total_applications = applications.count()
active_applications = applications.filter(
stage__in=["Applied", "Screening", "Exam", "Interview", "Offer"]
).count()
hired_candidates = candidates.filter(stage="Hired").count()
rejected_candidates = candidates.filter(stage="Rejected").count()
hired_applications = applications.filter(stage="Hired").count()
rejected_applications = applications.filter(stage="Rejected").count()
job_assignments=AgencyJobAssignment.objects.filter(agency=agency)
print(job_assignments)
context = {
"agency": agency,
"candidates": candidates[:10], # Show recent 10 candidates
"total_candidates": total_candidates,
"active_candidates": active_candidates,
"hired_candidates": hired_candidates,
"rejected_candidates": rejected_candidates,
"applications": applications[:10], # Show recent 10 applications
"total_applications": total_applications,
"active_applications": active_applications,
"hired_applications": hired_applications,
"rejected_applications": rejected_applications,
"generated_password": agency.generated_password
if agency.generated_password
else None,
@ -4343,7 +4358,7 @@ def agency_portal_submit_application_page(request, slug):
"total_submitted": total_submitted,
"job": assignment.job,
}
return render(request, "recruitment/agency_portal_submit_candidate.html", context)
return render(request, "recruitment/agency_portal_submit_application.html", context)
@agency_user_required
@ -4407,7 +4422,7 @@ def agency_portal_submit_application(request):
"title": f"Submit Candidate for {assignment.job.title}",
"button_text": "Submit Candidate",
}
return render(request, "recruitment/agency_portal_submit_candidate.html", context)
return render(request, "recruitment/agency_portal_submit_application.html", context)
def agency_portal_assignment_detail(request, slug):
@ -4450,7 +4465,7 @@ def agency_assignment_detail_agency(request, slug, assignment_id):
return redirect("agency_portal_dashboard")
# Get candidates submitted by this agency for this job
candidates = Application.objects.filter(
applications = Application.objects.filter(
hiring_agency=assignment.agency, job=assignment.job
).order_by("-created_at")
@ -4461,7 +4476,7 @@ def agency_assignment_detail_agency(request, slug, assignment_id):
# No messages to mark as read
# Pagination for candidates
paginator = Paginator(candidates, 20) # Show 20 candidates per page
paginator = Paginator(applications, 20) # Show 20 candidates per page
page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number)
@ -4471,12 +4486,12 @@ def agency_assignment_detail_agency(request, slug, assignment_id):
message_page_obj = message_paginator.get_page(message_page_number)
# Calculate progress ring offset for circular progress indicator
total_candidates = candidates.count()
max_candidates = assignment.max_candidates
total_applications = applications.count()
max_applications = assignment.max_candidates
circumference = 326.73 # 2 * π * r where r=52
if max_candidates > 0:
progress_percentage = total_candidates / max_candidates
if max_applications > 0:
progress_percentage = total_applications / max_applications
stroke_dashoffset = circumference - (circumference * progress_percentage)
else:
stroke_dashoffset = circumference
@ -4485,8 +4500,9 @@ def agency_assignment_detail_agency(request, slug, assignment_id):
"assignment": assignment,
"page_obj": page_obj,
"message_page_obj": message_page_obj,
"total_candidates": total_candidates,
"total_applications": total_applications,
"stroke_dashoffset": stroke_dashoffset,
"max_applications": max_applications,
}
return render(request, "recruitment/agency_portal_assignment_detail.html", context)
@ -4667,7 +4683,7 @@ def message_list(request):
"search_query": search_query,
}
if request.user.user_type != "staff":
return render(request, "messages/candidate_message_list.html", context)
return render(request, "messages/application_message_list.html", context)
return render(request, "messages/message_list.html", context)
@ -4749,7 +4765,7 @@ def message_create(request):
"form": form,
}
if request.user.user_type != "staff":
return render(request, "messages/candidate_message_form.html", context)
return render(request, "messages/application_message_form.html", context)
return render(request, "messages/message_form.html", context)
@ -4817,7 +4833,7 @@ def message_reply(request, message_id):
"parent_message": parent_message,
}
if request.user.user_type != "staff":
return render(request, "messages/candidate_message_form.html", context)
return render(request, "messages/application_message_form.html", context)
return render(request, "messages/message_form.html", context)
@ -4875,50 +4891,93 @@ def message_mark_unread(request, message_id):
@login_required
def message_delete(request, message_id):
"""Delete a message"""
"""
Deletes a message using a POST request, primarily designed for HTMX.
Redirects to the message list on success (either via standard redirect
or HTMX's hx-redirect header).
"""
# 1. Retrieve the message
# Use select_related to fetch linked objects efficiently for checks/logging
message = get_object_or_404(
Message.objects.select_related("sender", "recipient"), id=message_id
)
# Check if user has permission to delete this message
# 2. Permission Check
# Only the sender or recipient can delete the message
if message.sender != request.user and message.recipient != request.user:
messages.error(request, "You don't have permission to delete this message.")
# HTMX requests should handle redirection via client-side logic (hx-redirect)
if "HX-Request" in request.headers:
# Returning 403 or 400 is ideal, but 200 with an empty body is often accepted
# by HTMX and the message is shown on the next page/refresh.
return HttpResponse(status=403)
return HttpResponse(status=403)
# Standard navigation redirect
return redirect("message_list")
# 3. Handle POST Request (Deletion)
if request.method == "POST":
message.delete()
messages.success(request, "Message deleted successfully.")
# Handle HTMX requests
if "HX-Request" in request.headers:
return HttpResponse(status=200) # HTMX success response
# 1. Set the HTMX response header for redirection
response = HttpResponse(status=200)
response["HX-Redirect"] = reverse("message_list") # <--- EXPLICIT HEADER
return response
return redirect("message_list")
# Standard navigation fallback
return redirect("message_list")
# For GET requests, show confirmation page
context = {
"message": message,
"title": "Delete Message",
"message": f"Are you sure you want to delete this message from {message.sender.get_full_name() or message.sender.username}?",
"cancel_url": reverse("message_detail", kwargs={"message_id": message_id}),
}
return render(request, "messages/message_confirm_delete.html", context)
# @login_required
# def message_delete(request, message_id):
# """Delete a message"""
# """
# Deletes a message using a POST request, primarily designed for HTMX.
# Redirects to the message list on success (either via standard redirect
# or HTMX's hx-redirect header).
# """
# # 1. Retrieve the message
# # Use select_related to fetch linked objects efficiently for checks/logging
# message = get_object_or_404(
# Message.objects.select_related("sender", "recipient"), id=message_id
# )
# # Check if user has permission to delete this message
# if message.sender != request.user and message.recipient != request.user:
# messages.error(request, "You don't have permission to delete this message.")
# # HTMX requests should handle redirection via client-side logic (hx-redirect)
# if "HX-Request" in request.headers:
# # Returning 403 or 400 is ideal, but 200 with an empty body is often accepted
# # by HTMX and the message is shown on the next page/refresh.
# return HttpResponse(status=403)
# # Standard navigation redirect
# return redirect("message_list")
# if request.method == "POST":
# message.delete()
# messages.success(request, "Message deleted successfully.")
# # Handle HTMX requests
# if "HX-Request" in request.headers:
# return HttpResponse(status=200) # HTMX success response
# return redirect("message_list")
# # For GET requests, show confirmation page
# context = {
# "message": message,
# "title": "Delete Message",
# "message": f"Are you sure you want to delete this message from {message.sender.get_full_name() or message.sender.username}?",
# "cancel_url": reverse("message_detail", kwargs={"message_id": message_id}),
# }
# return render(request, "messages/message_confirm_delete.html", context)
@login_required
@ -5038,7 +5097,7 @@ def document_upload(request, slug):
if upload_target == 'person':
return redirect("applicant_portal_dashboard")
else:
return redirect("applicant_application_detail", application_slug=application.slug)
return redirect("applicant_application_detail", slug=application.slug)
# Handle GET request for AJAX
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
@ -5050,6 +5109,7 @@ def document_upload(request, slug):
def document_delete(request, document_id):
"""Delete a document"""
document = get_object_or_404(Document, id=document_id)
print(document)
# Initialize variables for redirection outside of the complex logic
is_htmx = "HX-Request" in request.headers
@ -5655,8 +5715,8 @@ def source_update(request, slug):
context = {
"form": form,
"source": source,
"title": f"Edit Source: {source.name}",
"button_text": "Update Source",
"title": _("Edit Source: %(name)s") % {'name': source.name},
"button_text": _("Update Source"),
}
return render(request, "recruitment/source_form.html", context)
@ -5674,8 +5734,8 @@ def source_delete(request, slug):
context = {
"source": source,
"title": "Delete Source",
"message": f'Are you sure you want to delete the source "{source.name}"?',
"title": _("Delete Source: %(name)s") % {'name': source.name},
"message": _('Are you sure you want to delete the source "%(name)s"?') % {'name': source.name},
"cancel_url": reverse("source_detail", kwargs={"slug": source.slug}),
}
return render(request, "recruitment/source_confirm_delete.html", context)
@ -5775,6 +5835,12 @@ def application_signup(request, slug):
"recruitment/applicant_signup.html",
{"form": form, "job": job},
)
else:
# messages.error(request, "Please correct the errors below.")
form = ApplicantSignupForm(request.POST)
return render(
request, "recruitment/applicant_signup.html", {"form": form, "job": job}
)
form = ApplicantSignupForm()
return render(

View File

@ -201,10 +201,10 @@
</h4>
{# Tag Badge (Prominent) #}
<span class="badge rounded-pill bg-kaauh-teal job-tag px-3 py-2 fs-6">
<i class="fas fa-tag me-1"></i>{% trans "Apply Before: " %}{{job.application_deadline}}
<span class="badge rounded-pill bg-kaauh-teal job-tag px-3 py-2 fs-6 d-none d-lg-inline-block">
<i class="fas fa-tag me-1"></i>{% trans "Apply Before: " %}{{job.application_deadline}}
</span>
</div>
{# Department/Context (Sub-text) #}

View File

@ -26,7 +26,7 @@
<form
method="post"
enctype="multipart/form-data"
hx-post="{% url 'document_upload' application.id %}"
hx-post="{% url 'application_document_upload' application.slug %}"
hx-target="#documents-pane"
hx-select="#documents-pane"
hx-swap="outerHTML"

View File

@ -8,14 +8,13 @@
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0">{% trans "Messages" %}</h4>
<h4 class="mb-0 text-primary-theme">{% trans "Messages" %}</h4>
<a href="{% url 'message_create' %}" class="btn btn-main-action">
<i class="fas fa-plus"></i> {% trans "Compose Message" %}
</a>
</div>
<!-- Filters -->
<div class="card mb-4">
<div class="card mb-4 border-primary-theme-subtle">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-3">
@ -41,41 +40,41 @@
<div class="input-group">
<input type="text" name="q" id="q" class="form-control"
value="{{ search_query }}" placeholder="{% trans 'Search messages...' %}">
<button class="btn btn-outline-secondary" type="submit">
<button class="btn btn-outline-primary-theme" type="submit">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<button type="submit" class="btn btn-secondary w-100">{% trans "Filter" %}</button>
<button type="submit" class="btn btn-main-action w-100">
<i class="fa-solid fa-filter me-1"></i>{% trans "Filter" %}</button>
</div>
</form>
</div>
</div>
<!-- Statistics -->
<div class="row mb-3">
<div class="col-md-6">
<div class="card bg-light">
<div class="card bg-primary-theme-subtle border-primary-theme-subtle">
<div class="card-body">
<h6 class="card-title">{% trans "Total Messages" %}</h6>
<h3 class="text-primary">{{ total_messages }}</h3>
<h6 class="card-title text-primary-theme">{% trans "Total Messages" %}</h6>
<h3 class="text-primary-theme">{{ total_messages }}</h3>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card bg-light">
<div class="card bg-warning-subtle border-warning-subtle">
<div class="card-body">
<h6 class="card-title">{% trans "Unread Messages" %}</h6>
<h6 class="card-title text-warning">{% trans "Unread Messages" %}</h6>
<h3 class="text-warning">{{ unread_messages }}</h3>
</div>
</div>
</div>
</div>
<!-- Messages List -->
<div class="card">
<div class="card border-primary-theme-subtle">
<div class="card-body">
{% if page_obj %}
<div class="table-responsive">
@ -93,14 +92,14 @@
</thead>
<tbody>
{% for message in page_obj %}
<tr class="{% if not message.is_read %}table-secondary{% endif %}">
<tr class="{% if not message.is_read %}table-secondary-theme-light{% endif %}">
<td>
<a href="{% url 'message_detail' message.id %}"
class="{% if not message.is_read %}fw-bold {% endif %}">
{{ message.subject }}
class="{% if not message.is_read %}fw-bold text-primary-theme text-decoration-none{% endif %}">
{{ message.subject|truncatechars:50 }}
</a>
{% if message.parent_message %}
<span class="badge bg-secondary ms-2">{% trans "Reply" %}</span>
<span class="badge bg-secondary-theme ms-2 text-decoration-none">{% trans "Reply" %}</span>
{% endif %}
</td>
<td>{{ message.sender.get_full_name|default:message.sender.username }}</td>
@ -120,27 +119,27 @@
<td>{{ message.created_at|date:"M d, Y H:i" }}</td>
<td>
<div class="btn-group" role="group">
<a href="{% url 'message_detail' message.id %}"
class="btn btn-sm btn-outline-primary" title="{% trans 'View' %}">
<a href="{% url 'message_detail' message.id %}"
class="btn btn-sm btn-outline-primary" title="{% trans 'View' %}">
<i class="fas fa-eye"></i>
</a>
{% if not message.is_read and message.recipient == request.user %}
<a href="{% url 'message_mark_read' message.id %}"
class="btn btn-sm btn-outline-success"
class="btn btn-sm btn-outline-primary"
hx-post="{% url 'message_mark_read' message.id %}"
title="{% trans 'Mark as Read' %}">
<i class="fas fa-check"></i>
</a>
{% endif %}
<a href="{% url 'message_reply' message.id %}"
class="btn btn-sm btn-outline-primary" title="{% trans 'Reply' %}">
class="btn btn-sm btn-outline-primary" title="{% trans 'Reply' %}">
<i class="fas fa-reply"></i>
</a>
<a href="{% url 'message_delete' message.id %}"
class="btn btn-sm btn-outline-danger"
hx-post="{% url 'message_delete' message.id %}"
hx-confirm="{% trans 'Are you sure you want to delete this message?' %}"
title="{% trans 'Delete' %}">
<a href="{% url 'message_delete' message.id %}"
class="btn btn-sm btn-outline-danger"
hx-post="{% url 'message_delete' message.id %}"
hx-confirm="{% trans 'Are you sure you want to delete this message?' %}"
title="{% trans 'Delete' %}">
<i class="fas fa-trash"></i>
</a>
</div>
@ -149,7 +148,7 @@
{% empty %}
<tr>
<td colspan="7" class="text-center text-muted">
<i class="fas fa-inbox fa-3x mb-3"></i>
<i class="fas fa-inbox fa-3x mb-3 text-primary-theme"></i>
<p class="mb-0">{% trans "No messages found." %}</p>
<p class="small">{% trans "Try adjusting your filters or compose a new message." %}</p>
</td>
@ -159,13 +158,12 @@
</table>
</div>
<!-- Pagination -->
{% 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" href="?page={{ page_obj.previous_page_number }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">
<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>
@ -174,18 +172,18 @@
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<span class="page-link">{{ num }}</span>
<span class="page-link bg-primary-theme border-primary-theme">{{ num }}</span>
</li>
{% else %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">{{ num }}</a>
<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" href="?page={{ page_obj.next_page_number }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">
<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>
@ -195,7 +193,7 @@
{% endif %}
{% else %}
<div class="text-center text-muted py-5">
<i class="fas fa-inbox fa-3x mb-3"></i>
<i class="fas fa-inbox fa-3x mb-3 text-primary-theme"></i>
<p class="mb-0">{% trans "No messages found." %}</p>
<p class="small">{% trans "Try adjusting your filters or compose a new message." %}</p>
<a href="{% url 'message_create' %}" class="btn btn-main-action">
@ -210,7 +208,7 @@
</div>
{% endblock %}
{% block extra_js %}
{% block customJS %}
<script>
// Auto-refresh unread count every 30 seconds
setInterval(() => {
@ -227,5 +225,4 @@ setInterval(() => {
.catch(error => console.error('Error fetching unread count:', error));
}, 30000);
</script>
{% endblock %}
{% endblock %}

View File

@ -109,6 +109,7 @@
</div>
<div class="row">
<!-- Assignment Overview -->
<div class="col-lg-8">
<!-- Assignment Details Card -->

View File

@ -161,9 +161,9 @@
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
<h6 style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-building me-2"></i> {{ title }}
</h1>
</h6>
<div class="d-flex gap-2">
{% if agency %}
<a href="{% url 'agency_detail' agency.slug %}" class="btn btn-outline-secondary">
@ -186,9 +186,7 @@
<div class="current-profile">
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
<div class="d-flex align-items-center">
<div class="current-image d-flex align-items-center justify-content-center bg-light">
<i class="fas fa-building text-muted"></i>
</div>
<div>
<h5 class="mb-1">{{ agency.name }}</h5>
{% if agency.contact_person %}

View File

@ -43,10 +43,10 @@
border-radius: 0.35rem;
font-weight: 700;
}
.status-ACTIVE { background-color: var(--kaauh-success); color: white; }
.status-EXPIRED { background-color: var(--kaauh-danger); color: white; }
.status-COMPLETED { background-color: var(--kaauh-info); color: white; }
.status-CANCELLED { background-color: var(--kaauh-warning); color: #856404; }
.status-ACTIVE { background-color: var(--kaauh-teal-dark); color: white; }
.status-EXPIRED { background-color: var(--kaauh-teal-dark); color: white; }
.status-COMPLETED { background-color: var(--kaauh-teal-dark); color: white; }
.status-CANCELLED { background-color: var(--kaauh-teal-dark); color: white; }
.progress-ring {
width: 120px;
@ -164,14 +164,14 @@
<i class="fas fa-exclamation-triangle me-1"></i>{% trans "Expired" %}
</small>
{% else %}
<small class="text-success">
<small class="text-primary-theme">
<i class="fas fa-clock me-1"></i>{{ assignment.days_remaining }} {% trans "days remaining" %}
</small>
{% endif %}
</div>
<div class="mb-3">
<label class="text-muted small">{% trans "Maximum applications" %}</label>
<div class="fw-bold">{{ assignment.max_candidates }} {% trans "applications" %}</div>
<div class="fw-bold">{{max_applications }} {% trans "applications" %}</div>
</div>
</div>
</div>
@ -228,7 +228,7 @@
<i class="fas fa-users me-2"></i>
{% trans "Submitted applications" %} ({{ total_applications }})
</h5>
<span class="badge bg-info">{{ total_applications }}/{{ assignment.max_applications }}</span>
<span class="badge bg-primary-theme">{{ total_applications }}/{{ max_applications }}</span>
</div>
{% if page_obj %}
@ -256,7 +256,7 @@
</div>
</td>
<td>
<span class="badge bg-info">{{ application.get_stage_display }}</span>
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span>
</td>
<td>
<div class="small text-muted">
@ -361,12 +361,12 @@
<div class="progress mt-3" style="height: 8px;">
{% widthratio total_applications assignment.max_candidates 100 as progress %}
<div class="progress-bar" style="width: {{ progress }}%"></div>
<div class="progress-bar bg-primary-theme" style="width: {{ progress }}%"></div>
</div>
<div class="mt-3 text-center">
{% if assignment.can_submit %}
<span class="badge bg-success">{% trans "Can Submit" %}</span>
<span class="badge bg-primary-theme">{% trans "Can Submit" %}</span>
{% else %}
<span class="badge bg-danger">{% trans "Cannot Submit" %}</span>
{% endif %}
@ -500,7 +500,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">
{% trans "Cancel" %}
</button>
<button type="submit" class="btn btn-main-action">

View File

@ -204,7 +204,7 @@
<div class="col-md-6">
<label for="search" class="form-label small text-muted">{% trans "Search by Name or Email" %}</label>
<div class="input-group input-group-lg">
<form method="get" action="" class="w-100">
<form method="get" action="." class="w-100">
{% include 'includes/search_form.html' %}
</form>
</div>
@ -426,9 +426,9 @@
{# ------------------------------------------------------------------------------------------ #}
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-danger shadow-lg">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="deleteModalLabel"><i class="fas fa-exclamation-triangle me-2"></i> {% trans "Confirm Deletion" %}</h5>
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title text-primary-theme" id="deleteModalLabel"><i class="fas fa-exclamation-triangle me-2"></i> {% trans "Confirm Deletion" %}</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@ -437,7 +437,7 @@
<p class="text-muted small">{% trans "This action is irreversible and the application data will be permanently removed." %}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<form id="deleteForm" method="post" action="">
{% csrf_token %}
<button type="submit" class="btn btn-danger d-flex align-items-center">

View File

@ -8,8 +8,8 @@
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">{{ title }}</h1>
<a href="{% url 'source_detail' source.slug %}" class="btn btn-outline-secondary">
<h4 class="h3 mb-0">{{ title }}</h4>
<a href="{% url 'source_detail' source.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Back to Source
</a>
</div>

View File

@ -171,9 +171,9 @@
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
<h4 style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-plug me-2"></i> {{ title }}
</h1>
</h4>
<div class="d-flex gap-2">
{% if source %}
<a href="{% url 'source_detail' source.pk %}" class="btn btn-outline-secondary">

View File

@ -68,23 +68,23 @@
{% for source in page_obj %}
<tr>
<td>
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none">
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none text-primary-theme">
<strong>{{ source.name }}</strong>
</a>
</td>
<td>
<span class="badge bg-info">{{ source.source_type }}</span>
<span class="badge bg-primary-theme">{{ source.source_type }}</span>
</td>
<td>
{% if source.is_active %}
<span class="badge bg-success">{% trans "Active" %}</span>
<span class="badge bg-primary-theme">{% trans "Active" %}</span>
{% else %}
<span class="badge bg-secondary">{% trans "Inactive" %}</span>
<span class="badge bg-primary-theme">{% trans "Inactive" %}</span>
{% endif %}
</td>
<td>
<code class="small">{{ source.api_key|truncatechars:20 }}</code>
<code class="small text-primary-theme">{{ source.api_key|truncatechars:20 }}</code>
</td>
<td>
<small class="text-muted">{{ source.created_at|date:"M d, Y" }}</small>

View File

@ -221,7 +221,7 @@
</a> {% endcomment %}
{# 2. Change Password Button (Key Icon) #}
<a href="{% url 'set_staff_password' user.pk %}" class="btn btn-sm btn-outline-info" title="{% trans 'Change Password' %}">
<a href="{% url 'set_staff_password' user.pk %}" class="btn btn-sm btn-main-action" title="{% trans 'Change Password' %}">
<i class="fas fa-key"></i>
</a>