947 lines
46 KiB
HTML
947 lines
46 KiB
HTML
{% load static i18n %}
|
|
{% load logo_tags %}
|
|
{% get_current_language as LANGUAGE_CODE %}
|
|
|
|
<!DOCTYPE html>
|
|
<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, maximum-scale=5.0, user-scalable=yes">
|
|
<meta name="mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
<title>{% block title %}{% trans 'University ATS' %}{% endblock %}</title>
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
'temple-red': '#9d2235',
|
|
'temple-dark': '#1a1a1a',
|
|
'temple-cream': '#f8f7f2',
|
|
'dashboard-blue': '#4e73df',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
|
|
|
|
body {
|
|
font-family: 'Inter', sans-serif;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
/* Smooth scrolling */
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
/* Custom scrollbar - desktop only */
|
|
@media (min-width: 1024px) {
|
|
.sidebar-scroll::-webkit-scrollbar { width: 4px; }
|
|
.sidebar-scroll::-webkit-scrollbar-thumb {
|
|
background: #333;
|
|
border-radius: 10px;
|
|
}
|
|
}
|
|
|
|
/* Touch-friendly tap highlight */
|
|
* {
|
|
-webkit-tap-highlight-color: rgba(157, 34, 53, 0.1);
|
|
}
|
|
|
|
/* Improved touch targets - minimum 44x44px */
|
|
button, a, .touch-target {
|
|
min-height: 44px;
|
|
min-width: 44px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* Prevent text selection on buttons */
|
|
button {
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
/* Smooth transitions */
|
|
.smooth-transition {
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
/* Mobile-optimized sidebar */
|
|
#sidebar {
|
|
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
/* Backdrop blur support */
|
|
@supports ((-webkit-backdrop-filter: blur(10px)) or (backdrop-filter: blur(10px))) {
|
|
.backdrop-blur-custom {
|
|
-webkit-backdrop-filter: blur(10px);
|
|
backdrop-filter: blur(10px);
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
}
|
|
}
|
|
|
|
/* Safe area padding for notched devices */
|
|
@supports (padding: env(dQrgH6E6C$$!@9safe-area-inset-bottom)) {
|
|
.safe-area-padding-bottom {
|
|
padding-bottom: calc(1.5rem + env(safe-area-inset-bottom));
|
|
}
|
|
.safe-area-padding-top {
|
|
padding-top: calc(0.75rem + env(safe-area-inset-top));
|
|
}
|
|
}
|
|
|
|
/* Sticky header optimization */
|
|
.sticky-header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 30;
|
|
-webkit-backface-visibility: hidden;
|
|
backface-visibility: hidden;
|
|
}
|
|
|
|
/* Alert animation */
|
|
.alert {
|
|
animation: slideInDown 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes slideInDown {
|
|
from {
|
|
transform: translateY(-100%);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* Mobile navigation optimization */
|
|
@media (max-width: 1023px) {
|
|
.nav-link {
|
|
padding: 0.875rem 0.75rem;
|
|
font-size: 0.9375rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.nav-icon {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
flex-shrink: 0;
|
|
margin-right: 0.75rem;
|
|
}
|
|
|
|
.nav-text {
|
|
line-height: 1.25rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Ensure flex items align properly */
|
|
.sidebar-nav a {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0;
|
|
}
|
|
|
|
/* Larger touch targets on mobile */
|
|
.sidebar-scroll {
|
|
padding-bottom: 2rem;
|
|
}
|
|
}
|
|
|
|
/* Desktop optimizations */
|
|
@media (min-width: 1024px) {
|
|
/* Smooth sidebar transitions */
|
|
#main-content {
|
|
transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
#sidebar {
|
|
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
}
|
|
|
|
/* Fix for iOS momentum scrolling */
|
|
.momentum-scroll {
|
|
-webkit-overflow-scrolling: touch;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* Prevent zoom on input focus (iOS) */
|
|
input[type="text"],
|
|
input[type="email"],
|
|
input[type="password"],
|
|
input[type="search"],
|
|
select,
|
|
textarea {
|
|
font-size: 16px;
|
|
}
|
|
|
|
/* Dropdown menu improvements */
|
|
.dropdown-menu {
|
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
/* Progress bar glow effect */
|
|
.progress-bar-glow {
|
|
box-shadow: 0 0 8px rgba(255,255,255,0.4);
|
|
}
|
|
|
|
/* Optimized badge positioning */
|
|
.nav-badge {
|
|
font-size: 0.625rem;
|
|
line-height: 1;
|
|
padding: 0.125rem 0.375rem;
|
|
}
|
|
|
|
/* Better visibility for active states */
|
|
.nav-link.active {
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Tooltip for collapsed sidebar */
|
|
.sidebar-tooltip {
|
|
position: fixed;
|
|
background-color: #1a1a1a;
|
|
color: white;
|
|
padding: 0.5rem 0.75rem;
|
|
border-radius: 0.5rem;
|
|
font-size: 0.875rem;
|
|
white-space: nowrap;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
z-index: 9999;
|
|
pointer-events: none;
|
|
max-width: 200px;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: opacity 0.2s, visibility 0.2s;
|
|
}
|
|
|
|
.sidebar-tooltip.visible {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
}
|
|
|
|
/* Mobile-friendly footer */
|
|
@media (max-width: 640px) {
|
|
footer {
|
|
font-size: 0.6875rem;
|
|
padding: 1rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'image/favicon/apple-touch-icon.png'%}">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'image/favicon/favicon-32x32.png'%}">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'image/favicon/favicon-16x16.png'%}">
|
|
<link rel="manifest" href="{% static 'image/favicon/site.webmanifest'%}">
|
|
|
|
{% block customCSS %}{% endblock %}
|
|
</head>
|
|
<body class="bg-temple-cream text-gray-800 flex min-h-screen overflow-x-hidden">
|
|
|
|
<!-- Tooltip Element -->
|
|
<div id="sidebar-tooltip" class="sidebar-tooltip"></div>
|
|
|
|
<!-- Mobile Sidebar Backdrop -->
|
|
<div id="sidebar-backdrop"
|
|
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-40 hidden lg:hidden backdrop-blur-custom"
|
|
onclick="closeMobileSidebar()"
|
|
role="button"
|
|
aria-label="{% trans 'Close sidebar' %}">
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<aside id="sidebar"
|
|
class="fixed inset-y-0 left-0 z-50 bg-temple-dark text-gray-400 transform -translate-x-full lg:translate-x-0 w-64 lg:w-64 border-r border-gray-800 smooth-transition"
|
|
role="navigation"
|
|
aria-label="{% trans 'Main navigation' %}">
|
|
<div class="flex flex-col h-full">
|
|
<!-- Sidebar Header -->
|
|
<div class="p-4 sm:p-6 flex items-center gap-3 text-white sidebar-header safe-area-padding-top">
|
|
<div class="sidebar-expanded-logo">
|
|
{% logo_tenhal_ats height="42" %}
|
|
</div>
|
|
<div class="sidebar-collapsed-logo hidden">
|
|
{% logo_icon_only height="40" %}
|
|
</div>
|
|
<button onclick="closeMobileSidebar()"
|
|
class="lg:hidden ml-auto text-gray-400 hover:text-white transition p-2 -mr-2"
|
|
aria-label="{% trans 'Close menu' %}">
|
|
<i data-lucide="x" class="w-6 h-6"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Navigation Menu -->
|
|
<nav class="flex-1 overflow-y-auto sidebar-scroll momentum-scroll px-3 sm:px-4 pb-4 sidebar-nav">
|
|
<div class="text-[10px] uppercase tracking-widest text-gray-600 font-bold mb-3 sm:mb-4 px-2 section-header">
|
|
{% trans "Main" %}
|
|
</div>
|
|
<ul class="space-y-1">
|
|
<li>
|
|
<a href="{% url 'dashboard' %}"
|
|
data-tooltip="{% trans 'Dashboard' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'dashboard' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'dashboard' %}page{% endif %}">
|
|
<i data-lucide="layout-grid" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if request.resolver_match.url_name == 'dashboard' %}text-temple-red{% endif %}"></i>
|
|
<span class="font-medium nav-text">{% trans "Dashboard" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'job_list' %}"
|
|
data-tooltip="{% trans 'Jobs' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'job_list' or request.resolver_match.url_name == 'job_create' or request.resolver_match.url_name == 'job_detail' or request.resolver_match.url_name == 'job_update' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'job_list' or request.resolver_match.url_name == 'job_create' or request.resolver_match.url_name == 'job_detail' or request.resolver_match.url_name == 'job_update' %}page{% endif %}">
|
|
<i data-lucide="briefcase" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if request.resolver_match.url_name == 'job_list' or request.resolver_match.url_name == 'job_create' or request.resolver_match.url_name == 'job_detail' or request.resolver_match.url_name == 'job_update' %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Jobs" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'job_bank' %}"
|
|
data-tooltip="{% trans 'Job Bank' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'job_bank' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'job_bank' %}page{% endif %}">
|
|
<i data-lucide="building-2" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if request.resolver_match.url_name == 'job_bank' %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Job Bank" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'application_list' %}"
|
|
data-tooltip="{% trans 'Applications' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'application_list' or request.resolver_match.url_name|slice:':11' == 'application_' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if 'application_' in request.resolver_match.url_name %}page{% endif %}">
|
|
<i data-lucide="users" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if 'application_' in request.resolver_match.url_name %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Applications" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'person_list' %}"
|
|
data-tooltip="{% trans 'Applicants' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'person_list' or request.resolver_match.url_name == 'person_detail' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name in 'person_list,person_detail' %}page{% endif %}">
|
|
<i data-lucide="user" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if request.resolver_match.url_name in 'person_list,person_detail' %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Applicants" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'agency_list' %}"
|
|
data-tooltip="{% trans 'Agencies' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'agency_list' or request.resolver_match.url_name == 'agency_detail' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name in 'agency_list,agency_detail' %}page{% endif %}">
|
|
<i data-lucide="building" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if request.resolver_match.url_name in 'agency_list,agency_detail' %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Agencies" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'interview_list' %}"
|
|
data-tooltip="{% trans 'Interviews' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'interview_list' or request.resolver_match.url_name|slice:':9' == 'interview_' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if 'interview_' in request.resolver_match.url_name %}page{% endif %}">
|
|
<i data-lucide="calendar" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if 'interview_' in request.resolver_match.url_name %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Interviews" %}</span>
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'message_list' %}"
|
|
data-tooltip="{% trans 'Messages' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link relative {% if request.resolver_match.url_name == 'message_list' or request.resolver_match.url_name|slice:':8' == 'message_' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if 'message_' in request.resolver_match.url_name %}page{% endif %}">
|
|
<i data-lucide="mail" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if 'message_' in request.resolver_match.url_name %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Messages" %}</span>
|
|
{% if request.user.get_unread_message_count > 0 %}
|
|
<span class="ml-auto bg-temple-red text-white px-2 py-0.5 rounded-full text-xs font-medium nav-badge">
|
|
{{ request.user.get_unread_message_count }}
|
|
</span>
|
|
{% endif %}
|
|
</a>
|
|
</li>
|
|
<li>
|
|
<a href="{% url 'kaauh_career' %}"
|
|
data-tooltip="{% trans 'Career Page' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'kaauh_career' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'kaauh_career' %}page{% endif %}">
|
|
<i data-lucide="globe" class="w-5 h-5 nav-icon mr-3 shrink-0 {% if request.resolver_match.url_name == 'kaauh_career' %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Career Page" %}</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
|
|
{% if request.user.is_authenticated and request.user.is_superuser %}
|
|
<div class="text-[10px] uppercase tracking-widest text-gray-600 font-bold mt-6 sm:mt-8 mb-3 sm:mb-4 px-2 section-header">
|
|
{% trans "System" %}
|
|
</div>
|
|
<ul class="space-y-1">
|
|
<li>
|
|
<a href="{% url 'settings' %}"
|
|
data-tooltip="{% trans 'Settings' %}"
|
|
class="flex items-center px-3 py-2.5 rounded-md transition nav-link {% if request.resolver_match.url_name == 'settings' %}text-white bg-temple-red/10 border-l-4 border-temple-red rounded-l-none{% else %}hover:bg-gray-800 hover:text-white{% endif %}"
|
|
aria-current="{% if request.resolver_match.url_name == 'settings' %}page{% endif %}">
|
|
<i data-lucide="settings" class="w-4 h-4 nav-icon mr-3 shrink-0 {% if request.resolver_match.url_name == 'settings' %}text-temple-red{% endif %}"></i>
|
|
<span class="nav-text">{% trans "Settings" %}</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
{% endif %}
|
|
</nav>
|
|
|
|
<!-- Logout Button -->
|
|
{% if request.user.is_authenticated %}
|
|
<div class="p-3 sm:p-4 border-t border-gray-800 sidebar-footer">
|
|
<form method="post" action="{% url 'account_logout'%}">
|
|
{% csrf_token %}
|
|
<button type="submit"
|
|
class="w-full flex items-center justify-center gap-2 bg-gray-800 hover:bg-gray-700 text-white py-2.5 px-4 rounded-lg text-sm transition touch-target">
|
|
<i data-lucide="log-out" class="w-4 h-4 shrink-0"></i>
|
|
<span class="nav-text">{% trans "Sign Out" %}</span>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Powered By Footer -->
|
|
<div class="p-3 sm:p-4 border-t border-gray-800 text-center sidebar-footer safe-area-padding-bottom">
|
|
<a href="https://tenhal.sa/" class="text-decoration-none block" target="_blank" rel="noopener noreferrer">
|
|
<div class="text-[10px] text-gray-600 uppercase tracking-widest">
|
|
{% trans "POWERED BY" %} <span class="text-white font-bold">TENHAL</span>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Main Content -->
|
|
<main id="main-content"
|
|
class="flex-1 flex flex-col min-h-screen smooth-transition lg:ml-64">
|
|
|
|
<!-- Header -->
|
|
<header class="sticky-header bg-white border-b px-3 sm:px-4 lg:px-8 py-3 flex justify-between items-center shadow-sm safe-area-padding-top">
|
|
<div class="flex items-center flex-1 min-w-0">
|
|
<!-- Mobile Menu Toggle -->
|
|
<button id="menu-toggle"
|
|
class="lg:hidden mr-2 sm:mr-4 p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition touch-target shrink-0"
|
|
aria-label="{% trans 'Open menu' %}"
|
|
aria-expanded="false"
|
|
aria-controls="sidebar">
|
|
<i data-lucide="menu" class="w-5 h-5"></i>
|
|
</button>
|
|
|
|
<!-- Desktop Sidebar Toggle -->
|
|
<button id="sidebar-toggle"
|
|
class="hidden lg:flex mr-4 p-2 text-gray-600 hover:text-temple-red transition shrink-0"
|
|
title="{% trans 'Toggle Sidebar' %}"
|
|
aria-label="{% trans 'Toggle sidebar' %}">
|
|
<i data-lucide="panel-left" class="w-5 h-5"></i>
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
<!-- Header Actions -->
|
|
<div class="flex items-center gap-1 sm:gap-2 lg:gap-4 shrink-0">
|
|
<!-- Fullscreen Toggle (Desktop Only) -->
|
|
<button id="fullscreen-toggle"
|
|
class="hidden lg:flex p-2 text-gray-600 hover:text-temple-red transition touch-target"
|
|
title="{% trans 'Toggle Fullscreen' %}"
|
|
aria-label="{% trans 'Toggle fullscreen' %}">
|
|
<i data-lucide="maximize" class="w-5 h-5" id="fullscreen-icon"></i>
|
|
</button>
|
|
|
|
<!-- Messages Icon -->
|
|
<a href="{% url 'message_list' %}"
|
|
class="relative p-2 text-gray-500 hover:text-temple-red transition touch-target"
|
|
aria-label="{% trans 'Messages' %}{% if request.user.get_unread_message_count > 0 %} ({{ request.user.get_unread_message_count }} {% trans 'unread' %}){% endif %}">
|
|
<i data-lucide="mail" class="w-5 h-5"></i>
|
|
{% if request.user.get_unread_message_count > 0 %}
|
|
<span class="absolute top-1 right-1 bg-temple-red border-2 border-white w-2.5 h-2.5 rounded-full" aria-hidden="true"></span>
|
|
{% endif %}
|
|
</a>
|
|
|
|
<!-- Language Switcher -->
|
|
{% if LANGUAGE_CODE == 'en' %}
|
|
<form action="{% url 'set_language' %}" method="post" class="relative">
|
|
{% csrf_token %}
|
|
<input name="language" type="hidden" value="ar">
|
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
|
<button class="flex items-center gap-1 sm:gap-2 px-2 sm:px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm transition touch-target"
|
|
aria-label="{% trans 'Switch to Arabic' %}">
|
|
<span aria-hidden="true">🇸🇦</span>
|
|
<span class="hidden sm:inline">{% trans "العربية" %}</span>
|
|
</button>
|
|
</form>
|
|
{% else %}
|
|
<form action="{% url 'set_language' %}" method="post" class="relative">
|
|
{% csrf_token %}
|
|
<input name="language" type="hidden" value="en">
|
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
|
<button class="flex items-center gap-1 sm:gap-2 px-2 sm:px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-sm transition touch-target"
|
|
aria-label="Switch to English">
|
|
<span aria-hidden="true">🇺🇸</span>
|
|
<span class="hidden sm:inline">English</span>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
|
|
<!-- User Menu Dropdown -->
|
|
<div class="dropdown relative">
|
|
<button class="flex items-center gap-2 p-1 touch-target"
|
|
id="user-menu-button"
|
|
aria-haspopup="true"
|
|
aria-expanded="false"
|
|
aria-label="{% trans 'User menu' %}">
|
|
{% if user.profile_image %}
|
|
<img src="{{ user.profile_image.url }}"
|
|
alt="{{ user.get_full_name|default:user.username }}"
|
|
class="w-8 h-8 sm:w-9 sm:h-9 rounded-full border shadow-sm object-cover">
|
|
{% else %}
|
|
<div class="w-8 h-8 sm:w-9 sm:h-9 rounded-full bg-temple-red flex items-center justify-center text-white font-bold text-sm">
|
|
{{ user.username|first|upper }}
|
|
</div>
|
|
{% endif %}
|
|
</button>
|
|
|
|
{% if request.user.is_authenticated %}
|
|
<div class="hidden absolute right-0 mt-2 w-48 sm:w-56 bg-white rounded-lg shadow-lg border border-gray-100 py-2 z-50 dropdown-menu"
|
|
id="user-menu-dropdown"
|
|
role="menu"
|
|
aria-labelledby="user-menu-button">
|
|
<div class="px-4 py-2 border-b border-gray-100">
|
|
<p class="text-sm font-bold text-gray-800 truncate">
|
|
{{ user.get_full_name|default:user.username }}
|
|
</p>
|
|
<p class="text-xs text-gray-500 truncate">{{ user.email }}</p>
|
|
</div>
|
|
<a href="{% url 'user_detail' request.user.pk %}"
|
|
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-gray-100 transition"
|
|
role="menuitem">
|
|
{% trans "Profile" %}
|
|
</a>
|
|
<hr class="my-2 border-gray-100">
|
|
<form method="post" action="{% url 'account_logout'%}">
|
|
{% csrf_token %}
|
|
<button type="submit"
|
|
class="w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-gray-100 transition"
|
|
role="menuitem">
|
|
{% trans "Sign Out" %}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Messages/Alerts -->
|
|
<div class="p-3 sm:p-4 lg:p-8 space-y-3 sm:space-y-4 flex-1 overflow-x-hidden">
|
|
{% if messages %}
|
|
<div class="space-y-2 sm:space-y-3" role="alert" aria-live="polite">
|
|
{% for message in messages %}
|
|
<div class="alert {% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-800{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-800{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-800{% else %}bg-blue-50 border-blue-200 text-blue-800{% endif %} border rounded-lg px-3 sm:px-4 py-2.5 sm:py-3 flex items-start sm:items-center justify-between gap-2">
|
|
<span class="flex-1 text-sm">{{ message }}</span>
|
|
<button type="button"
|
|
class="text-gray-400 hover:text-gray-600 p-1 shrink-0 touch-target"
|
|
onclick="this.parentElement.remove()"
|
|
aria-label="{% trans 'Dismiss' %}">
|
|
<i data-lucide="x" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Page Content -->
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<footer class="mt-auto p-4 sm:p-6 text-xs text-gray-400 text-center border-t border-gray-200 bg-white safe-area-padding-bottom">
|
|
© {% now "Y" %} Tenhal. {% trans "All rights reserved." %}
|
|
</footer>
|
|
</main>
|
|
|
|
<script>
|
|
// Initialize Lucide icons
|
|
lucide.createIcons();
|
|
|
|
// ========================================
|
|
// Mobile Sidebar Toggle
|
|
// ========================================
|
|
const menuToggle = document.getElementById('menu-toggle');
|
|
const sidebar = document.getElementById('sidebar');
|
|
const sidebarBackdrop = document.getElementById('sidebar-backdrop');
|
|
|
|
function openMobileSidebar() {
|
|
sidebar.classList.remove('-translate-x-full');
|
|
sidebar.classList.add('translate-x-0');
|
|
sidebarBackdrop.classList.remove('hidden');
|
|
document.body.style.overflow = 'hidden';
|
|
menuToggle?.setAttribute('aria-expanded', 'true');
|
|
}
|
|
|
|
function closeMobileSidebar() {
|
|
sidebar.classList.add('-translate-x-full');
|
|
sidebar.classList.remove('translate-x-0');
|
|
sidebarBackdrop.classList.add('hidden');
|
|
document.body.style.overflow = '';
|
|
menuToggle?.setAttribute('aria-expanded', 'false');
|
|
}
|
|
|
|
// Make functions available globally
|
|
window.openMobileSidebar = openMobileSidebar;
|
|
window.closeMobileSidebar = closeMobileSidebar;
|
|
|
|
if (menuToggle) {
|
|
menuToggle.addEventListener('click', openMobileSidebar);
|
|
}
|
|
|
|
// Close sidebar on escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && !sidebar.classList.contains('-translate-x-full')) {
|
|
closeMobileSidebar();
|
|
}
|
|
});
|
|
|
|
// ========================================
|
|
// Fullscreen Toggle (Desktop)
|
|
// ========================================
|
|
const fullscreenToggle = document.getElementById('fullscreen-toggle');
|
|
const fullscreenIcon = document.getElementById('fullscreen-icon');
|
|
|
|
if (fullscreenToggle && fullscreenIcon) {
|
|
fullscreenToggle.addEventListener('click', () => {
|
|
if (!document.fullscreenElement) {
|
|
// Enter fullscreen
|
|
if (document.documentElement.requestFullscreen) {
|
|
document.documentElement.requestFullscreen();
|
|
} else if (document.documentElement.mozRequestFullScreen) {
|
|
document.documentElement.mozRequestFullScreen();
|
|
} else if (document.documentElement.webkitRequestFullscreen) {
|
|
document.documentElement.webkitRequestFullscreen();
|
|
} else if (document.documentElement.msRequestFullscreen) {
|
|
document.documentElement.msRequestFullscreen();
|
|
}
|
|
} else {
|
|
// Exit fullscreen
|
|
if (document.exitFullscreen) {
|
|
document.exitFullscreen();
|
|
} else if (document.mozCancelFullScreen) {
|
|
document.mozCancelFullScreen();
|
|
} else if (document.webkitExitFullscreen) {
|
|
document.webkitExitFullscreen();
|
|
} else if (document.msExitFullscreen) {
|
|
document.msExitFullscreen();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update fullscreen icon on change
|
|
document.addEventListener('fullscreenchange', updateFullscreenIcon);
|
|
document.addEventListener('webkitfullscreenchange', updateFullscreenIcon);
|
|
document.addEventListener('mozfullscreenchange', updateFullscreenIcon);
|
|
document.addEventListener('MSFullscreenChange', updateFullscreenIcon);
|
|
|
|
function updateFullscreenIcon() {
|
|
if (document.fullscreenElement || document.webkitFullscreenElement ||
|
|
document.mozFullScreenElement || document.msFullscreenElement) {
|
|
// In fullscreen - show minimize icon
|
|
fullscreenIcon?.setAttribute('data-lucide', 'minimize');
|
|
fullscreenToggle?.setAttribute('title', '{% trans "Exit Fullscreen" %}');
|
|
fullscreenToggle?.setAttribute('aria-label', '{% trans "Exit fullscreen" %}');
|
|
} else {
|
|
// Not in fullscreen - show maximize icon
|
|
fullscreenIcon?.setAttribute('data-lucide', 'maximize');
|
|
fullscreenToggle?.setAttribute('title', '{% trans "Toggle Fullscreen" %}');
|
|
fullscreenToggle?.setAttribute('aria-label', '{% trans "Toggle fullscreen" %}');
|
|
}
|
|
// Reinitialize lucide icons
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// ========================================
|
|
// Desktop Sidebar Collapse/Expand Toggle
|
|
// ========================================
|
|
const sidebarToggle = document.getElementById('sidebar-toggle');
|
|
const mainContent = document.getElementById('main-content');
|
|
|
|
// Restore sidebar state from localStorage
|
|
let isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
|
|
|
|
// Apply saved state on page load
|
|
function applySidebarState() {
|
|
if (!sidebar || !mainContent) return;
|
|
|
|
if (isCollapsed) {
|
|
// Collapse sidebar
|
|
sidebar.style.width = '80px';
|
|
mainContent.style.marginLeft = '80px';
|
|
sidebar.classList.add('sidebar-collapsed');
|
|
|
|
// Hide expanded logo, show collapsed logo
|
|
document.querySelectorAll('.sidebar-expanded-logo').forEach(el => {
|
|
el.style.display = 'none';
|
|
});
|
|
document.querySelectorAll('.sidebar-collapsed-logo').forEach(el => {
|
|
el.style.display = 'flex';
|
|
el.classList.remove('hidden');
|
|
el.classList.add('flex', 'justify-center', 'items-center');
|
|
});
|
|
|
|
// Hide all text elements
|
|
document.querySelectorAll('.sidebar-text, .nav-text, .section-header').forEach(el => {
|
|
el.style.display = 'none';
|
|
});
|
|
|
|
// Center icons and remove margins
|
|
document.querySelectorAll('.nav-link').forEach(el => {
|
|
el.classList.add('justify-center');
|
|
el.classList.remove('justify-start');
|
|
});
|
|
document.querySelectorAll('.nav-icon').forEach(el => {
|
|
el.style.marginRight = '0';
|
|
});
|
|
|
|
// Adjust headers
|
|
document.querySelectorAll('.sidebar-header').forEach(el => {
|
|
el.classList.add('justify-center', 'p-4');
|
|
el.classList.remove('p-6', 'sm:p-6');
|
|
});
|
|
|
|
// Adjust nav and footer padding
|
|
document.querySelectorAll('.sidebar-nav, .sidebar-footer').forEach(el => {
|
|
el.classList.add('px-2');
|
|
el.classList.remove('px-3', 'sm:px-4', 'p-3', 'sm:p-4', 'p-4');
|
|
});
|
|
|
|
// Reposition badges
|
|
document.querySelectorAll('.nav-badge').forEach(el => {
|
|
el.classList.add('hidden');
|
|
});
|
|
} else {
|
|
// Expand sidebar
|
|
sidebar.style.width = '256px';
|
|
mainContent.style.marginLeft = '256px';
|
|
sidebar.classList.remove('sidebar-collapsed');
|
|
|
|
// Show expanded logo, hide collapsed logo
|
|
document.querySelectorAll('.sidebar-expanded-logo').forEach(el => {
|
|
el.style.display = 'flex';
|
|
el.classList.remove('hidden');
|
|
el.classList.add('flex', 'items-center', 'gap-3');
|
|
});
|
|
document.querySelectorAll('.sidebar-collapsed-logo').forEach(el => {
|
|
el.style.display = 'none';
|
|
});
|
|
|
|
// Show all text elements
|
|
document.querySelectorAll('.sidebar-text, .nav-text, .section-header').forEach(el => {
|
|
el.style.display = '';
|
|
});
|
|
|
|
// Left-align icons and add margins
|
|
document.querySelectorAll('.nav-link').forEach(el => {
|
|
el.classList.remove('justify-center');
|
|
el.classList.add('justify-start');
|
|
});
|
|
document.querySelectorAll('.nav-icon').forEach(el => {
|
|
el.style.marginRight = '';
|
|
});
|
|
|
|
// Restore headers
|
|
document.querySelectorAll('.sidebar-header').forEach(el => {
|
|
el.classList.remove('justify-center', 'p-4');
|
|
el.classList.add('p-4', 'sm:p-6');
|
|
});
|
|
|
|
// Restore nav and footer padding
|
|
document.querySelectorAll('.sidebar-nav').forEach(el => {
|
|
el.classList.remove('px-2');
|
|
el.classList.add('px-3', 'sm:px-4');
|
|
});
|
|
document.querySelectorAll('.sidebar-footer').forEach(el => {
|
|
el.classList.remove('px-2');
|
|
el.classList.add('p-3', 'sm:p-4');
|
|
});
|
|
|
|
// Restore badge visibility
|
|
document.querySelectorAll('.nav-badge').forEach(el => {
|
|
el.classList.remove('hidden');
|
|
});
|
|
}
|
|
}
|
|
|
|
// Apply state on page load (desktop only)
|
|
if (window.innerWidth >= 1024 && sidebar && mainContent) {
|
|
applySidebarState();
|
|
}
|
|
|
|
// Toggle sidebar on click
|
|
if (sidebarToggle && sidebar && mainContent) {
|
|
sidebarToggle.addEventListener('click', () => {
|
|
isCollapsed = !isCollapsed;
|
|
localStorage.setItem('sidebarCollapsed', isCollapsed);
|
|
applySidebarState();
|
|
});
|
|
}
|
|
|
|
// Reset sidebar state on window resize
|
|
let resizeTimer;
|
|
window.addEventListener('resize', () => {
|
|
clearTimeout(resizeTimer);
|
|
resizeTimer = setTimeout(() => {
|
|
if (window.innerWidth < 1024) {
|
|
// Mobile: reset inline styles
|
|
if (sidebar) {
|
|
sidebar.style.width = '';
|
|
}
|
|
if (mainContent) {
|
|
mainContent.style.marginLeft = '';
|
|
}
|
|
} else {
|
|
// Desktop: reapply sidebar state
|
|
applySidebarState();
|
|
}
|
|
}, 250);
|
|
});
|
|
|
|
// ========================================
|
|
// User Dropdown Toggle
|
|
// ========================================
|
|
const userMenuButton = document.getElementById('user-menu-button');
|
|
const userMenuDropdown = document.getElementById('user-menu-dropdown');
|
|
|
|
if (userMenuButton && userMenuDropdown) {
|
|
userMenuButton.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const isHidden = userMenuDropdown.classList.contains('hidden');
|
|
userMenuDropdown.classList.toggle('hidden');
|
|
userMenuButton.setAttribute('aria-expanded', isHidden ? 'true' : 'false');
|
|
});
|
|
|
|
// Close dropdown when clicking outside
|
|
document.addEventListener('click', (e) => {
|
|
if (!userMenuButton.contains(e.target) && !userMenuDropdown.contains(e.target)) {
|
|
userMenuDropdown.classList.add('hidden');
|
|
userMenuButton.setAttribute('aria-expanded', 'false');
|
|
}
|
|
});
|
|
|
|
// Close dropdown on escape key
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && !userMenuDropdown.classList.contains('hidden')) {
|
|
userMenuDropdown.classList.add('hidden');
|
|
userMenuButton.setAttribute('aria-expanded', 'false');
|
|
userMenuButton.focus();
|
|
}
|
|
});
|
|
}
|
|
|
|
// ========================================
|
|
// Auto-dismiss alerts after 5 seconds
|
|
// ========================================
|
|
document.querySelectorAll('.alert').forEach(alert => {
|
|
setTimeout(() => {
|
|
alert.style.opacity = '0';
|
|
alert.style.transform = 'translateY(-20px)';
|
|
setTimeout(() => alert.remove(), 300);
|
|
}, 5000);
|
|
});
|
|
|
|
// ========================================
|
|
// Prevent double-tap zoom on buttons (iOS)
|
|
// ========================================
|
|
let lastTouchEnd = 0;
|
|
document.addEventListener('touchend', (e) => {
|
|
const now = Date.now();
|
|
if (now - lastTouchEnd <= 300) {
|
|
e.preventDefault();
|
|
}
|
|
lastTouchEnd = now;
|
|
}, false);
|
|
|
|
// ========================================
|
|
// Tooltip functionality for collapsed sidebar
|
|
// ========================================
|
|
const tooltip = document.getElementById('sidebar-tooltip');
|
|
|
|
// Setup tooltip hover events for all nav links
|
|
function setupTooltips() {
|
|
const navLinks = document.querySelectorAll('.nav-link[data-tooltip]');
|
|
|
|
navLinks.forEach(link => {
|
|
link.addEventListener('mouseenter', (e) => {
|
|
// Only show tooltip if sidebar is collapsed
|
|
if (sidebar && sidebar.classList.contains('sidebar-collapsed')) {
|
|
const tooltipText = link.getAttribute('data-tooltip');
|
|
if (tooltip && tooltipText) {
|
|
tooltip.textContent = tooltipText;
|
|
tooltip.classList.add('visible');
|
|
|
|
// Position tooltip next to hovered element
|
|
const rect = link.getBoundingClientRect();
|
|
// Use requestAnimationFrame to ensure tooltip has been rendered
|
|
requestAnimationFrame(() => {
|
|
tooltip.style.left = (rect.right + 10) + 'px';
|
|
tooltip.style.top = (rect.top + (rect.height / 2) - (tooltip.offsetHeight / 2)) + 'px';
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
link.addEventListener('mouseleave', () => {
|
|
if (tooltip) {
|
|
tooltip.classList.remove('visible');
|
|
}
|
|
});
|
|
|
|
link.addEventListener('mousemove', (e) => {
|
|
// Only update position if sidebar is collapsed and tooltip is visible
|
|
if (sidebar && sidebar.classList.contains('sidebar-collapsed') && tooltip && tooltip.classList.contains('visible')) {
|
|
const rect = link.getBoundingClientRect();
|
|
tooltip.style.left = (rect.right + 10) + 'px';
|
|
tooltip.style.top = (rect.top + (rect.height / 2) - (tooltip.offsetHeight / 2)) + 'px';
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Initialize tooltips after DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', setupTooltips);
|
|
} else {
|
|
setupTooltips();
|
|
}
|
|
|
|
// ========================================
|
|
// Performance: Debounce scroll events
|
|
// ========================================
|
|
let ticking = false;
|
|
function onScroll() {
|
|
if (!ticking) {
|
|
window.requestAnimationFrame(() => {
|
|
// Add scroll-based functionality here if needed
|
|
ticking = false;
|
|
});
|
|
ticking = true;
|
|
}
|
|
}
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
</script>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
|
|
{% block customJS %}{% endblock %}
|
|
</body>
|
|
</html> |