after updating the stages

This commit is contained in:
Faheed 2026-02-01 19:47:32 +03:00
parent dba9af100a
commit b50e48f510
24 changed files with 2838 additions and 1340 deletions

1011
PRODUCTION_SETUP.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1049,6 +1049,10 @@ class Application(Base):
return self.scheduled_interviews.all() return self.scheduled_interviews.all()
@property @property
def get_latest_meeting(self):
"""Legacy compatibility - get latest scheduled interview for this application"""
return self.scheduled_interviews.order_by('-interview_date', '-interview_time').first()
@property
def has_future_meeting(self): def has_future_meeting(self):
"""Legacy compatibility - check for future meetings""" """Legacy compatibility - check for future meetings"""
now = timezone.now() now = timezone.now()

View File

@ -6,74 +6,90 @@ register = template.Library()
@register.simple_tag @register.simple_tag
def logo_tenhal_ats(height="40", width=None): def logo_tenhal_ats(height="40", width=None):
""" """
Renders the Tenhal ATS logo SVG. Renders Tenhal ATS full logo SVG (icon + text).
Optimized for all screen sizes.
Usage: {% logo_tenhal_ats height="40" %} Usage: {% logo_tenhal_ats height="40" %}
""" """
# Calculate width proportionally if not provided # Calculate width proportionally if not provided
if width is None: if width is None:
width = str(int(float(height) * 3.57)) # Aspect ratio of 500/140 width = str(int(float(height) * 4.2)) # Adjusted aspect ratio
svg = f''' svg = f'''
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 140" height="{height}" width="{width}" class="tenhal-logo"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 100" height="{height}" width="{width}" class="tenhal-logo">
<defs> <defs>
<!-- Vibrant red gradient for icon -->
<linearGradient id="redGradient-{height}" x1="0%" y1="0%" x2="0%" y2="100%"> <linearGradient id="redGradient-{height}" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#C41E3A;stop-opacity:1" /> <stop offset="0%" style="stop-color:#EF4444;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8B1228;stop-opacity:1" /> <stop offset="100%" style="stop-color:#DC2626;stop-opacity:1" />
</linearGradient> </linearGradient>
<filter id="shadow-{height}" x="-50%" y="-50%" width="200%" height="200%"> <!-- Subtle shadow -->
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/> <filter id="shadow-{height}">
<feOffset dx="0" dy="2" result="offsetblur"/> <feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.3"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.15"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter> </filter>
</defs> </defs>
<g transform="translate(40, 35)" filter="url(#shadow-{height})"> <!-- Icon: Modern hexagonal badge -->
<path d="M 30 5 L 50 15 L 50 35 L 30 45 L 10 35 L 10 15 Z" <g transform="translate(15, 15)" filter="url(#shadow-{height})">
<!-- Outer hexagon -->
<path d="M 35 5 L 60 20 L 60 50 L 35 65 L 10 50 L 10 20 Z"
fill="url(#redGradient-{height})" fill="url(#redGradient-{height})"
stroke="none"/> stroke="#EF4444"
stroke-width="1.5"/>
<path d="M 30 15 L 40 20 L 40 30 L 30 35 L 20 30 L 20 20 Z" <!-- Inner hexagon outline -->
<path d="M 35 18 L 50 26 L 50 44 L 35 52 L 20 44 L 20 26 Z"
fill="none" fill="none"
stroke="#ffffff" stroke="#FFFFFF"
stroke-width="2" stroke-width="3"
opacity="0.8"/> opacity="0.9"/>
<circle cx="30" cy="23" r="3" fill="#ffffff"/> <!-- Candidate icon -->
<path d="M 24 30 Q 30 27 36 30" <circle cx="35" cy="30" r="4" fill="#FFFFFF"/>
<path d="M 27 42 Q 35 38 43 42"
fill="none" fill="none"
stroke="#ffffff" stroke="#FFFFFF"
stroke-width="2" stroke-width="3"
stroke-linecap="round"/> stroke-linecap="round"/>
<circle cx="30" cy="2" r="1.5" fill="#C41E3A" opacity="0.6"/> <!-- Flow indicators (3 dots) -->
<circle cx="54" cy="25" r="1.5" fill="#C41E3A" opacity="0.6"/> <circle cx="35" cy="0" r="2.5" fill="#FFFFFF" opacity="0.7"/>
<circle cx="6" cy="25" r="1.5" fill="#C41E3A" opacity="0.6"/> <circle cx="65" cy="35" r="2.5" fill="#FFFFFF" opacity="0.7"/>
<circle cx="5" cy="35" r="2.5" fill="#FFFFFF" opacity="0.7"/>
</g> </g>
<rect x="95" y="35" width="2" height="55" fill="#E0E0E0"/> <!-- Vertical divider -->
<line x1="92" y1="25" x2="92" y2="75" stroke="#374151" stroke-width="2"/>
<text x="125" y="62" <!-- Company name: TENHAL -->
font-family="'Helvetica Neue', Arial, sans-serif" <text x="110" y="52"
font-size="38" font-family="'Segoe UI', 'Helvetica Neue', Arial, sans-serif"
font-weight="300" font-size="32"
fill="#f8f7f2" font-weight="700"
letter-spacing="4">TENHAL</text> fill="#FFFFFF"
letter-spacing="3">TENHAL</text>
<rect x="125" y="72" width="55" height="2.5" fill="#C41E3A"/> <!-- Product badge: ATS -->
<g transform="translate(245, 35)">
<rect x="0" y="0" width="65" height="28" rx="14"
fill="#DC2626"
opacity="0.9"/>
<text x="32.5" y="19"
font-family="'Segoe UI', 'Helvetica Neue', Arial, sans-serif"
font-size="14"
font-weight="700"
fill="#FFFFFF"
text-anchor="middle"
letter-spacing="2">ATS</text>
</g>
<text x="125" y="92" <!-- Tagline -->
font-family="'Helvetica Neue', Arial, sans-serif" <text x="110" y="72"
font-size="13" font-family="'Segoe UI', 'Helvetica Neue', Arial, sans-serif"
font-weight="400" font-size="14"
fill="#f8f7f2" font-weight="500"
letter-spacing="2.5">APPLICANT TRACKING SYSTEM</text> fill="#FFFFFF"
letter-spacing="1.5">Applicant Tracking System</text>
</svg> </svg>
''' '''
@ -82,60 +98,119 @@ def logo_tenhal_ats(height="40", width=None):
@register.simple_tag @register.simple_tag
def logo_ats(height="40", width=None): def logo_ats(height="40", width=None):
""" """
Renders the Tenhal ATS logo SVG. Renders compact Tenhal logo with ATS badge (icon + minimal text).
Usage: {% logo_tenhal_ats height="40" %} Perfect for collapsed sidebars and mobile.
Usage: {% logo_ats height="40" %}
""" """
# Calculate width proportionally if not provided # Calculate width proportionally
if width is None: if width is None:
width = height # Aspect ratio of 500/140 width = str(int(float(height) * 2.5))
svg = f''' svg = f'''
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 140" height="{height}" width="{width}" class="tenhal-logo"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 80" height="{height}" width="{width}" class="tenhal-logo-compact">
<defs> <defs>
<linearGradient id="redGradient-{height}" x1="0%" y1="0%" x2="0%" y2="100%"> <!-- Vibrant red gradient -->
<stop offset="0%" style="stop-color:#C41E3A;stop-opacity:1" /> <linearGradient id="redGradient-compact-{height}" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="100%" style="stop-color:#8B1228;stop-opacity:1" /> <stop offset="0%" style="stop-color:#EF4444;stop-opacity:1" />
<stop offset="100%" style="stop-color:#DC2626;stop-opacity:1" />
</linearGradient> </linearGradient>
<filter id="shadow-{height}" x="-50%" y="-50%" width="200%" height="200%"> <filter id="shadow-compact-{height}">
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/> <feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.3"/>
<feOffset dx="0" dy="2" result="offsetblur"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.15"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter> </filter>
</defs> </defs>
<g transform="translate(40, 35)" filter="url(#shadow-{height})"> <!-- Icon -->
<path d="M 30 5 L 50 15 L 50 35 L 30 45 L 10 35 L 10 15 Z" <g transform="translate(10, 10)" filter="url(#shadow-compact-{height})">
fill="url(#redGradient-{height})" <path d="M 30 5 L 50 17 L 50 43 L 30 55 L 10 43 L 10 17 Z"
stroke="none"/> fill="url(#redGradient-compact-{height})"
stroke="#EF4444"
stroke-width="1.5"/>
<path d="M 30 15 L 40 20 L 40 30 L 30 35 L 20 30 L 20 20 Z" <path d="M 30 16 L 42 23 L 42 37 L 30 44 L 18 37 L 18 23 Z"
fill="none" fill="none"
stroke="#ffffff" stroke="#FFFFFF"
stroke-width="2" stroke-width="2.5"
opacity="0.8"/> opacity="0.9"/>
<circle cx="30" cy="23" r="3" fill="#ffffff"/> <circle cx="30" cy="26" r="3.5" fill="#FFFFFF"/>
<path d="M 24 30 Q 30 27 36 30" <path d="M 24 36 Q 30 33 36 36"
fill="none" fill="none"
stroke="#ffffff" stroke="#FFFFFF"
stroke-width="2" stroke-width="2.5"
stroke-linecap="round"/> stroke-linecap="round"/>
<circle cx="30" cy="2" r="1.5" fill="#C41E3A" opacity="0.6"/>
<circle cx="54" cy="25" r="1.5" fill="#C41E3A" opacity="0.6"/>
<circle cx="6" cy="25" r="1.5" fill="#C41E3A" opacity="0.6"/>
</g> </g>
<!-- Text: T + ATS badge -->
<text x="75" y="48"
font-family="'Segoe UI', 'Helvetica Neue', Arial, sans-serif"
font-size="28"
font-weight="700"
fill="#FFFFFF"
letter-spacing="2">T</text>
<g transform="translate(105, 25)">
<rect x="0" y="0" width="55" height="24" rx="12"
fill="#DC2626"
opacity="0.9"/>
<text x="27.5" y="16.5"
font-family="'Segoe UI', 'Helvetica Neue', Arial, sans-serif"
font-size="12"
font-weight="700"
fill="#FFFFFF"
text-anchor="middle"
letter-spacing="1.5">ATS</text>
</g>
</svg>
'''
return mark_safe(svg)
@register.simple_tag
def logo_icon_only(height="40", width=None):
"""
Renders icon only (no text) - perfect for very small spaces.
Usage: {% logo_icon_only height="40" %}
"""
if width is None:
width = height
svg = f'''
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70 70" height="{height}" width="{width}" class="tenhal-icon">
<defs>
<linearGradient id="redGradient-icon-{height}" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#EF4444;stop-opacity:1" />
<stop offset="100%" style="stop-color:#DC2626;stop-opacity:1" />
</linearGradient>
<filter id="shadow-icon-{height}">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.3"/>
</filter>
</defs>
<g transform="translate(5, 5)" filter="url(#shadow-icon-{height})">
<path d="M 30 5 L 50 17 L 50 43 L 30 55 L 10 43 L 10 17 Z"
fill="url(#redGradient-icon-{height})"
stroke="#EF4444"
stroke-width="1.5"/>
<path d="M 30 16 L 42 23 L 42 37 L 30 44 L 18 37 L 18 23 Z"
fill="none"
stroke="#FFFFFF"
stroke-width="2.5"
opacity="0.9"/>
<circle cx="30" cy="26" r="3.5" fill="#FFFFFF"/>
<path d="M 24 36 Q 30 33 36 36"
fill="none"
stroke="#FFFFFF"
stroke-width="2.5"
stroke-linecap="round"/>
<circle cx="30" cy="0" r="2" fill="#FFFFFF" opacity="0.7"/>
<circle cx="55" cy="30" r="2" fill="#FFFFFF" opacity="0.7"/>
<circle cx="5" cy="30" r="2" fill="#FFFFFF" opacity="0.7"/>
</g>
</svg> </svg>
''' '''

View File

@ -1,56 +1,65 @@
{% load i18n %} {% extends "base.html" %}
{% load account %} {% load i18n account %}
{% get_current_language_bidi as LANGUAGE_BIDI %} {% get_current_language_bidi as LANGUAGE_BIDI %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!DOCTYPE html>
<html lang="{{LANGUAGE_CODE}}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans "Email Addresses" %}</title>
<script src="https://cdn.tailwindcss.com"></script> {% block title %}{% trans "Email Addresses" %} - {{ block.super }}{% endblock %}
<script src="https://unpkg.com/lucide@latest"></script>
<script> {% block content %}
tailwind.config = { <div class="max-w-4xl mx-auto py-6 px-4">
theme: {
extend: {
colors: {
'temple-red': '#9d2235',
'temple-dark': '#1a1a1a',
'temple-cream': '#f8f7f2',
}
}
}
}
</script>
<style> <!-- Breadcrumb -->
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap'); <nav class="mb-6" aria-label="breadcrumb">
body { font-family: 'Inter', sans-serif; } <ol class="flex items-center gap-2 text-sm flex-wrap">
</style> <li><a href="{% url 'account_email' %}" class="text-gray-500 hover:underline transition flex items-center gap-1">
</head> <i data-lucide="mail" class="w-4 h-4"></i> {% trans "Account" %}
<body class="bg-temple-cream min-h-screen py-8 px-4"> </a></li>
<li class="text-gray-400">/</li>
<li class="font-semibold" style="color: #9d2235;">{% trans "Email Addresses" %}</li>
</ol>
</nav>
<div class="max-w-2xl mx-auto"> <!-- Header -->
<div class="bg-white rounded-2xl shadow-xl border border-gray-100 p-8"> <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<h1 class="text-2xl sm:text-3xl font-bold flex items-center gap-3">
<div class="text-center mb-8"> <div class="w-12 h-12 rounded-xl flex items-center justify-center" style="background-color: rgba(157, 34, 53, 0.1);">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-temple-red/10 mb-4"> <i data-lucide="mail" class="w-6 h-6" style="color: #9d2235;"></i>
<i data-lucide="mail" class="w-8 h-8 text-temple-red"></i>
</div> </div>
<h1 class="text-2xl font-bold text-gray-900 mb-2">{% trans "Email Addresses" %}</h1> {% trans "Email Addresses" %}
<p class="text-sm text-gray-500"> </h1>
</div>
<!-- Info Card -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6 p-6">
<div class="rounded-lg p-4 border-l-4" style="background-color: #eff6ff; border-color: #9d2235;">
<h6 class="font-bold mb-2 flex items-center gap-2" style="color: #9d2235;">
<i data-lucide="info" class="w-4 h-4"></i>
{% trans "About Email Addresses" %}
</h6>
<p class="text-gray-600 text-sm">
{% trans "These email addresses are linked to your account. You can set primary address, resend verification, or remove an address." %} {% trans "These email addresses are linked to your account. You can set primary address, resend verification, or remove an address." %}
</p> </p>
</div> </div>
</div>
<!-- Email Addresses Card -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6">
<div class="px-6 py-4 border-b border-gray-100" style="background-color: #f8f9fa;">
<h2 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="mail" class="w-5 h-5" style="color: #9d2235;"></i>
{% trans "Linked Email Addresses" %}
</h2>
</div>
<div class="p-6">
<!-- Django Messages --> <!-- Django Messages -->
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="mb-4 p-4 rounded-xl {% if message.tags == 'error' %}bg-red-50 text-red-700{% else %}bg-green-50 text-green-700{% endif %}" role="alert"> <div class="mb-4 p-4 rounded-lg {% if message.tags == 'error' %}bg-red-50 text-red-700{% else %}bg-green-50 text-green-700{% endif %}" role="alert">
{{ message }} <div class="flex items-start gap-3">
<i data-lucide="{% if message.tags == 'error' %}alert-triangle{% else %}check-circle{% endif %}" class="w-5 h-5 flex-shrink-0 mt-0.5"></i>
<span>{{ message }}</span>
</div>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@ -58,31 +67,45 @@
{% if emailaddresses %} {% if emailaddresses %}
<div class="space-y-4"> <div class="space-y-4">
{% for emailaddress in emailaddresses %} {% for emailaddress in emailaddresses %}
<div class="flex flex-col sm:flex-row sm:items-center justify-between py-4 {% if not forloop.last %}border-b border-gray-200{% endif %} gap-4"> <div class="flex flex-col sm:flex-row sm:items-center justify-between p-4 rounded-lg {% if not forloop.last %}border-b border-gray-200{% endif %} gap-4 bg-white">
<div class="flex-1"> <div class="flex-1">
<span class="font-semibold text-temple-dark">{{ emailaddress.email }}</span> <span class="font-semibold text-gray-900 block">{{ emailaddress.email }}</span>
<!-- Status Badges --> <!-- Status Badges -->
<div class="flex flex-wrap gap-2 mt-2">
{% if emailaddress.primary %} {% if emailaddress.primary %}
<span class="inline-block ml-2 px-3 py-1 text-xs font-medium bg-blue-100 text-blue-700 rounded-full">{% trans "Primary" %}</span> <span class="inline-flex items-center gap-1 px-3 py-1 text-xs font-medium bg-blue-100 text-blue-700 rounded-full">
<i data-lucide="star" class="w-3 h-3"></i>
{% trans "Primary" %}
</span>
{% endif %} {% endif %}
{% if emailaddress.verified %} {% if emailaddress.verified %}
<span class="inline-block ml-2 px-3 py-1 text-xs font-medium bg-green-100 text-green-700 rounded-full">{% trans "Verified" %}</span> <span class="inline-flex items-center gap-1 px-3 py-1 text-xs font-medium bg-green-100 text-green-700 rounded-full">
<i data-lucide="check" class="w-3 h-3"></i>
{% trans "Verified" %}
</span>
{% else %} {% else %}
<span class="inline-block ml-2 px-3 py-1 text-xs font-medium bg-yellow-100 text-yellow-700 rounded-full">{% trans "Unverified" %}</span> <span class="inline-flex items-center gap-1 px-3 py-1 text-xs font-medium bg-yellow-100 text-yellow-700 rounded-full">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{% trans "Unverified" %}
</span>
{% endif %} {% endif %}
</div> </div>
</div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<!-- Make Primary --> <!-- Make Primary -->
{% if not emailaddress.primary %} {% if not emailaddress.primary %}
<form method="post" action="{% url 'account_email' %}" class="inline"> <form method="post" action="{% url 'account_email' %}" class="inline">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" /> <input type="hidden" name="email" value="{{ emailaddress.email }}" />
<button type="submit" name="action_primary" <button type="submit" name="action_primary"
class="px-4 py-2 text-sm font-medium text-temple-red border border-temple-red rounded-lg hover:bg-red-50 transition"> class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white rounded-lg transition-all duration-200"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235'">
<i data-lucide="star" class="w-4 h-4"></i>
{% trans "Make Primary" %} {% trans "Make Primary" %}
</button> </button>
</form> </form>
@ -94,7 +117,8 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" /> <input type="hidden" name="email" value="{{ emailaddress.email }}" />
<button type="submit" name="action_send" <button type="submit" name="action_send"
class="px-4 py-2 text-sm font-medium text-yellow-600 border border-yellow-600 rounded-lg hover:bg-yellow-50 transition"> class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium border-2 border-yellow-600 text-yellow-600 rounded-lg hover:bg-yellow-50 transition-all duration-200">
<i data-lucide="send" class="w-4 h-4"></i>
{% trans "Re-send Verification" %} {% trans "Re-send Verification" %}
</button> </button>
</form> </form>
@ -106,7 +130,8 @@
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="email" value="{{ emailaddress.email }}" /> <input type="hidden" name="email" value="{{ emailaddress.email }}" />
<button type="submit" name="action_remove" <button type="submit" name="action_remove"
class="px-4 py-2 text-sm font-medium text-red-600 border border-red-600 rounded-lg hover:bg-red-50 transition"> class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium border-2 border-red-600 text-red-600 rounded-lg hover:bg-red-50 transition-all duration-200">
<i data-lucide="trash-2" class="w-4 h-4"></i>
{% trans "Remove" %} {% trans "Remove" %}
</button> </button>
</form> </form>
@ -116,47 +141,96 @@
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
<p class="mb-6 p-4 bg-blue-50 text-blue-700 rounded-xl">{% trans "No email addresses found." %}</p> <p class="p-4 bg-blue-50 text-blue-700 rounded-xl flex items-center gap-3">
<i data-lucide="inbox" class="w-5 h-5"></i>
{% trans "No email addresses found." %}
</p>
{% endif %} {% endif %}
</div>
</div>
<div class="my-8 border-t border-gray-200"></div> <!-- Add Email Card -->
<!-- Add Email Form -->
{% if can_add_email %} {% if can_add_email %}
<h2 class="text-lg font-bold text-temple-dark mb-4">{% trans "Add Email Address" %}</h2> <div class="bg-white rounded-xl shadow-sm border border-gray-200">
<form method="post" action="{% url 'account_email' %}" class="space-y-4"> <div class="px-6 py-4 border-b border-gray-100" style="background-color: #f8f9fa;">
<h2 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="plus-circle" class="w-5 h-5" style="color: #9d2235;"></i>
{% trans "Add Email Address" %}
</h2>
</div>
<div class="p-6">
<form method="post" action="{% url 'account_email' %}" class="space-y-6" id="add-email-form">
{% csrf_token %} {% csrf_token %}
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="p-4 bg-red-50 text-red-700 rounded-xl" role="alert"> <div class="mb-6 p-4 rounded-lg bg-red-50 border border-red-200" role="alert">
{{ form.non_field_errors }} <div class="flex items-start gap-3">
<i data-lucide="alert-triangle" class="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5"></i>
<div>
<h5 class="font-semibold text-red-800 mb-1">{% trans "Error" %}</h5>
<p class="text-red-700 mb-0">{{ form.non_field_errors }}</p>
</div>
</div>
</div> </div>
{% endif %} {% endif %}
<div> <div>
<label for="id_email" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="id_email" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Email Address" %} {% trans "Email Address" %} <span class="text-red-500">*</span>
</label> </label>
<input type="email" <input type="email"
name="email" name="email"
id="id_email" id="id_email"
class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition bg-white"
placeholder="{% trans 'Enter your email' %}" placeholder="{% trans 'Enter your email' %}"
required> required>
</div> </div>
<button type="submit" name="action_add" <button type="submit" name="action_add"
class="w-full bg-temple-red hover:bg-[#7a1a29] text-white font-semibold py-4 rounded-xl text-sm transition shadow-md hover:shadow-lg flex items-center justify-center gap-2"> class="w-full inline-flex items-center justify-center gap-2 px-8 py-3 rounded-lg font-medium text-white transition-all duration-200"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235'">
<i data-lucide="plus" class="w-5 h-5"></i> <i data-lucide="plus" class="w-5 h-5"></i>
{% trans "Add Email" %} {% trans "Add Email" %}
</button> </button>
</form> </form>
</div>
</div>
{% endif %} {% endif %}
</div> </div>
</div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
if (typeof lucide !== 'undefined') {
lucide.createIcons(); lucide.createIcons();
</script> }
</body>
</html> // Form validation
const form = document.getElementById('add-email-form');
if (form) {
form.addEventListener('submit', function(e) {
const emailInput = document.getElementById('id_email');
const emailValue = emailInput.value.trim();
if (!emailValue) {
e.preventDefault();
alert("{% trans 'Please enter an email address.' %}");
emailInput.focus();
return false;
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(emailValue)) {
e.preventDefault();
alert("{% trans 'Please enter a valid email address.' %}");
emailInput.focus();
return false;
}
});
}
});
</script>
{% endblock %}

View File

@ -1,64 +1,62 @@
{% load static %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% get_current_language_bidi as LANGUAGE_BIDI %}
{% get_current_language as LANGUAGE_CODE %}
<!DOCTYPE html>
<html lang="{{LANGUAGE_CODE}}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% trans "Change Password" %} - KAAUH ATS</title>
<script src="https://cdn.tailwindcss.com"></script> {% block title %}{% trans "Change Password" %} - {{ block.super }}{% endblock %}
<script src="https://unpkg.com/lucide@latest"></script>
<script> {% block content %}
tailwind.config = { <div class="max-w-lg mx-auto py-6 px-4">
theme: {
extend: {
colors: {
'temple-red': '#9d2235',
'temple-dark': '#1a1a1a',
'temple-cream': '#f8f7f2',
}
}
}
}
</script>
<style> <!-- Breadcrumb -->
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap'); <nav class="mb-6" aria-label="breadcrumb">
body { font-family: 'Inter', sans-serif; } <ol class="flex items-center gap-2 text-sm flex-wrap">
</style> <li><a href="{% url 'user_detail' request.user.pk %}" class="text-gray-500 hover:underline transition flex items-center gap-1">
</head> <i data-lucide="user" class="w-4 h-4"></i> {% trans "Profile" %}
<body class="bg-temple-cream min-h-screen flex items-center justify-center p-6"> </a></li>
<li class="text-gray-400">/</li>
<li class="font-semibold" style="color: #9d2235;">{% trans "Change Password" %}</li>
</ol>
</nav>
<div class="w-full max-w-md"> <!-- Password Change Card -->
<div class="bg-white rounded-2xl shadow-xl border border-gray-100 p-8"> <div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-6 py-4 border-b border-gray-100" style="background-color: #f8f9fa;">
<div class="text-center mb-8"> <h2 class="text-lg font-bold flex items-center gap-2">
<div class="inline-flex items-center justify-center w-20 h-20 rounded-full bg-temple-red/10 mb-4"> <i data-lucide="lock" class="w-5 h-5" style="color: #9d2235;"></i>
<i data-lucide="lock" class="w-10 h-10 text-temple-red"></i> {% trans "Change Password" %}
</h2>
</div> </div>
<h1 class="text-2xl font-bold text-gray-900">{% trans "Change Password" %}</h1>
<p class="text-sm text-gray-500 mt-2"> <div class="p-6">
<div class="text-center mb-6">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full mb-4" style="background-color: rgba(157, 34, 53, 0.1);">
<i data-lucide="lock" class="w-8 h-8" style="color: #9d2235;"></i>
</div>
<p class="text-sm text-gray-600">
{% trans "Please enter your current password and a new password to secure your account." %} {% trans "Please enter your current password and a new password to secure your account." %}
</p> </p>
</div> </div>
<form method="post" action="{% url 'account_change_password' %}" class="space-y-6"> <form method="post" action="{% url 'account_change_password' %}" class="space-y-6" id="password-change-form">
{% csrf_token %} {% csrf_token %}
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="p-4 bg-red-50 text-red-700 rounded-xl" role="alert"> <div class="mb-6 p-4 rounded-lg bg-red-50 border border-red-200" role="alert">
{% for error in form.non_field_errors %}{{ error }}{% endfor %} <div class="flex items-start gap-3">
<i data-lucide="alert-triangle" class="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5"></i>
<div>
<h5 class="font-semibold text-red-800 mb-1">{% trans "Error" %}</h5>
{% for error in form.non_field_errors %}
<p class="text-red-700 mb-0">{{ error }}</p>
{% endfor %}
</div>
</div>
</div> </div>
{% endif %} {% endif %}
{% for field in form %} {% for field in form %}
<div> <div>
<label for="id_{{ field.name }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="id_{{ field.name }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ field.label }} {{ field.label }} <span class="text-red-500">*</span>
</label> </label>
<div class="relative"> <div class="relative">
<i data-lucide="lock" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i> <i data-lucide="lock" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i>
@ -67,32 +65,73 @@
{% if field.errors %} {% if field.errors %}
<p class="mt-1 text-xs text-red-600">{{ field.errors.0 }}</p> <p class="mt-1 text-xs text-red-600">{{ field.errors.0 }}</p>
{% endif %} {% endif %}
{% if field.help_text %}
<p class="mt-1 text-xs text-gray-500">{{ field.help_text }}</p>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
<div class="flex flex-col sm:flex-row justify-between items-center gap-4 pt-4 border-t border-gray-200">
<a href="{% url 'user_detail' request.user.pk %}"
class="inline-flex items-center gap-2 px-6 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Cancel" %}
</a>
<button type="submit" <button type="submit"
class="w-full bg-temple-red hover:bg-[#7a1a29] text-white font-semibold py-4 rounded-xl text-sm transition shadow-md hover:shadow-lg flex items-center justify-center gap-2"> class="inline-flex items-center justify-center gap-2 px-8 py-3 rounded-lg font-medium text-white transition-all duration-200"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235';">
<i data-lucide="lock" class="w-5 h-5"></i> <i data-lucide="lock" class="w-5 h-5"></i>
{% trans "Change Password" %} {% trans "Change Password" %}
</button> </button>
</div>
</form> </form>
<div class="mt-6 pt-6 text-center border-t border-gray-200">
<p class="text-gray-600 text-sm">
<i data-lucide="arrow-left" class="w-4 h-4 inline-block mr-1 align-text-bottom"></i>
<a href="{% url 'user_detail' request.user.pk %}" class="text-temple-red hover:text-temple-red/80 transition font-medium">{% trans "Return to Profile" %}</a>
</p>
</div>
</div> </div>
</div> </div>
<script> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
if (typeof lucide !== 'undefined') {
lucide.createIcons(); lucide.createIcons();
}
// Add Tailwind classes to password inputs // Add Tailwind classes to password inputs
document.querySelectorAll('input[type="password"]').forEach(input => { document.querySelectorAll('input[type="password"]').forEach(input => {
input.classList.add('w-full', 'pl-10', 'pr-4', 'py-3.5', 'border', 'border-gray-200', 'rounded-xl', 'text-sm', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition'); input.classList.add('w-full', 'pl-10', 'pr-4', 'py-3', 'border', 'border-gray-200', 'rounded-xl', 'text-sm', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition', 'bg-white');
}); });
</script>
</body> // Form validation
</html> const form = document.getElementById('password-change-form');
if (form) {
form.addEventListener('submit', function(e) {
const passwordInputs = form.querySelectorAll('input[type="password"]');
const oldPassword = passwordInputs[0].value;
const newPassword = passwordInputs[1].value;
const confirmPassword = passwordInputs[2].value;
if (!oldPassword || !newPassword || !confirmPassword) {
e.preventDefault();
alert("{% trans 'Please fill in all password fields.' %}");
return false;
}
if (newPassword !== confirmPassword) {
e.preventDefault();
alert("{% trans 'New password and confirmation do not match.' %}");
return false;
}
if (newPassword.length < 8) {
e.preventDefault();
alert("{% trans 'Password must be at least 8 characters long.' %}");
return false;
}
});
}
});
</script>
{% endblock %}

View File

@ -273,12 +273,12 @@
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<!-- Sidebar Header --> <!-- Sidebar Header -->
<div class="p-4 sm:p-6 flex items-center gap-3 text-white sidebar-header safe-area-padding-top"> <div class="p-4 sm:p-6 flex items-center gap-3 text-white sidebar-header safe-area-padding-top">
{% comment %} <div class="bg-temple-red p-2 rounded-lg shrink-0"> <div class="sidebar-expanded-logo">
<i data-lucide="shield-check" class="w-5 h-5 sm:w-6 sm:h-6 text-white"></i> {% logo_tenhal_ats height="42" %}
</div>
<div class="sidebar-collapsed-logo hidden">
{% logo_icon_only height="40" %}
</div> </div>
<span class="text-lg sm:text-xl font-bold tracking-tight sidebar-text">{% trans "ATS" %}</span> {% endcomment %}
{% logo_ats height="40" %}
{% logo_tenhal_ats height="40" %}
<button onclick="closeMobileSidebar()" <button onclick="closeMobileSidebar()"
class="lg:hidden ml-auto text-gray-400 hover:text-white transition p-2 -mr-2" class="lg:hidden ml-auto text-gray-400 hover:text-white transition p-2 -mr-2"
aria-label="{% trans 'Close menu' %}"> aria-label="{% trans 'Close menu' %}">
@ -447,10 +447,7 @@
<i data-lucide="panel-left" class="w-5 h-5"></i> <i data-lucide="panel-left" class="w-5 h-5"></i>
</button> </button>
<!-- Logo/Title -->
<div class="truncate">
<span class="text-temple-red font-bold text-sm sm:text-base lg:text-lg">{% trans "KAAUH ATS" %}</span>
</div>
</div> </div>
<!-- Header Actions --> <!-- Header Actions -->
@ -571,7 +568,7 @@
<!-- Footer --> <!-- 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"> <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">
&copy; {% now "Y" %} KAAUH ATS. {% trans "All rights reserved." %} &copy; {% now "Y" %} Tenhal. {% trans "All rights reserved." %}
</footer> </footer>
</main> </main>
@ -693,6 +690,16 @@
mainContent.style.marginLeft = '80px'; mainContent.style.marginLeft = '80px';
sidebar.classList.add('sidebar-collapsed'); 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 // Hide all text elements
document.querySelectorAll('.sidebar-text, .nav-text, .section-header').forEach(el => { document.querySelectorAll('.sidebar-text, .nav-text, .section-header').forEach(el => {
el.style.display = 'none'; el.style.display = 'none';
@ -729,6 +736,16 @@
mainContent.style.marginLeft = '256px'; mainContent.style.marginLeft = '256px';
sidebar.classList.remove('sidebar-collapsed'); 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 // Show all text elements
document.querySelectorAll('.sidebar-text, .nav-text, .section-header').forEach(el => { document.querySelectorAll('.sidebar-text, .nav-text, .section-header').forEach(el => {
el.style.display = ''; el.style.display = '';

View File

@ -1,6 +1,6 @@
{% load i18n %} {% load i18n %}
<form id="exam-update-form" hx-post="{% url 'update_application_status' job.slug application.slug 'exam' 'Failed' %}" hx-swap='outerHTML' hx-target="#status-result-{{ application.pk }}" <form id="exam-update-form" hx-post="{% url 'update_application_status' job.slug application.slug 'exam' 'Failed' %}" hx-swap="outerHTML" hx-target="#status-result-{{ application.pk }}"
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }"> hx-on::after-request="if (typeof closeCandidateModal === 'function') { closeCandidateModal(); }">
<div class="flex justify-center items-center gap-4 mb-4"> <div class="flex justify-center items-center gap-4 mb-4">
<label class="flex items-center gap-2 cursor-pointer"> <label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="exam_status" id="exam_passed" value="Passed" {% if application.exam_status == 'Passed' %}checked{% endif %} class="w-4 h-4 text-temple-red border-gray-300 focus:ring-temple-red/20"> <input type="radio" name="exam_status" id="exam_passed" value="Passed" {% if application.exam_status == 'Passed' %}checked{% endif %} class="w-4 h-4 text-temple-red border-gray-300 focus:ring-temple-red/20">

View File

@ -1,7 +1,7 @@
{% load i18n %} {% load i18n %}
<div class="flex justify-center items-center gap-3" hx-swap='outerHTML' hx-target="#interview-result-{{ application.pk }}" <div class="flex justify-center items-center gap-3" hx-swap="outerHTML" hx-target="#interview-result-{{ application.pk }}"
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }"> hx-on::after-request="if (typeof closeCandidateModal === 'function') { closeCandidateModal(); }">
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Passed' %}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-300 text-gray-700 hover:border-temple-red hover:text-temple-red rounded-lg text-sm font-medium transition"> <a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Passed' %}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-300 text-gray-700 hover:border-green-500 hover:text-green-600 rounded-lg text-sm font-medium transition">
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Passed" %} <i data-lucide="check" class="w-4 h-4"></i> {% trans "Passed" %}
</a> </a>
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Failed' %}" class="inline-flex items-center gap-2 px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-medium transition shadow-sm hover:shadow-md"> <a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Failed' %}" class="inline-flex items-center gap-2 px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-medium transition shadow-sm hover:shadow-md">

View File

@ -1,6 +1,6 @@
{% load i18n %} {% load i18n %}
<div class="flex justify-center items-center gap-3" hx-swap='outerHTML' hx-target="#status-result-{{ application.pk }}" <div class="flex justify-center items-center gap-3" hx-swap="outerHTML" hx-target="#status-result-{{ application.pk }}"
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }"> hx-on::after-request="if (typeof closeCandidateModal === 'function') { closeCandidateModal(); }">
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-300 text-gray-700 hover:border-temple-red hover:text-temple-red rounded-lg text-sm font-medium transition"> <a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-300 text-gray-700 hover:border-temple-red hover:text-temple-red rounded-lg text-sm font-medium transition">
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Accepted" %} <i data-lucide="check" class="w-4 h-4"></i> {% trans "Accepted" %}
</a> </a>

View File

@ -16,9 +16,9 @@
</h1> </h1>
<p class="text-gray-600 mt-1">{% trans "for" %} {{ application.name }} - {{ job.title }}</p> <p class="text-gray-600 mt-1">{% trans "for" %} {{ application.name }} - {{ job.title }}</p>
</div> </div>
<a href="{% url 'interview_create_type_selection' application.slug %}" <a href="{% url 'applications_interview_view' job.slug %}"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 border-gray-300 text-gray-700 hover:bg-gray-50 transition-all duration-200"> class="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 border-gray-300 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Type Selection" %} <i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to application interview list" %}
</a> </a>
</div> </div>
@ -60,7 +60,7 @@
<label for="{{ form.topic.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ form.topic.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="tag" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Topic" %} <i data-lucide="tag" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Topic" %}
</label> </label>
{{ form.topic|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"|attr:"placeholder" }} {{ form.topic|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.topic.errors %} {% if form.topic.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.topic.errors }}</div> <div class="mt-1 text-sm text-red-600">{{ form.topic.errors }}</div>
{% endif %} {% endif %}
@ -96,7 +96,7 @@
<label for="{{ form.duration.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ form.duration.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="timer" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Duration (minutes)" %} <i data-lucide="timer" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Duration (minutes)" %}
</label> </label>
{{ form.duration|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"|attr:"placeholder" }} {{ form.duration|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.duration.errors %} {% if form.duration.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.duration.errors }}</div> <div class="mt-1 text-sm text-red-600">{{ form.duration.errors }}</div>
{% endif %} {% endif %}
@ -107,7 +107,7 @@
<label for="{{ form.room_number.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ form.room_number.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="door-open" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Room Number" %} <i data-lucide="door-open" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Room Number" %}
</label> </label>
{{ form.room_number|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"|attr:"placeholder" }} {{ form.room_number|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.room_number.errors %} {% if form.room_number.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.room_number.errors }}</div> <div class="mt-1 text-sm text-red-600">{{ form.room_number.errors }}</div>
{% endif %} {% endif %}
@ -119,7 +119,7 @@
<label for="{{ form.physical_address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ form.physical_address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
<i data-lucide="map-pin" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Physical Address" %} <i data-lucide="map-pin" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Physical Address" %}
</label> </label>
{{ form.physical_address|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"|attr:"placeholder" }} {{ form.physical_address|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if form.physical_address.errors %} {% if form.physical_address.errors %}
<div class="mt-1 text-sm text-red-600">{{ form.physical_address.errors }}</div> <div class="mt-1 text-sm text-red-600">{{ form.physical_address.errors }}</div>
{% endif %} {% endif %}

View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n widget_tweaks %}
{% block title %}{% trans "Create Remote Interview" %}{% endblock %} {% block title %}{% trans "Create Remote Interview" %}{% endblock %}
@ -16,10 +16,11 @@
</h1> </h1>
<p class="text-gray-600 mt-1">{% trans "for" %} {{ application.name }} - {{ job.title }}</p> <p class="text-gray-600 mt-1">{% trans "for" %} {{ application.name }} - {{ job.title }}</p>
</div> </div>
<a href="{% url 'interview_create_type_selection' application.slug %}" <a href="{% url 'applications_interview_view' job.slug %}"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 border-gray-300 text-gray-700 hover:bg-gray-50 transition-all duration-200"> class="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 border-gray-300 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Type Selection" %} <i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to application interview list" %}
</a> </a>
</div> </div>
<!-- Form Card --> <!-- Form Card -->
@ -76,7 +77,7 @@
{% endif %} {% endif %}
{{ field.label }} {{ field.label }}
</label> </label>
{{ field|attr:"class: w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }} {{ field|add_class:"w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" }}
{% if field.errors %} {% if field.errors %}
<div class="mt-1 text-sm text-red-600">{{ field.errors }}</div> <div class="mt-1 text-sm text-red-600">{{ field.errors }}</div>
{% endif %} {% endif %}

View File

@ -1,10 +1,6 @@
{% extends "base.html" %} {% load static i18n %}
{% load i18n %}
{% block title %}{% trans "Create Interview - Select Type" %}{% endblock %} <div class="space-y-6" id="interview-type-selection-content">
{% block content %}
<div class="space-y-6">
<!-- Header --> <!-- Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4"> <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div> <div>
@ -16,10 +12,6 @@
</h1> </h1>
<p class="text-gray-600 mt-1">{% trans "for" %} {{ application.name }} - {{ job.title }}</p> <p class="text-gray-600 mt-1">{% trans "for" %} {{ application.name }} - {{ job.title }}</p>
</div> </div>
<a href="{% url 'job_detail' job.slug %}"
class="inline-flex items-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium border-2 border-gray-300 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Job" %}
</a>
</div> </div>
<!-- Selection Cards --> <!-- Selection Cards -->
@ -63,10 +55,8 @@
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { // Reinitialize Lucide icons after content loads
if (typeof lucide !== 'undefined') { if (typeof lucide !== 'undefined') {
lucide.createIcons(); lucide.createIcons();
} }
});
</script> </script>
{% endblock %}

View File

@ -1,245 +1,265 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% load static i18n widget_tweaks %} {% load static i18n %}
{% block title %}{{ title }} - ATS{% endblock %} {% block title %}{{ title }} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-light: #e0f7f9; /* Added for contrast */
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
body {
background-color: #f8f9fa; /* Light background for better contrast */
}
.kaauh-card {
padding: 2.5rem; /* Increased padding */
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Main Action Button (Teal Fill) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.25rem;
border-radius: 0.5rem;
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 Action Button (Teal Outline) */
.btn-outline-primary-teal {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
font-weight: 600;
padding: 0.6rem 1.25rem;
border-radius: 0.5rem;
}
.btn-outline-primary-teal:hover {
background-color: var(--kaauh-teal);
color: white;
}
/* Form Consistency */
.form-control:focus, .form-select:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
}
.form-label {
font-weight: 600;
color: var(--kaauh-primary-text);
}
/* Applying Bootstrap classes to Django fields if not done in the form definition */
.kaauh-field-control > input,
.kaauh-field-control > textarea,
.kaauh-field-control > select {
display: block;
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: var(--kaauh-primary-text);
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
appearance: none;
border-radius: 0.5rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
/* Specific overrides for different types */
.kaauh-field-control > select {
padding-right: 2.5rem; /* Space for the caret */
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="max-w-4xl mx-auto py-6 px-4">
<div class="row">
<div class="col-lg-8 mx-auto"> <!-- Breadcrumb -->
<div class="d-flex justify-content-between align-items-center mb-4"> <nav class="mb-6" aria-label="breadcrumb">
<div> <ol class="flex items-center gap-2 text-sm flex-wrap">
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <li><a href="{% url 'agency_assignment_list' %}" class="text-gray-500 hover:underline transition flex items-center gap-1">
<i class="fas fa-tasks me-2"></i> <i data-lucide="briefcase" class="w-4 h-4"></i> {% trans "Assignments" %}
{% trans "Create New Assignment" %} </a></li>
<li class="text-gray-400">/</li>
<li class="font-semibold" style="color: #9d2235;">{{ title }}</li>
</ol>
</nav>
<!-- Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<h1 class="text-2xl sm:text-3xl font-bold flex items-center gap-3">
<div class="w-12 h-12 rounded-xl flex items-center justify-center" style="background-color: rgba(157, 34, 53, 0.1);">
<i data-lucide="briefcase" class="w-6 h-6" style="color: #9d2235;"></i>
</div>
{{ title }}
</h1> </h1>
<p class="text-muted mb-0"> <a href="{% url 'agency_assignment_list' %}"
{% trans "Assign a job to an external hiring agency" %} class="inline-flex items-center gap-2 px-6 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Back to List" %}</span>
</a>
</div>
<!-- Info Card -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6 p-6">
<div class="rounded-lg p-4 border-l-4" style="background-color: #eff6ff; border-color: #9d2235;">
<h6 class="font-bold mb-2 flex items-center gap-2" style="color: #9d2235;">
<i data-lucide="info" class="w-4 h-4"></i>
{% trans "About Assignments" %}
</h6>
<p class="text-gray-600 text-sm">
{% trans "Assign a job to an external hiring agency. The agency will be able to submit candidates for this position until the deadline." %}
</p> </p>
</div> </div>
<a href="{% url 'agency_assignment_list' %}" class="btn btn-outline-primary-teal">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Assignments" %}
</a>
</div> </div>
<div class="kaauh-card"> <!-- Form Card -->
<form method="post" novalidate> <div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-6 py-4 border-b border-gray-100" style="background-color: #f8f9fa;">
<h2 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="briefcase" class="w-5 h-5" style="color: #9d2235;"></i>
{% trans "Assignment Details" %}
</h2>
</div>
<div class="p-6">
{% if form.non_field_errors %}
<div class="mb-6 p-4 rounded-lg bg-red-50 border border-red-200" role="alert">
<div class="flex items-start gap-3">
<i data-lucide="alert-triangle" class="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5"></i>
<div>
<h5 class="font-semibold text-red-800 mb-1">{% trans "Error" %}</h5>
{% for error in form.non_field_errors %}
<p class="text-red-700 mb-0">{{ error }}</p>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<form method="post" novalidate id="assignment-form" class="space-y-6">
{% csrf_token %} {% csrf_token %}
<div class="row g-3 mb-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Agency -->
<div class="col-md-6"> <div>
<label for="{{ form.agency.id_for_label }}" class="form-label"> <label for="{{ form.agency.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.agency.label }} <span class="text-danger">*</span> {{ form.agency.label }} <span class="text-red-500">*</span>
</label> </label>
{# Wrapper Div for styling consistency (Assumes agency is a SELECT field) #} <select name="agency" id="{{ form.agency.id_for_label }}"
<div class="kaauh-field-control"> class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition bg-white">
{{ form.agency|attr:'class:form-select' }} <option value="">{% trans "Select Agency" %}</option>
</div> {% for choice in form.agency.field.queryset %}
<option value="{{ choice.pk }}" {% if form.agency.value == choice.pk|stringformat:"s" %}selected{% endif %}>
{{ choice.name }}
</option>
{% endfor %}
</select>
{% if form.agency.errors %} {% if form.agency.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-1">{{ form.agency.errors.0 }}</div>
{% for error in form.agency.errors %}{{ error }}{% endfor %} {% endif %}
</div> {% if form.agency.help_text %}
<p class="text-xs text-gray-500 mt-1">{{ form.agency.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<div class="col-md-6"> <!-- Job -->
<label for="{{ form.job.id_for_label }}" class="form-label"> <div>
{{ form.job.label }} <span class="text-danger">*</span> <label for="{{ form.job.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.job.label }} <span class="text-red-500">*</span>
</label> </label>
{# Wrapper Div for styling consistency (Assumes job is a SELECT field) #} <select name="job" id="{{ form.job.id_for_label }}"
<div class="kaauh-field-control"> class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition bg-white">
{{ form.job|attr:'class:form-select' }} <option value="">{% trans "Select Job" %}</option>
</div> {% for choice in form.job.field.queryset %}
<option value="{{ choice.pk }}" {% if form.job.value == choice.pk|stringformat:"s" %}selected{% endif %}>
{{ choice.title }}
</option>
{% endfor %}
</select>
{% if form.job.errors %} {% if form.job.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-1">{{ form.job.errors.0 }}</div>
{% for error in form.job.errors %}{{ error }}{% endfor %} {% endif %}
</div> {% if form.job.help_text %}
<p class="text-xs text-gray-500 mt-1">{{ form.job.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
</div>
<div class="row g-3 mb-4"> <!-- Max Candidates -->
<div class="col-md-6"> <div>
<label for="{{ form.max_candidates.id_for_label }}" class="form-label"> <label for="{{ form.max_candidates.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.max_candidates.label }} <span class="text-danger">*</span> {{ form.max_candidates.label }} <span class="text-red-500">*</span>
</label> </label>
{# Wrapper Div for styling consistency (Assumes max_candidates is an INPUT field) #} <input type="number"
<div class="kaauh-field-control"> name="max_candidates"
{{ form.max_candidates|attr:'class:form-control' }} id="{{ form.max_candidates.id_for_label }}"
</div> value="{{ form.max_candidates.value|default:'' }}"
min="1"
class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Maximum candidates' %}">
{% if form.max_candidates.errors %} {% if form.max_candidates.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-1">{{ form.max_candidates.errors.0 }}</div>
{% for error in form.max_candidates.errors %}{{ error }}{% endfor %} {% endif %}
</div> {% if form.max_candidates.help_text %}
<p class="text-xs text-gray-500 mt-1">{{ form.max_candidates.help_text }}</p>
{% else %}
<p class="text-xs text-gray-500 mt-1">{% trans "Maximum number of candidates agency can submit" %}</p>
{% endif %} {% endif %}
<small class="form-text text-muted">
{% trans "Maximum number of candidates the agency can submit" %}
</small>
</div> </div>
<div class="col-md-6">
<label for="{{ form.deadline_date.id_for_label }}" class="form-label"> <!-- Deadline Date -->
{{ form.deadline_date.label }} <span class="text-danger">*</span> <div>
<label for="{{ form.deadline_date.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.deadline_date.label }} <span class="text-red-500">*</span>
</label> </label>
{# Wrapper Div for styling consistency (Assumes deadline_date is an INPUT field) #} <input type="datetime-local"
<div class="kaauh-field-control"> name="deadline_date"
{{ form.deadline_date|attr:'class:form-control' }} id="{{ form.deadline_date.id_for_label }}"
</div> value="{{ form.deadline_date.value|default:'' }}"
class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
{% if form.deadline_date.errors %} {% if form.deadline_date.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-1">{{ form.deadline_date.errors.0 }}</div>
{% for error in form.deadline_date.errors %}{{ error }}{% endfor %} {% endif %}
</div> {% if form.deadline_date.help_text %}
<p class="text-xs text-gray-500 mt-1">{{ form.deadline_date.help_text }}</p>
{% else %}
<p class="text-xs text-gray-500 mt-1">{% trans "Date and time when submission period ends" %}</p>
{% endif %} {% endif %}
<small class="form-text text-muted">
{% trans "Date and time when submission period ends" %}
</small>
</div> </div>
</div> </div>
<!-- Admin Notes (Full Width) -->
<div class="mb-4"> <div>
<label for="{{ form.admin_notes.id_for_label }}" class="form-label"> <label for="{{ form.admin_notes.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.admin_notes.label }} {{ form.admin_notes.label }}
</label> </label>
{# Wrapper Div for styling consistency (Assumes admin_notes is a TEXTAREA field) #} <textarea
<div class="kaauh-field-control"> name="admin_notes"
{{ form.admin_notes|attr:'class:form-control' }} id="{{ form.admin_notes.id_for_label }}"
</div> rows="4"
class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition resize-vertical"
placeholder="{% trans 'Internal notes about this assignment (not visible to agency)' %}">{{ form.admin_notes.value|default:'' }}</textarea>
{% if form.admin_notes.errors %} {% if form.admin_notes.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-1">{{ form.admin_notes.errors.0 }}</div>
{% for error in form.admin_notes.errors %}{{ error }}{% endfor %} {% endif %}
</div> {% if form.admin_notes.help_text %}
<p class="text-xs text-gray-500 mt-1">{{ form.admin_notes.help_text }}</p>
{% endif %} {% endif %}
<small class="form-text text-muted">
{% trans "Internal notes about this assignment (not visible to agency)" %}
</small>
</div> </div>
<div class="d-flex justify-content-between align-items-center pt-3 border-top"> <!-- Form Actions -->
<a href="{% url 'agency_assignment_list' %}" class="btn btn-outline-primary-teal"> <div class="flex flex-col sm:flex-row justify-between items-center gap-4 pt-4 border-t border-gray-200">
<i class="fas fa-times me-1"></i> {% trans "Cancel" %} <a href="{% url 'agency_assignment_list' %}"
class="inline-flex items-center gap-2 px-6 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %}
</a> </a>
<button type="submit" class="btn btn-main-action"> <button type="submit"
<i class="fas fa-save me-1"></i> {% trans "Create Assignment" %} class="inline-flex items-center gap-2 px-8 py-3 rounded-lg font-medium text-white transition-all duration-200"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235'">
<i data-lucide="save" class="w-4 h-4"></i>
{{ button_text|default:"{% trans 'Create Assignment' %}" }}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div>
</div>
{% endblock %}
{% block customJS %} </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// --- Consistency Check: Ensure Django widgets have the Bootstrap classes --- // Initialize Lucide icons
// If your form fields are NOT already adding classes via widget attrs in the Django form, if (typeof lucide !== 'undefined') {
// you MUST add the following utility filter to your project to make this template work: lucide.createIcons();
// `|attr:'class:form-control'` }
// Auto-populate agency field when job is selected // Form Validation
const jobSelect = document.getElementById('{{ form.job.id_for_label }}'); const form = document.getElementById('assignment-form');
if (form) {
form.addEventListener('submit', function(e) {
const agencySelect = document.getElementById('{{ form.agency.id_for_label }}'); const agencySelect = document.getElementById('{{ form.agency.id_for_label }}');
const jobSelect = document.getElementById('{{ form.job.id_for_label }}');
const maxCandidatesInput = document.getElementById('{{ form.max_candidates.id_for_label }}');
const deadlineInput = document.getElementById('{{ form.deadline_date.id_for_label }}');
if (jobSelect && agencySelect) { // Agency validation
jobSelect.addEventListener('change', function() { if (agencySelect && !agencySelect.value) {
// You could add logic here to filter agencies based on job requirements e.preventDefault();
// For now, just log the selection alert("{% trans 'Please select an agency.' %}");
console.log('Job selected:', this.value); agencySelect.focus();
return false;
}
// Job validation
if (jobSelect && !jobSelect.value) {
e.preventDefault();
alert("{% trans 'Please select a job.' %}");
jobSelect.focus();
return false;
}
// Max candidates validation
if (maxCandidatesInput && (!maxCandidatesInput.value || parseInt(maxCandidatesInput.value) < 1)) {
e.preventDefault();
alert("{% trans 'Please enter a valid number of candidates (minimum 1).' %}");
maxCandidatesInput.focus();
return false;
}
// Deadline validation
if (deadlineInput && !deadlineInput.value) {
e.preventDefault();
alert("{% trans 'Please select a deadline date and time.' %}");
deadlineInput.focus();
return false;
}
// Check deadline is in future
if (deadlineInput && deadlineInput.value) {
const deadline = new Date(deadlineInput.value);
const now = new Date();
if (deadline <= now) {
e.preventDefault();
alert("{% trans 'Deadline must be in the future.' %}");
deadlineInput.focus();
return false;
}
}
}); });
} }
@ -252,6 +272,18 @@ document.addEventListener('DOMContentLoaded', function() {
.slice(0, 16); .slice(0, 16);
deadlineInput.min = localDateTime; deadlineInput.min = localDateTime;
} }
// Auto-populate or log job selection
const jobSelect = document.getElementById('{{ form.job.id_for_label }}');
const agencySelect = document.getElementById('{{ form.agency.id_for_label }}');
if (jobSelect && agencySelect) {
jobSelect.addEventListener('change', function() {
// You could add logic here to filter agencies based on job requirements
// For now, just log the selection
console.log('Job selected:', this.value);
});
}
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -4,51 +4,56 @@
{% block title %}{{ title }} - {{ block.super }}{% endblock %} {% block title %}{{ title }} - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="space-y-6"> <div class="max-w-4xl mx-auto py-6 px-4">
<!-- Header Card with Gradient Background --> <!-- Breadcrumb -->
<div class="bg-gradient-to-r from-temple-red to-[#7a1a29] rounded-2xl shadow-lg p-6 md:p-8 text-white"> <nav class="mb-6" aria-label="breadcrumb">
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4"> <ol class="flex items-center gap-2 text-sm flex-wrap">
<div class="flex-1"> <li><a href="{% url 'agency_list' %}" class="text-gray-500 hover:underline transition flex items-center gap-1">
<h1 class="text-2xl md:text-3xl font-bold mb-2 flex items-center gap-3"> <i data-lucide="building-2" class="w-4 h-4"></i> {% trans "Agencies" %}
<i data-lucide="building" class="w-8 h-8"></i> </a></li>
<li class="text-gray-400">/</li>
<li class="font-semibold" style="color: #9d2235;">{{ title }}</li>
</ol>
</nav>
<!-- Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<h1 class="text-2xl sm:text-3xl font-bold flex items-center gap-3">
<div class="w-12 h-12 rounded-xl flex items-center justify-center" style="background-color: rgba(157, 34, 53, 0.1);">
<i data-lucide="building" class="w-6 h-6" style="color: #9d2235;"></i>
</div>
{{ title }} {{ title }}
</h1> </h1>
<p class="text-white/80 text-sm md:text-base"> <div class="flex gap-2">
{% if agency %}
{% trans "Update agency information" %}
{% else %}
{% trans "Enter details to create a new agency." %}
{% endif %}
</p>
</div>
<div class="flex flex-wrap gap-2">
{% if agency %} {% if agency %}
<a href="{% url 'agency_detail' agency.slug %}" <a href="{% url 'agency_detail' agency.slug %}"
class="inline-flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-white font-medium text-sm transition backdrop-blur-sm touch-target"> class="inline-flex items-center gap-2 px-4 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="eye" class="w-4 h-4"></i> <i data-lucide="eye" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "View Details" %}</span> <span class="hidden sm:inline">{% trans "View Details" %}</span>
</a> </a>
<a href="{% url 'agency_delete' agency.slug %}" <a href="{% url 'agency_delete' agency.slug %}"
class="inline-flex items-center gap-2 px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg text-white font-medium text-sm transition touch-target"> class="inline-flex items-center gap-2 px-4 py-3 rounded-lg font-medium text-white transition-all duration-200"
style="background-color: #dc2626;"
onmouseover="this.style.backgroundColor='#b91c1c'"
onmouseout="this.style.backgroundColor='#dc2626'">
<i data-lucide="trash-2" class="w-4 h-4"></i> <i data-lucide="trash-2" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Delete" %}</span> <span class="hidden sm:inline">{% trans "Delete" %}</span>
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'agency_list' %}" <a href="{% url 'agency_list' %}"
class="inline-flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-white font-medium text-sm transition backdrop-blur-sm touch-target"> class="inline-flex items-center gap-2 px-6 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="arrow-left" class="w-4 h-4"></i> <i data-lucide="arrow-left" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Back to List" %}</span> <span class="hidden sm:inline">{% trans "Back to List" %}</span>
</a> </a>
</div> </div>
</div> </div>
</div>
{% if agency %} {% if agency %}
<!-- Current Agency Info Card --> <!-- Current Agency Info Card -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 mb-6 p-6">
<div class="bg-temple-cream rounded-lg p-4 border-l-4 border-temple-red"> <div class="rounded-lg p-4 border-l-4" style="background-color: #fef2f2; border-color: #9d2235;">
<h6 class="text-temple-dark font-bold mb-3 flex items-center gap-2"> <h6 class="font-bold mb-3 flex items-center gap-2" style="color: #9d2235;">
<i data-lucide="info" class="w-4 h-4"></i> <i data-lucide="info" class="w-4 h-4"></i>
{% trans "Currently Editing" %} {% trans "Currently Editing" %}
</h6> </h6>
@ -71,22 +76,22 @@
<!-- Form Card --> <!-- Form Card -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200"> <div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="border-b border-gray-200 px-6 py-4 bg-gray-50 rounded-t-xl"> <div class="px-6 py-4 border-b border-gray-100" style="background-color: #f8f9fa;">
<h2 class="text-lg font-bold text-temple-red flex items-center gap-2"> <h2 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="file-text" class="w-5 h-5"></i> <i data-lucide="building-2" class="w-5 h-5" style="color: #9d2235;"></i>
{% trans "Agency Information" %} {% trans "Agency Information" %}
</h2> </h2>
</div> </div>
<div class="p-6 md:p-8"> <div class="p-6">
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="bg-red-50 border border-red-200 rounded-xl p-4 mb-6" role="alert"> <div class="mb-6 p-4 rounded-lg bg-red-50 border border-red-200" role="alert">
<div class="flex items-start gap-3"> <div class="flex items-start gap-3">
<i data-lucide="alert-circle" class="w-5 h-5 text-red-600 shrink-0 mt-0.5"></i> <i data-lucide="alert-triangle" class="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5"></i>
<div> <div>
<h5 class="font-bold text-red-800 mb-1">{% trans "Error" %}</h5> <h5 class="font-semibold text-red-800 mb-1">{% trans "Error" %}</h5>
{% for error in form.non_field_errors %} {% for error in form.non_field_errors %}
<p class="text-red-700 text-sm">{{ error }}</p> <p class="text-red-700 mb-0">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -96,231 +101,193 @@
<form method="post" novalidate id="agency-form" class="space-y-6"> <form method="post" novalidate id="agency-form" class="space-y-6">
{% csrf_token %} {% csrf_token %}
<!-- Two Column Form Layout --> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Name --> <!-- Name -->
<div class="space-y-2"> <div>
<label for="{{ form.name.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.name.label }} <span class="text-red-600">*</span> {{ form.name.label }} <span class="text-red-500">*</span>
</label> </label>
<input type="text" <input type="text"
name="name" name="name"
id="{{ form.name.id_for_label }}" id="{{ form.name.id_for_label }}"
value="{{ form.name.value|default:'' }}" value="{{ form.name.value|default:'' }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Agency Name' %}" placeholder="{% trans 'Agency Name' %}"
{% if form.name.field.required %}required{% endif %}> {% if form.name.field.required %}required{% endif %}>
{% if form.name.errors %} {% if form.name.errors %}
{% for error in form.name.errors %} <div class="text-red-600 text-sm mt-1">{{ form.name.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.name.help_text %} {% if form.name.help_text %}
<p class="text-gray-500 text-xs">{{ form.name.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.name.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- Contact Person --> <!-- Contact Person -->
<div class="space-y-2"> <div>
<label for="{{ form.contact_person.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.contact_person.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.contact_person.label }} {{ form.contact_person.label }}
</label> </label>
<input type="text" <input type="text"
name="contact_person" name="contact_person"
id="{{ form.contact_person.id_for_label }}" id="{{ form.contact_person.id_for_label }}"
value="{{ form.contact_person.value|default:'' }}" value="{{ form.contact_person.value|default:'' }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Contact Person' %}"> placeholder="{% trans 'Contact Person' %}">
{% if form.contact_person.errors %} {% if form.contact_person.errors %}
{% for error in form.contact_person.errors %} <div class="text-red-600 text-sm mt-1">{{ form.contact_person.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.contact_person.help_text %} {% if form.contact_person.help_text %}
<p class="text-gray-500 text-xs">{{ form.contact_person.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.contact_person.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- Phone --> <!-- Phone -->
<div class="space-y-2"> <div>
<label for="{{ form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.phone.label }} {{ form.phone.label }}
</label> </label>
<input type="tel" <input type="tel"
name="phone" name="phone"
id="{{ form.phone.id_for_label }}" id="{{ form.phone.id_for_label }}"
value="{{ form.phone.value|default:'' }}" value="{{ form.phone.value|default:'' }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Phone Number' %}"> placeholder="{% trans 'Phone Number' %}">
{% if form.phone.errors %} {% if form.phone.errors %}
{% for error in form.phone.errors %} <div class="text-red-600 text-sm mt-1">{{ form.phone.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.phone.help_text %} {% if form.phone.help_text %}
<p class="text-gray-500 text-xs">{{ form.phone.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.phone.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- Email --> <!-- Email -->
<div class="space-y-2"> <div>
<label for="{{ form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.email.label }} <span class="text-red-600">*</span> {{ form.email.label }} <span class="text-red-500">*</span>
</label> </label>
<input type="email" <input type="email"
name="email" name="email"
id="{{ form.email.id_for_label }}" id="{{ form.email.id_for_label }}"
value="{{ form.email.value|default:'' }}" value="{{ form.email.value|default:'' }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Email Address' %}" placeholder="{% trans 'Email Address' %}"
{% if form.email.field.required %}required{% endif %}> {% if form.email.field.required %}required{% endif %}>
{% if form.email.errors %} {% if form.email.errors %}
{% for error in form.email.errors %} <div class="text-red-600 text-sm mt-1">{{ form.email.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.email.help_text %} {% if form.email.help_text %}
<p class="text-gray-500 text-xs">{{ form.email.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.email.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- Website --> <!-- Website -->
<div class="space-y-2"> <div>
<label for="{{ form.website.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.website.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.website.label }} {{ form.website.label }}
</label> </label>
<input type="url" <input type="url"
name="website" name="website"
id="{{ form.website.id_for_label }}" id="{{ form.website.id_for_label }}"
value="{{ form.website.value|default:'' }}" value="{{ form.website.value|default:'' }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Website URL' %}"> placeholder="{% trans 'Website URL' %}">
{% if form.website.errors %} {% if form.website.errors %}
{% for error in form.website.errors %} <div class="text-red-600 text-sm mt-1">{{ form.website.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.website.help_text %} {% if form.website.help_text %}
<p class="text-gray-500 text-xs">{{ form.website.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.website.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- Country --> <!-- Country -->
<div class="space-y-2"> <div>
<label for="{{ form.country.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.country.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.country.label }} {{ form.country.label }}
</label> </label>
<input type="text" <input type="text"
name="country" name="country"
id="{{ form.country.id_for_label }}" id="{{ form.country.id_for_label }}"
value="{{ form.country.value|default:'' }}" value="{{ form.country.value|default:'' }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Country' %}"> placeholder="{% trans 'Country' %}">
{% if form.country.errors %} {% if form.country.errors %}
{% for error in form.country.errors %} <div class="text-red-600 text-sm mt-1">{{ form.country.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.country.help_text %} {% if form.country.help_text %}
<p class="text-gray-500 text-xs">{{ form.country.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.country.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- City --> <!-- City -->
<div class="space-y-2"> <div>
<label for="{{ form.city.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.city.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.city.label }} {{ form.city.label }}
</label> </label>
<input type="text" <input type="text"
name="city" name="city"
id="{{ form.city.id_for_label }}" id="{{ form.city.id_for_label }}"
value="{{ form.city.value|default:'' }}" value="{{ form.city.value|default:'' }}"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'City' %}"> placeholder="{% trans 'City' %}">
{% if form.city.errors %} {% if form.city.errors %}
{% for error in form.city.errors %} <div class="text-red-600 text-sm mt-1">{{ form.city.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.city.help_text %} {% if form.city.help_text %}
<p class="text-gray-500 text-xs">{{ form.city.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.city.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- Address (Full Width) --> <!-- Address (Full Width) -->
<div class="space-y-2"> <div>
<label for="{{ form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.address.label }} {{ form.address.label }}
</label> </label>
<textarea <textarea
name="address" name="address"
id="{{ form.address.id_for_label }}" id="{{ form.address.id_for_label }}"
rows="3" rows="3"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm resize-none" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition resize-vertical"
placeholder="{% trans 'Street Address' %}">{{ form.address.value|default:'' }}</textarea> placeholder="{% trans 'Street Address' %}">{{ form.address.value|default:'' }}</textarea>
{% if form.address.errors %} {% if form.address.errors %}
{% for error in form.address.errors %} <div class="text-red-600 text-sm mt-1">{{ form.address.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.address.help_text %} {% if form.address.help_text %}
<p class="text-gray-500 text-xs">{{ form.address.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.address.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- Description (Full Width) --> <!-- Description (Full Width) -->
<div class="space-y-2"> <div>
<label for="{{ form.description.id_for_label }}" class="block text-sm font-semibold text-gray-700"> <label for="{{ form.description.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.description.label }} {{ form.description.label }}
</label> </label>
<textarea <textarea
name="description" name="description"
id="{{ form.description.id_for_label }}" id="{{ form.description.id_for_label }}"
rows="4" rows="4"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm resize-none" class="w-full px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition resize-vertical"
placeholder="{% trans 'Agency Description' %}">{{ form.description.value|default:'' }}</textarea> placeholder="{% trans 'Agency Description' %}">{{ form.description.value|default:'' }}</textarea>
{% if form.description.errors %} {% if form.description.errors %}
{% for error in form.description.errors %} <div class="text-red-600 text-sm mt-1">{{ form.description.errors.0 }}</div>
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-3 h-3"></i>
{{ error }}
</p>
{% endfor %}
{% endif %} {% endif %}
{% if form.description.help_text %} {% if form.description.help_text %}
<p class="text-gray-500 text-xs">{{ form.description.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ form.description.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- Submit Button --> <!-- Form Actions -->
<div class="pt-4 border-t border-gray-200"> <div class="flex flex-col sm:flex-row justify-between items-center gap-4 pt-4 border-t border-gray-200">
<a href="{% url 'agency_list' %}"
class="inline-flex items-center gap-2 px-6 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %}
</a>
<button type="submit" <button type="submit"
class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-8 py-3 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold rounded-lg text-sm transition shadow-md hover:shadow-lg transform hover:-translate-y-0.5 touch-target"> class="inline-flex items-center gap-2 px-8 py-3 rounded-lg font-medium text-white transition-all duration-200"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235'">
<i data-lucide="save" class="w-4 h-4"></i> <i data-lucide="save" class="w-4 h-4"></i>
{{ button_text }} {{ button_text }}
</button> </button>
@ -328,101 +295,54 @@
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Reinitialize Lucide icons for dynamically added content // Initialize Lucide icons
if (typeof lucide !== 'undefined') {
lucide.createIcons(); lucide.createIcons();
}
// Form Validation // Form Validation
const form = document.getElementById('agency-form'); const form = document.getElementById('agency-form');
if (form) { if (form) {
form.addEventListener('submit', function(e) { form.addEventListener('submit', function(e) {
const submitBtn = form.querySelector('button[type="submit"]'); const nameInput = document.getElementById('{{ form.name.id_for_label }}');
submitBtn.classList.add('opacity-75', 'cursor-not-allowed'); const emailInput = document.getElementById('{{ form.email.id_for_label }}');
submitBtn.disabled = true; const websiteInput = document.getElementById('{{ form.website.id_for_label }}');
submitBtn.innerHTML = `
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
{% trans 'Saving...' %}
`;
// Basic validation // Name validation
const name = document.getElementById('{{ form.name.id_for_label }}'); if (nameInput && !nameInput.value.trim()) {
if (name && !name.value.trim()) {
e.preventDefault(); e.preventDefault();
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed'); alert("{% trans 'Agency name is required.' %}");
submitBtn.disabled = false; nameInput.focus();
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`; return false;
lucide.createIcons();
alert('{% trans "Agency name is required." %}');
return;
} }
const email = document.getElementById('{{ form.email.id_for_label }}'); // Email validation
if (email && email.value.trim() && !isValidEmail(email.value.trim())) { if (emailInput && emailInput.value.trim()) {
e.preventDefault();
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
submitBtn.disabled = false;
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
lucide.createIcons();
alert('{% trans "Please enter a valid email address." %}');
return;
}
const website = document.getElementById('{{ form.website.id_for_label }}');
if (website && website.value.trim() && !isValidURL(website.value.trim())) {
e.preventDefault();
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
submitBtn.disabled = false;
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
lucide.createIcons();
alert('{% trans "Please enter a valid website URL." %}');
return;
}
});
}
// Email validation helper
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email); if (!emailRegex.test(emailInput.value.trim())) {
} e.preventDefault();
alert("{% trans 'Please enter a valid email address.' %}");
// URL validation helper emailInput.focus();
function isValidURL(url) {
try {
new URL(url);
return true;
} catch (_) {
return false; return false;
} }
} }
// Warn before leaving if changes are made // Website validation
let formChanged = false; if (websiteInput && websiteInput.value.trim()) {
const formInputs = form ? form.querySelectorAll('input, select, textarea') : []; try {
new URL(websiteInput.value.trim());
formInputs.forEach(input => { } catch (_) {
input.addEventListener('change', function() {
formChanged = true;
});
});
window.addEventListener('beforeunload', function(e) {
if (formChanged) {
e.preventDefault(); e.preventDefault();
e.returnValue = '{% trans "You have unsaved changes. Are you sure you want to leave?" %}'; alert("{% trans 'Please enter a valid website URL.' %}");
return e.returnValue; websiteInput.focus();
return false;
}
} }
});
if (form) {
form.addEventListener('submit', function() {
formChanged = false;
}); });
} }
}); });

View File

@ -1,47 +1,77 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n crispy_forms_tags %} {% load static i18n %}
{% block title %}{% trans "Create Application" %} - {{ block.super }}{% endblock %} {% block title %}{% trans "Create Application" %} - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="px-4 py-6"> <div class="max-w-4xl mx-auto py-6 px-4">
<!-- Header Card -->
<div class="bg-gradient-to-br from-temple-red to-red-800 rounded-xl shadow-xl p-6 mb-6 text-white"> <!-- Breadcrumb -->
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4"> <nav class="mb-6" aria-label="breadcrumb">
<div class="flex-1"> <ol class="flex items-center gap-2 text-sm flex-wrap">
<h1 class="text-3xl font-bold mb-2 flex items-center gap-2"> <li><a href="{% url 'application_list' %}" class="text-gray-500 hover:underline transition flex items-center gap-1">
<i data-lucide="user-plus" class="w-8 h-8"></i> <i data-lucide="file-text" class="w-4 h-4"></i> {% trans "Applications" %}
</a></li>
<li class="text-gray-400">/</li>
<li class="font-semibold" style="color: #9d2235;">{% trans "Create Application" %}</li>
</ol>
</nav>
<!-- Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<h1 class="text-2xl sm:text-3xl font-bold flex items-center gap-3">
<div class="w-12 h-12 rounded-xl flex items-center justify-center" style="background-color: rgba(157, 34, 53, 0.1);">
<i data-lucide="file-plus" class="w-6 h-6" style="color: #9d2235;"></i>
</div>
{% trans "Create New Application" %} {% trans "Create New Application" %}
</h1> </h1>
<p class="text-red-100 text-lg">{% trans "Enter details to create a new application record." %}</p>
</div>
<div class="flex gap-2"> <div class="flex gap-2">
<button type="button" class="modal-trigger bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" data-modal="personModal"> <button type="button" id="openPersonModal"
class="inline-flex items-center gap-2 px-4 py-3 rounded-lg font-medium border-2 text-white transition-all duration-200"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235'">
<i data-lucide="user-plus" class="w-4 h-4"></i> <i data-lucide="user-plus" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Create New Applicant" %}</span> <span class="hidden sm:inline">{% trans "Create New Applicant" %}</span>
</button> </button>
<a href="{% url 'application_list' %}" class="bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" title="{% trans 'Back to List' %}"> <a href="{% url 'application_list' %}"
class="inline-flex items-center gap-2 px-6 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="arrow-left" class="w-4 h-4"></i> <i data-lucide="arrow-left" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Back to List" %}</span> <span class="hidden sm:inline">{% trans "Back to List" %}</span>
</a> </a>
</div> </div>
</div> </div>
</div>
<!-- Form Card --> <!-- Form Card -->
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-gray-200"> <div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50"> <div class="px-6 py-4 border-b border-gray-100" style="background-color: #f8f9fa;">
<h2 class="text-xl font-semibold text-temple-dark flex items-center gap-2"> <h2 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="file-text" class="w-5 h-5"></i> <i data-lucide="file-text" class="w-5 h-5" style="color: #9d2235;"></i>
{% trans "Application Information" %} {% trans "Application Information" %}
</h2> </h2>
</div> </div>
<div class="p-6"> <div class="p-6">
<form method="post" enctype="multipart/form-data"> {% if form.non_field_errors %}
<div class="mb-6 p-4 rounded-lg bg-red-50 border border-red-200" role="alert">
<div class="flex items-start gap-3">
<i data-lucide="alert-triangle" class="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5"></i>
<div>
<h5 class="font-semibold text-red-800 mb-1">{% trans "Error" %}</h5>
{% for error in form.non_field_errors %}
<p class="text-red-700 mb-0">{{ error }}</p>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<form method="post" enctype="multipart/form-data" id="application-form" class="space-y-6">
{% csrf_token %} {% csrf_token %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% for field in form %} {% for field in form %}
{% if field.name != 'person' %}
<div> <div>
<label for="{{ field.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ field.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ field.label }} {{ field.label }}
@ -49,44 +79,81 @@
</label> </label>
{{ field }} {{ field }}
{% if field.help_text %} {% if field.help_text %}
<p class="text-sm text-gray-500 mt-1">{{ field.help_text }}</p> <p class="text-xs text-gray-500 mt-1">{{ field.help_text }}</p>
{% endif %} {% endif %}
{% for error in field.errors %} {% for error in field.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
<div class="border-t border-gray-200 mt-6 pt-6"> <!-- Person Selection -->
<button type="submit" class="bg-temple-red hover:bg-red-800 text-white font-semibold px-8 py-3 rounded-xl transition shadow-md hover:shadow-lg flex items-center gap-2"> <div>
<i data-lucide="save" class="w-5 h-5"></i> <label for="{{ form.person.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ form.person.label }} <span class="text-red-500">*</span>
</label>
<div class="flex gap-2">
<select name="person" id="{{ form.person.id_for_label }}"
class="flex-1 px-4 py-3 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition bg-white">
<option value="">{% trans "Select Applicant" %}</option>
{% for choice in form.person.field.queryset %}
<option value="{{ choice.pk }}">{{ choice.get_full_name|default:choice.email }}</option>
{% endfor %}
</select>
<button type="button" id="openPersonModal"
class="inline-flex items-center justify-center px-4 py-3 rounded-xl border-2 text-white transition-all duration-200 whitespace-nowrap"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235'">
<i data-lucide="user-plus" class="w-4 h-4 mr-2"></i>
{% trans "New" %}
</button>
</div>
{% for error in form.person.errors %}
<div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %}
</div>
<!-- Form Actions -->
<div class="flex flex-col sm:flex-row justify-between items-center gap-4 pt-4 border-t border-gray-200">
<a href="{% url 'application_list' %}"
class="inline-flex items-center gap-2 px-6 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %}
</a>
<button type="submit"
class="inline-flex items-center gap-2 px-8 py-3 rounded-lg font-medium text-white transition-all duration-200"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235'">
<i data-lucide="save" class="w-4 h-4"></i>
{% trans "Create Application" %} {% trans "Create Application" %}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<!-- Modal --> <!-- Person Modal -->
<div class="hidden fixed inset-0 z-50 overflow-y-auto" id="personModal" role="dialog" aria-labelledby="personModalLabel"> <div class="hidden fixed inset-0 z-50 overflow-y-auto" id="personModal" role="dialog" aria-labelledby="personModalLabel">
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0"> <div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 bg-black/50 transition-opacity" aria-hidden="true"></div> <div class="fixed inset-0 bg-black/50 transition-opacity" aria-hidden="true"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen sm:align-middle" aria-hidden="true">&#8203;</span> <span class="hidden sm:inline-block sm:align-middle sm:h-screen sm:align-middle" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-2xl text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-3xl sm:w-full"> <div class="inline-block align-bottom bg-white rounded-2xl text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-3xl sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 border-b border-gray-200 flex justify-between items-center"> <div class="px-6 py-4 border-b border-gray-100" style="background-color: #f8f9fa;">
<h3 class="text-lg font-semibold text-gray-900 flex items-center gap-2" id="personModalLabel"> <h3 class="text-lg font-bold flex items-center gap-2" id="personModalLabel">
<i data-lucide="help-circle" class="w-5 h-5 text-temple-red"></i> <i data-lucide="user-plus" class="w-5 h-5" style="color: #9d2235;"></i>
{% trans "Create New Applicant" %} {% trans "Create New Applicant" %}
</h3> </h3>
<button type="button" class="modal-close-btn text-gray-400 hover:text-gray-600 transition">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div class="px-4 pt-5 pb-4 sm:p-6"> <div class="p-6">
<form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"job"}' hx-target="#div_id_person" hx-select="#div_id_person" hx-swap="outerHTML"> <form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"job"}' hx-target="#div_id_person" hx-select="#div_id_person" hx-swap="outerHTML">
{% csrf_token %} {% csrf_token %}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div> <div>
<label for="{{ person_form.first_name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ person_form.first_name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
@ -94,7 +161,7 @@
</label> </label>
{{ person_form.first_name }} {{ person_form.first_name }}
{% for error in person_form.first_name.errors %} {% for error in person_form.first_name.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div> <div>
@ -103,7 +170,7 @@
</label> </label>
{{ person_form.middle_name }} {{ person_form.middle_name }}
{% for error in person_form.middle_name.errors %} {% for error in person_form.middle_name.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div> <div>
@ -112,10 +179,11 @@
</label> </label>
{{ person_form.last_name }} {{ person_form.last_name }}
{% for error in person_form.last_name.errors %} {% for error in person_form.last_name.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div> <div>
<label for="{{ person_form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ person_form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
@ -123,7 +191,7 @@
</label> </label>
{{ person_form.email }} {{ person_form.email }}
{% for error in person_form.email.errors %} {% for error in person_form.email.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div> <div>
@ -132,10 +200,11 @@
</label> </label>
{{ person_form.phone }} {{ person_form.phone }}
{% for error in person_form.phone.errors %} {% for error in person_form.phone.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div> <div>
<label for="{{ person_form.gpa.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ person_form.gpa.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
@ -143,7 +212,7 @@
</label> </label>
{{ person_form.gpa }} {{ person_form.gpa }}
{% for error in person_form.gpa.errors %} {% for error in person_form.gpa.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div> <div>
@ -152,10 +221,11 @@
</label> </label>
{{ person_form.national_id }} {{ person_form.national_id }}
{% for error in person_form.national_id.errors %} {% for error in person_form.national_id.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div> <div>
<label for="{{ person_form.date_of_birth.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ person_form.date_of_birth.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
@ -163,7 +233,7 @@
</label> </label>
{{ person_form.date_of_birth }} {{ person_form.date_of_birth }}
{% for error in person_form.date_of_birth.errors %} {% for error in person_form.date_of_birth.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
<div> <div>
@ -172,61 +242,78 @@
</label> </label>
{{ person_form.nationality }} {{ person_form.nationality }}
{% for error in person_form.nationality.errors %} {% for error in person_form.nationality.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<label for="{{ person_form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2"> <label for="{{ person_form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.address.label }} {{ person_form.address.label }}
</label> </label>
{{ person_form.address }} {{ person_form.address }}
{% for error in person_form.address.errors %} {% for error in person_form.address.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p> <div class="text-red-600 text-sm mt-1">{{ error }}</div>
{% endfor %} {% endfor %}
</div> </div>
</form> </form>
</div> </div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> <div class="px-6 py-4 border-t border-gray-100" style="background-color: #f8f9fa;">
<button type="submit" class="modal-save-btn w-full inline-flex justify-center rounded-xl border border-transparent shadow-sm px-4 py-2 bg-temple-red text-base font-medium text-white hover:bg-red-800 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm"> <div class="flex flex-col sm:flex-row justify-end gap-3">
<i data-lucide="save" class="w-4 h-4 mr-2"></i>{% trans "Save" %} <button type="button" id="closePersonModal"
class="inline-flex items-center justify-center gap-2 px-6 py-3 rounded-lg font-medium border-2 border-gray-200 text-gray-700 hover:bg-gray-50 transition-all duration-200">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %}
</button> </button>
<button type="button" class="modal-close-btn mt-3 w-full inline-flex justify-center rounded-xl border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> <button type="submit" form="person_form"
<i data-lucide="x" class="w-4 h-4 mr-2"></i>{% trans "Close" %} class="inline-flex items-center justify-center gap-2 px-8 py-3 rounded-lg font-medium text-white transition-all duration-200"
style="background-color: #9d2235;"
onmouseover="this.style.backgroundColor='#7a1a29'"
onmouseout="this.style.backgroundColor='#9d2235'">
<i data-lucide="save" class="w-4 h-4"></i>
{% trans "Create Applicant" %}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
{% endblock %}
{% block customJS %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons // Initialize Lucide icons
if (typeof lucide !== 'undefined') {
lucide.createIcons(); lucide.createIcons();
}
// Apply consistent form styling
const formInputs = document.querySelectorAll('#application-form input, #application-form select, #application-form textarea, #person_form input, #person_form select, #person_form textarea');
formInputs.forEach(input => {
if (!input.classList.contains('hidden')) {
input.classList.add('w-full', 'px-4', 'py-3', 'border', 'border-gray-200', 'rounded-xl', 'text-sm', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition', 'bg-white');
}
});
// Modal functionality // Modal functionality
const modal = document.getElementById('personModal'); const modal = document.getElementById('personModal');
const openBtns = document.querySelectorAll('#openPersonModal');
const closeBtns = document.querySelectorAll('#closePersonModal');
let isModalOpen = false; let isModalOpen = false;
// Open modal buttons // Open modal
const openModalBtns = document.querySelectorAll('.modal-trigger'); openBtns.forEach(btn => {
openModalBtns.forEach(btn => {
btn.addEventListener('click', function(e) { btn.addEventListener('click', function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
if (modal) { if (modal) {
modal.classList.remove('hidden'); modal.classList.remove('hidden');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
isModalOpen = true; isModalOpen = true;
setTimeout(() => lucide.createIcons(), 100);
} }
}); });
}); });
// Close modal buttons // Close modal
document.querySelectorAll('.modal-close-btn').forEach(btn => { closeBtns.forEach(btn => {
btn.addEventListener('click', function(e) { btn.addEventListener('click', function(e) {
e.preventDefault(); e.preventDefault();
if (modal && isModalOpen) { if (modal && isModalOpen) {
@ -257,21 +344,30 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
// Add form styling // Form validation
const formInputs = document.querySelectorAll('input, select, textarea'); const form = document.getElementById('application-form');
formInputs.forEach(input => { if (form) {
input.classList.add('w-full', 'px-3', 'py-2.5', 'border', 'border-gray-300', 'rounded-lg', 'focus:ring-2', 'focus:ring-temple-red', 'focus:border-transparent', 'transition'); form.addEventListener('submit', function(e) {
const personSelect = document.getElementById('id_person');
if (!personSelect.value) {
e.preventDefault();
alert("{% trans 'Please select an applicant.' %}");
personSelect.focus();
return false;
}
}); });
}
// Reinitialize Lucide icons after HTMX updates // Reinitialize Lucide icons after HTMX updates
document.body.addEventListener('htmx:afterSwap', function(evt) { document.body.addEventListener('htmx:afterSwap', function(evt) {
lucide.createIcons(); lucide.createIcons();
// Re-apply form styling to new elements // Re-apply form styling to new elements
const newFormInputs = evt.detail.xhr.response.querySelectorAll ? const newFormInputs = document.querySelectorAll('#person_form input, #person_form select, #person_form textarea');
evt.detail.xhr.response.querySelectorAll('input, select, textarea') : [];
newFormInputs.forEach(input => { newFormInputs.forEach(input => {
input.classList.add('w-full', 'px-3', 'py-2.5', 'border', 'border-gray-300', 'rounded-lg', 'focus:ring-2', 'focus:ring-temple-red', 'focus:border-transparent', 'transition'); if (!input.classList.contains('hidden')) {
input.classList.add('w-full', 'px-4', 'py-3', 'border', 'border-gray-200', 'rounded-xl', 'text-sm', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition', 'bg-white');
}
}); });
}); });
}); });

View File

@ -95,7 +95,7 @@
</div> </div>
<button id="changeStage" type="submit" <button id="changeStage" type="submit"
class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2"> class="w-full sm:w-auto px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2">
<i data-lucide="arrow-right" class="w-4 h-4"></i> <i data-lucide="arrow-right" class="w-4 h-4"></i>
{% trans "Change Stage" %} {% trans "Change Stage" %}
</button> </button>
@ -105,7 +105,7 @@
<!-- Email Button --> <!-- Email Button -->
<button id="emailBotton" type="button" <button id="emailBotton" type="button"
class="flex-1 w-full sm:w-auto px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium flex items-center justify-center gap-2" class="w-full sm:w-auto px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium flex items-center justify-center gap-2"
onclick="openEmailModal()" onclick="openEmailModal()"
title="{% trans 'Email Participants' %}"> title="{% trans 'Email Participants' %}">
<i data-lucide="mail" class="w-4 h-4"></i> <i data-lucide="mail" class="w-4 h-4"></i>

View File

@ -46,7 +46,7 @@
<i data-lucide="users" class="w-5 h-5"></i> <i data-lucide="users" class="w-5 h-5"></i>
{% trans "Application List" %} {% trans "Application List" %}
<span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full"> <span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full">
{{ applications|length }} {{ applications|length }} / {{ total_candidates }} {% trans "Total" %}
</span> </span>
</h2> </h2>
</div> </div>
@ -56,54 +56,41 @@
<!-- Bulk Action Bar --> <!-- Bulk Action Bar -->
{% if applications %} {% if applications %}
<div class="p-3 sm:p-4 bg-gray-50 border-b border-gray-200"> <div class="p-3 sm:p-4 bg-gray-50 border-b border-gray-200">
<div class="flex flex-col sm:flex-row gap-3 sm:gap-4 items-end">
<!-- Stage Change Form -->
<form hx-boost="true" hx-include="#application-form" <form hx-boost="true" hx-include="#application-form"
action="{% url 'application_update_status' job.slug %}" action="{% url 'application_update_status' job.slug %}"
method="post" method="post"
class="flex items-end gap-2 flex-1 w-full sm:w-auto"> class="flex flex-col sm:flex-row gap-3 sm:gap-4 items-end">
{% csrf_token %} {% csrf_token %}
<div class="flex-1 w-full sm:w-auto"> <div class="flex-1 w-full sm:w-auto">
<select name="mark_as" id="update_status" <select name="mark_as" id="update_status"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent text-sm"> class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent text-sm">
<option selected>----------</option> <option value="" selected>----------</option>
<option value="Document Review">{% trans "To Document Review" %}</option> <option value="Document Review">{% trans "To Document Review" %}</option>
<option value="Exam">{% trans "To Exam" %}</option> <option value="Exam">{% trans "To Exam" %}</option>
</select> </select>
</div> </div>
<button id="changeStage" type="submit" <button id="changeStage" type="submit"
class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2"> class="w-full sm:w-auto px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed">
<i data-lucide="arrow-right" class="w-4 h-4"></i> <i data-lucide="arrow-right" class="w-4 h-4"></i>
{% trans "Change Stage" %} {% trans "Change Stage" %}
</button> </button>
</form>
<div class="hidden sm:block w-px h-7 bg-gray-300"></div> <a href="{% url 'schedule_interviews' job.slug %}"
class="w-full sm:w-auto px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
<!-- Bulk Schedule Form --> id="scheduleInterview">
<form hx-boost="true" hx-include="#application-form"
action="{% url 'schedule_interviews' job.slug %}"
method="get"
class="flex-1 w-full sm:w-auto">
<button id="scheduleInterview" type="submit"
class="w-full sm:w-auto px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2">
<i data-lucide="calendar-plus" class="w-4 h-4"></i> <i data-lucide="calendar-plus" class="w-4 h-4"></i>
{% trans "Bulk Schedule Interviews" %} {% trans "Bulk Schedule Interviews" %}
</button> </a>
</form>
<div class="hidden sm:block w-px h-7 bg-gray-300"></div>
<!-- Email Button -->
<button id="emailBotton" type="button" <button id="emailBotton" type="button"
class="flex-1 w-full sm:w-auto px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium flex items-center justify-center gap-2" class="w-full sm:w-auto px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
onclick="openEmailModal()" onclick="openEmailModal()"
title="{% trans 'Email Participants' %}"> title="{% trans 'Email Participants' %}">
<i data-lucide="mail" class="w-4 h-4"></i> <i data-lucide="mail" class="w-4 h-4"></i>
</button> </button>
</div> </form>
</div> </div>
{% endif %} {% endif %}
@ -192,7 +179,6 @@
<i data-lucide="plus" class="w-3 h-3"></i> <i data-lucide="plus" class="w-3 h-3"></i>
</button> </button>
{% else %} {% else %}
{% if application.interview_status %}
<button type="button" <button type="button"
class="px-3 py-1.5 {% if application.interview_status == 'Passed' %}bg-green-500 hover:bg-green-600 text-white{% else %}bg-red-500 hover:bg-red-600 text-white{% endif %} rounded-lg transition text-xs font-medium" class="px-3 py-1.5 {% if application.interview_status == 'Passed' %}bg-green-500 hover:bg-green-600 text-white{% else %}bg-red-500 hover:bg-red-600 text-white{% endif %} rounded-lg transition text-xs font-medium"
onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}')" onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}')"
@ -200,39 +186,11 @@
{{ application.interview_status }} {{ application.interview_status }}
</button> </button>
{% endif %} {% endif %}
{% endif %}
</td> </td>
<td class="px-4 py-3 border-b border-gray-200 text-center"> <td class="px-4 py-3 text-center border-b border-gray-200">
<div class="flex items-center justify-center gap-1"> <div class="flex items-center justify-center gap-1">
{% if application.get_latest_meeting %}
{% if application.get_latest_meeting.location_type == 'Remote' %}
<button type="button"
class="px-2 py-1.5 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-xs font-medium"
onclick="openCandidateModal('{% url 'reschedule_meeting_for_application' job.slug application.pk application.get_latest_meeting.pk %}')"
title="{% trans 'Reschedule' %}">
<i data-lucide="refresh-ccw" class="w-3 h-3"></i>
</button>
<button type="button"
class="px-2 py-1.5 border-2 border-red-300 text-red-600 rounded-lg hover:bg-red-500 hover:text-white transition text-xs font-medium"
onclick="openCandidateModal('{% url 'schedule_meeting_for_application' job.slug application.pk application.get_latest_meeting.pk %}')"
title="{% trans 'Delete Meeting' %}">
<i data-lucide="trash-2" class="w-3 h-3"></i>
</button>
{% else %}
<button type="button"
class="px-2 py-1.5 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-xs font-medium"
onclick="openCandidateModal('{% url 'reschedule_onsite_meeting' job.slug application.pk application.get_latest_meeting.pk %}')"
title="{% trans 'Reschedule' %}">
<i data-lucide="refresh-ccw" class="w-3 h-3"></i>
</button>
<button type="button"
class="px-2 py-1.5 border-2 border-red-300 text-red-600 rounded-lg hover:bg-red-500 hover:text-white transition text-xs font-medium"
onclick="openCandidateModal('{% url 'delete_onsite_meeting_for_application' job.slug application.pk application.get_latest_meeting.pk %}')"
title="{% trans 'Delete Meeting' %}">
<i data-lucide="trash-2" class="w-3 h-3"></i>
</button>
{% endif %}
{% else %}
<button type="button" <button type="button"
class="px-3 py-1.5 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-xs font-medium" class="px-3 py-1.5 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-xs font-medium"
onclick="openCandidateModal('{% url 'interview_create_type_selection' application.slug %}')" onclick="openCandidateModal('{% url 'interview_create_type_selection' application.slug %}')"
@ -240,7 +198,7 @@
<i data-lucide="calendar-plus" class="w-3 h-3 inline mr-1"></i> <i data-lucide="calendar-plus" class="w-3 h-3 inline mr-1"></i>
{% trans "Schedule Interview" %} {% trans "Schedule Interview" %}
</button> </button>
{% endif %}
</div> </div>
</td> </td>
</tr> </tr>
@ -342,6 +300,9 @@
</div> </div>
</div> </div>
<!-- Include Note Modal -->
{% include "recruitment/partials/note_modal.html" %}
<!-- Stage Confirmation Modal --> <!-- Stage Confirmation Modal -->
<div id="stageConfirmationModal" class="fixed inset-0 z-50 hidden" aria-labelledby="stageConfirmationModalLabel" role="dialog" aria-modal="true"> <div id="stageConfirmationModal" class="fixed inset-0 z-50 hidden" aria-labelledby="stageConfirmationModalLabel" role="dialog" aria-modal="true">
<!-- Backdrop --> <!-- Backdrop -->
@ -540,12 +501,11 @@
const changeStageButton = document.getElementById('changeStage'); const changeStageButton = document.getElementById('changeStage');
const emailButton = document.getElementById('emailBotton'); const emailButton = document.getElementById('emailBotton');
const updateStatus = document.getElementById('update_status'); const updateStatus = document.getElementById('update_status');
const scheduleInterviewButton = document.getElementById('scheduleInterview');
const confirmStageChangeButton = document.getElementById('confirmStageChangeButton'); const confirmStageChangeButton = document.getElementById('confirmStageChangeButton');
let isConfirmed = false; let isConfirmed = false;
if (selectAllCheckbox) { if (selectAllCheckbox) {
// Function to safely update header checkbox state // Function to safely update the header checkbox state
function updateSelectAllState() { function updateSelectAllState() {
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length; const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
const totalCount = rowCheckboxes.length; const totalCount = rowCheckboxes.length;
@ -556,21 +516,18 @@
if (changeStageButton) changeStageButton.disabled = true; if (changeStageButton) changeStageButton.disabled = true;
if (emailButton) emailButton.disabled = true; if (emailButton) emailButton.disabled = true;
if (updateStatus) updateStatus.disabled = true; if (updateStatus) updateStatus.disabled = true;
if (scheduleInterviewButton) scheduleInterviewButton.disabled = true;
} else if (checkedCount === totalCount) { } else if (checkedCount === totalCount) {
selectAllCheckbox.checked = true; selectAllCheckbox.checked = true;
selectAllCheckbox.indeterminate = false; selectAllCheckbox.indeterminate = false;
if (changeStageButton) changeStageButton.disabled = false; if (changeStageButton) changeStageButton.disabled = false;
if (emailButton) emailButton.disabled = false; if (emailButton) emailButton.disabled = false;
if (updateStatus) updateStatus.disabled = false; if (updateStatus) updateStatus.disabled = false;
if (scheduleInterviewButton) scheduleInterviewButton.disabled = false;
} else { } else {
selectAllCheckbox.checked = false; selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = true; selectAllCheckbox.indeterminate = true;
if (changeStageButton) changeStageButton.disabled = false; if (changeStageButton) changeStageButton.disabled = false;
if (emailButton) emailButton.disabled = false; if (emailButton) emailButton.disabled = false;
if (updateStatus) updateStatus.disabled = false; if (updateStatus) updateStatus.disabled = false;
if (scheduleInterviewButton) scheduleInterviewButton.disabled = false;
} }
} }

View File

@ -72,7 +72,7 @@
</div> </div>
<button id="changeStage" type="submit" <button id="changeStage" type="submit"
class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2"> class="w-full sm:w-auto px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2">
<i data-lucide="arrow-right" class="w-4 h-4"></i> <i data-lucide="arrow-right" class="w-4 h-4"></i>
{% trans "Change Stage" %} {% trans "Change Stage" %}
</button> </button>
@ -82,7 +82,7 @@
<!-- Email Button --> <!-- Email Button -->
<button id="emailBotton" type="button" <button id="emailBotton" type="button"
class="flex-1 w-full sm:w-auto px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium flex items-center justify-center gap-2" class="w-full sm:w-auto px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium flex items-center justify-center gap-2"
onclick="openEmailModal()" onclick="openEmailModal()"
title="{% trans 'Email Participants' %}"> title="{% trans 'Email Participants' %}">
<i data-lucide="mail" class="w-4 h-4"></i> <i data-lucide="mail" class="w-4 h-4"></i>

View File

@ -1,18 +1,28 @@
<td class="text-center" id="status-result-{{ application.pk}}"> {% load i18n %}
<td class="px-4 py-3 text-center border-b border-gray-200" id="status-result-{{ application.pk }}">
{% if application.exam_status %} {% if application.exam_status %}
<button type="button" class="btn btn-{% if application.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm" <button type="button"
data-bs-toggle="modal" class="px-3 py-1.5 {% if application.exam_status == 'Passed' %}bg-green-500 hover:bg-green-600 text-white{% else %}bg-red-500 hover:bg-red-600 text-white{% endif %} rounded-lg transition text-xs font-medium"
data-bs-target="#candidateviewModal" onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}')"
hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}" title="{% trans 'Update Exam Status' %}">
hx-target="#candidateviewModalBody"
title="Pass Exam">
{{ application.exam_status }} {{ application.exam_status }}
</button> </button>
{% else %} {% else %}
-- <button type="button"
class="px-3 py-1.5 bg-yellow-400 hover:bg-yellow-500 text-yellow-900 rounded-lg transition text-xs font-medium"
onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}')"
title="{% trans 'Add Exam Result' %}">
<i data-lucide="plus" class="w-3 h-3"></i>
</button>
{% endif %} {% endif %}
</td> </td>
<td id="exam-score-{{ application.pk}}" hx-swap-oob="true"> <td class="px-4 py-3 text-center border-b border-gray-200" id="exam-score-{{ application.pk }}" hx-swap-oob="true">
{{application.exam_score|default:"--"}} {{ application.exam_score|default:"--" }}
</td> </td>
<script>
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
</script>

View File

@ -1,25 +1,24 @@
<td class="text-center" id="interview-result-{{ application.pk}}"> {% load i18n %}
<td class="px-4 py-3 text-center border-b border-gray-200" id="interview-result-{{ application.pk }}">
{% if not application.interview_status %} {% if not application.interview_status %}
<button type="button" class="btn btn-warning btn-sm" <button type="button"
data-bs-toggle="modal" class="px-3 py-1.5 bg-yellow-400 hover:bg-yellow-500 text-yellow-900 rounded-lg transition text-xs font-medium"
data-bs-target="#candidateviewModal" onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}')"
hx-get="{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}" title="{% trans 'Add Result' %}">
hx-target="#candidateviewModalBody" <i data-lucide="plus" class="w-3 h-3"></i>
title="Pass Exam">
<i class="fas fa-plus"></i>
</button> </button>
{% else %} {% else %}
{% if application.interview_status %} <button type="button"
<button type="button" class="btn btn-{% if application.interview_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm" class="px-3 py-1.5 {% if application.interview_status == 'Passed' %}bg-green-500 hover:bg-green-600 text-white{% else %}bg-red-500 hover:bg-red-600 text-white{% endif %} rounded-lg transition text-xs font-medium"
data-bs-toggle="modal" onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}')"
data-bs-target="#candidateviewModal" title="{% trans 'Update Result' %}">
hx-get="{% url 'update_application_status' job.slug application.slug 'interview' 'passed' %}"
hx-target="#candidateviewModalBody"
title="Pass Exam">
{{ application.interview_status }} {{ application.interview_status }}
</button> </button>
{% else %}
--
{% endif %}
{% endif %} {% endif %}
</td> </td>
<script>
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
</script>

View File

@ -1,16 +1,22 @@
{% load static i18n %} {% load static i18n %}
<div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true"> <div id="noteModal" class="fixed inset-0 z-50 hidden" role="dialog" aria-labelledby="noteModalLabel" aria-modal="true">
<div class="modal-dialog modal-lg" role="document"> <!-- Backdrop -->
<div class="modal-content bg-white border border-gray-200 rounded-xl shadow-lg"> <div class="fixed inset-0 bg-black/50 backdrop-blur-sm transition-opacity" onclick="closeModal('noteModal')"></div>
<div class="modal-header flex justify-between items-center px-6 py-4 border-b border-gray-200">
<h5 class="modal-title text-lg font-bold text-temple-dark" id="noteModalLabel"> <!-- Modal Content -->
<div class="fixed inset-0 flex items-center justify-center p-4 pointer-events-none">
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden pointer-events-auto flex flex-col">
<!-- Header -->
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
<h5 class="text-lg font-bold text-temple-dark" id="noteModalLabel">
<i data-lucide="sticky-note" class="w-5 h-5 inline mr-2"></i>{% trans "Add Note" %} <i data-lucide="sticky-note" class="w-5 h-5 inline mr-2"></i>{% trans "Add Note" %}
</h5> </h5>
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" data-bs-dismiss="modal" aria-label="Close"> <button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeModal('noteModal')" aria-label="{% trans 'Close' %}">
<i data-lucide="x" class="w-5 h-5"></i> <i data-lucide="x" class="w-5 h-5"></i>
</button> </button>
</div> </div>
<div class="modal-body notemodal p-6"> <!-- Body -->
<div class="notemodal flex-1 overflow-y-auto p-6">
<!-- Content will be loaded via HTMX --> <!-- Content will be loaded via HTMX -->
</div> </div>
</div> </div>

View File

@ -1,25 +1,24 @@
<td class="text-center" id="status-result-{{ application.pk}}"> {% load i18n %}
<td class="px-4 py-3 text-center border-b border-gray-200" id="status-result-{{ application.pk }}">
{% if not application.offer_status %} {% if not application.offer_status %}
<button type="button" class="btn btn-warning btn-sm" <button type="button"
data-bs-toggle="modal" class="px-3 py-1.5 bg-yellow-400 hover:bg-yellow-500 text-yellow-900 rounded-lg transition text-xs font-medium"
data-bs-target="#candidateviewModal" onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'offer' 'passed' %}')"
hx-get="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" title="{% trans 'Add Offer Status' %}">
hx-target="#candidateviewModalBody" <i data-lucide="plus" class="w-3 h-3"></i>
title="Pass Exam">
<i class="fas fa-plus"></i>
</button> </button>
{% else %} {% else %}
{% if application.offer_status %} <button type="button"
<button type="button" class="btn btn-{% if application.offer_status == 'Accepted' %}success{% else %}danger{% endif %} btn-sm" class="px-3 py-1.5 {% if application.offer_status == 'Accepted' %}bg-green-500 hover:bg-green-600 text-white{% else %}bg-red-500 hover:bg-red-600 text-white{% endif %} rounded-lg transition text-xs font-medium"
data-bs-toggle="modal" onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'offer' 'passed' %}')"
data-bs-target="#candidateviewModal" title="{% trans 'Update Offer Status' %}">
hx-get="{% url 'update_application_status' job.slug application.slug 'offer' 'Rejected' %}"
hx-target="#candidateviewModalBody"
title="Pass Exam">
{{ application.offer_status }} {{ application.offer_status }}
</button> </button>
{% else %}
--
{% endif %}
{% endif %} {% endif %}
</td> </td>
<script>
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
</script>

View File

@ -4,297 +4,295 @@
{% block title %}{{ source.name }} - {% trans "Source Details" %}{% endblock %} {% block title %}{{ source.name }} - {% trans "Source Details" %}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<nav aria-label="breadcrumb">
<ol class="breadcrumb"> <!-- Breadcrumb -->
<li class="breadcrumb-item"><a href="{% url 'source_list' %}" class="text-decoration-none text-secondary">{% trans "Souce Settings" %}</a></li> <nav aria-label="breadcrumb" class="mb-6">
<li class="breadcrumb-item active" aria-current="page" style=" <ol class="flex items-center gap-2 text-sm">
color: #F43B5E; /* Rosy Accent Color */ <li>
font-weight: 600; <a href="{% url 'source_list' %}"
">{% trans "Source Detail" %}</li> class="text-gray-500 hover:text-temple-red transition-colors flex items-center gap-1">
<i data-lucide="database" class="w-4 h-4"></i>
{% trans "Source Settings" %}
</a>
</li>
<li class="text-gray-400">/</li>
<li class="text-temple-red font-semibold">{% trans "Source Detail" %}</li>
</ol> </ol>
</nav> </nav>
<div class="row">
<div class="col-12"> <!-- Page Header -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<h1 class="h3 mb-0">{{ source.name }}</h1> <div>
<div class="btn-group"> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
<a href="{% url 'source_update' source.pk %}" class="btn btn-outline-secondary"> <div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i class="fas fa-edit"></i> {% trans "Edit" %} <i data-lucide="database" class="w-6 h-6 text-temple-red"></i>
</a> </div>
{% comment %} <a href="{% url 'generate_api_keys' source.pk %}" class="btn btn-warning"> {{ source.name }}
<i class="fas fa-key"></i> Generate Keys </h1>
</a> {% endcomment %} <p class="text-gray-500 text-sm sm:text-base">
{% trans "View and manage integration source details" %}
</p>
</div>
<div class="flex gap-3">
<button id="toggle-source-status" <button id="toggle-source-status"
type="button" type="button"
class="btn btn-outline-{{ source.is_active|yesno:'warning,success' }}" class="inline-flex items-center gap-2 px-5 py-3 border-2 {% if source.is_active %}border-yellow-500 text-yellow-600 hover:bg-yellow-50{% else %}border-emerald-500 text-emerald-600 hover:bg-emerald-50{% endif %} rounded-xl font-semibold transition"
hx-post="{% url 'toggle_source_status' source.pk %}" hx-post="{% url 'toggle_source_status' source.pk %}"
hx-target="#toggle-source-status" hx-target="#toggle-source-status"
hx-select="#toggle-source-status" hx-select="#toggle-source-status"
hx-select-oob="#source-status" hx-select-oob="#source-status"
hx-swap="outerHTML" hx-swap="outerHTML"
hx-confirm="{% blocktrans %}Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?{% endblocktrans %}"> hx-confirm="{% blocktrans %}Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?{% endblocktrans %}">
<i class="fas fa-{{ source.is_active|yesno:'pause,play' }}"></i> <i data-lucide="{% if source.is_active %}pause{% else %}play{% endif %}" class="w-5 h-5"></i>
{{ source.is_active|yesno:'Deactivate,Activate' }} {% if source.is_active %}{% trans "Deactivate" %}{% else %}{% trans "Activate" %}{% endif %}
</button> </button>
<a href="{% url 'source_delete' source.pk %}" class="btn btn-outline-danger"> <a href="{% url 'source_update' source.pk %}"
<i class="fas fa-trash"></i> {% trans "Delete" %} class="inline-flex items-center gap-2 px-5 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300 transition">
<i data-lucide="edit-3" class="w-5 h-5"></i>
{% trans "Edit" %}
</a>
<a href="{% url 'source_delete' source.pk %}"
class="inline-flex items-center gap-2 px-5 py-3 bg-red-500 text-white rounded-xl font-semibold shadow-lg hover:shadow-xl transition">
<i data-lucide="trash-2" class="w-5 h-5"></i>
{% trans "Delete" %}
</a> </a>
</div> </div>
</div> </div>
<!-- Source Information --> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="row"> <!-- Main Content Column -->
<div class="col-md-8"> <div class="lg:col-span-2 space-y-6">
<div class="card mb-4">
<div class="card-header"> <!-- Source Information Card -->
<h6 class="mb-0">{% trans "Source Information" %}</h6> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="info" class="w-5 h-5 text-temple-red"></i>
</div> </div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label text-muted">{% trans "Name" %}</label>
<div class="fw-bold">{{ source.name }}</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label text-muted">{% trans "Type" %}</label>
<div> <div>
<span class="badge bg-info">{{ source.get_source_type_display }}</span> <h2 class="text-lg font-bold text-gray-900">{% trans "Source Information" %}</h2>
<p class="text-sm text-gray-500">{% trans "Basic integration details" %}</p>
</div> </div>
</div> </div>
</div> </div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i data-lucide="tag" class="w-4 h-4 text-gray-400"></i>
{% trans "Name" %}
</label>
<div class="text-gray-900 font-medium">{{ source.name }}</div>
</div>
<div>
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i data-lucide="server" class="w-4 h-4 text-gray-400"></i>
{% trans "Type" %}
</label>
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2.5 py-1 rounded-full bg-temple-red/10 text-temple-red border border-temple-red">
{{ source.get_source_type_display }}
</span>
</div>
</div> </div>
{% if source.description %} {% if source.description %}
<div class="mb-3"> <div class="mt-6">
<label class="form-label text-muted">{{ _("Description") }}</label> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div>{{ source.description|linebreaks }}</div> <i data-lucide="align-left" class="w-4 h-4 text-gray-400"></i>
{% trans "Description" %}
</label>
<div class="text-gray-700 bg-gray-50 rounded-xl p-4">{{ source.description|linebreaks }}</div>
</div> </div>
{% endif %} {% endif %}
<div class="row"> <div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-md-6"> <div>
<div class="mb-3"> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<label class="form-label text-muted">{{ _("Contact Email") }}</label> <i data-lucide="mail" class="w-4 h-4 text-gray-400"></i>
{% trans "Contact Email" %}
</label>
<div> <div>
{% if source.contact_email %} {% if source.contact_email %}
<a href="mailto:{{ source.contact_email }}">{{ source.contact_email }}</a> <a href="mailto:{{ source.contact_email }}" class="text-temple-red hover:text-[#7a1a29] transition-colors">{{ source.contact_email }}</a>
{% else %} {% else %}
<span class="text-muted">{{ _("Not specified") }}</span> <span class="text-gray-400">{% trans "Not specified" %}</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> <div>
<div class="col-md-6"> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div class="mb-3"> <i data-lucide="phone" class="w-4 h-4 text-gray-400"></i>
<label class="form-label text-muted">{{ _("Contact Phone") }}</label> {% trans "Contact Phone" %}
</label>
<div> <div>
{% if source.contact_phone %} {% if source.contact_phone %}
{{ source.contact_phone }} <span class="text-gray-700">{{ source.contact_phone }}</span>
{% else %} {% else %}
<span class="text-muted">{{ _("Not specified") }}</span> <span class="text-gray-400">{% trans "Not specified" %}</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="row"> <div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-md-6"> <div>
<div class="mb-3"> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<label class="form-label text-muted">{{ _("Status") }}</label> <i data-lucide="power" class="w-4 h-4 text-gray-400"></i>
{% trans "Status" %}
</label>
<div id="source-status"> <div id="source-status">
{% if source.is_active %} {% if source.is_active %}
<span class="badge bg-success">{{ _("Active") }}</span> <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-100 text-emerald-700 font-semibold text-sm">
<i data-lucide="check-circle-2" class="w-4 h-4"></i> {% trans "Active" %}
</span>
{% else %} {% else %}
<span class="badge bg-secondary">{{ _("Inactive") }}</span> <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 text-gray-600 font-semibold text-sm">
<i data-lucide="x-circle" class="w-4 h-4"></i> {% trans "Inactive" %}
</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> <div>
<div class="col-md-6"> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div class="mb-3"> <i data-lucide="shield" class="w-4 h-4 text-gray-400"></i>
<label class="form-label text-muted">{% trans "Requires Authentication" %}</label> {% trans "Requires Authentication" %}
</label>
<div> <div>
{% if source.requires_auth %} {% if source.requires_auth %}
<span class="badge bg-warning">{% trans "Yes" %}</span> <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-yellow-100 text-yellow-700 font-semibold text-sm">
<i data-lucide="lock" class="w-4 h-4"></i> {% trans "Yes" %}
</span>
{% else %} {% else %}
<span class="badge bg-secondary">{% trans "No" %}</span> <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-gray-100 text-gray-600 font-semibold text-sm">
<i data-lucide="unlock" class="w-4 h-4"></i> {% trans "No" %}
</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div>
{% if source.webhook_url %} {% if source.webhook_url %}
<div class="mb-3"> <div class="mt-6">
<label class="form-label text-muted">{% trans "Webhook URL" %}</label> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div><code>{{ source.webhook_url }}</code></div> <i data-lucide="link" class="w-4 h-4 text-gray-400"></i>
{% trans "Webhook URL" %}
</label>
<div class="bg-gray-50 rounded-xl p-4">
<code class="text-sm text-temple-red break-all">{{ source.webhook_url }}</code>
</div>
</div> </div>
{% endif %} {% endif %}
{% if source.api_timeout %} {% if source.api_timeout %}
<div class="mb-3"> <div class="mt-6">
<label class="form-label text-muted">{{ _("API Timeout") }}</label> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div>{{ source.api_timeout }} {{ _("seconds") }}</div> <i data-lucide="clock" class="w-4 h-4 text-gray-400"></i>
{% trans "API Timeout" %}
</label>
<div class="text-gray-700">{{ source.api_timeout }} {% trans "seconds" %}</div>
</div> </div>
{% endif %} {% endif %}
{% if source.notes %} {% if source.notes %}
<div class="mb-3"> <div class="mt-6">
<label class="form-label text-muted">{{ _("Notes") }}</label> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div>{{ source.notes|linebreaks }}</div> <i data-lucide="file-text" class="w-4 h-4 text-gray-400"></i>
{% trans "Notes" %}
</label>
<div class="text-gray-700 bg-gray-50 rounded-xl p-4">{{ source.notes|linebreaks }}</div>
</div> </div>
{% endif %} {% endif %}
<div class="row"> <div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-6 pt-6 border-t border-gray-100">
<div class="col-md-6"> <div>
<div class="mb-3"> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<label class="form-label text-muted">{{ _("Created") }}</label> <i data-lucide="calendar-plus" class="w-4 h-4 text-gray-400"></i>
<div>{{ source.created_at|date:"M d, Y H:i" }}</div> {% trans "Created" %}
</label>
<div class="text-gray-700">{{ source.created_at|date:"M d, Y H:i" }}</div>
</div> </div>
</div> <div>
<div class="col-md-6"> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div class="mb-3"> <i data-lucide="refresh-cw" class="w-4 h-4 text-gray-400"></i>
<label class="form-label text-muted">{{ _("Last Updated") }}</label> {% trans "Last Updated" %}
<div>{{ source.updated_at|date:"M d, Y H:i" }}</div> </label>
</div> <div class="text-gray-700">{{ source.updated_at|date:"M d, Y H:i" }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<!-- API Credentials -->
<div class="card mb-4">
<div class="card-header">
<h6 class="mb-0">{% trans "API Credentials" %}</h6>
</div>
<div id="api-credentials" class="card-body">
<div class="mb-3">
<label class="form-label text-muted">{% trans "API Key" %}</label>
<div class="input-group">
<input type="text" class="form-control" value="{{ source.api_key }}" readonly>
<button type="button" class="btn btn-outline-secondary"
hx-post="{% url 'copy_to_clipboard' %}"
hx-vals='{"text": "{{ source.api_key }}"}'
title="{% trans "Copy to clipboard" %}">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label text-muted">{% trans "API Secret" %}</label>
<div class="input-group">
<input type="password" class="form-control" value="{{ source.api_secret }}" readonly id="api-secret">
<button type="button" class="btn btn-outline-secondary" onclick="toggleSecretVisibility()">
<i class="fas fa-eye" id="secret-toggle-icon"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
hx-post="{% url 'copy_to_clipboard' %}"
hx-vals='{"text": "{{ source.api_secret }}"}'
title="{% trans "Copy to clipboard" %}">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="text-end">
<a hx-post="{% url 'generate_api_keys' source.pk %}" hx-target="#api-credentials" hx-select="#api-credentials" hx-swap="outerHTML" class="btn btn-main-action btn-sm">
<i class="fas fa-key"></i> {% trans "Generate New Keys" %}
</a>
</div>
</div>
</div>
<!-- Statistics -->
<div class="card mb-4">
<div class="card-header">
<h6 class="mb-0">{% trans "Integration Statistics" %}</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label text-muted">{% trans "Total API Calls" %}</label>
<div class="h5 mb-0">{{ total_logs }}</div>
</div>
<div class="mb-3">
<label class="form-label text-muted">{% trans "Successful Calls" %}</label>
<div class="h5 mb-0 text-success">{{ successful_logs }}</div>
</div>
<div class="mb-3">
<label class="form-label text-muted">{% trans "Failed Calls" %}</label>
<div class="h5 mb-0 text-danger">{{ failed_logs }}</div>
</div>
{% if total_logs > 0 %}
<div class="mb-3">
<label class="form-label text-muted">{% trans "Success Rate" %}</label>
<div class="h5 mb-0">
{% widthratio successful_logs total_logs 100 %}%
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Integration Logs --> <!-- Integration Logs Card -->
<div class="card"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white flex justify-between items-center">
<h6 class="mb-0">{% trans "Recent Integration Logs" %}</h6> <div class="flex items-center gap-3">
<small class="text-muted">{% trans "Last 10 logs" %}</small> <div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="list" class="w-5 h-5 text-temple-red"></i>
</div> </div>
<div class="card-body"> <div>
<h2 class="text-lg font-bold text-gray-900">{% trans "Recent Integration Logs" %}</h2>
<p class="text-sm text-gray-500">{% trans "Last 10 logs" %}</p>
</div>
</div>
</div>
<div class="p-6">
{% if integration_logs %} {% if integration_logs %}
<div class="table-responsive"> <div class="overflow-x-auto">
<table class="table table-sm"> <table class="min-w-full divide-y divide-gray-200">
<thead class="table-light"> <thead class="bg-gray-50">
<tr> <tr>
<th>{% trans "Timestamp" %}</th> <th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Timestamp" %}</th>
<th>{% trans "Method" %}</th> <th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Method" %}</th>
<th>{% trans "Status" %}</th> <th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Status" %}</th>
<th>{% trans "Response Time" %}</th> <th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Response Time" %}</th>
<th>{% trans "Details" %}</th> <th scope="col" class="px-4 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Details" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-100">
{% for log in integration_logs %} {% for log in integration_logs %}
<tr> <tr class="hover:bg-gray-50 transition-colors">
<td> <td class="px-4 py-4 text-sm text-gray-700">
<small>{{ log.created_at|date:"M d, Y H:i:s" }}</small> <small>{{ log.created_at|date:"M d, Y H:i:s" }}</small>
</td> </td>
<td> <td class="px-4 py-4">
<span class="badge bg-secondary">{{ log.method }}</span> <span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded bg-gray-100 text-gray-600">
{{ log.method }}
</span>
</td> </td>
<td> <td class="px-4 py-4">
{% if log.status_code >= 200 and log.status_code < 300 %} {% if log.status_code >= 200 and log.status_code < 300 %}
<span class="badge bg-success">{{ log.status_code }}</span> <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700 font-semibold text-xs">
<i data-lucide="check-circle" class="w-3 h-3"></i> {{ log.status_code }}
</span>
{% elif log.status_code >= 400 %} {% elif log.status_code >= 400 %}
<span class="badge bg-danger">{{ log.status_code }}</span> <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-red-100 text-red-700 font-semibold text-xs">
<i data-lucide="x-circle" class="w-3 h-3"></i> {{ log.status_code }}
</span>
{% else %} {% else %}
<span class="badge bg-warning">{{ log.status_code }}</span> <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-yellow-100 text-yellow-700 font-semibold text-xs">
<i data-lucide="alert-circle" class="w-3 h-3"></i> {{ log.status_code }}
</span>
{% endif %} {% endif %}
</td> </td>
<td> <td class="px-4 py-4 text-sm text-gray-700">
{% if log.response_time_ms %} {% if log.response_time_ms %}
<small>{{ log.response_time_ms }}ms</small> <small>{{ log.response_time_ms }}ms</small>
{% else %} {% else %}
<span class="text-muted">-</span> <span class="text-gray-400">-</span>
{% endif %} {% endif %}
</td> </td>
<td> <td class="px-4 py-4">
{% if log.request_data %} {% if log.request_data %}
<button type="button" class="btn btn-sm btn-outline-info" <button type="button"
data-bs-toggle="modal" class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-temple-red/30 text-temple-red hover:bg-temple-red hover:text-white transition"
data-bs-target="#logDetailModal{{ log.id }}" onclick="openLogDetailModal({{ log.id }})"
title="View details"> title="{% trans 'View details' %}">
<i class="fas fa-eye"></i> <i data-lucide="eye" class="w-4 h-4"></i>
</button> </button>
{% else %} {% else %}
<span class="text-muted">{{ _("No data") }}</span> <span class="text-gray-400 text-sm">{% trans "No data" %}</span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -303,9 +301,116 @@
</table> </table>
</div> </div>
{% else %} {% else %}
<div class="text-center py-4"> <div class="text-center py-12">
<i class="fas fa-clipboard-list fa-2x text-muted mb-3"></i> <i data-lucide="clipboard-list" class="w-16 h-16 text-gray-300 mx-auto mb-4"></i>
<p class="text-muted">{{ _("No integration logs found") }}</p> <h3 class="text-lg font-semibold text-gray-900 mb-2">{% trans "No integration logs found" %}</h3>
<p class="text-gray-500">{% trans "There are no logs recorded for this source yet." %}</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Sidebar Column -->
<div class="space-y-6">
<!-- API Credentials Card -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="key" class="w-5 h-5 text-temple-red"></i>
</div>
<div>
<h2 class="text-lg font-bold text-gray-900">{% trans "API Credentials" %}</h2>
<p class="text-sm text-gray-500">{% trans "API access keys" %}</p>
</div>
</div>
</div>
<div id="api-credentials" class="p-6 space-y-4">
<div>
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i data-lucide="key" class="w-4 h-4 text-gray-400"></i>
{% trans "API Key" %}
</label>
<div class="flex">
<input type="text" value="{{ source.api_key }}" readonly
class="flex-1 px-4 py-2.5 border border-gray-200 border-r-0 rounded-l-xl bg-gray-50 text-sm focus:outline-none">
<button type="button"
hx-post="{% url 'copy_to_clipboard' %}"
hx-vals='{"text": "{{ source.api_key }}"}'
title="{% trans 'Copy to clipboard' %}"
class="inline-flex items-center justify-center px-3 border border-gray-200 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-r-xl transition">
<i data-lucide="copy" class="w-4 h-4"></i>
</button>
</div>
</div>
<div>
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i data-lucide="lock" class="w-4 h-4 text-gray-400"></i>
{% trans "API Secret" %}
</label>
<div class="flex">
<input type="password" value="{{ source.api_secret }}" readonly id="api-secret"
class="flex-1 px-4 py-2.5 border border-gray-200 rounded-l-xl bg-gray-50 text-sm focus:outline-none">
<button type="button" onclick="toggleSecretVisibility()"
class="inline-flex items-center justify-center px-3 border border-l-0 border-r-0 border-gray-200 bg-gray-100 hover:bg-gray-200 text-gray-600 transition">
<i data-lucide="eye" id="secret-toggle-icon" class="w-4 h-4"></i>
</button>
<button type="button"
hx-post="{% url 'copy_to_clipboard' %}"
hx-vals='{"text": "{{ source.api_secret }}"}'
title="{% trans 'Copy to clipboard' %}"
class="inline-flex items-center justify-center px-3 border border-l-0 border-gray-200 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-r-xl transition">
<i data-lucide="copy" class="w-4 h-4"></i>
</button>
</div>
</div>
<div class="pt-2">
<a hx-post="{% url 'generate_api_keys' source.pk %}"
hx-target="#api-credentials"
hx-select="#api-credentials"
hx-swap="outerHTML"
class="inline-flex items-center justify-center gap-2 w-full px-5 py-3 bg-yellow-500 text-white rounded-xl font-semibold shadow hover:shadow-lg transition">
<i data-lucide="refresh-cw" class="w-5 h-5"></i>
{% trans "Generate New Keys" %}
</a>
</div>
</div>
</div>
<!-- Statistics Card -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="bar-chart-2" class="w-5 h-5 text-temple-red"></i>
</div>
<div>
<h2 class="text-lg font-bold text-gray-900">{% trans "Integration Statistics" %}</h2>
<p class="text-sm text-gray-500">{% trans "API call metrics" %}</p>
</div>
</div>
</div>
<div class="p-6 space-y-4">
<div>
<label class="text-sm font-semibold text-gray-700 mb-2">{% trans "Total API Calls" %}</label>
<div class="text-2xl font-bold text-gray-900">{{ total_logs }}</div>
</div>
<div>
<label class="text-sm font-semibold text-gray-700 mb-2">{% trans "Successful Calls" %}</label>
<div class="text-2xl font-bold text-emerald-600">{{ successful_logs }}</div>
</div>
<div>
<label class="text-sm font-semibold text-gray-700 mb-2">{% trans "Failed Calls" %}</label>
<div class="text-2xl font-bold text-red-600">{{ failed_logs }}</div>
</div>
{% if total_logs > 0 %}
<div class="pt-4 border-t border-gray-100">
<label class="text-sm font-semibold text-gray-700 mb-2">{% trans "Success Rate" %}</label>
<div class="text-2xl font-bold text-gray-900">
{% widthratio successful_logs total_logs 100 %}%
</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -314,41 +419,72 @@
</div> </div>
</div> </div>
<!-- Log Detail Modals --> <!-- Log Detail Modal -->
{% for log in integration_logs %} {% for log in integration_logs %}
{% if log.request_data %} {% if log.request_data %}
<div class="modal fade" id="logDetailModal{{ log.id }}" tabindex="-1"> <div id="logDetailModal{{ log.id }}" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden">
<div class="modal-dialog modal-lg"> <div class="flex items-center justify-center min-h-screen p-4">
<div class="modal-content"> <div class="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-hidden">
<div class="modal-header"> <div class="px-6 py-4 border-b border-gray-100 flex justify-between items-center bg-gradient-to-r from-gray-50 to-white">
<h5 class="modal-title">{% trans "Integration Log Details" %}</h5> <div class="flex items-center gap-3">
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
</div> </div>
<div class="modal-body"> <div>
<div class="row"> <h3 class="text-lg font-bold text-gray-900">{% trans "Integration Log Details" %}</h3>
<div class="col-md-6"> <p class="text-sm text-gray-500">ID: {{ log.id }}</p>
<strong>{{ _("Timestamp:") }}:</strong><br>
{{ log.created_at|date:"M d, Y H:i:s" }}
</div>
<div class="col-md-6">
<strong>{{ _("Method:") }}:</strong><br>
<span class="badge bg-secondary">{{ log.method }}</span>
</div> </div>
</div> </div>
<hr> <button type="button" onclick="closeLogDetailModal({{ log.id }})"
<div class="row"> class="inline-flex items-center justify-center w-10 h-10 rounded-lg text-gray-400 hover:bg-gray-100 transition">
<div class="col-md-6"> <i data-lucide="x" class="w-5 h-5"></i>
<strong>{{ _("Status Code:") }}:</strong><br> </button>
</div>
<div class="p-6 overflow-y-auto max-h-[calc(90vh-80px)]">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i data-lucide="calendar" class="w-4 h-4 text-gray-400"></i>
{% trans "Timestamp" %}
</label>
<div class="text-gray-700 bg-gray-50 rounded-xl p-3">{{ log.created_at|date:"M d, Y H:i:s" }}</div>
</div>
<div>
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i data-lucide="git-commit" class="w-4 h-4 text-gray-400"></i>
{% trans "Method" %}
</label>
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2 py-0.5 rounded bg-gray-100 text-gray-600">
{{ log.method }}
</span>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div>
<label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i data-lucide="badge-check" class="w-4 h-4 text-gray-400"></i>
{% trans "Status Code" %}
</label>
{% if log.status_code >= 200 and log.status_code < 300 %} {% if log.status_code >= 200 and log.status_code < 300 %}
<span class="badge bg-success">{{ log.status_code }}</span> <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-100 text-emerald-700 font-semibold text-sm">
<i data-lucide="check-circle-2" class="w-4 h-4"></i> {{ log.status_code }}
</span>
{% elif log.status_code >= 400 %} {% elif log.status_code >= 400 %}
<span class="badge bg-danger">{{ log.status_code }}</span> <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-red-100 text-red-700 font-semibold text-sm">
<i data-lucide="x-circle" class="w-4 h-4"></i> {{ log.status_code }}
</span>
{% else %} {% else %}
<span class="badge bg-warning">{{ log.status_code }}</span> <span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-yellow-100 text-yellow-700 font-semibold text-sm">
<i data-lucide="alert-circle" class="w-4 h-4"></i> {{ log.status_code }}
</span>
{% endif %} {% endif %}
</div> </div>
<div class="col-md-6"> <div>
<strong>{{ _("Response Time:") }}:</strong><br> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i data-lucide="zap" class="w-4 h-4 text-gray-400"></i>
{% trans "Response Time" %}
</label>
<div class="text-gray-700 bg-gray-50 rounded-xl p-3">
{% if log.response_time_ms %} {% if log.response_time_ms %}
{{ log.response_time_ms }}ms {{ log.response_time_ms }}ms
{% else %} {% else %}
@ -356,26 +492,40 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<hr> </div>
<div class="mb-3"> <div class="mb-6">
<strong>{{ _("Request Data:") }}:</strong> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<pre class="bg-light p-2 rounded"><code>{{ log.request_data|pprint }}</code></pre> <i data-lucide="upload" class="w-4 h-4 text-gray-400"></i>
{% trans "Request Data" %}
</label>
<pre class="bg-gray-900 text-gray-100 rounded-xl p-4 overflow-x-auto text-sm"><code>{{ log.request_data|pprint }}</code></pre>
</div> </div>
{% if log.response_data %} {% if log.response_data %}
<div class="mb-3"> <div class="mb-6">
<strong>{{ _("Response Data:") }}:</strong> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<pre class="bg-light p-2 rounded"><code>{{ log.response_data|pprint }}</code></pre> <i data-lucide="download" class="w-4 h-4 text-gray-400"></i>
{% trans "Response Data" %}
</label>
<pre class="bg-gray-900 text-gray-100 rounded-xl p-4 overflow-x-auto text-sm"><code>{{ log.response_data|pprint }}</code></pre>
</div> </div>
{% endif %} {% endif %}
{% if log.error_message %} {% if log.error_message %}
<div class="mb-3"> <div class="mb-6">
<strong>{{ _("Error Message:") }}:</strong> <label class="text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div class="alert alert-danger">{{ log.error_message }}</div> <i data-lucide="alert-triangle" class="w-4 h-4 text-gray-400"></i>
{% trans "Error Message" %}
</label>
<div class="bg-red-50 border border-red-200 rounded-xl p-4 text-red-700">{{ log.error_message }}</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="modal-footer"> <div class="px-6 py-4 border-t border-gray-100 bg-gray-50 flex justify-end">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ _("Close") }}</button> <button type="button" onclick="closeLogDetailModal({{ log.id }})"
class="inline-flex items-center gap-2 px-5 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300 transition">
<i data-lucide="x" class="w-5 h-5"></i>
{% trans "Close" %}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -385,46 +535,90 @@
{% block customJS %} {% block customJS %}
<script> <script>
function toggleSecretVisibility() { // Initialize Lucide icons
lucide.createIcons();
// Toggle Secret Visibility
function toggleSecretVisibility() {
const secretInput = document.getElementById('api-secret'); const secretInput = document.getElementById('api-secret');
const toggleIcon = document.getElementById('secret-toggle-icon'); const toggleIcon = document.getElementById('secret-toggle-icon');
if (secretInput.type === 'password') { if (secretInput.type === 'password') {
secretInput.type = 'text'; secretInput.type = 'text';
toggleIcon.classList.remove('fa-eye'); toggleIcon.setAttribute('data-lucide', 'eye-off');
toggleIcon.classList.add('fa-eye-slash');
} else { } else {
secretInput.type = 'password'; secretInput.type = 'password';
toggleIcon.classList.remove('fa-eye-slash'); toggleIcon.setAttribute('data-lucide', 'eye');
toggleIcon.classList.add('fa-eye'); }
lucide.createIcons();
} }
}
// Handle HTMX copy to clipboard feedback // Open Log Detail Modal
document.body.addEventListener('htmx:afterRequest', function(evt) { function openLogDetailModal(logId) {
const modal = document.getElementById('logDetailModal' + logId);
if (modal) {
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
}
// Close Log Detail Modal
function closeLogDetailModal(logId) {
const modal = document.getElementById('logDetailModal' + logId);
if (modal) {
modal.classList.add('hidden');
document.body.style.overflow = '';
}
}
// Close modal on escape key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
document.querySelectorAll('[id^="logDetailModal"]').forEach(modal => {
if (!modal.classList.contains('hidden')) {
modal.classList.add('hidden');
document.body.style.overflow = '';
}
});
}
});
// Close modal when clicking outside
document.querySelectorAll('[id^="logDetailModal"]').forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.add('hidden');
document.body.style.overflow = '';
}
});
});
// Handle HTMX copy to clipboard feedback
document.body.addEventListener('htmx:afterRequest', function(evt) {
if (evt.detail.successful && evt.detail.target.matches('[hx-post*="copy_to_clipboard"]')) { if (evt.detail.successful && evt.detail.target.matches('[hx-post*="copy_to_clipboard"]')) {
const button = evt.detail.target; const button = evt.detail.target;
const originalIcon = button.innerHTML; const originalHTML = button.innerHTML;
button.innerHTML = '<i class="fas fa-check"></i>'; button.innerHTML = '<i data-lucide="check" class="w-4 h-4"></i>';
button.classList.remove('btn-outline-secondary'); button.classList.remove('bg-gray-100', 'hover:bg-gray-200', 'text-gray-600', 'border-gray-200');
button.classList.add('btn-success'); button.classList.add('bg-emerald-500', 'text-white', 'border-emerald-500');
lucide.createIcons();
setTimeout(() => { setTimeout(() => {
button.innerHTML = originalIcon; button.innerHTML = originalHTML;
button.classList.remove('btn-success'); button.classList.remove('bg-emerald-500', 'text-white', 'border-emerald-500');
button.classList.add('btn-outline-secondary'); button.classList.add('bg-gray-100', 'hover:bg-gray-200', 'text-gray-600', 'border-gray-200');
lucide.createIcons();
}, 2000); }, 2000);
} }
}); });
// Auto-refresh after status toggle // Auto-refresh after status toggle
document.body.addEventListener('htmx:afterRequest', function(evt) { document.body.addEventListener('htmx:afterRequest', function(evt) {
if (evt.detail.successful && evt.detail.target.matches('[hx-post*="toggle_source_status"]')) { if (evt.detail.successful && evt.detail.target.matches('[hx-post*="toggle_source_status"]')) {
// Reload the page after a short delay to show updated status
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 500); }, 500);
} }
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -167,6 +167,77 @@
color: #dc2626; color: #dc2626;
font-weight: 500; font-weight: 500;
} }
/* Custom Checkbox Styling */
.custom-checkbox {
position: relative;
display: inline-flex;
align-items: center;
cursor: pointer;
user-select: none;
}
.custom-checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkbox-visual {
position: relative;
width: 1.375rem;
height: 1.375rem;
background-color: white;
border: 2px solid #d1d5db;
border-radius: 0.5rem;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
flex-shrink: 0;
}
.custom-checkbox:hover .checkbox-visual {
border-color: #9d2235;
box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
}
.custom-checkbox input:checked ~ .checkbox-visual {
background-color: #9d2235;
border-color: #9d2235;
background-image: linear-gradient(135deg, #9d2235 0%, #8b1228 100%);
box-shadow: 0 4px 12px rgba(157, 34, 53, 0.3);
transform: scale(1.02);
}
.custom-checkbox input:focus-visible ~ .checkbox-visual {
outline: 2px solid #9d2235;
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(157, 34, 53, 0.2);
}
.checkbox-checkmark {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(0);
transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}
.custom-checkbox input:checked ~ .checkbox-visual .checkbox-checkmark {
transform: translate(-50%, -50%) scale(1);
}
.checkbox-label {
margin-left: 0.75rem;
color: #374151;
font-size: 0.95rem;
font-weight: 500;
transition: color 0.2s ease;
}
.custom-checkbox:hover .checkbox-label {
color: #9d2235;
}
</style> </style>
{% endblock %} {% endblock %}
@ -421,10 +492,18 @@
<i data-lucide="power" class="w-4 h-4 text-gray-400"></i> <i data-lucide="power" class="w-4 h-4 text-gray-400"></i>
{% trans "Active Status" %} {% trans "Active Status" %}
</label> </label>
<div class="flex items-center gap-3"> <label class="custom-checkbox" for="{{ form.is_active.id_for_label }}">
{{ form.is_active.as_widget }} <input type="checkbox"
<span class="text-sm text-gray-600">{% trans "Enable this integration source" %}</span> name="{{ form.is_active.name }}"
</div> id="{{ form.is_active.id_for_label }}"
{% if form.is_active.value %}checked{% endif %}>
<span class="checkbox-visual">
<svg class="checkbox-checkmark w-3.5 h-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</span>
<span class="checkbox-label">{% trans "Enable this integration source" %}</span>
</label>
{% if form.is_active.help_text %} {% if form.is_active.help_text %}
<p class="form-helptext">{{ form.is_active.help_text }}</p> <p class="form-helptext">{{ form.is_active.help_text }}</p>
{% endif %} {% endif %}
@ -535,11 +614,6 @@
sourceTypeSelect.classList.add('form-select'); sourceTypeSelect.classList.add('form-select');
} }
// Apply Tailwind classes to is_active checkbox
const isActiveCheckbox = document.querySelector('input[name="is_active"]');
if (isActiveCheckbox) {
isActiveCheckbox.classList.add('w-5', 'h-5', 'text-temple-red', 'rounded', 'border-gray-300', 'focus:ring-temple-red', 'focus:border-temple-red');
}
// Form Validation // Form Validation
const form = document.getElementById('source-form'); const form = document.getElementById('source-form');