ui changes for theme

This commit is contained in:
Faheed 2025-10-07 13:39:44 +03:00
parent 221380645d
commit 5856f1e0cb
24 changed files with 4000 additions and 2247 deletions

59
.gitignore vendored Normal file
View File

@ -0,0 +1,59 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.pyc
*.pyd
*.pyo
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# Django stuff:
*.log
*.pot
*.sqlite3
local_settings.py
db.sqlite3
# Virtual environment
venv/
env/
# IDE files
.idea/
.vscode/
*.swp
*.bak
*.swo
# OS generated files
.DS_Store
Thumbs.db
# Testing
.tox/
.coverage
.pytest_cache/
htmlcov/
# Media and Static files (if served locally and not meant for version control)
media/
static/
# Deployment files
*.tar.gz
*.zip

View File

@ -206,4 +206,11 @@ CORS_ALLOW_CREDENTIALS = True
# Celery + Redis for long running background i will be using it
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
LINKEDIN_CLIENT_ID = '867jwsiyem1504'
LINKEDIN_CLIENT_SECRET = 'WPL_AP1.QNH5lYnfRSQpp0Qp.GO8Srw=='
LINKEDIN_REDIRECT_URI = 'http://127.0.0.1:8000/jobs/linkedin/callback/'

Binary file not shown.

View File

@ -24,8 +24,8 @@ import asyncio
@receiver(post_save, sender=models.Candidate)
def score_candidate_resume(sender, instance, created, **kwargs):
# Skip if no resume or OpenRouter not configured
if instance.resume is None:
return
if instance.resume:
return
if kwargs.get('update_fields') is not None:
return

View File

@ -214,9 +214,7 @@ def candidate_detail(request, slug):
})
def candidate_update_stage(request, slug):
"""Handle HTMX stage update requests"""
from time import sleep
sleep(5)
"""Handle HTMX stage update requests"""
try:
if not request.user.is_staff:
return render(request, 'recruitment/partials/error.html', {'error': 'Permission denied'}, status=403)

View File

@ -1,159 +1,16 @@
{% comment %} {% load static i18n %}
{% load i18n static %}
<!DOCTYPE html>
<html lang="{% if request.LANGUAGE_CODE %}{{ request.LANGUAGE_CODE }}{% else %}en{% endif %}">
<html lang="{{ LANGUAGE_CODE }}" dir="{% if LANGUAGE_CODE == 'ar' %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}NorahUniversity ATS{% endblock %}</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js"></script>
{% comment %} <script src="https://unpkg.com/htmx.org@2.0.3/dist/htmx.min.js"></script> {% endcomment %}
{% comment %} <meta name="csrf-token" content="{{ csrf_token }}"> {% endcomment %}
<!-- FilePond CSS -->
{% comment %} <link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body> {% endcomment %}
{% comment %} <header class="header">
<div class="container">
<div class="d-flex justify-content-between align-items-center py-3">
<div class="logo h4 mb-0">NorahUniversity ATS</div>
<div class="user-info d-flex align-items-center gap-3">
{% if user.is_authenticated %}
<span class="text-muted">{{ user.username }}</span>
<a href="{% url 'account_logout' %}" class="btn btn-outline-secondary btn-sm">
<span class="d-flex align-items-center gap-1">
{% include "icons/logout.html" %}
Logout
</span>
</a>
{% else %}
<a href="{% url 'account_login' %}" class="btn btn-primary btn-sm">{% trans "Login" %}</a>
{% endif %}
</div>
</div>
</div>
</header> {% endcomment %}
{% comment %}
<nav class="navbar navbar-expand-lg navbar-light border-bottom">
<div class="container">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}" href="{% url 'dashboard' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/dashboard.html" %}
Dashboard
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'job_list' %}active{% endif %}" href="{% url 'job_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/jobs.html" %}
Jobs
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'candidate_list' %}active{% endif %}" href="{% url 'candidate_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/users.html" %}
Candidates
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'training_list' %}active{% endif %}" href="{% url 'training_list' %}">
<span class="d-flex align-items-center gap-2">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
Training
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'list_meetings' %}active{% endif %}" href="{% url 'list_meetings' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/meeting.html" %}
Meetings
</span>
</a>
</li>
</ul>
</div>
</div>
</nav> {% endcomment %}
{% comment %} <main class="container my-4">
{% if messages %}
<div class="messages">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% block content %}
{% endblock %}
</main> {% endcomment %}
<!-- Delete Modal -->
{% comment %} {% include 'includes/delete_modal.html' %} {% endcomment %}
{% comment %} <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const csrfToken = "{{ csrf_token }}";
const staticUrl = "{% static '' %}";
</script>
<!-- JavaScript Libraries -->
<script src="https://unpkg.com/petite-vue" defer init></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script src="https://unpkg.com/filepond/dist/filepond.js"></script>
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"></script>
<script src="https://unpkg.com/filepond-plugin-file-validate-type/dist/filepond-plugin-file-validate-type.js"></script>
<script src="https://unpkg.com/filepond-plugin-file-validate-size/dist/filepond-plugin-file-validate-size.js"></script>
<script src="{% static 'js/modal_handlers.js' %}"></script>
<script>
// Initialize tooltips
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
});
</script>
{% block extra_js %}{% endblock %} {% endcomment %}
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="King Abdullah Academic University Hospital - Applicant Tracking System">
<title>{% block title %}University ATS{% endblock %}</title>
<meta name="description" content="{% trans 'King Abdullah Academic University Hospital - Applicant Tracking System' %}">
<title>{% block title %}{% trans 'University ATS' %}{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet">
{% comment %} <link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet">
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet"> {% endcomment %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<style>
@ -186,16 +43,13 @@
padding: 0.25rem 0.5rem;
}
.top-bar .logo-container img {
height: 50px;
height: 40px;
object-fit: contain;
}
@media (max-width: 767.98px) {
.top-bar .logo-container {
order: -1;
margin-bottom: 0.5rem;
}
.top-bar .contact-info {
justify-content: center;
.top-bar {
display: none;
}
}
@ -203,7 +57,10 @@
.navbar-brand {
font-weight: 700;
letter-spacing: -0.5px;
font-size: 1.35rem;
font-size: 1.25rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.navbar-dark {
background-color: var(--kaauh-teal) !important;
@ -212,6 +69,7 @@
.nav-link {
font-weight: 500;
transition: all 0.2s ease;
padding: 0.5rem 0.75rem;
}
.nav-link:hover,
.nav-link.active {
@ -220,49 +78,68 @@
border-radius: 4px;
}
/* Dropdown */
/* Dropdown - Enhanced UX */
.dropdown-menu {
border: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-radius: 6px;
backdrop-filter: blur(4px);
background-color: rgba(255, 255, 255, 0.98);
border: 1px solid var(--kaauh-border);
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
border-radius: 8px;
padding: 0.5rem 0;
min-width: 200px;
will-change: transform, opacity;
transition: transform 0.2s ease, opacity 0.2s ease;
}
.dropdown-item {
padding: 0.5rem 1.25rem;
transition: background-color 0.15s;
}
.dropdown-item:hover {
background-color: rgba(0, 99, 110, 0.08);
background-color: var(--kaauh-light-bg);
color: var(--kaauh-teal-dark);
}
/* Language Toggle Button Style */
.language-toggle-btn {
color: white !important;
background: none !important;
border: none !important;
display: flex;
align-items: center;
gap: 0.3rem;
padding: 0.5rem 0.75rem !important;
font-weight: 500;
transition: all 0.2s ease;
}
.language-toggle-btn:hover {
background: rgba(255,255,255,0.12) !important;
border-radius: 4px;
}
@media (min-width: 992px) {
/* This hover effect is what overrides the click for desktop */
/* For the profile dropdown, we generally want the click behavior,
so let's make sure it doesn't have the desktop hover override.
The current CSS applies to all .dropdown:hover, which is fine,
but we should rely on Bootstrap's JS for this one. */
/* Removed unnecessary desktop hover override for this specific context
or ensured it works by click */
.dropdown:hover > .dropdown-menu {
display: block;
margin-top: 0;
}
.dropdown-menu {
display: block !important;
opacity: 0;
visibility: hidden;
transform: translateY(8px);
transition: opacity 0.25s, transform 0.25s;
}
.dropdown:hover .dropdown-menu {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
/* Profile Avatar - Enhanced Feedback */
.profile-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--kaauh-teal);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 0.85rem;
transition: transform 0.1s ease;
}
/* Subtle hover effect for the profile button */
.navbar-nav .dropdown-toggle:hover .profile-avatar {
transform: scale(1.05);
}
.navbar-nav .dropdown-toggle.p-0:hover {
background: none !important; /* Keep avatar background fixed */
}
/* === Job Table === */
/* === Job Table and other sections CSS remain the same... === */
.job-table-wrapper {
background: white;
border-radius: 10px;
@ -293,6 +170,7 @@
font-weight: 600;
border-radius: 6px;
transition: all 0.2s;
white-space: nowrap;
}
.btn-apply:hover {
background: var(--kaauh-teal-dark);
@ -300,12 +178,24 @@
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
@media (max-width: 575.98px) {
.table-responsive {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.job-table th,
.job-table td {
white-space: nowrap;
font-size: 0.875rem;
}
}
/* === Footer & Alerts === */
.footer {
background: var(--kaauh-light-bg);
padding: 2rem 0;
padding: 1.5rem 0;
border-top: 1px solid var(--kaauh-border);
font-size: 0.95rem;
font-size: 0.9rem;
color: #555;
}
.alert {
@ -314,77 +204,78 @@
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
main.container {
min-height: calc(100vh - 220px);
padding: 2rem 0;
min-height: calc(100vh - 200px);
padding: 1.5rem 0;
}
/* === Profile Avatar === */
.profile-avatar {
width: 38px;
height: 38px;
border-radius: 50%;
background: white;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 0.9rem;
/* === RTL Support === */
html[dir="rtl"] {
text-align: right;
direction: rtl;
}
/* Ensures the dropdown-toggle style is minimal for the avatar */
.navbar-nav .dropdown-toggle.p-0::after {
display: none;
html[dir="rtl"] .navbar-brand {
flex-direction: row-reverse;
}
html[dir="rtl"] .dropdown-menu {
/* Ensures RTL dropdown menu opens left aligned to its button when dropdown-menu-end isn't used */
right: auto;
left: 0;
}
html[dir="rtl"] .me-3 { margin-right: 0 !important; margin-left: 1rem !important; }
html[dir="rtl"] .ms-3 { margin-left: 0 !important; margin-right: 1rem !important; }
html[dir="rtl"] .me-2 { margin-right: 0 !important; margin-left: 0.5rem !important; }
html[dir="rtl"] .ms-2 { margin-left: 0 !important; margin-right: 0.5rem !important; }
html[dir="rtl"] .ms-auto { margin-left: 0 !important; margin-right: auto !important; }
html[dir="rtl"] .me-auto { margin-right: 0 !important; margin-left: auto !important; }
</style>
{% block customCSS %}{% endblock %}
</head>
<body class="d-flex flex-column min-vh-100">
<div class="top-bar">
<div class="container d-flex flex-wrap justify-content-between align-items-center gap-2">
<div class="top-bar d-none d-md-block">
<div class="container d-flex justify-content-between align-items-center gap-2">
<div class="d-flex align-items-center gap-3 social-icons">
<span class="text-muted d-none d-sm-inline">Follow Us:</span>
{% comment %} <span class="text-muted">{% trans "Follow Us:" %}</span>
<a href="#" aria-label="Facebook"><i class="fab fa-facebook-f"></i></a>
<a href="#" aria-label="Twitter"><i class="fab fa-twitter"></i></a>
<a href="#" aria-label="Instagram"><i class="fab fa-instagram"></i></a>
<a href="#" aria-label="Instagram"><i class="fab fa-instagram"></i></a> {% endcomment %}
</div>
<div class="contact-info d-flex flex-wrap justify-content-center gap-2">
<div class="contact-item">
<div class="contact-info d-flex gap-3">
{% comment %} <div class="contact-item">
<i class="fas fa-envelope text-primary"></i>
<span class="d-none d-sm-inline">info@kaauh.edu.sa</span>
<span>info@kaauh.edu.sa</span>
</div>
<div class="contact-item">
<i class="fas fa-phone text-primary"></i>
<span class="d-none d-sm-inline">+966 11 820 0000</span>
</div>
<span>+966 11 820 0000</span>
</div> {% endcomment %}
</div>
<div class="logo-container d-flex gap-2">
<img src="{% static 'image/vision.svg' %}" alt="Saudi Vision 2030" loading="lazy">
<img src="{% static 'image/hospital_logo_3.png' %}" alt="King Abdullah Academic University Hospital" loading="lazy">
<img src="{% static 'image/vision.svg' %}" alt="{% trans 'Saudi Vision 2030' %}" loading="lazy">
<img src="{% static 'image/hospital_logo_3.png' %}" alt="{% trans 'King Abdullah Academic University Hospital' %}" loading="lazy">
</div>
</div>
</div>
<nav class="navbar navbar-expand-lg navbar-dark sticky-top">
<div class="container d-flex align-items-center">
<a class="navbar-brand text-white" href="#">
<i class="fas fa-hospital me-1"></i> KAAUH ATS
<div class="container">
<a class="navbar-brand text-white" href="{% url 'dashboard' %}">
<i class="fas fa-hospital"></i>
<span class="d-lg-none ms-1">KAAUH ATS</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
aria-controls="navbarNav" aria-expanded="false" aria-label="{% trans 'Toggle navigation' %}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}" href="{% url 'dashboard' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/dashboard.html" %}
Dashboard
{% trans "Dashboard" %}
</span>
</a>
</li>
@ -392,7 +283,7 @@
<a class="nav-link {% if request.resolver_match.url_name == 'job_list' %}active{% endif %}" href="{% url 'job_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/jobs.html" %}
Jobs
{% trans "Jobs" %}
</span>
</a>
</li>
@ -400,118 +291,148 @@
<a class="nav-link {% if request.resolver_match.url_name == 'candidate_list' %}active{% endif %}" href="{% url 'candidate_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/users.html" %}
Candidates
{% trans "Candidates" %}
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'training_list' %}active{% endif %}" href="{% url 'training_list' %}">
<span class="d-flex align-items-center gap-2">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" stroke-width="2"></path>
</svg>
Training
{% trans "Training" %}
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'list_meetings' %}active{% endif %}" href="{% url 'list_meetings' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/meeting.html" %}
Meetings
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" stroke-width="2"></path>
</svg>
{% trans "Meetings" %}
</span>
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">Meeting & Schedule</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#"><i class="fas fa-calendar me-2"></i> Meetings</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-clock me-2"></i> Schedule</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">Jobs</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#"><i class="fas fa-briefcase me-2"></i> Active Jobs</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-file-alt me-2"></i> Draft Jobs</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="#"><i class="fas fa-list me-1"></i> Job List</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">Candidates</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#"><i class="fas fa-users me-2"></i> All Candidates</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-user-plus me-2"></i> New Candidates</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<i class="fas fa-plus-circle me-1"></i> Create Job
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
data-bs-offset="0, 8" data-bs-auto-close="outside">
{% trans "More" %}
</a>
<ul class="dropdown-menu" data-bs-popper="static">
<li><a class="dropdown-item" href="#"><i class="fas fa-calendar me-2"></i> {% trans "Meetings" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-clock me-2"></i> {% trans "Schedule" %}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-briefcase me-2"></i> {% trans "Active Jobs" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-file-alt me-2"></i> {% trans "Draft Jobs" %}</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-users me-2"></i> {% trans "All Candidates" %}</a></li>
<li><a class="dropdown-item" href="#"><i class="fas fa-user-plus me-2"></i> {% trans "New Candidates" %}</a></li>
</ul>
</li>
</ul>
{% if not request.session.linkedin_authenticated %}
<ul class="navbar-nav ms-auto ms-lg-0">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'linkedin_login' %}">
<i class="fab fa-linkedin me-1"></i> Connect LinkedIn
<i class="fab fa-linkedin me-1"></i> {% trans "Connect LinkedIn" %}
</a>
</li>
</ul>
{% else %}
<ul class="navbar-nav ms-auto ms-lg-0">
<li class="nav-item d-none d-lg-block">
<span class="nav-link text-success">
<i class="fab fa-linkedin me-1"></i> LinkedIn Connected
</span>
</li>
</ul>
<span class="text-success d-none d-lg-inline ms-auto me-3">
<i class="fab fa-linkedin me-1"></i> {% trans "LinkedIn Connected" %}
</span>
{% endif %}
</div>
<ul class="navbar-nav ms-2 ms-lg-3">
<li class="nav-item dropdown">
<a class="nav-link p-0 dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<div class="profile-avatar">
{{ user.username|first|upper }}
</div>
</a>
<ul class="dropdown-menu dropdown-menu-end py-2 shadow" style="min-width: 220px;">
<li class="px-3 py-2">
<div class="d-flex align-items-center">
<div class="me-3">
<div class="profile-avatar" style="width: 40px; height: 40px;">
{{ user.username|first|upper }}
<ul class="navbar-nav me-2">
<li class="nav-item dropdown">
<a class="language-toggle-btn dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}">
<i class="fas fa-globe"></i>
<span class="d-none d-lg-inline">{{ LANGUAGE_CODE|upper }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" data-bs-popper="static">
<li>
<form action="" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit">
<span class="me-2">🇺🇸</span> English
</button>
</form>
</li>
<li>
<form action="" method="post" class="d-inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit">
<span class="me-2">🇸🇦</span> العربية (Arabic)
</button>
</form>
</li>
</ul>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<button
class="nav-link p-0 border-0 bg-transparent dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
aria-label="{% trans 'Toggle user menu' %}"
data-bs-auto-close="outside"
data-bs-offset="0, 8"
>
<div class="profile-avatar" title="{% trans 'Your account' %}">
{{ user.username|first|upper }}
</div>
</button>
<ul
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
data-bs-popper="static"
style="min-width: 240px;"
>
<li class="px-4 py-3 bg-dark-subtle">
<div class="d-flex align-items-center">
<div class="me-3">
<div class="profile-avatar" style="width: 44px; height: 44px; background-color: var(--kaauh-teal);">
{{ user.username|first|upper }}
</div>
</div>
<div>
<div class="fw-semibold text-dark">{{ user.get_full_name|default:user.username }}</div>
<div class="text-muted small">{{ user.email|truncatechars:24 }}</div>
</div>
</div>
<div>
<div class="fw-semibold">{{ user.get_full_name|default:user.username }}</div>
<div class="text-muted small">{{ user.email|truncatechars:24 }}</div>
</div>
</div>
</li>
<li><hr class="dropdown-divider my-1"></li>
<li><a class="dropdown-item py-2" href="#"><i class="fas fa-user-circle me-2 text-primary"></i> My Profile</a></li>
<li><a class="dropdown-item py-2" href="#"><i class="fas fa-cog me-2 text-primary"></i> Settings</a></li>
<li><a class="dropdown-item py-2" href="#"><i class="fas fa-history me-2 text-primary"></i> Activity Log</a></li>
<li><a class="dropdown-item py-2" href="#"><i class="fas fa-question-circle me-2 text-primary"></i> Help & Support</a></li>
<li><hr class="dropdown-divider my-1"></li>
<li>
<form method="post" action="#" class="d-inline">
{% csrf_token %}
<button type="submit" class="dropdown-item py-2 text-danger">
<i class="fas fa-sign-out-alt me-2"></i> Sign Out
</button>
</form>
</li>
</ul>
</li>
</ul>
</div>
</li>
<li><hr class="dropdown-divider my-1"></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-user-circle me-3 text-primary fs-5"></i> <span>{% trans "My Profile" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-cog me-3 text-primary fs-5"></i> <span>{% trans "Settings" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-history me-3 text-primary fs-5"></i> <span>{% trans "Activity Log" %}</span></a></li>
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-question-circle me-3 text-primary fs-5"></i> <span>{% trans "Help & Support" %}</span></a></li>
<li><hr class="dropdown-divider my-1"></li>
<li>
<form method="post" action="" class="d-inline">
{% csrf_token %}
<button
type="submit"
class="dropdown-item py-2 px-4 text-danger d-flex align-items-center border-0 bg-transparent text-start"
aria-label="{% trans 'Sign out' %}"
>
<i class="fas fa-sign-out-alt me-3 fs-5"></i>
<span>{% trans "Sign Out" %}</span>
</button>
</form>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<main class="container flex-grow-1">
@ -519,42 +440,77 @@
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
</div>
{% endfor %}
{% endif %}
{% block content %}
{% endblock %}
</main>
<!-- Delete Modal -->
{% include 'includes/delete_modal.html' %}
<footer class="footer mt-auto">
<div class="container text-center">
<p class="mb-0">
&copy; {% now "Y" %} King Abdullah Academic University Hospital (KAAUH).<br>
<small>All rights reserved.</small>
&copy; {% now "Y" %} {% trans "King Abdullah Academic University Hospital (KAAUH)." %}<br>
<small>{% trans "All rights reserved." %}</small>
</p>
</div>
</footer>
{% include 'includes/delete_modal.html' %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Navbar collapse auto-close on link click (Standard Mobile UX)
const navbarCollapse = document.getElementById('navbarNav');
const navLinks = navbarCollapse.querySelectorAll('.nav-link:not(.dropdown-toggle)');
navLinks.forEach(link => {
link.addEventListener('click', () => {
const bsCollapse = bootstrap.Collapse.getInstance(navbarCollapse);
if (bsCollapse && navbarCollapse.classList.contains('show')) {
bsCollapse.hide();
if (navbarCollapse) {
const navLinks = navbarCollapse.querySelectorAll('.nav-link:not(.dropdown-toggle)');
const bsCollapse = bootstrap.Collapse.getInstance(navbarCollapse) || new bootstrap.Collapse(navbarCollapse, { toggle: false });
navLinks.forEach(link => {
link.addEventListener('click', () => {
// Only collapse if the nav is actually shown (i.e., on mobile)
if (navbarCollapse.classList.contains('show')) {
bsCollapse.hide();
}
});
});
}
// Mobile logout confirmation (Good UX for small screens)
const logoutButton = document.querySelector('form[action$="/logout/"] button');
if (logoutButton) {
logoutButton.addEventListener('click', (e) => {
// Check if screen is small (e.g., Bootstrap large breakpoint is 992px)
if (window.innerWidth <= 992) {
const confirmed = confirm('{% trans "Are you sure you want to sign out?" %}');
if (!confirmed) e.preventDefault();
}
});
}
// Handle language form submission: Manually trigger click on button inside form
document.querySelectorAll('.language-toggle-btn').forEach(toggle => {
const menu = toggle.nextElementSibling;
if (menu) {
menu.querySelectorAll('.dropdown-item').forEach(item => {
item.addEventListener('click', (e) => {
// Find the containing form and submit it
const form = item.closest('form');
if (form) {
form.submit();
}
});
});
}
});
});
</script>
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.5/bundles/datastar.js"></script>
{% block customJS %}{% endblock %}
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +1,166 @@
<!-- templates/form_templates_list.html -->
{% extends 'base.html' %}
{% load static i18n %}
{% block title %}Form Templates - ATS{% endblock %}
{% block extra_css %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
background-color: white;
}
/* Template Card hover effect (re-themed) */
.template-card {
transition: transform 0.2s, box-shadow 0.2s;
height: 100%;
box-shadow: 0 2px 8px rgba(0,0,0,0.05) !important;
}
.template-card:hover {
transform: translateY(-5px);
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
}
.stat-value {
font-size: 1.25rem;
/* Card Header Theming */
.card-header {
background-color: #f0f8ff !important; /* Light blue tint for header */
border-bottom: 1px solid var(--kaauh-border);
color: var(--kaauh-teal-dark);
font-weight: 600;
padding: 1rem 1.25rem;
border-radius: 0.75rem 0.75rem 0 0;
}
.card-header h3 {
color: var(--kaauh-teal-dark);
font-weight: 700;
color: var(--bs-primary);
}
.card-header .fas {
color: var(--kaauh-teal);
}
/* Stats Theming */
.stat-value {
font-size: 1.5rem;
font-weight: 800;
color: var(--kaauh-teal-dark); /* Using dark teal for stats */
}
.stat-label {
font-size: 0.875rem;
color: var(--bs-gray-600);
font-size: 0.85rem;
color: var(--kaauh-primary-text);
font-weight: 500;
}
.card-description {
min-height: 60px;
color: var(--kaauh-primary-text);
}
/* Search Input Theming */
.form-control {
border-radius: 0.5rem 0 0 0.5rem;
border-color: var(--kaauh-border);
}
.input-group .btn-outline-secondary {
border-radius: 0 0.5rem 0.5rem 0;
border-left: none;
color: var(--kaauh-teal);
border-color: var(--kaauh-border);
background-color: #f8f9fa;
}
.input-group .btn-outline-secondary:hover {
background-color: #e9ecef;
}
/* Action Buttons (Re-theming outline colors) */
.btn-outline-primary {
--bs-btn-color: var(--kaauh-teal-dark);
--bs-btn-border-color: var(--kaauh-teal);
--bs-btn-hover-bg: var(--kaauh-teal);
--bs-btn-hover-border-color: var(--kaauh-teal);
--bs-btn-hover-color: white;
}
/* Delete Button - keep bold red */
.btn-outline-danger {
--bs-btn-color: #dc3545;
--bs-btn-border-color: #dc3545;
--bs-btn-hover-bg: #dc3545;
--bs-btn-hover-color: white;
}
/* Empty State Theming */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--bs-gray-600);
color: var(--kaauh-primary-text);
border: 2px dashed var(--kaauh-border);
border-radius: 0.75rem;
background-color: #f8f9fa;
}
.empty-state i {
font-size: 3rem;
margin-bottom: 1.5rem;
color: var(--bs-gray-400);
font-size: 3.5rem;
margin-bottom: 1rem;
color: var(--kaauh-teal);
}
.action-btn {
min-width: 90px;
/* Pagination Styling */
.pagination .page-link {
color: var(--kaauh-teal-dark);
}
@media (max-width: 768px) {
.card-actions .btn {
width: 100%;
margin-bottom: 0.5rem;
}
.pagination .page-item.active .page-link {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
.pagination .page-item:not(.active) .page-link:hover {
background-color: #e9ecef;
}
</style>
{% endblock %}
{% block content %}
<div class="container py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0"><i class="fas fa-file-alt me-2"></i>Form Templates</h1>
<a href="{% url 'form_builder' %}" class="btn btn-primary">
<div class="d-flex justify-content-between align-items-center mb-4 pb-2 border-bottom border-primary">
<h1 class="h3 mb-0 fw-bold text-primary">
<i class="fas fa-file-alt me-2"></i>Form Templates
</h1>
<a href="{% url 'form_builder' %}" class="btn btn-main-action">
<i class="fas fa-plus me-1"></i> Create New Template
</a>
</div>
@ -86,8 +180,8 @@
<div class="row g-4">
{% for template in templates %}
<div class="col-lg-4 col-md-6">
<div class="card template-card h-100">
<div class="card-header bg-light">
<div class="card template-card h-100 shadow-sm">
<div class="card-header">
<h3 class="h5 mb-2">{{ template.name }}</h3>
<div class="d-flex justify-content-between text-muted small">
<span><i class="fas fa-calendar me-1"></i> {{ template.created_at|date:"M d, Y" }}</span>
@ -105,14 +199,14 @@
<div class="stat-label">Fields</div>
</div>
</div>
<p class="card-text card-description flex-grow-1">
<p class="card-text card-description flex-grow-1 small">
{% if template.description %}
{{ template.description|truncatewords:20 }}
{% else %}
<em class="text-muted">No description provided</em>
{% endif %}
</p>
<div class="card-actions d-grid gap-2 d-md-flex justify-content-md-end">
<div class="card-actions d-grid gap-2 d-md-flex justify-content-md-end pt-3 border-top border-light-subtle">
<a href="{% url 'form_builder' template.id %}" class="btn btn-outline-primary btn-sm action-btn">
<i class="fas fa-edit me-1"></i> Edit
</a>
@ -165,29 +259,27 @@
<div class="empty-state">
<i class="fas fa-file-contract"></i>
<h3 class="h4 mb-3">No Form Templates Found</h3>
<p class="mb-4">
<p class="mb-4 text-muted">
{% if query %}No templates match your search "{{ query }}".{% else %}You haven't created any form templates yet.{% endif %}
</p>
<a href="{% url 'form_builder' %}" class="btn btn-primary">
<a href="{% url 'form_builder' %}" class="btn btn-main-action">
<i class="fas fa-plus me-1"></i> Create Your First Template
</a>
</div>
{% endif %}
</div>
<!-- Delete Confirmation Modal -->
{% include 'includes/delete_modal.html' %}
{% endblock %}
{% block extra_js %}
<script>
// Note: JS logic remains the same, assuming 'csrfToken' is available in 'base.html' or elsewhere.
// Initialize Bootstrap modal
const deleteModal = new bootstrap.Modal(document.getElementById('deleteModal'));
// CSRF Token for AJAX requests
// Create toast container if it doesn't exist
// Create toast container if it doesn't exist (using theme styles)
let toastContainer = document.querySelector('.toast-container');
if (!toastContainer) {
toastContainer = document.createElement('div');
@ -198,11 +290,13 @@
// Function to create toast notification
function createToast(message, type = 'success') {
const toastId = 'toast-' + Date.now();
const iconClass = type === 'success' ? 'check-circle text-success' : 'exclamation-circle text-danger';
const title = type === 'success' ? 'Success' : 'Error';
const toastHtml = `
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div id="${toastId}" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="5000">
<div class="toast-header">
<i class="fas fa-${type === 'success' ? 'check-circle text-success' : 'exclamation-circle text-danger'} me-2"></i>
<strong class="me-auto">${type === 'success' ? 'Success' : 'Error'}</strong>
<i class="fas fa-${iconClass} me-2"></i>
<strong class="me-auto">${title}</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
@ -225,6 +319,7 @@
// Search functionality
document.getElementById('searchBtn').addEventListener('click', function() {
const query = document.getElementById('searchInput').value;
// Assuming the query parameter for search is 'q' based on pagination logic
window.location.href = query ? `?q=${encodeURIComponent(query)}` : '?';
});
@ -252,13 +347,18 @@
e.preventDefault();
if (!templateToDelete) return;
// This relies on 'csrfToken' being defined somewhere, which is typical for Django templates.
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
try {
// Note: Update this API path if necessary based on your actual URL configuration
const response = await fetch(`/api/templates/${templateToDelete}/delete/`, {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
'X-Requested-With': 'XMLHttpRequest'
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json'
}
});
@ -273,28 +373,31 @@
// Remove the template card from the DOM after a short delay
setTimeout(() => {
// Find and remove the card with matching template ID
const cardToRemove = document.querySelector(`[data-template-id="${templateToDelete}"]`).closest('.col-lg-4');
if (cardToRemove) {
cardToRemove.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
cardToRemove.style.opacity = '0';
cardToRemove.style.transform = 'scale(0.8)';
// Find the button that was clicked and its parent card column
const buttonClicked = document.querySelector(`button[data-template-id="${templateToDelete}"]`);
if(buttonClicked) {
const cardToRemove = buttonClicked.closest('.col-lg-4');
if (cardToRemove) {
cardToRemove.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out';
cardToRemove.style.opacity = '0';
cardToRemove.style.transform = 'scale(0.8)';
setTimeout(() => {
cardToRemove.remove();
setTimeout(() => {
cardToRemove.remove();
// Check if any templates remain
const remainingCards = document.querySelectorAll('.col-lg-4');
if (remainingCards.length === 0) {
// Redirect to empty state
window.location.reload();
}
}, 300);
// Check if any templates remain
const remainingCards = document.querySelectorAll('.col-lg-4');
if (remainingCards.length === 0) {
// Reload to show the empty state
window.location.reload();
}
}, 300);
}
}
}, 1000);
}, 100);
} else {
// Show error toast
createToast('Error: ' + result.error, 'error');
createToast('Error: ' + (result.error || 'Could not delete template.'), 'error');
}
} catch (error) {
console.error('Error:', error);
@ -309,4 +412,4 @@
templateToDelete = null;
});
</script>
{% endblock %}
{% endblock %}

View File

@ -12,10 +12,10 @@
value="{{ search_query }}"
aria-label="{% trans 'Search' %}">
</div>
<button type="submit" class="btn btn-primary">
{% comment %} <button type="submit" class="btn btn-primary">
<svg class="heroicon icon-sm" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Search" %}
</button>
</button> {% endcomment %}
</form>

View File

@ -1,269 +1,397 @@
{% extends "base.html" %}
{% load static i18n %}
{% block title %}Create New Job Post - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary/Cancel Button Style */
.btn-secondary {
background-color: #f8f9fa;
color: var(--kaauh-teal-dark);
border: 1px solid var(--kaauh-border);
font-weight: 500;
}
.btn-secondary:hover {
background-color: #e9ecef;
color: var(--kaauh-teal-dark);
border-color: #ced4da;
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Themed Card Header (replacing bg-light) */
.card-header-themed {
background-color: #f0f8ff; /* Very light blue background */
border-bottom: 2px solid var(--kaauh-teal);
color: var(--kaauh-teal-dark);
font-weight: 700;
padding: 1rem 1.5rem;
}
.card-header-themed h5 {
font-weight: 700;
color: var(--kaauh-teal-dark);
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
/* Form Fixes for manual rendering consistency */
.form-label {
font-weight: 600;
color: var(--kaauh-primary-text);
margin-bottom: 0.3rem;
display: block;
}
/* CRITICAL FIX: Target all relevant input types inside the form to mimic form-control class */
.card-body input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]),
.card-body textarea,
.card-body select {
/* Apply form-control styling */
border-radius: 0.5rem;
border: 1px solid #ced4da;
width: 100%;
padding: 0.375rem 0.75rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
/* Ensure inputs rendered without class attribute are styled */
box-sizing: border-box;
}
.card-body textarea {
min-height: 8rem;
}
/* File input styling is kept minimal as it's harder to style universally */
.card-body input[type="file"] {
padding-top: 0.5rem;
}
</style>
{% endblock %}
{% block content %}
<form method="post" id="jobForm" class="mb-5">
{% csrf_token %}
<div class="container-fluid py-4">
<h1 class="h3 mb-4 text-primary fw-bold">
<i class="fas fa-bullhorn me-2"></i> {% trans "Create New Job Posting" %}
</h1>
<!-- Basic Information Section -->
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Basic Information</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
<div class="mb-3">
<label for="{{ form.title.id_for_label }}" class="form-label">Job Title <span class="text-danger">*</span></label>
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger mt-1">{{ form.title.errors }}</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.job_type.id_for_label }}" class="form-label">Job Type <span class="text-danger">*</span></label>
{{ form.job_type }}
{% if form.job_type.errors %}
<div class="text-danger mt-1">{{ form.job_type.errors }}</div>
{% endif %}
</div>
</div>
<form method="post" id="jobForm" class="mb-5">
{% csrf_token %}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-info-circle"></i> {% trans "Basic Information" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-8">
<div>
<label for="{{ form.title.id_for_label }}" class="form-label">{% trans "Job Title" %} <span class="text-danger">*</span></label>
{# Removed |attr:"class:form-control" #}
{{ form.title }}
{% if form.title.errors %}
<div class="text-danger small mt-1">{{ form.title.errors }}</div>
{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.job_type.id_for_label }}" class="form-label">{% trans "Job Type" %} <span class="text-danger">*</span></label>
{# Removed |attr:"class:form-control" #}
{{ form.job_type }}
{% if form.job_type.errors %}
<div class="text-danger small mt-1">{{ form.job_type.errors }}</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.department.id_for_label }}" class="form-label">Department</label>
{{ form.department }}
{% if form.department.errors %}
<div class="text-danger mt-1">{{ form.department.errors }}</div>
{% endif %}
<div class="col-md-6">
<div>
<label for="{{ form.department.id_for_label }}" class="form-label">{% trans "Department" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.department }}
{% if form.department.errors %}
<div class="text-danger small mt-1">{{ form.department.errors }}</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.position_number.id_for_label }}" class="form-label">Position Number</label>
{{ form.position_number }}
{% if form.position_number.errors %}
<div class="text-danger mt-1">{{ form.position_number.errors }}</div>
{% endif %}
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.position_number }}
{% if form.position_number.errors %}
<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.workplace_type.id_for_label }}" class="form-label">Workplace Type <span class="text-danger">*</span></label>
{{ form.workplace_type }}
{% if form.workplace_type.errors %}
<div class="text-danger mt-1">{{ form.workplace_type.errors }}</div>
{% endif %}
<div class="col-md-6">
<div>
<label for="{{ form.workplace_type.id_for_label }}" class="form-label">{% trans "Workplace Type" %} <span class="text-danger">*</span></label>
{# Removed |attr:"class:form-control" #}
{{ form.workplace_type }}
{% if form.workplace_type.errors %}
<div class="text-danger small mt-1">{{ form.workplace_type.errors }}</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.created_by.id_for_label }}" class="form-label">Created By</label>
{{ form.created_by }}
{% if form.created_by.errors %}
<div class="text-danger mt-1">{{ form.created_by.errors }}</div>
{% endif %}
<div class="col-md-6">
<div>
<label for="{{ form.created_by.id_for_label }}" class="form-label">{% trans "Created By" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.created_by }}
{% if form.created_by.errors %}
<div class="text-danger small mt-1">{{ form.created_by.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Location Section -->
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-map-marker-alt"></i> Location</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.location_city.id_for_label }}" class="form-label">City</label>
{{ form.location_city }}
{% if form.location_city.errors %}
<div class="text-danger mt-1">{{ form.location_city.errors }}</div>
{% endif %}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.location_city }}
{% if form.location_city.errors %}
<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.location_state.id_for_label }}" class="form-label">State/Province</label>
{{ form.location_state }}
{% if form.location_state.errors %}
<div class="text-danger mt-1">{{ form.location_state.errors }}</div>
{% endif %}
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.location_state }}
{% if form.location_state.errors %}
<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.location_country.id_for_label }}" class="form-label">Country</label>
{{ form.location_country }}
{% if form.location_country.errors %}
<div class="text-danger mt-1">{{ form.location_country.errors }}</div>
{% endif %}
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.location_country }}
{% if form.location_country.errors %}
<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Job Details Section -->
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-file-alt"></i> Job Details</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="{{ form.description.id_for_label }}" class="form-label">Job Description <span class="text-danger">*</span></label>
{{ form.description }}
{% if form.description.errors %}
<div class="text-danger mt-1">{{ form.description.errors }}</div>
{% endif %}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-file-alt"></i> {% trans "Job Details" %}</h5>
</div>
<div class="mb-3">
<label for="{{ form.qualifications.id_for_label }}" class="form-label">Qualifications and Requirements</label>
{{ form.qualifications }}
{% if form.qualifications.errors %}
<div class="text-danger mt-1">{{ form.qualifications.errors }}</div>
{% endif %}
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.salary_range.id_for_label }}" class="form-label">Salary Range</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}
<div class="text-danger mt-1">{{ form.salary_range.errors }}</div>
{% endif %}
<div class="card-body">
<div class="row g-4">
<div class="col-12">
<div>
<label for="{{ form.description.id_for_label }}" class="form-label">{% trans "Job Description" %} <span class="text-danger">*</span></label>
{# Removed |attr:"class:form-control" #}
{{ form.description }}
{% if form.description.errors %}
<div class="text-danger small mt-1">{{ form.description.errors }}</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.benefits.id_for_label }}" class="form-label">Benefits</label>
{{ form.benefits }}
{% if form.benefits.errors %}
<div class="text-danger mt-1">{{ form.benefits.errors }}</div>
{% endif %}
<div class="col-12">
<div>
<label for="{{ form.qualifications.id_for_label }}" class="form-label">{% trans "Qualifications and Requirements" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.qualifications }}
{% if form.qualifications.errors %}
<div class="text-danger small mt-1">{{ form.qualifications.errors }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.salary_range }}
{% if form.salary_range.errors %}
<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.benefits.id_for_label }}" class="form-label">{% trans "Benefits" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.benefits }}
{% if form.benefits.errors %}
<div class="text-danger small mt-1">{{ form.benefits.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Application Information Section -->
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-file-signature"></i> Application Information</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="{{ form.application_url.id_for_label }}" class="form-label">Application URL <span class="text-danger">*</span></label>
{{ form.application_url }}
{% if form.application_url.errors %}
<div class="text-danger mt-1">{{ form.application_url.errors }}</div>
{% endif %}
<div class="form-text">Full URL where candidates will apply</div>
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-file-signature"></i> {% trans "Application Information" %}</h5>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">Application Deadline</label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}
<div class="text-danger mt-1">{{ form.application_deadline.errors }}</div>
{% endif %}
<div class="card-body">
<div class="row g-4">
<div class="col-12">
<div>
<label for="{{ form.application_url.id_for_label }}" class="form-label">{% trans "Application URL" %} <span class="text-danger">*</span></label>
{# Removed |attr:"class:form-control" #}
{{ form.application_url }}
{% if form.application_url.errors %}
<div class="text-danger small mt-1">{{ form.application_url.errors }}</div>
{% endif %}
<div class="form-text">{% trans "Full URL where candidates will apply" %}</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.start_date.id_for_label }}" class="form-label">Desired Start Date</label>
{{ form.start_date }}
{% if form.start_date.errors %}
<div class="text-danger mt-1">{{ form.start_date.errors }}</div>
{% endif %}
<div class="col-md-6">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.application_deadline }}
{% if form.application_deadline.errors %}
<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="mb-3">
<label for="{{ form.application_instructions.id_for_label }}" class="form-label">Application Instructions</label>
{{ form.application_instructions }}
{% if form.application_instructions.errors %}
<div class="text-danger mt-1">{{ form.application_instructions.errors }}</div>
{% endif %}
</div>
</div>
</div>
<!-- Post Reach -->
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-star"></i>Post Reach Field</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-12">
<div class="mb-3">
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">Hashtags</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}
<div class="text-danger mt-1">{{ form.hash_tags.errors }}</div>
{% endif %}
<div class="col-md-6">
<div>
<label for="{{ form.start_date.id_for_label }}" class="form-label">{% trans "Desired Start Date" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.start_date }}
{% if form.start_date.errors %}
<div class="text-danger small mt-1">{{ form.start_date.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Internal Information Section -->
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0"><i class="fas fa-building"></i> Internal Information</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">Reports To</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}
<div class="text-danger mt-1">{{ form.reporting_to.errors }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.open_positions.id_for_label }}" class="form-label">Open Positions</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}
<div class="text-danger mt-1">{{ form.open_positions.errors }}</div>
{% endif %}
<div class="col-12">
<div>
<label for="{{ form.application_instructions.id_for_label }}" class="form-label">{% trans "Application Instructions" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.application_instructions }}
{% if form.application_instructions.errors %}
<div class="text-danger small mt-1">{{ form.application_instructions.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-star"></i>{% trans "Post Reach Field" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.hash_tags }}
{% if form.hash_tags.errors %}
<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-building"></i> {% trans "Internal Information" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.reporting_to }}
{% if form.reporting_to.errors %}
<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{# Removed |attr:"class:form-control" #}
{{ form.open_positions }}
{% if form.open_positions.errors %}
<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Form Actions -->
<div class="d-flex justify-content-between">
<a href="{% url 'job_list' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Cancel
</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Create Job
</button>
</div>
</form>
<div class="d-flex justify-content-between pt-2">
<a href="{% url 'job_list' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-1"></i> {% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-main-action">
<i class="fas fa-save me-1"></i> {% trans "Create Job" %}
</button>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,292 +1,569 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{{ job.title }} - University ATS{% endblock %}
{% block customCSS %}
<style>
/* Custom styles for the Job Detail Page (using variables from base.html) */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Header styling */
.job-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.job-header-card h2 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
/* Status badge */
.status-badge {
font-size: 0.9rem;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.7px;
}
/* Mapped color classes for status badges */
.bg-success { background-color: var(--kaauh-teal) !important; }
.bg-warning { background-color: #ffc107 !important; }
.bg-secondary { background-color: #6c757d !important; }
.bg-danger { background-color: #dc3545 !important; }
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
transition: transform 0.2s;
background-color: white;
}
.card:not(.no-hover):hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
}
/* Standard Card Header (used for single cards or fallback) */
.card-header {
font-weight: 600;
padding: 1rem 1.25rem;
background-color: #f8f9fa; /* Light background */
border-bottom: 1px solid var(--kaauh-border);
}
.card-header h5 {
font-weight: 600;
color: var(--kaauh-primary-text);
}
.card-footer {
padding: 1rem 1.25rem;
background-color: #f8f9fa;
border-top: 1px solid var(--kaauh-border);
}
/* Left Column Tabs Theming (main details) */
.nav-tabs {
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa;
padding: 0 1.25rem;
}
.nav-tabs .nav-link {
border: none;
border-bottom: 3px solid transparent;
color: var(--kaauh-primary-text);
font-weight: 500;
padding: 0.75rem 1rem;
margin-right: 0.5rem;
transition: all 0.2s;
}
.nav-tabs .nav-link:hover {
color: var(--kaauh-teal);
border-color: #e9ecef;
background-color: #fff;
}
.nav-tabs .nav-link.active {
color: var(--kaauh-teal-dark);
background-color: white;
border-bottom: 3px solid var(--kaauh-teal);
font-weight: 600;
}
/* ==================================== */
/* RIGHT COLUMN TABS STYLING (IMPROVED) */
/* ==================================== */
.right-column-tabs {
/* Use the .card wrapper for the main structure */
padding: 0;
margin-bottom: 0;
border-bottom: 1px solid var(--kaauh-border);
}
.right-column-tabs .nav-tabs {
padding: 0;
margin-bottom: 0;
border-bottom: none;
background-color: transparent;
display: flex; /* Ensure the nav-items take up equal space */
}
.right-column-tabs .nav-item {
flex-grow: 1;
text-align: center;
}
.right-column-tabs .nav-link {
/* Base style for all right column tabs */
padding: 0.9rem 1rem; /* Slightly larger padding for better spacing */
font-size: 0.95rem;
font-weight: 600;
color: var(--kaauh-primary-text);
border-radius: 0;
border-right: 1px solid var(--kaauh-border);
border-bottom: 1px solid var(--kaauh-border); /* Separator line */
background-color: #f8f9fa; /* Subtle background for non-active tabs */
}
.right-column-tabs .nav-item:last-child .nav-link {
border-right: none;
}
/* Active Tab */
.right-column-tabs .nav-link.active {
background-color: white; /* Lift the active tab */
color: var(--kaauh-teal-dark);
border-bottom: 3px solid var(--kaauh-teal); /* Strong accent */
border-right-color: transparent; /* Clean up border next to it */
margin-bottom: -1px; /* Overlap the border for a cleaner look */
}
/* Hover state for non-active tabs */
.right-column-tabs .nav-link:not(.active):hover {
background-color: #f0f4f7; /* Darken slightly on hover */
color: var(--kaauh-teal);
}
.right-column-tabs .tab-content {
padding: 1.5rem 1.25rem; /* Increased padding inside the content for breathing room */
background-color: white;
}
/* Section styling */
.job-section h5 {
color: var(--kaauh-teal);
font-weight: 700;
margin-bottom: 0.75rem;
font-size: 1.25rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--kaauh-border);
}
/* Applicant stats */
.applicant-stats .stat-item {
padding: 0.75rem;
text-align: center;
border-radius: 0.5rem;
background-color: #f8f9fa;
border: 1px solid var(--kaauh-border);
}
.applicant-stats .stat-item div:first-child {
font-size: 1.6rem;
font-weight: 700;
}
.applicant-stats .stat-item small {
font-size: 0.8rem;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
.text-info { color: #17a2b8 !important; }
.text-success { color: #28a745 !important; }
.text-secondary { color: #6c757d !important; }
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary outline button (for forms/back links) */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Specific styling for the deadline box */
.deadline-box {
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid;
}
/* Table styling for the Applicant preview */
.table-applicants tbody tr:hover {
background-color: #f3f9f9; /* Light teal hover for rows */
}
.table-applicants td {
border-top: 1px solid var(--kaauh-border);
}
</style>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2>{{ job.title }}</h2>
<span class="badge bg-{{ job.status|lower }} status-badge">
{{ job.get_status_display }}
</span>
</div>
<div class="card-body">
<!-- Job Details -->
<div class="row mb-3">
<div class="col-md-6">
<strong>Department:</strong> {{ job.department|default:"Not specified" }}
</div>
<div class="col-md-6">
<strong>Position Number:</strong> {{ job.position_number|default:"Not specified" }}
</div>
<div class="container-fluid py-4">
<div class="row g-4">
{# LEFT COLUMN: JOB DETAILS WITH TABS #}
<div class="col-lg-8">
<div class="card shadow-sm no-hover">
{# HEADER SECTION #}
<div class="job-header-card d-flex justify-content-between align-items-center flex-wrap">
<h2>{{ job.title }}</h2>
<span class="badge bg-{{ job.status|lower|striptags|yesno:'success,warning,secondary,danger' }} status-badge">
{{ job.get_status_display }}
</span>
</div>
<div class="row mb-3">
<div class="col-md-6">
<strong>Job Type:</strong> {{ job.get_job_type_display }}
</div>
<div class="col-md-6">
<strong>Workplace:</strong> {{ job.get_workplace_type_display }}
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<strong>Location:</strong> {{ job.get_location_display }}
</div>
<div class="col-md-6">
<strong>Created By:</strong> {{ job.created_by|default:"Not specified" }}
</div>
</div>
{% if job.salary_range %}
<div class="row mb-3">
<div class="col-12">
<strong>Salary Range:</strong> {{ job.salary_range }}
</div>
</div>
{% endif %}
{% if job.start_date %}
<div class="row mb-3">
<div class="col-12">
<strong>Start Date:</strong> {{ job.start_date }}
</div>
</div>
{% endif %}
{% if job.application_deadline %}
<div class="row mb-3">
<div class="col-12">
<strong>Application Deadline:</strong> {{ job.application_deadline }}
{% if job.is_expired %}
<span class="badge bg-danger">EXPIRED</span>
{% endif %}
</div>
</div>
{% endif %}
<!-- Description -->
{% if job.description %}
<div class="mb-3">
<h5>Description</h5>
<div>{{ job.description|linebreaks }}</div>
</div>
{% endif %}
{% if job.qualifications %}
<div class="mb-3">
<h5>Qualifications</h5>
<div>{{ job.qualifications|linebreaks }}</div>
</div>
{% endif %}
{% if job.benefits %}
<div class="mb-3">
<h5>Benefits</h5>
<div>{{ job.benefits|linebreaks }}</div>
</div>
{% endif %}
{% if job.application_instructions %}
<div class="mb-3">
<h5>Application Instructions</h5>
<div>{{ job.application_instructions|linebreaks }}</div>
</div>
{% endif %}
<!-- Application URL -->
{% if job.application_url %}
<div class="mb-3">
<button type="button" class="btn btn-info" data-bs-toggle="modal" data-bs-target="#myModalForm">
Upload Image for Post
</button>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-lg-4">
<!-- LinkedIn Integration Card -->
<div class="card mb-4">
<div class="card-header">
<h5><i class="fab fa-linkedin"></i> LinkedIn Integration</h5>
</div>
<div class="card-body">
{% if job.posted_to_linkedin %}
<div class="alert alert-success">
<i class="fas fa-check-circle"></i> Posted to LinkedIn successfully!
</div>
{% if job.linkedin_post_url %}
<a href="{{ job.linkedin_post_url }}" target="_blank" class="btn btn-primary w-100 mb-2">
<i class="fab fa-linkedin"></i> View on LinkedIn
</a>
{# LEFT TABS NAVIGATION #}
<ul class="nav nav-tabs" id="jobTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="details-tab" data-bs-toggle="tab" data-bs-target="#details" type="button" role="tab" aria-controls="details" aria-selected="true">
<i class="fas fa-info-circle me-1"></i> {% trans "Core Details" %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="description-tab" data-bs-toggle="tab" data-bs-target="#description" type="button" role="tab" aria-controls="description" aria-selected="false">
<i class="fas fa-file-alt me-1"></i> {% trans "Description & Requirements" %}
</button>
</li>
{% if job.application_instructions %}
<li class="nav-item" role="presentation">
<button class="nav-link" id="instructions-tab" data-bs-toggle="tab" data-bs-target="#instructions" type="button" role="tab" aria-controls="instructions" aria-selected="false">
<i class="fas fa-paper-plane me-1"></i> {% trans "Application" %}
</button>
</li>
{% endif %}
<small class="text-muted">Posted on: {{ job.linkedin_posted_at|date:"M d, Y" }}</small>
{% else %}
<p class="text-muted">This job has not been posted to LinkedIn yet.</p>
{% endif %}
</ul>
<form method="post" action="{% url 'post_to_linkedin' job.slug %}" class="mt-3">
{% csrf_token %}
<button type="submit" class="btn btn-primary w-100"
{% if not request.session.linkedin_authenticated %}disabled{% endif %}>
<i class="fab fa-linkedin"></i>
{% if job.posted_to_linkedin %}Re-post to LinkedIn{% else %}Post to LinkedIn{% endif %}
</button>
</form>
{% if not request.session.linkedin_authenticated %}
<small class="text-muted">You need to <a href="{% url 'linkedin_login' %}">authenticate with LinkedIn</a> first.</small>
{% endif %}
{% if job.linkedin_post_status and 'ERROR' in job.linkedin_post_status %}
<div class="alert alert-danger mt-2">
<small>{{ job.linkedin_post_status }}</small>
</div>
{% endif %}
</div>
</div>
<div class="card border-secondary shadow-sm mb-4">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0">Applicant Form Management</h5>
</div>
<div class="card-body d-grid gap-2">
<p class="text-muted mb-3">
Manage the custom application forms associated with this job posting.
</p>
{# Primary Action: Highlight the creation of a NEW form #}
<a href=""
class="btn btn-lg btn-success">
<i class="fas fa-plus-circle me-2"></i> Create New Form
</a>
{# Secondary Action: Make the list button less prominent #}
<a href=""
class="btn btn-sm btn-outline-primary">
View All Existing Forms
</a>
</div>
</div>
<!-- Applicants List Card -->
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-users"></i> Applicants ({{ total_candidates }})
</h5>
</div>
<div class="card-body">
{% if total_candidates > 0 %}
<!-- Quick Stats -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex justify-content-around text-center">
<div class="p-2">
<div class="h5 text-primary">{{ applied_count }}</div>
<small class="text-muted">Applied</small>
<div class="card-body">
<div class="tab-content" id="jobTabsContent">
{# TAB 1 CONTENT: CORE DETAILS #}
<div class="tab-pane fade show active" id="details" role="tabpanel" aria-labelledby="details-tab">
<h5 class="text-muted mb-3">{% trans "Administrative & Location" %}</h5>
<div class="row g-3 mb-4 border-bottom pb-3 small text-secondary">
<div class="col-md-6">
<i class="fas fa-building me-2 text-primary"></i> <strong>{% trans "Department:" %}</strong> {{ job.department|default:"N/A" }}
</div>
<div class="p-2">
<div class="h5 text-info">{{ interview_count }}</div>
<small class="text-muted">Interview</small>
<div class="col-md-6">
<i class="fas fa-hashtag me-2 text-primary"></i> <strong>{% trans "Position No:" %}</strong> {{ job.position_number|default:"N/A" }}
</div>
<div class="p-2">
<div class="h5 text-success">{{ offer_count }}</div>
<small class="text-muted">Offer</small>
<div class="col-md-6">
<i class="fas fa-briefcase me-2 text-primary"></i> <strong>{% trans "Job Type:" %}</strong> {{ job.get_job_type_display }}
</div>
<div class="col-md-6">
<i class="fas fa-map-pin me-2 text-primary"></i> <strong>{% trans "Workplace:" %}</strong> {{ job.get_workplace_type_display }}
</div>
<div class="col-md-6">
<i class="fas fa-globe me-2 text-primary"></i> <strong>{% trans "Location:" %}</strong> {{ job.get_location_display }}
</div>
<div class="col-md-6">
<i class="fas fa-user-tie me-2 text-primary"></i> <strong>{% trans "Created By:" %}</strong> {{ job.created_by|default:"N/A" }}
</div>
</div>
<h5 class="text-muted mb-3">{% trans "Financial & Timeline" %}</h5>
<div class="row g-3">
{% if job.salary_range %}
<div class="col-md-4">
<div class="deadline-box border-success">
<i class="fas fa-money-bill-wave me-2 text-success"></i>
<strong class="text-success">{% trans "Salary:" %}</strong> <span class="text-dark">{{ job.salary_range }}</span>
</div>
</div>
{% endif %}
{% if job.start_date %}
<div class="col-md-4">
<div class="deadline-box border-info">
<i class="far fa-calendar-alt me-2 text-info"></i>
<strong class="text-info">{% trans "Start Date:" %}</strong> <span class="text-dark">{{ job.start_date }}</span>
</div>
</div>
{% endif %}
{% if job.application_deadline %}
<div class="col-md-4">
<div class="deadline-box border-{% if job.is_expired %}danger{% else %}primary{% endif %} text-{% if job.is_expired %}danger{% else %}primary{% endif %}">
<i class="fas fa-calendar-times me-2"></i>
<strong>{% trans "Deadline:" %}</strong> <span class="text-dark">{{ job.application_deadline }}</span>
{% if job.is_expired %}
<span class="badge bg-danger ms-1">{% trans "EXPIRED" %}</span>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Applicants List -->
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Stage</th>
<th>Date Applied</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for candidate in candidates %}
<tr>
<td>
<strong>{{ candidate.first_name }} {{ candidate.last_name }}</strong>
</td>
<td>{{ candidate.email }}</td>
<td>
<span class="badge bg-{% if candidate.stage == 'Applied' %}primary{% elif candidate.stage == 'Interview' %}info{% elif candidate.stage == 'Offer' %}success{% else %}secondary{% endif %}">
{{ candidate.stage }}
</span>
</td>
<td>{{ candidate.created_at|date:"M d, Y" }}</td>
<td>
<a href="{% url 'candidate_detail' candidate.slug %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye"></i> View
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if candidates|length > 5 %}
<div class="text-center mt-3">
<a href="{% url 'job_candidates_list' job.slug %}" class="btn btn-sm btn-outline-primary">
View All Applicants ({{ total_candidates }})
</a>
{# TAB 2 CONTENT: DESCRIPTION & REQUIREMENTS #}
<div class="tab-pane fade" id="description" role="tabpanel" aria-labelledby="description-tab">
{% if job.description %}
<div class="mb-4">
<h5>{% trans "Job Description" %}</h5>
<div class="text-secondary">{{ job.description|linebreaks }}</div>
</div>
{% endif %}
{% if job.qualifications %}
<div class="mb-4">
<h5>{% trans "Required Qualifications" %}</h5>
<div class="text-secondary">{{ job.qualifications|linebreaks }}</div>
</div>
{% endif %}
{% if job.benefits %}
<div class="mb-4">
<h5>{% trans "Benefits" %}</h5>
<div class="text-secondary">{{ job.benefits|linebreaks }}</div>
</div>
{% endif %}
</div>
{# TAB 3 CONTENT: APPLICATION INSTRUCTIONS #}
{% if job.application_instructions %}
<div class="tab-pane fade" id="instructions" role="tabpanel" aria-labelledby="instructions-tab">
<div class="mb-4">
<h5>{% trans "Application Instructions" %}</h5>
<div class="text-secondary">{{ job.application_instructions|linebreaks }}</div>
</div>
</div>
{% endif %}
</div>
</div>
{# FOOTER ACTIONS #}
<div class="card-footer d-flex flex-wrap gap-2">
<a href="{% url 'job_update' job.slug %}" class="btn btn-main-action">
<i class="fas fa-edit"></i> {% trans "Edit Job" %}
</a>
{% if job.application_url %}
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#myModalForm">
<i class="fas fa-image me-1"></i> {% trans "Upload Image for Post" %}
</button>
{% endif %}
{% else %}
<div class="text-center py-4">
<i class="fas fa-user-slash fa-3x text-muted mb-3"></i>
<h6 class="text-muted">No applicants yet</h6>
<p class="text-muted small">Candidates will appear here once they apply for this position.</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Internal Info Card -->
<div class="card">
<div class="card-header">
<h5>Internal Information</h5>
</div>
<div class="card-body">
<p><strong>Internal Job ID:</strong> {{ job.internal_job_id }}</p>
<p><strong>Created:</strong> {{ job.created_at|date:"M d, Y" }}</p>
<p><strong>Last Updated:</strong> {{ job.updated_at|date:"M d, Y" }}</p>
{% if job.reporting_to %}
<p><strong>Reports To:</strong> {{ job.reporting_to }}</p>
{% endif %}
{# RIGHT COLUMN: TABBED CARDS #}
<div class="col-lg-4">
<div class="card shadow-sm no-hover">
{# RIGHT TABS NAVIGATION #}
<ul class="nav nav-tabs right-column-tabs" id="rightJobTabs" role="tablist">
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link active" id="applicants-tab" data-bs-toggle="tab" data-bs-target="#applicants-pane" type="button" role="tab" aria-controls="applicants-pane" aria-selected="true">
<i class="fas fa-users me-1 text-primary"></i> {% trans "Applicants" %}
</button>
</li>
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link" id="manage-tab" data-bs-toggle="tab" data-bs-target="#manage-pane" type="button" role="tab" aria-controls="manage-pane" aria-selected="false">
<i class="fas fa-cogs me-1 text-secondary"></i> {% trans "Manage" %}
</button>
</li>
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link" id="internal-tab" data-bs-toggle="tab" data-bs-target="#internal-pane" type="button" role="tab" aria-controls="internal-pane" aria-selected="false">
<i class="fas fa-info me-1 text-muted"></i> {% trans "Info" %}
</button>
</li>
</ul>
<div class="tab-content mx-2 my-3" id="rightJobTabsContent">
{# TAB 1: APPLICANTS CONTENT #}
<div class="tab-pane fade show active" id="applicants-pane" role="tabpanel" aria-labelledby="applicants-tab">
<h5 class="mb-3">{% trans "Candidates" %} (<span id="total_candidates">{{ total_candidates }}</span>)</h5>
{% if total_candidates > 0 %}
<div class="row mb-4 applicant-stats">
<div class="col-4">
<div class="stat-item">
<div class="text-primary">{{ applied_count }}</div>
<small class="text-muted">{% trans "Applied" %}</small>
</div>
</div>
<div class="col-4">
<div class="stat-item">
<div class="text-info">{{ interview_count }}</div>
<small class="text-muted">{% trans "Interview" %}</small>
</div>
</div>
<div class="col-4">
<div class="stat-item">
<div class="text-success">{{ offer_count }}</div>
<small class="text-muted">{% trans "Offer" %}</small>
</div>
</div>
</div>
<div class="table-responsive small">
<table class="table table-sm table-hover mb-0 table-applicants">
<tbody>
{% for candidate in candidates|slice:":5" %}
<tr>
<td>
<strong>{{ candidate.first_name }} {{ candidate.last_name }}</strong>
</td>
<td>
<span class="badge bg-{% if candidate.stage == 'Applied' %}success{% elif candidate.stage == 'Interview' %}info{% elif candidate.stage == 'Offer' %}success{% else %}secondary{% endif %}">
{{ candidate.stage }}
</span>
</td>
<td class="text-end">
<a href="{% url 'candidate_detail' candidate.slug %}" class="btn btn-sm btn-outline-secondary py-0 px-2">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if candidates|length > 5 %}
<div class="text-center mt-3">
<a href="{% url 'job_candidates_list' job.slug %}" class="btn btn-sm btn-outline-secondary">
{% trans "View All Applicants" %} ({{ total_candidates }})
</a>
</div>
{% endif %}
{% else %}
<div class="text-center py-4">
<i class="fas fa-user-slash fa-3x text-muted mb-3"></i>
<h6 class="text-muted">{% trans "No applicants yet" %}</h6>
<p class="text-muted small">{% trans "Candidates will appear here once they apply for this position." %}</p>
</div>
{% endif %}
</div>
{# TAB 2: MANAGEMENT (LinkedIn & Forms) CONTENT #}
<div class="tab-pane fade" id="manage-pane" role="tabpanel" aria-labelledby="manage-tab">
{# LinkedIn Integration (Content from old card) #}
<h5 class="mb-3"><i class="fab fa-linkedin me-2 text-info"></i>{% trans "LinkedIn Integration" %}</h5>
<div class="mb-4">
{% if job.posted_to_linkedin %}
<div class="alert alert-success p-2 mb-3 small">
<i class="fas fa-check-circle me-1"></i> {% trans "Posted successfully!" %}
</div>
{% if job.linkedin_post_url %}
<a href="{{ job.linkedin_post_url }}" target="_blank" class="btn btn-main-action w-100 mb-2">
<i class="fab fa-linkedin me-1"></i> {% trans "View on LinkedIn" %}
</a>
{% endif %}
<small class="text-muted d-block text-center mb-3">
{% trans "Posted on:" %} {{ job.linkedin_posted_at|date:"M d, Y" }}
</small>
{% else %}
<p class="text-muted small mb-3">{% trans "This job has not been posted to LinkedIn yet." %}</p>
{% endif %}
<form method="post" action="{% url 'post_to_linkedin' job.slug %}" class="mt-2">
{% csrf_token %}
<button type="submit" class="btn btn-main-action w-100"
{% if not request.session.linkedin_authenticated %}disabled{% endif %}>
<i class="fab fa-linkedin me-1"></i>
{% if job.posted_to_linkedin %}{% trans "Re-post to LinkedIn" %}{% else %}{% trans "Post to LinkedIn" %}{% endif %}
</button>
</form>
{% if not request.session.linkedin_authenticated %}
<small class="text-muted d-block mt-2 text-center">
{% trans "You need to" %} <a href="{% url 'linkedin_login' %}">{% trans "authenticate with LinkedIn" %}</a> {% trans "first." %}
</small>
{% endif %}
{% if job.linkedin_post_status and 'ERROR' in job.linkedin_post_status %}
<div class="alert alert-danger mt-3 p-2 small">
<i class="fas fa-exclamation-triangle me-1"></i>
<small>{% trans "Error:" %} {{ job.linkedin_post_status }}</small>
</div>
{% endif %}
</div>
{# Applicant Form Management (Content from old card) #}
<h5 class="mb-3"><i class="fas fa-clipboard-list me-2 text-primary"></i>{% trans "Form Management" %}</h5>
<div class="d-grid gap-2">
<p class="text-muted small mb-3">
{% trans "Manage the custom application forms associated with this job posting." %}
</p>
<a href="" class="btn btn-main-action">
<i class="fas fa-plus-circle me-2"></i> {% trans "Create New Form" %}
</a>
<a href="" class="btn btn-outline-secondary">
<i class="fas fa-list-alt me-1"></i> {% trans "View All Existing Forms" %}
</a>
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-success me-2">
<i class="fas fa-user-plus"></i> {% trans "Create Candidate" %}
</a>
</div>
</div>
{# TAB 3: INTERNAL INFO CONTENT #}
<div class="tab-pane fade" id="internal-pane" role="tabpanel" aria-labelledby="internal-tab">
<h5 class="mb-3"><i class="fas fa-info-circle me-2 text-secondary"></i>{% trans "Internal Information" %}</h5>
<div class="small">
<p class="mb-1"><strong>{% trans "Internal Job ID:" %}</strong> {{ job.internal_job_id }}</p>
<p class="mb-1"><strong>{% trans "Created:" %}</strong> {{ job.created_at|date:"M d, Y" }}</p>
<p class="mb-1"><strong>{% trans "Last Updated:" %}</strong> {{ job.updated_at|date:"M d, Y" }}</p>
{% if job.reporting_to %}
<p class="mb-0"><strong>{% trans "Reports To:" %}</strong> {{ job.reporting_to }}</p>
{% endif %}
</div>
<div class="mt-4">
<a href="{% url 'job_list' %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-arrow-left"></i> {% trans "Back to Jobs" %}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-3 mb-5">
<a href="{% url 'job_update' job.slug %}" class="btn btn-outline-primary me-2">
<i class="fas fa-edit"></i> Edit Job
</a>
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-success me-2">
<i class="fas fa-user-plus"></i> Create Candidate
</a>
<a href="{% url 'job_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Back to Jobs
</a>
</div>
<!--modal class for upload file-->
{% include "jobs/partials/image_upload.html" %}
{% endblock %}
{% endblock %}

View File

@ -1,113 +1,250 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Job Postings - University ATS{% endblock %}
{% block customCSS %}
<style>
/* UI Variables for the KAAT-S Theme */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Enhanced Card Styling */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
}
/* Main Action Button Style (Teal Theme) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary Button Style (using theme border) */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Job Card Specifics */
.job-card .card-title {
color: var(--kaauh-teal-dark);
font-weight: 600;
font-size: 1.15rem;
}
.job-card .card-text i {
color: var(--kaauh-teal);
width: 1.25rem; /* Align icons vertically */
}
/* Status Badges */
.status-badge {
font-size: 0.8rem;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.7px;
}
/* Mapped color classes to theme */
.bg-draft { background-color: #6c757d !important; } /* secondary */
.bg-active { background-color: var(--kaauh-teal) !important; } /* primary teal */
.bg-closed { background-color: #dc3545 !important; } /* danger */
.bg-archived { background-color: #343a40 !important; } /* dark */
.bg-info { background-color: #17a2b8 !important; } /* LinkedIn badge */
/* 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;
}
/* Filter & Search Layout Adjustments */
.filter-buttons {
display: flex;
gap: 0.5rem; /* Space between filter and clear buttons */
}
.form-control-search {
/* Optional: Add slight shadow/focus effect to search bar */
box-shadow: none;
border-color: var(--kaauh-border);
}
.form-control-search:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.1rem rgba(0, 99, 110, 0.25);
}
</style>
{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1><i class="fas fa-briefcase"></i> Job Postings</h1>
<a href="{% url 'job_create' %}" class="btn btn-primary">
<i class="fas fa-plus"></i> Create New Job
</a>
</div>
<!-- Filters -->
<div class="card mb-4">
<div class="card-body">
<form method="get" class="row g-3">
<div class="col-md-3">
<label for="status" class="form-label">Status</label>
<select name="status" id="status" class="form-select">
<option value="">All Statuses</option>
<option value="DRAFT" {% if status_filter == 'DRAFT' %}selected{% endif %}>Draft</option>
<option value="ACTIVE" {% if status_filter == 'ACTIVE' %}selected{% endif %}>Active</option>
<option value="CLOSED" {% if status_filter == 'CLOSED' %}selected{% endif %}>Closed</option>
<option value="ARCHIVED" {% if status_filter == 'ARCHIVED' %}selected{% endif %}>Archived</option>
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-outline-primary">Filter</button>
</div>
</form>
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-briefcase me-2"></i> Job Postings
</h1>
<a href="{% url 'job_create' %}" class="btn btn-main-action">
<i class="fas fa-plus me-1"></i> Create New Job
</a>
</div>
</div>
<!-- Job List -->
{% if page_obj %}
<div class="row">
{% for job in page_obj %}
<div class="col-md-6 col-lg-4 mb-4">
<div class="card job-card h-100 {% if job.posted_to_linkedin %}linkedin-posted{% endif %}">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title mb-1">{{ job.title }}</h5>
<span class="badge bg-{{ job.status|lower }} status-badge">
{{ job.get_status_display }}
</span>
</div>
<div class="card mb-4 shadow-sm no-hover">
<div class="card-body">
<h5 class="card-title text-muted mb-3" style="font-weight: 500;">Filter & Search</h5>
<form method="get" class="row g-3 align-items-end">
<div class="col-md-4">
<label for="search" class="form-label small text-muted">Search by Title or Department</label>
<div class="input-group input-group-lg">
<span class="input-group-text bg-white border-end-0"><i class="fas fa-search text-muted"></i></span>
<input type="text" name="q" id="search" class="form-control form-control-search border-start-0"
placeholder="e.g., 'Professor' or 'Marketing'"
value="{{ search_query|default_if_none:'' }}">
</div>
</div>
<p class="card-text text-muted small">
<i class="fas fa-building"></i> {{ job.department|default:"No Department" }}<br>
<i class="fas fa-map-marker-alt"></i> {{ job.get_location_display }}<br>
<i class="fas fa-clock"></i> {{ job.get_job_type_display }}
</p>
<div class="col-md-3">
<label for="status" class="form-label small text-muted">Filter by Status</label>
<select name="status" id="status" class="form-select form-select-sm">
<option value="">All Statuses</option>
<option value="DRAFT" {% if status_filter == 'DRAFT' %}selected{% endif %}>Draft</option>
<option value="ACTIVE" {% if status_filter == 'ACTIVE' %}selected{% endif %}>Active</option>
<option value="CLOSED" {% if status_filter == 'CLOSED' %}selected{% endif %}>Closed</option>
<option value="ARCHIVED" {% if status_filter == 'ARCHIVED' %}selected{% endif %}>Archived</option>
</select>
</div>
<div class="mt-3">
{% if job.posted_to_linkedin %}
<span class="badge bg-info">
<i class="fab fa-linkedin"></i> Posted to LinkedIn
<div class="col-md-5">
<div class="filter-buttons">
<button type="submit" class="btn btn-main-action btn-lg">
<i class="fas fa-filter me-1"></i> Apply Filters
</button>
{# Show Clear button if any filter/search is active #}
{% if status_filter or search_query %}
<a href="{% url 'job_list' %}" class="btn btn-outline-danger btn-sm">
<i class="fas fa-times me-1"></i> Clear Filters
</a>
{% endif %}
</div>
</div>
</form>
</div>
</div>
{% if page_obj %}
<div class="row">
{% for job in page_obj %}
<div class="col-md-6 col-lg-4 mb-4">
<div class="card job-card h-100">
<div class="card-body d-flex flex-column">
<div class="d-flex justify-content-between align-items-start mb-2">
<h5 class="card-title flex-grow-1 me-3">{{ job.title }}</h5>
<span class="badge bg-{{ job.status|lower|striptags|yesno:'active,draft,closed,archived' }} status-badge">
{{ job.get_status_display }}
</span>
{% endif %}
</div>
<div class="mt-2">
<a href="{% url 'job_detail' job.slug %}" class="btn btn-sm btn-outline-primary me-2">
View
</a>
<a href="{% url 'job_update' job.slug %}" class="btn btn-sm btn-outline-secondary">
Edit
</a>
<p class="card-text text-muted small mb-3">
<i class="fas fa-building fa-fw"></i> {{ job.department|default:"No Department" }}<br>
<i class="fas fa-map-marker-alt fa-fw"></i> {{ job.get_location_display }}<br>
<i class="fas fa-clock fa-fw"></i> {{ job.get_job_type_display }}
</p>
<div class="mt-auto pt-2 border-top">
{% if job.posted_to_linkedin %}
<span class="badge bg-info mb-2">
<i class="fab fa-linkedin me-1"></i> Posted to LinkedIn
</span>
{% endif %}
<div class="d-flex gap-2">
<a href="{% url 'job_detail' job.slug %}" class="btn btn-sm btn-main-action">
<i class="fas fa-eye"></i> View
</a>
<a href="{% url 'job_update' job.slug %}" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-edit"></i> Edit
</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% if page_obj.has_other_pages %}
<nav aria-label="Job pagination" class="mt-4">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if status_filter %}&status={{ status_filter }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if status_filter %}&status={{ status_filter }}{% endif %}{% if search_query %}&q={{ search_query }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="text-center py-5 card shadow-sm">
<div class="card-body">
<i class="fas fa-briefcase fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
<h3>No job postings found</h3>
<p class="text-muted">Create your first job posting to get started or adjust your filters.</p>
<a href="{% url 'job_create' %}" class="btn btn-main-action mt-3">
<i class="fas fa-plus me-1"></i> Create Job
</a>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<nav aria-label="Job pagination">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page=1{% if status_filter %}&status={{ status_filter }}{% endif %}">First</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if status_filter %}&status={{ status_filter }}{% endif %}">Previous</a>
</li>
{% endif %}
<li class="page-item active">
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if status_filter %}&status={{ status_filter }}{% endif %}">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if status_filter %}&status={{ status_filter }}{% endif %}">Last</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-briefcase fa-3x text-muted mb-3"></i>
<h3>No job postings found</h3>
<p class="text-muted">Create your first job posting to get started.</p>
<a href="{% url 'job_create' %}" class="btn btn-primary">Create Job</a>
</div>
{% endif %}
{% endblock %}
</div>
{% endblock %}

View File

@ -4,91 +4,144 @@
{% block title %}{% trans "Zoom Meetings" %} - {{ block.super }}{% endblock %}
{% block extra_css %}
<style>
.meetings-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.meeting-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
border: 1px solid #e0e0e0;
transition: transform 0.2s ease, box-shadow 0.2s ease;
height: 100%;
}
.meeting-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.meeting-topic {
font-size: 1.1rem;
font-weight: 600;
color: #1b8354;
margin-bottom: 1rem;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 0.5rem;
}
.meeting-detail {
display: flex;
margin-bottom: 0.75rem;
align-items: flex-start;
}
.detail-label {
font-weight: 600;
min-width: 80px;
color: #666;
font-size: 0.9rem;
flex-shrink: 0;
}
.detail-value {
flex: 1;
font-size: 0.9rem;
word-break: break-word;
}
.status-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}
.status-waiting {
background: #fff8e1;
color: #ff8f00;
}
.status-started {
background: #d4edda;
color: #155724;
}
.status-ended {
background: #f8d7da;
color: #721c24;
}
.actions {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn-small {
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
}
/* New Teal/Aqua Theme Variables */
:root {
--primary: #0081a7; /* Deep Teal/Cyan */
--primary-light: #00b4d8; /* Brighter Aqua/Cyan */
--secondary: #005a78; /* Darker Teal for hover/accent */
--success: #00cc99; /* Bright Greenish-Teal for success */
--light: #f4fcfc; /* Very light off-white (slightly blue tinted) */
--dark: #212529; /* Near black text */
--gray: #6c757d; /* Standard gray text */
--light-gray: #e0f0f4; /* Lighter background for hover/disabled */
--border: #c4d7e0; /* Lighter, softer border color */
--danger: #e53935; /* Kept red for danger/delete actions */
--warning: #ff8f00; /* Kept orange for waiting status */
}
@media (max-width: 768px) {
/* GENERAL GRID AND CARD STYLES */
.meetings-grid {
grid-template-columns: 1fr;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
padding: 1.5rem; /* Added padding to the grid container */
}
.meeting-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 1.5rem;
border: 1px solid var(--border);
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
height: 100%;
}
.meeting-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
border-color: var(--primary-light); /* Highlight border on hover */
}
/* TOPIC AND DETAILS STYLES */
.meeting-topic {
font-size: 1.1rem;
font-weight: 600;
color: var(--primary); /* Uses the new primary color */
margin-bottom: 1rem;
border-bottom: 1px solid var(--border);
padding-bottom: 0.5rem;
}
.meeting-detail {
flex-direction: column;
gap: 0.25rem;
display: flex;
margin-bottom: 0.75rem;
align-items: flex-start;
}
.detail-label {
min-width: auto;
font-weight: 600;
min-width: 80px;
color: var(--gray); /* Uses gray variable */
font-size: 0.9rem;
flex-shrink: 0;
}
.detail-value {
flex: 1;
font-size: 0.9rem;
word-break: break-word;
color: var(--dark);
}
/* STATUS BADGE STYLES (Updated to use theme-based colors) */
.status-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}
/* Waiting (Warning) */
.status-waiting {
background: rgba(255, 143, 0, 0.15); /* Light orange tint */
color: var(--warning);
}
/* Started (Success) */
.status-started {
background: rgba(0, 204, 153, 0.15); /* Light greenish-teal tint */
color: var(--success);
}
/* Ended (Danger) */
.status-ended {
background: rgba(229, 57, 53, 0.15); /* Light red tint */
color: var(--danger);
}
/* ACTION AREA STYLES */
.actions {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.btn-sm { /* Re-styled the small button to inherit from main theme structure */
padding: 0.3rem 0.6rem;
font-size: 0.8rem;
}
/* Ensure the primary/outline buttons are defined (assuming a global class) */
.btn-primary {
background: var(--primary);
color: white;
border: 1px solid var(--primary);
}
.btn-primary:hover {
background: var(--secondary);
border-color: var(--secondary);
}
.btn-outline-primary {
background: transparent;
border: 1px solid var(--primary);
color: var(--primary);
}
.btn-outline-primary:hover {
background: var(--primary);
color: white;
}
/* Card header style for H1 to match theme */
.card-header h1 {
color: var(--primary);
}
/* RESPONSIVE STYLES */
@media (max-width: 768px) {
.meetings-grid {
grid-template-columns: 1fr;
padding: 1rem;
}
.meeting-detail {
flex-direction: column;
gap: 0.25rem;
}
.detail-label {
min-width: auto;
}
}
}
</style>
{% endblock %}
@ -101,7 +154,6 @@
{% trans "Zoom Meetings" %}
</h1>
<div class="d-flex gap-2 align-items-center">
<!-- Search Form -->
{% include "includes/search_form.html" with search_query=search_query %}
<a href="{% url 'create_meeting' %}" class="btn btn-primary">
@ -138,7 +190,11 @@
<div class="meeting-detail">
<div class="detail-label">{% trans "Status" %}:</div>
<div class="detail-value">
<span class="badge {% if meeting.status == 'waiting' %}bg-warning{% elif meeting.status == 'started' %}bg-success{% elif meeting.status == 'ended' %}bg-danger{% endif %}">
<span class="status-badge
{% if meeting.status == 'waiting' %}status-waiting
{% elif meeting.status == 'started' %}status-started
{% elif meeting.status == 'ended' %}status-ended
{% endif %}">
{% if meeting.status == 'waiting' %}
{% trans "Waiting" %}
{% elif meeting.status == 'started' %}
@ -234,4 +290,4 @@
</div>
{% endif %}
</div>
{% endblock %}
{% endblock %}

View File

@ -3,27 +3,135 @@
{% block title %}Create Candidate - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES (FROM JOB DETAIL) */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles */
.btn-secondary, .btn-outline-secondary {
background-color: #f8f9fa;
color: var(--kaauh-teal-dark);
border: 1px solid var(--kaauh-teal);
font-weight: 500;
}
.btn-secondary:hover, .btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Colored Header Card */
.candidate-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.candidate-header-card h1 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
.heroicon {
width: 1.25rem;
height: 1.25rem;
vertical-align: text-bottom;
stroke: currentColor;
margin-right: 0.5rem;
}
</style>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4v16m8-8H4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Create Candidate" %}
</h1>
<a href="{% url 'candidate_list' %}" class="btn btn-secondary">
{% trans "Back to List" %}
</a>
<div class="container-fluid py-4">
<div class="card mb-4">
<div class="candidate-header-card">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<div class="flex-grow-1">
<h1 class="h3 mb-1">
<i class="fas fa-user-plus"></i>
{% trans "Create New Candidate" %}
</h1>
<p class="text-white opacity-75 mb-0">{% trans "Enter details to create a new candidate record." %}</p>
</div>
<div class="d-flex gap-2 mt-1">
<a href="{% url 'candidate_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
<i class="fas fa-arrow-left"></i>
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
</a>
</div>
</div>
</div>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Create" %}</button>
</form>
<div class="card shadow-sm">
<div class="card-header bg-white border-bottom">
<h2 class="h5 mb-0 text-primary">
<i class="fas fa-file-alt me-1"></i>
{% trans "Candidate Information" %}
</h2>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{# Split form into two columns for better horizontal use #}
<div class="row g-4">
{% for field in form %}
<div class="col-md-6">
{{ field|as_crispy_field }}
</div>
{% endfor %}
</div>
<hr class="mt-4 mb-4">
<button class="btn btn-main-action" type="submit">
<i class="fas fa-save me-1"></i>
{% trans "Create Candidate" %}
</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -3,182 +3,382 @@
{% block title %}{{ candidate.name }} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES (FROM JOB DETAIL) */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
.text-info { color: #17a2b8 !important; }
.text-success { color: #28a745 !important; }
.text-secondary { color: #6c757d !important; }
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles */
.btn-outline-primary {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
}
.btn-outline-primary:hover {
background-color: var(--kaauh-teal);
color: white;
}
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
.btn-outline-info {
color: #17a2b8;
border-color: #17a2b8;
}
.btn-outline-info:hover {
background-color: #17a2b8;
color: white;
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Candidate Header Card (The teal header) */
.candidate-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.candidate-header-card h1 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
.candidate-header-card .badge {
font-size: 0.9rem;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
font-weight: 700;
}
/* Left Column Tabs (Main Content Tabs) */
.main-tabs {
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa;
padding: 0 1.25rem;
}
.main-tabs .nav-link {
border: none;
border-bottom: 3px solid transparent;
color: var(--kaauh-primary-text);
font-weight: 500;
padding: 0.75rem 1rem;
margin-right: 0.5rem;
transition: all 0.2s;
}
.main-tabs .nav-link:hover {
color: var(--kaauh-teal);
}
.main-tabs .nav-link.active {
color: var(--kaauh-teal-dark);
background-color: white;
border-bottom: 3px solid var(--kaauh-teal);
font-weight: 600;
}
/* Right Column Tabs (Parsed Data/Activity) */
.right-column-card .nav-tabs {
padding: 0;
margin-bottom: 0;
border-bottom: none;
background-color: transparent;
}
.right-column-card .nav-link {
padding: 0.9rem 1rem;
font-size: 0.95rem;
font-weight: 600;
color: var(--kaauh-primary-text);
border-right: 1px solid var(--kaauh-border);
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa;
}
.right-column-card .nav-item:last-child .nav-link {
border-right: none;
}
.right-column-card .nav-link.active {
background-color: white;
color: var(--kaauh-teal-dark);
border-bottom: 3px solid var(--kaauh-teal);
border-right-color: transparent;
margin-bottom: -1px;
}
.right-column-card .tab-content {
padding: 1.5rem 1.25rem;
background-color: white;
}
/* ==================================== */
/* NEW: PARSED DATA GRID OPTIMIZATION */
/* ==================================== */
.parsed-data-item {
/* Ensure the border/bg container takes full width of the grid column */
width: 100%;
}
.parsed-data-item .data-value {
/* Allow data values to wrap without breaking the layout */
word-wrap: break-word;
word-break: break-word;
}
</style>
{% endblock %}
{% block content %}
<!-- Candidate Header Card -->
<div class="card mb-4" data-signals-stage="'{{ candidate.stage }}'">
<div class="card-header">
<div class="d-flex justify-content-between align-items-start">
<div>
<h1 class="h3 mb-2">{{ candidate.name }}</h1>
<div class="d-flex align-items-center gap-2 mb-2">
<span class="badge {% if candidate.applied %}bg-success{% else %}bg-warning{% endif %}">
{{ candidate.applied|yesno:"Applied,Pending" }}
</span>
<span id="stageDisplay" class="badge" data-class="{'bg-primary': $stage == 'Applied', 'bg-info': $stage == 'Exam', 'bg-warning': $stage == 'Interview', 'bg-success': $stage == 'Offer'}">
Stage: <span data-text="$stage"></span>
</span>
<div class="container-fluid py-4">
<div class="row g-4">
{# LEFT COLUMN: MAIN CANDIDATE DETAILS AND TABS #}
<div class="col-lg-8">
<div class="card shadow-sm no-hover">
{# HEADER SECTION (The original Candidate Header Card content, redesigned) #}
<div class="candidate-header-card">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<div>
<h1 class="h3 mb-2">{{ candidate.name }}</h1>
<div class="d-flex align-items-center gap-2 mb-2">
<span class="badge {% if candidate.applied %}bg-success{% else %}bg-warning{% endif %}">
{{ candidate.applied|yesno:"Applied,Pending" }}
</span>
<span id="stageDisplay" class="badge"
data-class="{'bg-primary': $stage == 'Applied', 'bg-info': $stage == 'Exam', 'bg-warning': $stage == 'Interview', 'bg-success': $stage == 'Offer'}"
data-signals-stage="'{{ candidate.stage }}'">
{% trans "Stage:" %}
<span data-text="'{{ candidate.stage }}'">{{ candidate.stage }}</span>
</span>
</div>
<small class="text-white opacity-75">
{% trans "Applied for:" %} <strong>{{ candidate.job.title }}</strong>
</small>
</div>
{# Action buttons moved to the right column, keeping only the most visible here for quick access if preferred #}
{% if user.is_staff %}
<button type="button" class="btn btn-outline-light btn-sm mt-1" data-bs-toggle="modal" data-bs-target="#stageUpdateModal">
<i class="fas fa-exchange-alt"></i> {% trans "Change Stage" %}
</button>
{% endif %}
</div>
</div>
{% if user.is_staff %}
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-info btn-sm" data-bs-toggle="modal" data-bs-target="#stageUpdateModal" title="{% trans 'Update Stage' %}">
<i class="fas fa-exchange-alt"></i>
<span class="d-none d-sm-inline">{% trans "Update Stage" %}</span>
{# LEFT TABS NAVIGATION #}
<ul class="nav nav-tabs main-tabs" id="candidateTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="contact-tab" data-bs-toggle="tab" data-bs-target="#contact-pane" type="button" role="tab" aria-controls="contact-pane" aria-selected="true">
<i class="fas fa-id-card me-1"></i> {% trans "Contact & Job" %}
</button>
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Edit' %}">
{% include "icons/edit.html" %}
<span class="d-none d-sm-inline">{% trans "Edit" %}</span>
</a>
<a href="{% url 'candidate_delete' candidate.slug %}" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% include "icons/delete.html" %}
<span class="d-none d-sm-inline">{% trans "Delete" %}</span>
</a>
<a href="{% url 'candidate_list' %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Back to List' %}">
{% include "icons/back.html" %}
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
</a>
</li>
{% if candidate.resume %}
<li class="nav-item" role="presentation">
<button class="nav-link" id="resume-tab" data-bs-toggle="tab" data-bs-target="#resume-pane" type="button" role="tab" aria-controls="resume-pane" aria-selected="false">
<i class="fas fa-file-pdf me-1"></i> {% trans "Resume" %}
</button>
</li>
{% endif %}
{% if candidate.parsed_summary %}
<li class="nav-item" role="presentation">
<button class="nav-link" id="summary-tab" data-bs-toggle="tab" data-bs-target="#summary-pane" type="button" role="tab" aria-controls="summary-pane" aria-selected="false">
<i class="fas fa-chart-bar me-1"></i> {% trans "Summary" %}
</button>
</li>
{% endif %}
</ul>
<div class="card-body">
<div class="tab-content" id="candidateTabsContent">
{# TAB 1 CONTENT: CONTACT & DATES (Original Contact Card) #}
<div class="tab-pane fade show active" id="contact-pane" role="tabpanel" aria-labelledby="contact-tab">
<h5 class="text-primary mb-4">{% trans "Core Details" %}</h5>
<div class="row g-4">
<div class="col-md-6">
<div class="d-flex align-items-center">
<i class="fas fa-envelope fa-2x text-muted me-3"></i>
<div>
<small class="text-muted d-block">{% trans "Email" %}</small>
<strong>{{ candidate.email }}</strong>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center">
<i class="fas fa-briefcase fa-2x text-muted me-3"></i>
<div>
<small class="text-muted d-block">{% trans "Position Applied" %}</small>
<strong>{{ candidate.job.title }}</strong>
</div>
</div>
</div>
<div class="col-12">
<div class="d-flex align-items-center">
<i class="fas fa-calendar-check fa-2x text-muted me-3"></i>
<div>
<small class="text-muted d-block">{% trans "Applied Date" %}</small>
<div class="d-flex align-items-center gap-2">
<strong>{{ candidate.created_at|date:"M d, Y H:i" }}</strong>
<span class="badge bg-light text-dark">
<i class="far fa-clock me-1"></i>
{{ candidate.created_at|naturaltime }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
{# TAB 2 CONTENT: RESUME (Original Resume Card) #}
{% if candidate.resume %}
<div class="tab-pane fade" id="resume-pane" role="tabpanel" aria-labelledby="resume-tab">
<h5 class="text-primary mb-4">{% trans "Resume Document" %}</h5>
<div class="d-flex align-items-center justify-content-between p-3 border rounded">
<div>
<p class="mb-1"><strong>{{ candidate.resume.name }}</strong></p>
<small class="text-muted">{{ candidate.resume.name|truncatechars:30 }}</small>
</div>
<a href="{{ candidate.resume.url }}" download class="btn btn-main-action">
<i class="fas fa-download me-1"></i>
{% trans "Download Resume" %}
</a>
</div>
</div>
{% endif %}
{# TAB 3 CONTENT: PARSED SUMMARY (Original Parsed Summary Card) #}
{% if candidate.parsed_summary %}
<div class="tab-pane fade" id="summary-pane" role="tabpanel" aria-labelledby="summary-tab">
<h5 class="text-primary mb-4">{% trans "AI Generated Summary" %}</h5>
<div class="border-start border-primary ps-3 pt-1 pb-1">
<p class="mb-0 text-secondary">{{ candidate.parsed_summary|linebreaks }}</p>
</div>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
{# RIGHT COLUMN: ACTIONS AND PARSED DATA TABS #}
<div class="col-lg-4">
{# ACTIONS CARD (The new consolidated action card) #}
{% if user.is_staff %}
<div class="card shadow-sm mb-4 p-3">
<h5 class="text-muted mb-3"><i class="fas fa-cog me-2"></i>{% trans "Management Actions" %}</h5>
<div class="d-grid gap-2">
{# MODAL TRIGGER #}
{% comment %} <button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#stageUpdateModal">
<i class="fas fa-exchange-alt"></i> {% trans "Update Stage" %}
</button> {% endcomment %}
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-primary">
<i class="fas fa-edit"></i> {% trans "Edit Details" %}
</a>
<a href="{% url 'candidate_delete' candidate.slug %}" class="btn btn-outline-danger" onclick="return confirm('{% trans "Are you sure you want to delete this candidate?" %}')">
<i class="fas fa-trash-alt"></i> {% trans "Delete Candidate" %}
</a>
<a href="{% url 'candidate_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> {% trans "Back to List" %}
</a>
</div>
</div>
{% endif %}
{# PARSED DATA TABS (Original Parsed Data Card, now tabbed) #}
{% if parsed %}
<div class="card right-column-card shadow-sm">
<ul class="nav nav-tabs" id="parsedDataTabs" role="tablist">
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link active" id="data-tab" data-bs-toggle="tab" data-bs-target="#data-pane" type="button" role="tab" aria-controls="data-pane" aria-selected="true">
<i class="fas fa-database me-1"></i> {% trans "Parsed Data" %}
</button>
</li>
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link" id="activity-tab" data-bs-toggle="tab" data-bs-target="#activity-pane" type="button" role="tab" aria-controls="activity-pane" aria-selected="false">
<i class="fas fa-history me-1"></i> {% trans "Activity" %}
</button>
</li>
</ul>
<div class="tab-content" id="parsedDataTabsContent">
{# TAB 1: PARSED DATA - UPDATED TO 2-COLUMN GRID #}
<div class="tab-pane fade show active" id="data-pane" role="tabpanel" aria-labelledby="data-tab">
<h6 class="text-muted small text-uppercase mb-3">{% trans "Structured Resume Data" %}</h6>
<div class="row g-3">
{% for key, value in parsed.items %}
<div class="col-md-6 parsed-data-item">
<div class="p-3 border rounded h-100 bg-light">
<h6 class="text-muted small text-uppercase mb-1">
<i class="fas fa-tag me-1 text-primary"></i> {{ key|title }}
</h6>
<p class="mb-0 text-dark small data-value">
{{ value|default:"N/A"|linebreaksbr }}
</p>
</div>
</div>
{% endfor %}
</div>
</div>
{# TAB 2: ACTIVITY (Placeholder) #}
<div class="tab-pane fade" id="activity-pane" role="tabpanel" aria-labelledby="activity-tab">
<p class="text-muted">
{% trans "Activity feed (e.g., stage changes, notes, interview history) will appear here." %}
</p>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Contact Information Card -->
<div class="card mb-4">
<div class="card-header">
<h2 class="h5 mb-0">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Contact Information" %}
</h2>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3">
<div class="d-flex align-items-center">
<svg class="heroicon icon-sm text-muted me-3" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M22 6l-10 7L2 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
<div>
<small class="text-muted d-block">{% trans "Email" %}</small>
<strong>{{ candidate.email }}</strong>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="d-flex align-items-center">
<svg class="heroicon icon-sm text-muted me-3" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M3.27 6.96L12 12.01l8.73-5.05" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M3 16v5a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
<div>
<small class="text-muted d-block">{% trans "Job Position" %}</small>
<strong>{{ candidate.job.title }}</strong>
</div>
</div>
</div>
<div class="col-12">
<div class="d-flex align-items-center">
<svg class="heroicon icon-sm text-muted me-3" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
<div>
<small class="text-muted d-block">{% trans "Applied Date" %}</small>
<div class="d-flex align-items-center gap-2">
<strong>{{ candidate.created_at|date:"M d, Y H:i" }}</strong>
<span class="badge bg-light text-dark">
<svg class="heroicon icon-sm" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" stroke="currentColor" stroke-width="2"></path>
</svg>
{{ candidate.created_at|naturaltime }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Resume Card -->
{% if candidate.resume %}
<div class="card mb-4">
<div class="card-header">
<h2 class="h5 mb-0">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M14 2v6h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M16 13H8M16 17H8M10 9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Resume" %}
</h2>
</div>
<div class="card-body">
<div class="d-flex align-items-center justify-content-between">
<div>
<p class="mb-1"><strong>{{ candidate.resume.name }}</strong></p>
<small class="text-muted">{{ candidate.resume }} • {{ candidate.resume.name|truncatechars:30 }}</small>
</div>
<a href="{{ candidate.resume.url }}" download class="btn btn-primary">
{% include "icons/download.html" %}
{% trans "Download Resume" %}
</a>
</div>
</div>
</div>
{% endif %}
<!-- Parsed Summary Card -->
{% if candidate.parsed_summary %}
<div class="card mb-4">
<div class="card-header">
<h2 class="h5 mb-0">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Parsed Summary" %}
</h2>
</div>
<div class="card-body">
<div class="border-start border-primary ps-3">
<p class="mb-0">{{ candidate.parsed_summary|linebreaks }}</p>
</div>
</div>
</div>
{% endif %}
<!-- Parsed Data Card -->
{% if parsed %}
<div class="card">
<div class="card-header">
<h2 class="h5 mb-0">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Parsed Data" %}
</h2>
</div>
<div class="card-body">
<div class="row">
{% for key, value in parsed.items %}
<div class="col-md-6 col-lg-4 mb-3">
<div class="border rounded p-3 h-100">
<h6 class="text-muted small text-uppercase mb-1">{{ key|title }}</h6>
<p class="mb-0">{{ value }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Stage Update Modal -->
{% if user.is_staff %}
{% include "recruitment/partials/stage_update_modal.html" with candidate=candidate form=stage_form %}
{% endif %}
{% endblock %}
{% endblock %}

View File

@ -3,121 +3,243 @@
{% block title %}Candidates - {{ block.super }}{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0">
{% include "icons/users.html" %}
{% trans "Candidates" %}
</h1>
<div class="d-flex gap-2 align-items-center">
<!-- Search Form -->
{% include "includes/search_form.html" with search_query=search_query %}
{% block customCSS %}
<style>
/* UI Variables for the KAAT-S Theme */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Enhanced Card Styling */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
transition: transform 0.2s, box-shadow 0.2s;
background-color: white;
}
.card:hover {
/* Remove hover effect from the main list card to avoid visual noise */
transform: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
.card-header {
font-weight: 600;
padding: 1.25rem;
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa; /* Light header background */
}
/* Main Action Button Style (Teal Theme) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary Action Buttons (used in table) */
.btn-group .btn-outline-primary {
color: var(--kaauh-teal);
border-color: var(--kaauh-border);
background-color: white;
}
.btn-group .btn-outline-primary:hover {
background-color: var(--kaauh-teal);
color: white;
border-color: var(--kaauh-teal);
}
/* Table Styling */
.table thead th {
color: var(--kaauh-teal-dark);
font-weight: 600;
border-bottom: 2px solid var(--kaauh-teal); /* Highlight bottom border */
vertical-align: middle;
padding: 0.75rem 1rem;
font-size: 0.95rem;
}
.table tbody tr:hover {
background-color: #f3f9f9; /* Light teal hover for rows */
}
.table tbody td {
vertical-align: middle;
padding: 0.75rem 1rem;
}
/* Badge Styling */
.badge {
font-weight: 600;
padding: 0.4em 0.7em;
border-radius: 0.3rem;
}
/* Status Badge Mapping */
.table .bg-primary { background-color: var(--kaauh-teal) !important; color: white !important;} /* Job Title Badge */
.table .bg-success { background-color: #28a745 !important; } /* Applied: Yes */
.table .bg-warning { background-color: #ffc107 !important; } /* Applied: No */
/* 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;
}
/* Icon Sizing in Buttons */
.btn-group .btn-sm svg {
width: 1rem;
height: 1rem;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="card shadow-sm">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0" style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-users me-2"></i>
{% trans "Candidate Profiles" %}
</h1>
<div class="d-flex gap-3 align-items-center">
{# The search form structure should be updated to align with the job_list style if possible #}
{% include "includes/search_form.html" with search_query=search_query %}
{% if user.is_staff %}
<a href="{% url 'candidate_create' %}" class="btn btn-main-action">
<i class="fas fa-plus"></i>
{% trans "Add New Candidate" %}
</a>
{% endif %}
</div>
</div>
</div>
{% if candidates %}
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th scope="col" style="width: 20%;">{% trans "Name" %}</th>
<th scope="col" style="width: 20%;">{% trans "Email" %}</th>
<th scope="col" style="width: 15%;">{% trans "Phone" %}</th>
<th scope="col" style="width: 15%;">{% trans "Job" %}</th>
<th scope="col" style="width: 10%;">{% trans "Applied" %}</th>
<th scope="col" style="width: 10%;">{% trans "Created" %}</th>
<th scope="col" style="width: 10%;" class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for candidate in candidates %}
<tr>
<td><strong>{{ candidate.name }}</strong></td>
<td>{{ candidate.email }}</td>
<td>{{ candidate.phone }}</td>
<td> <span class="badge bg-primary">{{ candidate.job.title }}</span></td>
<td>
<span class="badge {% if candidate.applied %}bg-success{% else %}bg-warning{% endif %}">
{{ candidate.applied|yesno:"Yes,No" }}
</span>
</td>
<td>{{ candidate.created_at|date:"M d, Y" }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group">
<a href="{% url 'candidate_detail' candidate.slug %}" class="btn btn-outline-primary" title="{% trans 'View' %}">
<i class="fas fa-eye"></i>
</a>
{% if user.is_staff %}
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-primary" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<button type="button" class="btn btn-outline-danger" title="{% trans 'Delete' %}"
data-bs-toggle="deleteModal"
data-delete-url="{% url 'candidate_delete' candidate.slug %}"
data-item-name="{{ candidate.name }}">
<i class="fas fa-trash-alt"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if is_paginated %}
<div class="card-footer bg-white border-top">
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
{% endif %}
{# Simplified pagination logic for theme consistency #}
{% 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:'-2' and num < page_obj.number|add:'2' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% 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 %}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-users fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
<h4 class="text-muted">{% trans "No candidates found." %}</h4>
<p class="text-muted">{% trans "Start by adding a new profile or adjusting your search filters." %}</p>
{% if user.is_staff %}
<a href="{% url 'candidate_create' %}" class="btn btn-primary">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4v16m8-8H4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Add New Candidate" %}
<a href="{% url 'candidate_create' %}" class="btn btn-main-action mt-3">
<i class="fas fa-plus me-1"></i>
{% trans "Add Your First Candidate" %}
</a>
{% endif %}
</div>
</div>
</div>
{% if candidates %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">{% trans "Name" %}</th>
<th scope="col">{% trans "Email" %}</th>
<th scope="col">{% trans "Phone" %}</th>
<th scope="col">{% trans "Job" %}</th>
<th scope="col">{% trans "Applied" %}</th>
<th scope="col">{% trans "Created" %}</th>
<th scope="col" class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for candidate in candidates %}
<tr>
<td><strong>{{ candidate.name }}</strong></td>
<td>{{ candidate.email }}</td>
<td>{{ candidate.phone }}</td>
<td> <span class="badge bg-primary">{{ candidate.job.title }}</span></td>
<td>
<span class="badge {% if candidate.applied %}bg-success{% else %}bg-warning{% endif %}">
{{ candidate.applied|yesno:"Yes,No" }}
</span>
</td>
<td>{{ candidate.created_at|date:"M d, Y" }}</td>
<td class="text-center">
<div class="btn-group" role="group">
<a href="{% url 'candidate_detail' candidate.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'View' %}">
{% include "icons/view.html" %}
</a>
{% if user.is_staff %}
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Edit' %}">
{% include "icons/edit.html" %}
</a>
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}"
data-bs-toggle="deleteModal"
data-delete-url="{% url 'candidate_delete' candidate.slug %}"
data-item-name="{{ candidate.name }}">
{% include "icons/delete.html" %}
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if is_paginated %}
<nav aria-label="Page navigation">
<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 }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</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 }}">{{ 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 }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="text-center py-4">
<p class="text-muted">{% trans "No candidates found." %}</p>
{% if user.is_staff %}
<a href="{% url 'candidate_create' %}" class="btn btn-primary">
{% trans "Add Your First Candidate" %}
</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -3,46 +3,139 @@
{% block title %}Update Candidate - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES (FROM JOB DETAIL) */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Colored Header Card */
.candidate-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.candidate-header-card h1 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
.heroicon {
width: 1.25rem;
height: 1.25rem;
vertical-align: text-bottom;
stroke: currentColor;
margin-right: 0.5rem;
}
</style>
{% endblock %}
{% block content %}
<!-- Header Card -->
<div class="card mb-4">
<div class="card-header">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<h1 class="h3 mb-2">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
Update Candidate: {{ object.name }}
</h1>
<p class="text-muted mb-0">{% trans "Edit candidate information and details" %}</p>
</div>
<div class="d-flex gap-2">
<a href="{% url 'candidate_list' %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Back to List' %}">
{% include "icons/back.html" %}
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
</a>
<div class="container-fluid py-4">
<div class="card mb-4">
<div class="candidate-header-card">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<div class="flex-grow-1">
<h1 class="h3 mb-1">
<i class="fas fa-user-edit"></i>
{% trans "Update Candidate:" %} {{ object.name }}
</h1>
<p class="text-white opacity-75 mb-0">{% trans "Edit candidate information and details" %}</p>
</div>
<div class="d-flex gap-2 mt-1">
<a href="{% url 'candidate_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
<i class="fas fa-arrow-left"></i>
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
</a>
{% if object.slug %}
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-outline-light btn-sm" title="{% trans 'View Candidate' %}">
<i class="fas fa-eye"></i>
<span class="d-none d-sm-inline">{% trans "View" %}</span>
</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Form Card -->
<div class="card">
<div class="card-header">
<h2 class="h5 mb-0">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Candidate Information" %}
</h2>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Update" %}</button>
</form>
<div class="card shadow-sm">
<div class="card-header bg-white border-bottom">
<h2 class="h5 mb-0 text-primary">
<i class="fas fa-file-alt me-1"></i>
{% trans "Candidate Form" %}
</h2>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{# Use Crispy Forms to render fields. The two-column layout is applied to the main form content #}
<div class="row g-4">
{% for field in form %}
<div class="col-md-6">
{{ field|as_crispy_field }}
</div>
{% endfor %}
</div>
<hr class="mt-4 mb-4">
<button class="btn btn-main-action" type="submit">
<i class="fas fa-save me-1"></i>
{% trans "Update Candidate" %}
</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,57 +1,156 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Dashboard - {{ block.super }}{% endblock %}
{% block title %}Recruitment Dashboard - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* UI Variables for the KAAT-S Theme (Teal/Consistent Look) */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-light: #0093a3;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--color-success: #28a745;
--color-info: #17a2b8;
}
/* General Card Styling */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
transition: transform 0.2s, box-shadow 0.2s;
background-color: white;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
}
.card-header {
font-weight: 600;
padding: 1.25rem;
border-bottom: 1px solid var(--kaauh-border);
background-color: #f8f9fa;
}
/* Stats Grid Layout - Six columns for better detail display */
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1.5rem;
margin-bottom: 3rem;
}
/* Stat Card Specific Styling */
.stat-value {
font-size: 2.8rem;
text-align: center;
color: var(--kaauh-teal-dark);
font-weight: 700;
padding: 1rem 1rem 0.5rem;
}
.stat-caption {
font-size: 0.9rem;
text-align: center;
color: #6c757d;
padding-bottom: 1rem;
}
/* Header Styling */
.card-header h3, .card-header h2 {
display: flex;
align-items: center;
color: var(--kaauh-primary-text);
font-size: 1.25rem;
font-weight: 600;
margin: 0;
}
.stat-icon {
color: var(--kaauh-teal);
font-size: 1.75rem;
margin-right: 0.75rem;
}
/* Chart Container */
.chart-container {
padding: 2rem;
}
</style>
{% endblock %}
{% block content %}
<div class="stats" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem; margin-bottom: 3rem;">
<div class="card">
<div class="card-header">
<h3 style="display: flex; align-items: center; color: var(--primary-color);">
{% include "icons/jobs.html" %}
Total Jobs
</h3>
</div>
<div style="font-size: 2.5rem; text-align: center; color: var(--primary-color); font-weight: bold;">
{{ total_jobs }}
</div>
</div>
<div class="card">
<div class="card-header">
<h3 style="display: flex; align-items: center; color: var(--primary-color);">
{% include "icons/users.html" %}
Total Candidates
</h3>
</div>
<div style="font-size: 2.5rem; text-align: center; color: var(--primary-color); font-weight: bold;">
{{ total_candidates }}
</div>
</div>
<div class="card">
<div class="card-header">
<h3 style="display: flex; align-items: center; color: var(--primary-color);">
{% include "icons/meeting.html" %}
Avg. Applications per Job
</h3>
</div>
<div style="font-size: 2.5rem; text-align: center; color: var(--primary-color); font-weight: bold;">
{{ average_applications|floatformat:1 }}
</div>
</div>
</div>
<div class="container-fluid py-4">
<h1 class="mb-4" style="color: var(--kaauh-teal-dark); font-weight: 700;">Recruitment Overview 🚀</h1>
<div class="card">
<div class="card-header">
<h2 style="display: flex; align-items: center; color: var(--primary-color);">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="width: 2rem; height: 2rem; margin-right: 1rem;">
<path d="M3 7v10c0 .5 .4 .9.9 .9h16.2c.4 0 .8 -.4.8 -.9V7c0 -.5 -.4 -.9 -.9 -.9H3.9c-.5 0 -.9 .4 -.9 .9z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M3 7h16.2c.5 0 .9 .4.9 .9v8.2c0 .5 -.4 .9 -.9 .9H3.9a.9 .9 0 0 1 -.9 -.9V7.9c0 -.5 .4 -.9.9 -.9z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
Applications per Job
</h2>
<div class="stats">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-briefcase stat-icon"></i> Total Jobs</h3>
</div>
<div class="stat-value">{{ total_jobs }}</div>
<div class="stat-caption">Active & Drafted Positions</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users stat-icon"></i> Total Candidates</h3>
</div>
<div class="stat-value">{{ total_candidates }}</div>
<div class="stat-caption">All Profiles in ATS</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-chart-line stat-icon"></i> Avg. Apps per Job</h3>
</div>
<div class="stat-value">{{ average_applications|floatformat:1 }}</div>
<div class="stat-caption">Key Efficiency Metric</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-arrow-alt-circle-up stat-icon" style="color: var(--color-success);"></i> Active Listings</h3>
</div>
<div class="stat-value">22</div>
<div class="stat-caption">Jobs currently open for application</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-clock stat-icon" style="color: var(--color-info);"></i> Time to Hire</h3>
</div>
<div class="stat-value">35d</div>
<div class="stat-caption">Average days from apply to offer</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-handshake stat-icon" style="color: var(--kaauh-teal-light);"></i> Offer Acceptance</h3>
</div>
<div class="stat-value">85%</div>
<div class="stat-caption">Successful offers vs. Total offers</div>
</div>
</div>
<div style="padding: 2rem;">
<canvas id="applicationsChart" style="max-height: 400px;"></canvas>
<div class="card shadow-lg">
<div class="card-header">
<h2 class="d-flex align-items-center mb-0">
<i class="fas fa-chart-bar stat-icon"></i>
Applications Volume by Job
</h2>
</div>
<div class="chart-container">
<canvas id="applicationsChart"></canvas>
</div>
</div>
</div>
@ -65,8 +164,8 @@
datasets: [{
label: 'Applications',
data: {{ job_app_counts|safe }},
backgroundColor: 'rgba(27, 131, 84, 0.8)', // Green theme
borderColor: 'rgba(27, 131, 84, 1)',
backgroundColor: ' #00636e', // Green theme
borderColor: ' #004a53',
borderWidth: 1,
barThickness: 50
}]
@ -102,4 +201,5 @@
}
});
</script>
{% endblock %}
{% endblock %}

View File

@ -3,26 +3,162 @@
{% block title %}Create Training Material - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles */
.btn-secondary, .btn-outline-secondary {
background-color: #f8f9fa;
color: var(--kaauh-teal-dark);
border: 1px solid var(--kaauh-teal);
font-weight: 500;
}
.btn-secondary:hover, .btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Colored Header Card */
.list-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.list-header-card h1 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
.heroicon {
width: 1.25rem;
height: 1.25rem;
vertical-align: text-bottom;
stroke: currentColor;
margin-right: 0.5rem;
}
/* ================================================= */
/* FIX: CRISPY FORMS ALIGNMENT/SPACING */
/* ================================================= */
/* Crispy forms wraps each field in a div with .mb-3.
This rule ensures the label and input are vertically aligned
and have clean spacing when inside a grid column. */
.card-body .form-group .form-label,
.card-body .form-group .input-group-text {
/* Align text within label if necessary (usually unnecessary) */
vertical-align: middle;
}
/* Ensures the form elements themselves are cleanly aligned */
.card-body .form-group {
/* Ensures consistent bottom spacing for all form groups */
margin-bottom: 1.2rem;
}
/* Override for fields inside the grid, using Bootstrap 5's default
g-x for horizontal and g-y for vertical padding from the row */
.card-body .row .col-md-6 .form-group {
margin-bottom: 0 !important; /* Let the row's g-4 handle vertical spacing */
}
</style>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4v16m8-8H4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Create Training Material" %}
</h1>
<a href="{% url 'training_list' %}" class="btn btn-secondary">
{% trans "Back to List" %}
</a>
<div class="container-fluid py-4">
<div class="card mb-4">
<div class="list-header-card">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<div class="flex-grow-1">
<h1 class="h3 mb-1">
<i class="fas fa-file-upload"></i>
{% trans "Create New Training Material" %}
</h1>
<p class="text-white opacity-75 mb-0">{% trans "Upload a new document or guide for your team." %}</p>
</div>
<div class="d-flex gap-2 mt-1">
<a href="{% url 'training_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
<i class="fas fa-arrow-left"></i>
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
</a>
</div>
</div>
</div>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
</form>
<div class="card shadow-sm">
<div class="card-header bg-white border-bottom">
<h2 class="h5 mb-0 text-primary">
<i class="fas fa-book me-1"></i>
{% trans "Material Details" %}
</h2>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{# Use a row structure with g-3 for slightly tighter spacing #}
<div class="row g-3">
{# Iterate through all form fields #}
{% for field in form %}
<div class="col-md-{% if field.field.widget.input_type == 'textarea' or field.field.widget.input_type == 'file' %}12{% else %}6{% endif %}">
{# Crispy field rendering #}
{{ field|as_crispy_field }}
</div>
{% endfor %}
</div>
<hr class="mt-4 mb-4">
<button class="btn btn-main-action" type="submit">
<i class="fas fa-save me-1"></i>
{% trans "Create Material" %}
</button>
</form>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -3,113 +3,233 @@
{% block title %}{% trans "Training Materials" %} - {{ block.super }}{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Training Materials" %}
</h1>
<div class="d-flex gap-2 align-items-center">
<!-- Search Form -->
{% include "includes/search_form.html" with search_query=search_query %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES (FROM JOB DETAIL) */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style (Applied to .btn-primary) */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none; /* Ensure links look like buttons */
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles for Table Actions */
.btn-outline-primary {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
}
.btn-outline-primary:hover {
background-color: var(--kaauh-teal);
color: white;
}
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Danger button remains standard for deletion */
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Colored Header Card */
.list-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.list-header-card h1 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
.heroicon {
width: 1.25rem;
height: 1.25rem;
vertical-align: text-bottom;
stroke: currentColor;
margin-right: 0.5rem;
}
/* Table Styling */
.table thead th {
background-color: #f8f9fa;
color: var(--kaauh-primary-text);
font-weight: 600;
border-bottom: 2px solid var(--kaauh-border);
}
.table tbody tr:hover {
background-color: #f0f8ff; /* Light hover color */
}
.btn-group .btn-sm {
padding: 0.35rem 0.6rem;
}
/* Pagination Styling */
.pagination .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;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="card shadow-sm">
<div class="list-header-card">
<div class="d-flex justify-content-between align-items-center flex-wrap">
<h1 class="h3 mb-0">
<i class="fas fa-graduation-cap me-2"></i>
{% trans "Training Materials" %}
</h1>
<div class="d-flex gap-3 align-items-center mt-2 mt-md-0">
<div class="order-3 order-md-1">
{% include "includes/search_form.html" with search_query=search_query %}
</div>
{% if user.is_authenticated %}
<a href="{% url 'training_create' %}" class="btn btn-main-action btn-sm order-1 order-md-2" title="{% trans 'Add New Material' %}">
<i class="fas fa-plus"></i>
<span class="d-none d-sm-inline">{% trans "Add New Material" %}</span>
</a>
{% endif %}
</div>
</div>
</div>
{% if materials %}
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead>
<tr>
<th scope="col">{% trans "Title" %}</th>
<th scope="col">{% trans "Created By" %}</th>
<th scope="col">{% trans "Created" %}</th>
<th scope="col" class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for material in materials %}
<tr>
<td><strong class="text-primary">{{ material.title }}</strong></td>
<td>{{ material.created_by.username|default:"Anonymous" }}</td>
<td>{{ material.created_at|date:"M d, Y" }}</td>
<td class="text-center">
<div class="btn-group btn-group-sm" role="group">
<a href="{% url 'training_detail' material.pk %}" class="btn btn-outline-primary" title="{% trans 'View' %}">
<i class="fas fa-eye"></i>
</a>
{% if user.is_authenticated and material.created_by == user %}
<a href="{% url 'training_update' material.pk %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i>
</a>
<button type="button" class="btn btn-outline-danger" title="{% trans 'Delete' %}"
data-bs-toggle="deleteModal"
data-delete-url="{% url 'training_delete' material.pk %}"
data-item-name="{{ material.title }}">
<i class="fas fa-trash-alt"></i>
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if is_paginated %}
<div class="card-footer bg-light border-top">
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center mb-0">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</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 }}">{{ 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 }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
{% endif %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-book-open fa-3x text-muted mb-3"></i>
<h5 class="mb-3">{% trans "No training materials found." %}</h5>
<p class="text-muted mb-4">{% trans "It looks like there are no materials yet. Start by adding one!" %}</p>
{% if user.is_authenticated %}
<a href="{% url 'training_create' %}" class="btn btn-primary">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4v16m8-8H4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{% trans "Add New Material" %}
<a href="{% url 'training_create' %}" class="btn btn-main-action">
<i class="fas fa-plus me-1"></i>
{% trans "Create Your First Material" %}
</a>
{% endif %}
</div>
</div>
</div>
{% if materials %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">{% trans "Title" %}</th>
<th scope="col">{% trans "Created By" %}</th>
<th scope="col">{% trans "Created" %}</th>
<th scope="col" class="text-center">{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for material in materials %}
<tr>
<td><strong>{{ material.title }}</strong></td>
<td>{{ material.created_by.username|default:"Anonymous" }}</td>
<td>{{ material.created_at|date:"M d, Y" }}</td>
<td class="text-center">
<div class="btn-group" role="group">
<a href="{% url 'training_detail' material.pk %}" class="btn btn-outline-primary btn-sm" title="{% trans 'View' %}">
{% include "icons/view.html" %}
</a>
{% if user.is_authenticated and material.created_by == user %}
<a href="{% url 'training_update' material.pk %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}">
{% include "icons/edit.html" %}
</a>
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}"
data-bs-toggle="deleteModal"
data-delete-url="{% url 'training_delete' material.pk %}"
data-item-name="{{ material.title }}">
{% include "icons/delete.html" %}
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if is_paginated %}
<nav aria-label="Page navigation">
<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 }}" aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</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 }}">{{ 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 }}" aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="text-center py-4">
<p class="text-muted">{% trans "No training materials found." %}</p>
{% if user.is_authenticated %}
<a href="{% url 'training_create' %}" class="btn btn-primary">
{% trans "Create Your First Material" %}
</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% endblock %}

View File

@ -1,46 +1,191 @@
{% extends "base.html" %}
{% load static %}
{% load static i18n crispy_forms_tags %}
{% block title %}Update Training Material - {{ block.super }}{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h1>
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
Update Training Material: {{ object.title }}
</h1>
<a href="{% url 'training_list' %}" class="btn btn-secondary">Back to List</a>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-row">
<label class="form-label">Title</label>
{{ form.title }}
</div>
<div class="form-row">
<label class="form-label">Content</label>
{{ form.content }}
</div>
<div class="form-row">
<label class="form-label">Video Link</label>
{{ form.video_link }}
</div>
<div class="form-row">
<label class="form-label">File</label>
{{ form.file }}
</div>
<div class="form-row">
<button type="submit" class="btn btn-primary">
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
Update Material
</button>
<a href="{% url 'training_delete' object.pk %}" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this material?')">Delete</a>
</div>
</form>
</div>
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles */
.btn-secondary, .btn-outline-secondary {
background-color: #f8f9fa;
color: var(--kaauh-teal-dark);
border: 1px solid var(--kaauh-teal);
font-weight: 500;
}
.btn-secondary:hover, .btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Colored Header Card */
.list-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.list-header-card h1 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
.heroicon {
width: 1.25rem;
height: 1.25rem;
vertical-align: text-bottom;
stroke: currentColor;
margin-right: 0.5rem;
}
/* Form Fixes for consistency with manual rendering */
.form-label {
font-weight: 600;
color: var(--kaauh-primary-text);
margin-bottom: 0.3rem; /* Small space under label */
display: block; /* Ensure full width for label */
}
/* Ensure form fields use Bootstrap's standard look */
.form-control {
border-radius: 0.5rem;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="card mb-4">
<div class="list-header-card">
<div class="d-flex justify-content-between align-items-start flex-wrap">
<div class="flex-grow-1">
<h1 class="h3 mb-1">
<i class="fas fa-edit"></i>
{% trans "Update Training Material:" %} {{ object.title }}
</h1>
<p class="text-white opacity-75 mb-0">{% trans "Edit the details of this training document or guide." %}</p>
</div>
<div class="d-flex gap-2 mt-1">
<a href="{% url 'training_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
<i class="fas fa-arrow-left"></i>
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
</a>
{% if object.pk %}
<a href="{% url 'training_detail' object.pk %}" class="btn btn-outline-light btn-sm" title="{% trans 'View Material' %}">
<i class="fas fa-eye"></i>
<span class="d-none d-sm-inline">{% trans "View" %}</span>
</a>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card shadow-sm">
<div class="card-header bg-white border-bottom">
<h2 class="h5 mb-0 text-primary">
<i class="fas fa-file-alt me-1"></i>
{% trans "Material Details" %}
</h2>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="card-body">
<div class="row g-4">
{# Title and Video Link on a single row #}
<div class="col-md-6">
<div class="mb-0">
<label for="{{ form.title.id_for_label }}" class="form-label">{% trans "Title" %}</label>
{{ form.title|attr:"class:form-control" }}
</div>
</div>
<div class="col-md-6">
<div class="mb-0">
<label for="{{ form.video_link.id_for_label }}" class="form-label">{% trans "Video Link" %}</label>
{{ form.video_link|attr:"class:form-control" }}
</div>
</div>
{# Content (Full width) #}
<div class="col-12">
<div class="mb-0">
<label for="{{ form.content.id_for_label }}" class="form-label">{% trans "Content" %}</label>
{{ form.content|attr:"class:form-control"|attr:"rows:6" }}
</div>
</div>
{# File Upload (Full width as it's often complex) #}
<div class="col-12">
<div class="mb-0">
<label for="{{ form.file.id_for_label }}" class="form-label">{% trans "File" %}</label>
{{ form.file }}
{% if form.file.help_text %}
<div class="form-text">{{ form.file.help_text }}</div>
{% endif %}
</div>
</div>
</div>
</div>
<div class="card-footer bg-light border-top">
<div class="d-flex justify-content-end gap-3">
<button type="submit" class="btn btn-main-action">
<i class="fas fa-save me-1"></i>
{% trans "Update Material" %}
</button>
<a href="{% url 'training_delete' object.pk %}" class="btn btn-danger" onclick="return confirm('{% trans "Are you sure you want to delete this material?" %}')">
<i class="fas fa-trash-alt me-1"></i>
{% trans "Delete" %}
</a>
</div>
</div>
</form>
</div>
</div>
{% endblock %}