ui changes for theme
This commit is contained in:
parent
221380645d
commit
5856f1e0cb
59
.gitignore
vendored
Normal file
59
.gitignore
vendored
Normal 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
|
||||
Binary file not shown.
@ -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/'
|
||||
|
||||
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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">
|
||||
© {% now "Y" %} King Abdullah Academic University Hospital (KAAUH).<br>
|
||||
<small>All rights reserved.</small>
|
||||
© {% 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
@ -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 %}
|
||||
@ -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>
|
||||
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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">«</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">»</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">«</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">»</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 %}
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
@ -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">«</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">»</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">«</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">»</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 %}
|
||||
@ -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 %}
|
||||
Loading…
x
Reference in New Issue
Block a user