From d98682c0e5688a86583e4456d21607ab68678dec Mon Sep 17 00:00:00 2001 From: Faheed Date: Wed, 24 Dec 2025 03:05:48 +0300 Subject: [PATCH] added new navigation ui --- recruitment/views.py | 97 +- static/css/main.css | 182 +++- templates/base.html | 939 +++--------------- templates/forms/form_builder.html | 6 +- templates/jobs/job_applicants.html | 5 - templates/jobs/job_bank.html | 6 +- templates/portal_base.html | 402 +++----- templates/recruitment/interview_calendar.html | 180 ++-- 8 files changed, 612 insertions(+), 1205 deletions(-) diff --git a/recruitment/views.py b/recruitment/views.py index 5100fb0..81c4a71 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -879,6 +879,8 @@ def form_builder(request, template_slug=None): context = {} if template_slug: template = get_object_or_404(FormTemplate, slug=template_slug) + job_slug=template.job.slug + context['job_slug']=job_slug context["template"] = template context["template_slug"] = template.slug context["template_name"] = template.name @@ -4814,60 +4816,73 @@ def interview_list(request): return render(request, "interviews/interview_list.html", context) -from django_ratelimit.decorators import ratelimit -@ratelimit(key='user_or_ip', rate='1/m', block=True) +# from django_ratelimit.decorators import ratelimit +# @ratelimit(key='user_or_ip', rate='1/m', block=True) +from django.core.cache import cache @login_required @staff_user_required def generate_ai_questions(request, slug): """Generate AI-powered interview questions for a scheduled interview""" + lock_key = f"ai_lock_{request.user.id}" + is_new_request = cache.add(lock_key, "locked", timeout=60) + if not is_new_request: + return JsonResponse({ + "error": "Request in progress. Please wait a moment." + }, status=429) from django_q.tasks import async_task schedule = get_object_or_404(ScheduledInterview, slug=slug) messages.info(request,_("Generating interview questions.")) - - if request.method == "POST": - # Queue the AI question generation task - - task_id = async_task( - "recruitment.tasks.generate_interview_questions", - schedule.id, - sync=False - ) + try: + + if request.method == "POST": + # Queue the AI question generation task - # if request.headers.get("X-Requested-With") == "XMLHttpRequest": - # return JsonResponse({ - # "status": "success", - # "message": "AI question generation started in background", - # "task_id": task_id - # }) - # else: - # messages.success( - # request, - # "AI question generation started. Questions will appear shortly." - # ) - # return redirect("interview_detail", slug=slug) + task_id = async_task( + "recruitment.tasks.generate_interview_questions", + schedule.id, + sync=False + ) + - # # For GET requests, return existing questions if any - # questions = schedule.ai_questions.all().order_by("created_at") + # if request.headers.get("X-Requested-With") == "XMLHttpRequest": + # return JsonResponse({ + # "status": "success", + # "message": "AI question generation started in background", + # "task_id": task_id + # }) + # else: + # messages.success( + # request, + # "AI question generation started. Questions will appear shortly." + # ) + # return redirect("interview_detail", slug=slug) - # if request.headers.get("X-Requested-With") == "XMLHttpRequest": - # return JsonResponse({ - # "status": "success", - # "questions": [ - # { - # "id": q.id, - # "text": q.question_text, - # "type": q.question_type, - # "difficulty": q.difficulty_level, - # "category": q.category, - # "created_at": q.created_at.isoformat() - # } - # for q in questions - # ] - # }) + # # For GET requests, return existing questions if any + # questions = schedule.ai_questions.all().order_by("created_at") - return redirect("interview_detail", slug=slug) + # if request.headers.get("X-Requested-With") == "XMLHttpRequest": + # return JsonResponse({ + # "status": "success", + # "questions": [ + # { + # "id": q.id, + # "text": q.question_text, + # "type": q.question_type, + # "difficulty": q.difficulty_level, + # "category": q.category, + # "created_at": q.created_at.isoformat() + # } + # for q in questions + # ] + # }) + + return redirect("interview_detail", slug=slug) + except Exception: + # If the task fails to enqueue, release the lock so they can try again + cache.delete(lock_key) + raise @login_required diff --git a/static/css/main.css b/static/css/main.css index 211e35c..accadf3 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -777,4 +777,184 @@ html[dir="rtl"] .me-auto { margin-right: 0 !important; margin-left: auto !import .pagination .page-item:last-child .page-link { border-top-right-radius: 0.5rem; border-bottom-right-radius: 0.5rem; - } \ No newline at end of file + } + + + + /* ---------------------------------- */ +/* 7. CRM Sidebar Layout (V2025) */ +/* ---------------------------------- */ + +:root { + --sidebar-width: 260px; + --topbar-height: 64px; + --sidebar-bg: #00363a; + --sidebar-hover: rgba(255, 255, 255, 0.1); + --sidebar-active: #007c8a; /* kaauh-light-teal */ +} + +/* Sidebar Container */ +.sidebar { + width: var(--sidebar-width); + height: 100vh; + position: fixed; + top: 0; + left: 0; /* Default LTR */ + background-color: var(--sidebar-bg); + z-index: 1050; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + display: flex; + flex-direction: column; + box-shadow: 4px 0 10px rgba(0,0,0,0.1); +} + +.sidebar-brand { + padding: 1.25rem; + text-align: center; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} + +.sidebar-nav { + padding: 1.25rem 0; + flex-grow: 1; + overflow-y: auto; +} + +/* CRM-style Nav Links */ +.nav-link-custom { + display: flex; + align-items: center; + padding: 0.85rem 1.5rem; + color: rgba(255, 255, 255, 0.7); + text-decoration: none !important; + transition: all 0.2s ease; + margin-bottom: 0.25rem; + font-weight: 500; + font-size: 0.95rem; + border-left: 4px solid transparent; +} + +.nav-link-custom:hover { + color: #fff; + background-color: var(--sidebar-hover); +} + +.nav-link-custom.active { + color: #fff; + background-color: var(--sidebar-active); + border-left: 4px solid var(--kaauh-warning); /* Highlight accent */ +} + +.nav-link-custom i { + width: 28px; + font-size: 1.1rem; + opacity: 0.9; +} + +/* Main Wrapper - Offsets content for sidebar */ +.main-wrapper { + margin-left: var(--sidebar-width); + min-height: 100vh; + display: flex; + flex-direction: column; + transition: all 0.3s ease; +} + +/* Top Horizontal Content Header */ +.content-header { + height: var(--topbar-height); + background: #ffffff; + border-bottom: 1px solid var(--kaauh-border); + display: flex; + align-items: center; + padding: 0 1.5rem; + position: sticky; + top: 0; + z-index: 1000; + box-shadow: 0 2px 4px rgba(0,0,0,0.02); +} + +.main-container { + padding: 1.5rem; + flex-grow: 1; +} + +/* ---------------------------------- */ +/* 8. CRM Mobile Responsiveness */ +/* ---------------------------------- */ + +@media (max-width: 991.98px) { + .sidebar { + left: calc(-1 * var(--sidebar-width)); + } + + .sidebar.show { + left: 0; + } + + .main-wrapper { + margin-left: 0 !important; + margin-right: 0 !important; + } +} + +/* ---------------------------------- */ +/* 9. CRM RTL Adjustments */ +/* ---------------------------------- */ + +html[dir="rtl"] .sidebar { + left: auto; + right: 0; + box-shadow: -4px 0 10px rgba(0,0,0,0.1); +} + +html[dir="rtl"] .main-wrapper { + margin-left: 0; + margin-right: var(--sidebar-width); +} + +html[dir="rtl"] .nav-link-custom { + border-left: none; + border-right: 4px solid transparent; +} + +html[dir="rtl"] .nav-link-custom.active { + border-right: 4px solid var(--kaauh-warning); +} + +@media (max-width: 991.98px) { + html[dir="rtl"] .sidebar { + right: calc(-1 * var(--sidebar-width)); + left: auto; + } + + html[dir="rtl"] .sidebar.show { + right: 0; + } +} + + +.sidebar .mt-auto { + background: rgba(0, 0, 0, 0.1); /* Slightly darker than sidebar bg */ +} + +.sidebar .mt-auto span { + opacity: 0.8; + transition: opacity 0.3s; +} + +.sidebar .mt-auto:hover span { + opacity: 1; +} + + + +.powered-by-footer { + letter-spacing: 0.5px; + opacity: 0.7; + transition: opacity 0.3s ease; +} + +.powered-by-footer:hover { + opacity: 1; +} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 91c7ac6..b3c87de 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,822 +6,191 @@ - - {% block title %}{% trans 'University ATS' %}{% endblock %} - - - - - - - {% comment %} Load correct Bootstrap CSS file for RTL/LTR {% endcomment %} {% if LANGUAGE_CODE == 'ar' %} {% else %} {% endif %} + + + + + - - - + - - {% block customCSS %}{% endblock %} + {% block customCSS %}{% endblock %} - + -
-
-
-
-
-
-
-
- {% trans 'Saudi Vision 2030' %} -
-
-
{% trans "Princess Nourah bint Abdulrahman University"%}
-
{% trans "King Abdullah bin Abdulaziz University Hospital"%}
-
-
- KAAUH Logo -
-
+
- - + {% endif %} + + +
+
+
+ +
+ {% trans "KAAUH Applicant Tracking System" %} +
+
-
- {% if messages %} +
+ + + {% if request.user.get_unread_message_count > 0 %} + + {{ request.user.get_unread_message_count }} + + {% endif %} + + + + {% if LANGUAGE_CODE == 'en' %} +
{% csrf_token %} + + + +
+ {% else %} +
{% csrf_token %} + + + +
+ {% endif %} + + +
+
+ +
+ {% if messages %} {% for message in messages %} -
- -
- -
- - {% include 'includes/delete_modal.html' %} - + - - - - - - - - {% comment %} {% if request.user.is_authenticated and request.user.is_staff %} - {% endcomment %} - {% comment %} {% endif %} {% endcomment %} - {% block customJS %}{% endblock %} - + \ No newline at end of file diff --git a/templates/forms/form_builder.html b/templates/forms/form_builder.html index 1e8d205..e019c5e 100644 --- a/templates/forms/form_builder.html +++ b/templates/forms/form_builder.html @@ -1,10 +1,10 @@ -{% load static i18n %} +{% load static %} - {% trans "ATS Form Builder" %} + ATS Form Builder @@ -678,7 +678,7 @@ const djangoConfig = { csrfToken: "{{ csrf_token }}", saveUrl: "{% url 'save_form_template' %}", - loadUrl: {% if template_slug %}"{% url 'load_form_template' template_slug %}"{% else %}null{% endif %}, + loadUrl: {% if template_slug %}"{% url 'load_form_template' job_slug %}"{% else %}null{% endif %}, templateId: {% if template_slug %}'{{ template_slug }}'{% else %}null{% endif %}, jobId: {% if job_id %}{{ job_id }}{% else %}null{% endif %} // Add this if you need it }; diff --git a/templates/jobs/job_applicants.html b/templates/jobs/job_applicants.html index 5eaff67..328d60b 100644 --- a/templates/jobs/job_applicants.html +++ b/templates/jobs/job_applicants.html @@ -5,11 +5,6 @@ {% block customCSS %} {% endblock %} {% block content %} -
-
-
-

{% trans "Interview Calendar" %}

-
- {{ job.title }} -
+
+
+
+

{% trans "Interview Calendar" %}

+

{{ job.title|default:"Global Schedule" }}

+
+
+

Tenhal | تنحل

@@ -145,25 +110,27 @@
-